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-text-input/src/TextInput/index.tsx b/packages/ui-text-input/src/TextInput/index.tsx
index 137484dc0a..b9d808133b 100644
--- a/packages/ui-text-input/src/TextInput/index.tsx
+++ b/packages/ui-text-input/src/TextInput/index.tsx
@@ -357,6 +357,7 @@ class TextInput extends Component {
inputContainerRef={inputContainerRef}
layout={this.props.layout}
elementRef={this.handleRef}
+ margin={this.props.margin}
>
{renderBeforeOrAfter ? (
diff --git a/packages/ui-text-input/src/TextInput/props.ts b/packages/ui-text-input/src/TextInput/props.ts
index ab3c64ba03..d6421554b0 100644
--- a/packages/ui-text-input/src/TextInput/props.ts
+++ b/packages/ui-text-input/src/TextInput/props.ts
@@ -34,7 +34,7 @@ import type {
PropValidators,
TextInputTheme
} from '@instructure/shared-types'
-import type { WithStyleProps, ComponentStyle } from '@instructure/emotion'
+import type { WithStyleProps, ComponentStyle, Spacing } from '@instructure/emotion'
import type {
InteractionType,
WithDeterministicIdProps
@@ -172,6 +172,11 @@ type TextInputOwnProps = {
* Callback fired when input receives focus.
*/
onFocus?: (event: React.FocusEvent) => 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 TextInputOwnProps
@@ -223,7 +228,8 @@ const propTypes: PropValidators = {
renderAfterInput: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
onChange: PropTypes.func,
onBlur: PropTypes.func,
- onFocus: PropTypes.func
+ onFocus: PropTypes.func,
+ margin: PropTypes.string,
}
const allowedProps: AllowedPropKeys = [
@@ -249,7 +255,8 @@ const allowedProps: AllowedPropKeys = [
'renderAfterInput',
'onChange',
'onBlur',
- 'onFocus'
+ 'onFocus',
+ 'margin',
]
type TextInputState = {
diff --git a/packages/ui-themes/src/sharedThemeTokens/spacing.ts b/packages/ui-themes/src/sharedThemeTokens/spacing.ts
index 1409d4d175..1301c179ae 100644
--- a/packages/ui-themes/src/sharedThemeTokens/spacing.ts
+++ b/packages/ui-themes/src/sharedThemeTokens/spacing.ts
@@ -25,6 +25,7 @@
import { Spacing } from '@instructure/shared-types'
const spacing: Spacing = Object.freeze({
+ // legacy spacing tokens:
xxxSmall: '0.125rem', // 2px
xxSmall: '0.375rem', // 6px
xSmall: '0.5rem', // 8px
@@ -33,7 +34,37 @@ const spacing: Spacing = Object.freeze({
medium: '1.5rem', // 24px
large: '2.25rem', // 36px
xLarge: '3rem', // 48px
- xxLarge: '3.75rem' // 60px
+ xxLarge: '3.75rem', // 60px
+
+ // new spacing tokens:
+ 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',
} as const)
export default spacing
diff --git a/todo-add-margin-componentlist.md b/todo-add-margin-componentlist.md
new file mode 100644
index 0000000000..408d8919a0
--- /dev/null
+++ b/todo-add-margin-componentlist.md
@@ -0,0 +1,77 @@
+- [] Alert
+- [] AppNav
+- [] Avatar
+- [] Badge
+- [] Billboard
+- [] Breadcrumb
+- [] Button
+- [] Byline
+- [x] Calendar
+- [x] Checkbox
+- [x] CheckboxGroup
+- [] CloseButton
+- [] ColorContrast
+- [x] ColorIndicator
+- [x] ColorMixer
+- [x] ColorPicker
+- [x] ColorPreset
+- [] CondensedButton
+- [] ContextView
+- [x] DateInput
+- [x] DateInput2
+- [x] DateTimeInput
+- [] DrawerLayout
+- [?] Drilldown
+- [x] Editable
+- [] FileDrop
+- [] Flex
+- [x] FormField
+- [x] FormFieldGroup
+- [x] Grid
+- [] Heading
+- [] IconButton
+- [] Img
+- [] InlineList
+- [x] InPlaceEdit
+- [] Link
+- [] List
+- [x] Menu
+- [x] Metric
+- [x] MetricGroup
+- [] Modal
+- [x] NumberInput
+- [x] Options
+- [] Overlay
+- [] Pages
+- [] Pagination
+- [] Pill
+- [] Popover
+- [] ProgressBar
+- [] ProgressCircle
+- [x] RadioInput
+- [x] RadioInputGroup
+- [x] RangeInput
+- [] Rating
+- [?] Responsive
+- [x] Select
+- [x] Selectable
+- [] SideNavBar
+- [x] SimpleSelect
+- [x] SourceCodeEditor
+- [] Spinner
+- [] Table
+- [] Tabs
+- [] Tag
+- [x] Text
+- [x] TextArea
+- [x] TextInput
+- [x] TimeSelect
+- [x] ToggleButton
+- [x] ToggleDetails
+- [x] ToggleGroup
+- [] Tooltip
+- [] TopNavBar
+- [] Tray
+- [x] TreeBrowser
+- [x] TruncateText
+- [] View