diff --git a/docs/contributor-docs/v10-upgrade-guide.md b/docs/contributor-docs/v10-upgrade-guide.md
index 2f9590c4d2..085a462f05 100644
--- a/docs/contributor-docs/v10-upgrade-guide.md
+++ b/docs/contributor-docs/v10-upgrade-guide.md
@@ -1,7 +1,7 @@
---
title: Upgrade Guide for Version 10.0
category: Guides
-order: 7
+order: 98
---
# Upgrade Guide for Version 10
diff --git a/docs/guides/layout-spacing.md b/docs/guides/layout-spacing.md
new file mode 100644
index 0000000000..93a928bc53
--- /dev/null
+++ b/docs/guides/layout-spacing.md
@@ -0,0 +1,92 @@
+---
+title: Layout Spacing
+category: Guides
+order: 8
+---
+
+# Layout Spacing
+
+Our design system provides a set of spacing tokens for consistent layouts and components. Some tokens share values but should be used semantically. For instance, while both `space12` and `buttons` are 12px, `buttons` should be used for spacing between buttons.
+
+## Tokens
+
+| Key | Value |
+|--------------------|--------|
+| space0 | 0px |
+| space2 | 2px |
+| space4 | 4px |
+| space8 | 8px |
+| space12 | 12px |
+| space16 | 16px |
+| space24 | 24px |
+| space36 | 36px |
+| space48 | 48px |
+| space60 | 60px |
+| sections | 36px |
+| sectionElements | 24px |
+| trayElements | 24px |
+| modalElements | 24px |
+| moduleElements | 16px |
+| paddingCardLarge | 24px |
+| paddingCardMedium | 16px |
+| paddingCardSmall | 12px |
+| selects | 16px |
+| textAreas | 16px |
+| inputFields | 16px |
+| checkboxes | 16px |
+| radios | 16px |
+| toggles | 16px |
+| buttons | 12px |
+| tags | 12px |
+| statusIndicators | 12px |
+| dataPoints | 12px |
+
+## Applying Spacing
+
+There are three main ways to apply spacing in our component library:
+
+### 1. Using the `margin` Prop
+
+Most components in the library support a `margin` prop that works similarly to the CSS margin property. You can specify a single value or fine-tune individual margins (e.g., top, right, bottom, left).
+
+```ts
+---
+type: example
+---
+
+
+
+
+```
+
+### 2. Using a Container Component with the `gap` Prop
+
+For layouts, container components like `Flex` and `Grid` can be used with the gap prop to manage spacing between child elements.
+
+```ts
+---
+type: example
+---
+
+
+
+
+```
+
+### 3. Importing Values from the Theme
+
+If you need to directly reference spacing values, you can import them from the theme. This approach is useful for applying spacing in inline styles or custom components.
+
+```ts
+---
+type: code
+---
+// import the canvas theme
+import canvas from '@instructure/ui-themes'
+
+// use spacing values
+
+
+
+
+```
diff --git a/packages/emotion/src/styleUtils/ThemeablePropValues.ts b/packages/emotion/src/styleUtils/ThemeablePropValues.ts
index 9becdbcf47..63dc278c08 100644
--- a/packages/emotion/src/styleUtils/ThemeablePropValues.ts
+++ b/packages/emotion/src/styleUtils/ThemeablePropValues.ts
@@ -84,7 +84,7 @@ const ThemeablePropValues = {
medium: 'medium',
large: 'large',
xLarge: 'x-large',
- xxLarge: 'xx-large'
+ xxLarge: 'xx-large',
}
} as const
diff --git a/packages/ui-date-input/src/DateInput2/index.tsx b/packages/ui-date-input/src/DateInput2/index.tsx
index 3a26f5a6c1..95f183a651 100644
--- a/packages/ui-date-input/src/DateInput2/index.tsx
+++ b/packages/ui-date-input/src/DateInput2/index.tsx
@@ -152,7 +152,7 @@ const DateInput2 = ({
placeholder,
dateFormat,
onRequestValidateDate,
- // margin, TODO enable this prop
+ margin,
...rest
}: DateInput2Props) => {
const localeContext = useContext(ApplyLocaleContext)
@@ -277,7 +277,6 @@ const DateInput2 = ({
return (
void
- // margin?: Spacing // TODO enable this prop
+
+ /**
+ * Margin around the component. Accepts a `Spacing` token. See token values and example usage in [this guide](/#layout-spacing).
+ */
+ margin?: Spacing
}
type PropKeys = keyof DateInput2OwnProps
@@ -195,7 +200,8 @@ const propTypes: PropValidators = {
timezone: PropTypes.string,
withYearPicker: PropTypes.object,
dateFormat: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
- onRequestValidateDate: PropTypes.func
+ onRequestValidateDate: PropTypes.func,
+ margin: PropTypes.string,
}
export type { DateInput2Props }
diff --git a/packages/ui-form-field/src/FormField/index.tsx b/packages/ui-form-field/src/FormField/index.tsx
index b97bd66202..a5cfe3cfa3 100644
--- a/packages/ui-form-field/src/FormField/index.tsx
+++ b/packages/ui-form-field/src/FormField/index.tsx
@@ -70,6 +70,7 @@ class FormField extends Component {
as="label"
htmlFor={this.props.id}
elementRef={this.handleRef}
+ margin={this.props.margin}
/>
)
}
diff --git a/packages/ui-form-field/src/FormField/props.ts b/packages/ui-form-field/src/FormField/props.ts
index 81dc172264..11b016ea17 100644
--- a/packages/ui-form-field/src/FormField/props.ts
+++ b/packages/ui-form-field/src/FormField/props.ts
@@ -32,6 +32,7 @@ import type {
PropValidators
} from '@instructure/shared-types'
import type { FormMessage } from '../FormPropTypes'
+import type { Spacing } from '@instructure/emotion'
type FormFieldOwnProps = {
label: React.ReactNode
@@ -62,6 +63,11 @@ type FormFieldOwnProps = {
* provides a reference to the underlying html root element
*/
elementRef?: (element: Element | null) => void
+
+ /**
+ * Margin around the component. Accepts a `Spacing` token. See token values and example usage in [this guide](/#layout-spacing).
+ */
+ margin?: Spacing
}
type PropKeys = keyof FormFieldOwnProps
@@ -82,7 +88,8 @@ const propTypes: PropValidators = {
vAlign: PropTypes.oneOf(['top', 'middle', 'bottom']),
width: PropTypes.string,
inputContainerRef: PropTypes.func,
- elementRef: PropTypes.func
+ elementRef: PropTypes.func,
+ margin: PropTypes.string,
}
const allowedProps: AllowedPropKeys = [
@@ -97,7 +104,8 @@ const allowedProps: AllowedPropKeys = [
'vAlign',
'width',
'inputContainerRef',
- 'elementRef'
+ 'elementRef',
+ 'margin'
]
export type { FormFieldOwnProps, FormFieldProps }
diff --git a/packages/ui-form-field/src/FormFieldLayout/index.tsx b/packages/ui-form-field/src/FormFieldLayout/index.tsx
index cf7c810c6c..95f2c21aba 100644
--- a/packages/ui-form-field/src/FormFieldLayout/index.tsx
+++ b/packages/ui-form-field/src/FormFieldLayout/index.tsx
@@ -45,6 +45,7 @@ import generateStyle from './styles'
import { propTypes, allowedProps } from './props'
import type { FormFieldLayoutProps } from './props'
+import generateComponentTheme from './theme'
/**
---
@@ -52,7 +53,7 @@ parent: FormField
---
**/
@withDeterministicId()
-@withStyle(generateStyle, null)
+@withStyle(generateStyle, generateComponentTheme)
class FormFieldLayout extends Component {
static readonly componentId = 'FormFieldLayout'
diff --git a/packages/ui-form-field/src/FormFieldLayout/props.ts b/packages/ui-form-field/src/FormFieldLayout/props.ts
index 837443e7e5..8b3313f7da 100644
--- a/packages/ui-form-field/src/FormFieldLayout/props.ts
+++ b/packages/ui-form-field/src/FormFieldLayout/props.ts
@@ -31,7 +31,7 @@ import type {
OtherHTMLAttributes,
PropValidators
} from '@instructure/shared-types'
-import type { WithStyleProps, ComponentStyle } from '@instructure/emotion'
+import type { WithStyleProps, ComponentStyle, Spacing } from '@instructure/emotion'
import type { FormMessage } from '../FormPropTypes'
import type { WithDeterministicIdProps } from '@instructure/ui-react-utils'
@@ -68,6 +68,11 @@ type FormFieldLayoutOwnProps = {
*/
elementRef?: (element: Element | null) => void
isGroup?: boolean
+
+ /**
+ * Margin around the component. Accepts a `Spacing` token. See token values and example usage in [this guide](/#layout-spacing).
+ */
+ margin?: Spacing
}
type PropKeys = keyof FormFieldLayoutOwnProps
@@ -97,7 +102,8 @@ const propTypes: PropValidators = {
width: PropTypes.string,
inputContainerRef: PropTypes.func,
elementRef: PropTypes.func,
- isGroup: PropTypes.bool
+ isGroup: PropTypes.bool,
+ margin: PropTypes.string,
}
const allowedProps: AllowedPropKeys = [
@@ -112,7 +118,8 @@ const allowedProps: AllowedPropKeys = [
'labelAlign',
'width',
'inputContainerRef',
- 'elementRef'
+ 'elementRef',
+ 'margin'
// added vAlign because FormField and FormFieldGroup passes it, but not adding
// it to allowedProps to prevent it from getting passed through accidentally
diff --git a/packages/ui-form-field/src/FormFieldLayout/styles.ts b/packages/ui-form-field/src/FormFieldLayout/styles.ts
index 542ad8e95d..5b361b25a5 100644
--- a/packages/ui-form-field/src/FormFieldLayout/styles.ts
+++ b/packages/ui-form-field/src/FormFieldLayout/styles.ts
@@ -35,10 +35,11 @@ import type { FormFieldLayoutProps, FormFieldLayoutStyle } from './props'
* @return {Object} The final style object, which will be used in the component
*/
const generateStyle = (
- _componentTheme: null,
+ componentTheme: any,
props: FormFieldLayoutProps
): FormFieldLayoutStyle => {
- const { inline } = props
+ const { inline, margin } = props
+ const { spacing } = componentTheme
return {
groupErrorMessage: {
@@ -49,7 +50,7 @@ const generateStyle = (
all: 'initial',
border: '0',
padding: '0',
- margin: '0',
+ margin: margin ? spacing[margin] : '0',
minWidth: '0',
direction: 'inherit',
textAlign: 'start',
diff --git a/packages/ui-form-field/src/FormFieldLayout/theme.ts b/packages/ui-form-field/src/FormFieldLayout/theme.ts
new file mode 100644
index 0000000000..6a7a7a6807
--- /dev/null
+++ b/packages/ui-form-field/src/FormFieldLayout/theme.ts
@@ -0,0 +1,33 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - present Instructure, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import type { Theme } from '@instructure/ui-themes'
+
+const generateComponentTheme = (theme: Theme): any => {
+ const { spacing } = theme
+
+ return { spacing }
+}
+
+export default generateComponentTheme
diff --git a/packages/ui-number-input/src/NumberInput/index.tsx b/packages/ui-number-input/src/NumberInput/index.tsx
index 4c40a1e2f6..6b1e4bc72f 100644
--- a/packages/ui-number-input/src/NumberInput/index.tsx
+++ b/packages/ui-number-input/src/NumberInput/index.tsx
@@ -238,7 +238,8 @@ class NumberInput extends Component {
value,
width,
styles,
- allowStringValue
+ allowStringValue,
+ margin,
} = this.props
const { interaction } = this
@@ -268,6 +269,7 @@ class NumberInput extends Component {
inline={display === 'inline-block'}
id={this.id}
elementRef={this.handleRef}
+ margin={margin}
>
= {
onKeyDown: PropTypes.func,
inputMode: PropTypes.oneOf(['numeric', 'decimal', 'tel']),
textAlign: PropTypes.oneOf(['start', 'center']),
- allowStringValue: PropTypes.bool
+ allowStringValue: PropTypes.bool,
+ margin: PropTypes.string,
}
const allowedProps: AllowedPropKeys = [
@@ -250,7 +255,8 @@ const allowedProps: AllowedPropKeys = [
'onKeyDown',
'inputMode',
'textAlign',
- 'allowStringValue'
+ 'allowStringValue',
+ 'margin',
]
export type {
diff --git a/packages/ui-text-area/src/TextArea/index.tsx b/packages/ui-text-area/src/TextArea/index.tsx
index 942b5c0319..a65f28956e 100644
--- a/packages/ui-text-area/src/TextArea/index.tsx
+++ b/packages/ui-text-area/src/TextArea/index.tsx
@@ -315,7 +315,8 @@ class TextArea extends Component {
maxHeight,
textareaRef,
resize,
- styles
+ styles,
+ margin,
} = this.props
const props = omitProps(this.props, TextArea.allowedProps)
@@ -380,6 +381,7 @@ class TextArea extends Component {
elementRef={(el) => {
this.ref = el
}}
+ margin={margin}
>