Skip to content

Commit 1b47c5f

Browse files
balzssclaude
andcommitted
feat(many): support current spacing tokens in the margin prop for v2 components
Surface the current (era-3) spacing tokens (general.*, gap.*, padding.card.* dot-paths) on the `margin` prop of v2 components. View v2 already resolved them at runtime, but the `Spacing` type and component docs only advertised the older token eras. - emotion: add CurrentSpacingValues dot-path literals to the Spacing union and mark the legacy (era-1) and phased-out (era-2) unions as @deprecated - v2 components: sync the margin JSDoc across all v2 components to point at the spacing guide and use a current-token example - docs: re-add the Layout Spacing guide as a v11.7+ version-gated page, following the theme-overrides version-banner pattern; convert legacy margin values in v2 README examples to the current general.* tokens (value-preserving mapping) - tests: cover era-3 dot-path resolution in calcSpacingFromShorthand and add View v2 integration tests; add regression-test examples 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent aac2b17 commit 1b47c5f

80 files changed

Lines changed: 670 additions & 406 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/guides/layout-spacing.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
title: Layout Spacing
3+
category: Guides
4+
order: 7
5+
relevantForAI: true
6+
---
7+
8+
## Layout Spacing
9+
10+
```js
11+
---
12+
type: embed
13+
---
14+
<Alert variant="warning" margin="0 0 medium">
15+
The spacing tokens documented on this page (such as <code>general.spaceMd</code>) require <strong>v11.7+</strong> components and are applied through the <code>margin</code> and <code>padding</code> props. If you are viewing the v11.6 version, <Link href={window.location.pathname.match(/v\d+_\d+/) ? window.location.pathname.replace(/v\d+_\d+/, 'v11_7') : `/v11_7${window.location.pathname}`}>switch to v11.7</Link> to see the examples working correctly.
16+
</Alert>
17+
```
18+
19+
Our design system provides a set of spacing tokens for consistent layouts and components. The current tokens are organized into a general t-shirt scale plus a handful of semantic tokens. Some tokens share a value but carry different meaning — prefer the semantically correct token for the context (e.g. use `gap.buttons` for spacing between buttons).
20+
21+
The `margin` and `padding` props on InstUI components accept these tokens via **dot-path notation** (for example `margin="general.spaceMd"` or `padding="padding.card.lg"`), and support the familiar CSS-like 1–4 value shorthand.
22+
23+
## Tokens
24+
25+
### General scale
26+
27+
| Key | Value | Value in pixels |
28+
| ----------------- | -------- | --------------- |
29+
| general.spaceNone | 0rem | 0px |
30+
| general.space2xs | 0.125rem | 2px |
31+
| general.spaceXs | 0.25rem | 4px |
32+
| general.spaceSm | 0.5rem | 8px |
33+
| general.spaceMd | 0.75rem | 12px |
34+
| general.spaceLg | 1rem | 16px |
35+
| general.spaceXl | 1.5rem | 24px |
36+
| general.space2xl | 2rem | 32px |
37+
38+
### Semantic tokens
39+
40+
| Key | Value | Value in pixels |
41+
| ----------------------------- | ------- | --------------- |
42+
| gap.sections | 3rem | 48px |
43+
| gap.buttons | 0.75rem | 12px |
44+
| gap.cards.sm | 0.75rem | 12px |
45+
| gap.cards.md | 1rem | 16px |
46+
| gap.cards.lg | 1.5rem | 24px |
47+
| gap.cards.nestedContainers.sm | 0.5rem | 8px |
48+
| gap.cards.nestedContainers.md | 0.75rem | 12px |
49+
| gap.cards.nestedContainers.lg | 1rem | 16px |
50+
| gap.inputs.horizontal | 0.75rem | 12px |
51+
| gap.inputs.vertical | 1rem | 16px |
52+
| padding.card.sm | 0.5rem | 8px |
53+
| padding.card.md | 0.75rem | 12px |
54+
| padding.card.lg | 1rem | 16px |
55+
56+
## Applying spacing
57+
58+
### Using the `margin` prop
59+
60+
Most components support a `margin` prop that works like the CSS `margin` property. Pass a single token or use the 1–4 value shorthand to fine-tune individual edges.
61+
62+
```js
63+
---
64+
type: example
65+
---
66+
<View as="div" display="block" borderWidth="small" padding="general.spaceSm">
67+
<Button margin="0 general.spaceSm 0 0">Button 1</Button>
68+
<Button>Button 2</Button>
69+
</View>
70+
```
71+
72+
### Using the `padding` prop
73+
74+
Components that render their own surface accept a `padding` prop, which resolves the same tokens. Semantic `padding.card.*` tokens are a good fit for card-like containers.
75+
76+
```js
77+
---
78+
type: example
79+
---
80+
<View as="div" display="block" borderWidth="small" padding="padding.card.lg">
81+
This container uses <code>padding.card.lg</code>.
82+
</View>
83+
```
84+
85+
## Deprecated tokens
86+
87+
For compatibility reasons we still resolve two earlier generations of spacing tokens at runtime, but they should **not** be used when creating new layouts — prefer the current tokens above.
88+
89+
### Phased-out tokens
90+
91+
The flat `space0`–`space60` scale and the flat semantic tokens (`sections`, `buttons`, `paddingCardLarge`, `selects`, `tags`, etc.) were an interim set and have been superseded by the current general scale and dot-path semantic tokens.
92+
93+
### Legacy tokens
94+
95+
The original legacy keywords (`xxxSmall`, `small`, `medium`, `large`, `xxLarge`, etc.) remain so old layouts don't break when updating InstUI.

packages/emotion/src/styleUtils/ThemeablePropValues.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,18 @@ const ThemeablePropValues = {
9090

9191
// SPACING
9292
type OldSpacingKeys = keyof typeof ThemeablePropValues.SPACING
93+
/**
94+
* @deprecated Era-1 (legacy) spacing keywords. Still resolved at runtime, but
95+
* prefer the current `CurrentSpacingValues` dot-path tokens (e.g. `general.spaceMd`).
96+
* See https://instructure.design/layout-spacing.
97+
*/
9398
type OldSpacingValues = (typeof ThemeablePropValues.SPACING)[OldSpacingKeys]
99+
/**
100+
* @deprecated Era-2 spacing tokens (the `space0`–`space60` and flat semantic set)
101+
* have been phased out. Still resolved at runtime, but prefer the current
102+
* `CurrentSpacingValues` dot-path tokens (e.g. `general.spaceMd`, `gap.cards.md`).
103+
* See https://instructure.design/layout-spacing.
104+
*/
94105
type NewSpacingValues =
95106
| 'space0'
96107
| 'space2'
@@ -120,7 +131,38 @@ type NewSpacingValues =
120131
| 'tags'
121132
| 'statusIndicators'
122133
| 'dataPoints'
123-
type SpacingValues = OldSpacingValues | NewSpacingValues
134+
/**
135+
* Current (era-3) spacing tokens. Referenced via dot-path notation and resolved
136+
* against the new theme's `sharedTokens.spacing` map (see
137+
* `@instructure/ui-themes` `newThemeTokens`). These are the recommended values
138+
* for the `margin`/`padding` props. See https://instructure.design/layout-spacing.
139+
*/
140+
type CurrentSpacingValues =
141+
// general t-shirt scale
142+
| 'general.spaceNone'
143+
| 'general.space2xs'
144+
| 'general.spaceXs'
145+
| 'general.spaceSm'
146+
| 'general.spaceMd'
147+
| 'general.spaceLg'
148+
| 'general.spaceXl'
149+
| 'general.space2xl'
150+
// semantic gap tokens
151+
| 'gap.sections'
152+
| 'gap.buttons'
153+
| 'gap.cards.sm'
154+
| 'gap.cards.md'
155+
| 'gap.cards.lg'
156+
| 'gap.cards.nestedContainers.sm'
157+
| 'gap.cards.nestedContainers.md'
158+
| 'gap.cards.nestedContainers.lg'
159+
| 'gap.inputs.horizontal'
160+
| 'gap.inputs.vertical'
161+
// semantic padding tokens
162+
| 'padding.card.sm'
163+
| 'padding.card.md'
164+
| 'padding.card.lg'
165+
type SpacingValues = CurrentSpacingValues | OldSpacingValues | NewSpacingValues
124166
// adding `(string & {})` allows to use any string while still allowing specific string values to be suggested by IDEs
125167
type Spacing = SpacingValues | (string & {})
126168

@@ -152,6 +194,7 @@ export default ThemeablePropValues
152194
export { ThemeablePropValues }
153195
export type {
154196
SpacingValues,
197+
CurrentSpacingValues,
155198
Spacing,
156199
Shadow,
157200
Stacking,

packages/emotion/src/styleUtils/__tests__/calcSpacingFromShorthand.test.tsx

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,4 +239,70 @@ describe('calcSpacingFromShorthand', () => {
239239
consoleWarnSpy.mockRestore()
240240
})
241241
})
242+
243+
// Era-3 (current) tokens are referenced via dot-path notation and resolved
244+
// against the new theme's nested `sharedTokens.spacing` map. This block mirrors
245+
// that shape to verify the real consumer syntax (e.g. `margin="general.spaceMd"`).
246+
describe('era-3 (current) spacing tokens', () => {
247+
const eraThreeMap = {
248+
general: {
249+
spaceNone: '0rem',
250+
space2xs: '0.125rem',
251+
spaceXs: '0.25rem',
252+
spaceSm: '0.5rem',
253+
spaceMd: '0.75rem',
254+
spaceLg: '1rem',
255+
spaceXl: '1.5rem',
256+
space2xl: '2rem'
257+
},
258+
gap: {
259+
sections: '3rem',
260+
buttons: '0.75rem',
261+
cards: {
262+
sm: '0.75rem',
263+
md: '1rem',
264+
lg: '1.5rem',
265+
nestedContainers: { sm: '0.5rem', md: '0.75rem', lg: '1rem' }
266+
},
267+
inputs: { horizontal: '0.75rem', vertical: '1rem' }
268+
},
269+
padding: {
270+
card: { sm: '0.5rem', md: '0.75rem', lg: '1rem' }
271+
}
272+
}
273+
274+
it('resolves a general scale token', () => {
275+
expect(calcSpacingFromShorthand('general.spaceMd', eraThreeMap)).toBe('0.75rem')
276+
})
277+
278+
it('resolves general.spaceNone', () => {
279+
expect(calcSpacingFromShorthand('general.spaceNone', eraThreeMap)).toBe('0rem')
280+
})
281+
282+
it('resolves a deeply nested semantic gap token', () => {
283+
expect(
284+
calcSpacingFromShorthand('gap.cards.nestedContainers.md', eraThreeMap)
285+
).toBe('0.75rem')
286+
})
287+
288+
it('resolves a semantic padding token', () => {
289+
expect(calcSpacingFromShorthand('padding.card.lg', eraThreeMap)).toBe('1rem')
290+
})
291+
292+
it('handles CSS-like shorthand mixing the general scale with auto', () => {
293+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
294+
295+
expect(
296+
calcSpacingFromShorthand('general.spaceLg auto general.spaceXl', eraThreeMap)
297+
).toBe('1rem auto 1.5rem')
298+
299+
consoleWarnSpy.mockRestore()
300+
})
301+
302+
it('mixes semantic gap and padding tokens', () => {
303+
expect(
304+
calcSpacingFromShorthand('gap.cards.sm padding.card.md', eraThreeMap)
305+
).toBe('0.75rem 0.75rem')
306+
})
307+
})
242308
})

packages/ui-alerts/src/Alert/v2/README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type: example
2424
<Alert
2525
variant="success"
2626
renderCloseButtonLabel="Close"
27-
margin="small"
27+
margin="general.spaceMd"
2828
transition="none"
2929
variantScreenReaderLabel="Success, "
3030
>
@@ -33,15 +33,15 @@ type: example
3333
<Alert
3434
variant="info"
3535
renderCloseButtonLabel="Close"
36-
margin="small"
36+
margin="general.spaceMd"
3737
variantScreenReaderLabel="Information, "
3838
>
3939
Sample info text. I will fade out if you close me.
4040
</Alert>
4141
<Alert
4242
variant="error"
4343
renderCloseButtonLabel="Close"
44-
margin="small"
44+
margin="general.spaceMd"
4545
variantScreenReaderLabel="Error, "
4646
>
4747
Sample error text that continues for a while
@@ -51,7 +51,7 @@ type: example
5151
</Alert>
5252
<Alert
5353
variant="warning"
54-
margin="small"
54+
margin="general.spaceMd"
5555
variantScreenReaderLabel="Warning, "
5656
>
5757
Sample warning text. This alert is not dismissible and cannot be closed.
@@ -67,7 +67,7 @@ type: example
6767
---
6868
<Alert
6969
variant="info"
70-
margin="small"
70+
margin="general.spaceMd"
7171
timeout={5000}
7272
variantScreenReaderLabel="Information, "
7373
>
@@ -119,14 +119,14 @@ const Example = () => {
119119
<Button onClick={addAlert}>Add Alert</Button>
120120
{alerts.map((alert) => {
121121
return (
122-
<View key={alert.key} display="block" margin="small 0">
122+
<View key={alert.key} display="block" margin="general.spaceMd 0">
123123
<Alert
124124
variant={alert.variant}
125125
renderCloseButtonLabel="Close"
126126
onDismiss={() => closeAlert(alert.key)}
127127
liveRegion={() => document.getElementById('flash-messages')}
128128
liveRegionPoliteness={alert.politeness}
129-
margin="small 0"
129+
margin="general.spaceMd 0"
130130
>
131131
This is {alert.politeness === 'polite' ? 'a' : 'an'}{' '}
132132
{alert.politeness} {alert.variant} alert
@@ -164,7 +164,7 @@ const Example = () => {
164164
return (
165165
<div>
166166
<Button onClick={changeMessage}>Change Message</Button>
167-
<Button onClick={clearMessage} margin="0 0 0 small">
167+
<Button onClick={clearMessage} margin="0 0 0 general.spaceMd">
168168
Clear Message
169169
</Button>
170170
<Alert
@@ -194,13 +194,13 @@ type: example
194194
padding="small medium"
195195
borderWidth="small"
196196
borderRadius="small"
197-
margin="x-small 0"
197+
margin="general.spaceSm 0"
198198
>
199199
{lorem.paragraph()}
200200
</View>
201201
<Alert
202202
variant="info"
203-
margin="x-small 0"
203+
margin="general.spaceSm 0"
204204
renderCloseButtonLabel="Close"
205205
hasShadow={false}
206206
>
@@ -212,7 +212,7 @@ type: example
212212
padding="small medium"
213213
borderWidth="small"
214214
borderRadius="small"
215-
margin="x-small 0"
215+
margin="general.spaceSm 0"
216216
>
217217
{lorem.paragraph()}
218218
</View>

packages/ui-alerts/src/Alert/v2/props.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@ type AlertOwnProps = {
7979
*/
8080
timeout?: number
8181
/**
82-
* Valid values are `0`, `none`, `auto`, `xxx-small`, `xx-small`, `x-small`,
83-
* `small`, `medium`, `large`, `x-large`, `xx-large`. Apply these values via
84-
* familiar CSS-like shorthand. For example: `margin="small auto large"`.
82+
* Valid values are `0`, `none`, `auto`, and Spacing token values,
83+
* see https://instructure.design/layout-spacing. Apply these values via
84+
* familiar CSS-like shorthand. For example, `margin="general.spaceMd auto"`.
8585
*/
8686
margin?: Spacing
8787
/**

packages/ui-avatar/src/Avatar/v2/props.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ type AvatarOwnProps = {
8181
/**
8282
* Valid values are `0`, `none`, `auto`, and Spacing token values,
8383
* see https://instructure.design/layout-spacing. Apply these values via
84-
* familiar CSS-like shorthand. For example, `margin="small auto large"`.
84+
* familiar CSS-like shorthand. For example, `margin="general.spaceMd auto"`.
8585
*/
8686
margin?: Spacing
8787
/**

0 commit comments

Comments
 (0)