@discourser/design-system 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -0
- package/dist/index.cjs +121 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +203 -62
- package/dist/index.d.ts +203 -62
- package/dist/index.js +118 -96
- package/dist/index.js.map +1 -1
- package/guidelines/Guidelines.md +88 -0
- package/guidelines/components/button.md +314 -0
- package/guidelines/components/card.md +353 -0
- package/guidelines/components/dialog.md +465 -0
- package/guidelines/components/icon-button.md +417 -0
- package/guidelines/components/input.md +499 -0
- package/guidelines/components/switch.md +457 -0
- package/guidelines/design-tokens/colors.md +187 -0
- package/guidelines/design-tokens/elevation.md +274 -0
- package/guidelines/design-tokens/spacing.md +289 -0
- package/guidelines/design-tokens/typography.md +226 -0
- package/guidelines/overview-components.md +156 -0
- package/package.json +9 -2
- package/styled-system/recipes/heading.d.ts +34 -0
- package/styled-system/recipes/heading.mjs +40 -0
- package/styled-system/recipes/index.d.ts +3 -0
- package/styled-system/recipes/index.mjs +3 -0
- package/styled-system/recipes/input-addon.d.ts +38 -0
- package/styled-system/recipes/input-addon.mjs +40 -0
- package/styled-system/recipes/input-group.d.ts +34 -0
- package/styled-system/recipes/input-group.mjs +49 -0
- package/styled-system/recipes/radio-group.d.ts +4 -0
- package/styled-system/recipes/radio-group.mjs +8 -2
- package/styled-system/tokens/index.mjs +352 -220
- package/styled-system/tokens/tokens.d.ts +13 -10
- package/styled-system/types/prop-type.d.ts +16 -1
- package/styled-system/types/style-props.d.ts +20 -20
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# Elevation Tokens
|
|
2
|
+
|
|
3
|
+
The design system uses Material Design 3 elevation system combining surface tints and shadows to create depth and hierarchy.
|
|
4
|
+
|
|
5
|
+
## What is Elevation?
|
|
6
|
+
|
|
7
|
+
Elevation creates visual hierarchy by making elements appear to float above the background. M3 uses two techniques:
|
|
8
|
+
1. **Surface Tints**: Background color changes (via `surfaceContainer*` tokens)
|
|
9
|
+
2. **Shadows**: Subtle shadows for additional depth (optional)
|
|
10
|
+
|
|
11
|
+
**Important**: M3 primarily uses surface tints, not heavy shadows like older Material Design versions.
|
|
12
|
+
|
|
13
|
+
## Elevation Levels
|
|
14
|
+
|
|
15
|
+
| Token | Shadow Value | Usage |
|
|
16
|
+
|-------|--------------|-------|
|
|
17
|
+
| `level0` | none | Flat elements, no elevation |
|
|
18
|
+
| `level1` | `0px 1px 2px rgba(0,0,0,0.3), 0px 1px 3px 1px rgba(0,0,0,0.15)` | Cards (low elevation) |
|
|
19
|
+
| `level2` | `0px 1px 2px rgba(0,0,0,0.3), 0px 2px 6px 2px rgba(0,0,0,0.15)` | Raised cards, FAB (resting) |
|
|
20
|
+
| `level3` | `0px 4px 8px 3px rgba(0,0,0,0.15), 0px 1px 3px rgba(0,0,0,0.3)` | Dialogs, menus |
|
|
21
|
+
| `level4` | `0px 6px 10px 4px rgba(0,0,0,0.15), 0px 2px 3px rgba(0,0,0,0.3)` | FAB (hover), navigation drawers |
|
|
22
|
+
| `level5` | `0px 8px 12px 6px rgba(0,0,0,0.15), 0px 4px 4px rgba(0,0,0,0.3)` | Modals, navigation bars |
|
|
23
|
+
|
|
24
|
+
## M3 Elevation Strategy
|
|
25
|
+
|
|
26
|
+
### Primary Method: Surface Containers
|
|
27
|
+
|
|
28
|
+
M3 primarily uses surface container colors for elevation. Higher elevations get different background colors:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { css } from '@discourser/design-system/styled-system/css';
|
|
32
|
+
|
|
33
|
+
// Level 0 (flat, on page)
|
|
34
|
+
const flat = css({ bg: 'surface' });
|
|
35
|
+
|
|
36
|
+
// Level 1 (low elevation - cards)
|
|
37
|
+
const card = css({ bg: 'surfaceContainerLow' });
|
|
38
|
+
|
|
39
|
+
// Level 3 (default containers)
|
|
40
|
+
const container = css({ bg: 'surfaceContainer' });
|
|
41
|
+
|
|
42
|
+
// Level 4 (high elevation - dialogs)
|
|
43
|
+
const dialog = css({ bg: 'surfaceContainerHigh' });
|
|
44
|
+
|
|
45
|
+
// Level 5 (highest elevation)
|
|
46
|
+
const modal = css({ bg: 'surfaceContainerHighest' });
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Secondary Method: Shadows (Optional)
|
|
50
|
+
|
|
51
|
+
Shadows can be added for additional depth, but use sparingly:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// Card with subtle shadow
|
|
55
|
+
const card = css({
|
|
56
|
+
bg: 'surfaceContainerLow',
|
|
57
|
+
boxShadow: 'level1' // Optional shadow
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Dialog with shadow
|
|
61
|
+
const dialog = css({
|
|
62
|
+
bg: 'surfaceContainerHigh',
|
|
63
|
+
boxShadow: 'level3'
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Component Elevation Mapping
|
|
68
|
+
|
|
69
|
+
| Component | Surface Container | Shadow Level | Elevation |
|
|
70
|
+
|-----------|-------------------|--------------|-----------|
|
|
71
|
+
| Page background | `surface` | `level0` | 0dp |
|
|
72
|
+
| Filled Card | `surface` | `level0` | 0dp |
|
|
73
|
+
| Outlined Card | `surface` | `level0` | 0dp |
|
|
74
|
+
| Elevated Card | `surfaceContainerLow` | `level1` | 1dp |
|
|
75
|
+
| Input (filled) | `surfaceContainerHighest` | `level0` | 0dp |
|
|
76
|
+
| Button (filled) | `primary` | `level0` | 0dp |
|
|
77
|
+
| Button (elevated) | `surfaceContainerLow` | `level1` | 1dp |
|
|
78
|
+
| Dialog | `surfaceContainerHigh` | `level3` | 3dp |
|
|
79
|
+
| Menu | `surfaceContainer` | `level2` | 2dp |
|
|
80
|
+
| Navigation Drawer | `surfaceContainerLow` | `level1` | 1dp |
|
|
81
|
+
|
|
82
|
+
## Usage Patterns
|
|
83
|
+
|
|
84
|
+
### Cards
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// Filled card (no elevation)
|
|
88
|
+
const filledCard = css({
|
|
89
|
+
bg: 'surface',
|
|
90
|
+
borderColor: 'outlineVariant',
|
|
91
|
+
borderWidth: '1px'
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Elevated card
|
|
95
|
+
const elevatedCard = css({
|
|
96
|
+
bg: 'surfaceContainerLow',
|
|
97
|
+
boxShadow: 'level1' // Optional
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Outlined card (no elevation)
|
|
101
|
+
const outlinedCard = css({
|
|
102
|
+
bg: 'surface',
|
|
103
|
+
borderColor: 'outline',
|
|
104
|
+
borderWidth: '1px'
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Dialogs and Modals
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// Dialog
|
|
112
|
+
const dialog = css({
|
|
113
|
+
bg: 'surfaceContainerHigh',
|
|
114
|
+
boxShadow: 'level3',
|
|
115
|
+
borderRadius: 'large'
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Full-screen modal
|
|
119
|
+
const modal = css({
|
|
120
|
+
bg: 'surfaceContainerHighest',
|
|
121
|
+
boxShadow: 'level5'
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Scrim (overlay behind dialog)
|
|
125
|
+
const scrim = css({
|
|
126
|
+
bg: 'scrim',
|
|
127
|
+
opacity: 0.32
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Interactive States
|
|
132
|
+
|
|
133
|
+
Elevation can change on interaction:
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
// Button hover (increase elevation)
|
|
137
|
+
const button = css({
|
|
138
|
+
bg: 'surfaceContainerLow',
|
|
139
|
+
boxShadow: 'level1',
|
|
140
|
+
_hover: {
|
|
141
|
+
boxShadow: 'level2' // Increase shadow on hover
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Card hover
|
|
146
|
+
const card = css({
|
|
147
|
+
bg: 'surfaceContainerLow',
|
|
148
|
+
boxShadow: 'level1',
|
|
149
|
+
transition: 'box-shadow 0.2s',
|
|
150
|
+
_hover: {
|
|
151
|
+
boxShadow: 'level2'
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## What NOT to Do
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// ❌ NEVER use arbitrary shadow values
|
|
160
|
+
const wrong = css({
|
|
161
|
+
boxShadow: '0 4px 6px rgba(0,0,0,0.1)'
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// ❌ NEVER use heavy shadows (old Material Design style)
|
|
165
|
+
const wrong = css({
|
|
166
|
+
boxShadow: '0 10px 40px rgba(0,0,0,0.5)'
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// ❌ NEVER use elevation without surface tints
|
|
170
|
+
const wrong = css({
|
|
171
|
+
bg: '#FFFFFF', // Raw color
|
|
172
|
+
boxShadow: 'level1'
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// ✅ ALWAYS use surface containers + optional shadows
|
|
176
|
+
const correct = css({
|
|
177
|
+
bg: 'surfaceContainerLow',
|
|
178
|
+
boxShadow: 'level1'
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Elevation Hierarchy Rules
|
|
183
|
+
|
|
184
|
+
1. **Higher elements** should have higher elevation
|
|
185
|
+
2. **Overlays and modals** should be highest (level 4-5)
|
|
186
|
+
3. **Dialogs and menus** should be mid-high (level 2-3)
|
|
187
|
+
4. **Cards and containers** should be low (level 0-1)
|
|
188
|
+
5. **Page backgrounds** should be level 0
|
|
189
|
+
|
|
190
|
+
### Layering Example
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
Page Background (surface, level0)
|
|
194
|
+
├─ Filled Card (surface, level0)
|
|
195
|
+
├─ Elevated Card (surfaceContainerLow, level1)
|
|
196
|
+
└─ Dialog (surfaceContainerHigh, level3)
|
|
197
|
+
└─ Button in Dialog (primary, level0)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Accessibility
|
|
201
|
+
|
|
202
|
+
### Color Contrast
|
|
203
|
+
Surface tint changes must maintain adequate contrast:
|
|
204
|
+
- Text on elevated surfaces still uses `onSurface` or `onSurfaceVariant`
|
|
205
|
+
- The design system automatically handles this
|
|
206
|
+
|
|
207
|
+
### Motion and Transitions
|
|
208
|
+
When elevation changes on interaction:
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
const card = css({
|
|
212
|
+
transition: 'box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
|
213
|
+
_hover: {
|
|
214
|
+
boxShadow: 'level2'
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Use M3 motion tokens for consistent timing:
|
|
220
|
+
- `fast` (100ms) - Subtle changes
|
|
221
|
+
- `normal` (200ms) - Standard transitions
|
|
222
|
+
- `slow` (300ms) - Emphasized changes
|
|
223
|
+
|
|
224
|
+
## Dark Mode
|
|
225
|
+
|
|
226
|
+
Elevation behaves differently in dark mode:
|
|
227
|
+
- **Light mode**: Higher elevation = lighter background
|
|
228
|
+
- **Dark mode**: Higher elevation = lighter background (more tint)
|
|
229
|
+
|
|
230
|
+
The surface container tokens handle this automatically. No code changes needed.
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
// Automatically adapts to dark mode
|
|
234
|
+
const card = css({ bg: 'surfaceContainerLow' });
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Component Usage
|
|
238
|
+
|
|
239
|
+
Most components handle elevation automatically:
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
import { Card, Dialog, Button } from '@discourser/design-system';
|
|
243
|
+
|
|
244
|
+
// Card handles elevation via variant
|
|
245
|
+
<Card variant="elevated">Content</Card> // Auto uses surfaceContainerLow + level1
|
|
246
|
+
|
|
247
|
+
// Dialog handles elevation automatically
|
|
248
|
+
<Dialog.Content>Dialog content</Dialog.Content> // Auto uses surfaceContainerHigh + level3
|
|
249
|
+
|
|
250
|
+
// Button elevated variant
|
|
251
|
+
<Button variant="elevated">Action</Button> // Auto uses surfaceContainerLow + level1
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Only use manual elevation tokens when creating custom layouts outside of components.
|
|
255
|
+
|
|
256
|
+
## Best Practices
|
|
257
|
+
|
|
258
|
+
1. **Prefer surface containers** over shadows alone
|
|
259
|
+
2. **Use minimal shadows** - M3 is more subtle than older Material Design
|
|
260
|
+
3. **Maintain hierarchy** - overlays above dialogs above cards
|
|
261
|
+
4. **Animate elevation changes** for smooth interactions
|
|
262
|
+
5. **Test in dark mode** to ensure proper contrast
|
|
263
|
+
6. **Let components handle elevation** when possible
|
|
264
|
+
7. **Use level 0-1 for most content** - save higher levels for overlays
|
|
265
|
+
|
|
266
|
+
## Common Elevation Mistakes
|
|
267
|
+
|
|
268
|
+
| ❌ Wrong | ✅ Right | Why |
|
|
269
|
+
|---------|---------|-----|
|
|
270
|
+
| Dialog at level1 | Dialog at level3 | Dialogs should float above content |
|
|
271
|
+
| Heavy `boxShadow: '0 10px 50px'` | `boxShadow: 'level1'` | M3 uses subtle shadows |
|
|
272
|
+
| Card at level5 | Card at level1 | Cards shouldn't float too high |
|
|
273
|
+
| `bg: '#FFF', boxShadow: 'level1'` | `bg: 'surfaceContainerLow'` | Use surface containers, not raw colors |
|
|
274
|
+
| No elevation for dialogs | `bg: 'surfaceContainerHigh'` | Dialogs need visual separation |
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# Spacing Tokens
|
|
2
|
+
|
|
3
|
+
The design system uses a consistent spacing scale based on an 8px grid system for predictable, harmonious layouts.
|
|
4
|
+
|
|
5
|
+
## Spacing Scale
|
|
6
|
+
|
|
7
|
+
| Token | Value | Usage |
|
|
8
|
+
|-------|-------|-------|
|
|
9
|
+
| `none` | 0px | No spacing, reset margins/padding |
|
|
10
|
+
| `xxs` | 2px | Minimal gaps, icon spacing, borders |
|
|
11
|
+
| `xs` | 4px | Very tight spacing, inline elements |
|
|
12
|
+
| `sm` | 8px | Small gaps, compact layouts |
|
|
13
|
+
| `md` | 16px | Default spacing, most common use |
|
|
14
|
+
| `lg` | 24px | Larger sections, comfortable spacing |
|
|
15
|
+
| `xl` | 32px | Major sections, page margins |
|
|
16
|
+
| `xxl` | 48px | Large sections, page padding |
|
|
17
|
+
| `xxxl` | 64px | Maximum spacing, hero sections |
|
|
18
|
+
|
|
19
|
+
## Usage Principles
|
|
20
|
+
|
|
21
|
+
### The 8px Grid
|
|
22
|
+
All spacing values (except `xxs` and `xs`) are multiples of 8px. This creates:
|
|
23
|
+
- Visual rhythm and consistency
|
|
24
|
+
- Predictable alignment
|
|
25
|
+
- Easier mental math for designers and developers
|
|
26
|
+
- Better cross-browser rendering
|
|
27
|
+
|
|
28
|
+
### Common Patterns
|
|
29
|
+
|
|
30
|
+
#### Component Internal Spacing
|
|
31
|
+
```typescript
|
|
32
|
+
import { css } from '@discourser/design-system/styled-system/css';
|
|
33
|
+
|
|
34
|
+
// ✅ Button padding (handled by component)
|
|
35
|
+
const button = css({
|
|
36
|
+
px: 'md', // 16px horizontal
|
|
37
|
+
py: 'sm' // 8px vertical
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// ✅ Card padding
|
|
41
|
+
const card = css({
|
|
42
|
+
p: 'lg' // 24px all sides
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// ✅ Input field
|
|
46
|
+
const input = css({
|
|
47
|
+
px: 'md', // 16px horizontal
|
|
48
|
+
py: 'sm' // 8px vertical
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
#### Layout Spacing
|
|
53
|
+
```typescript
|
|
54
|
+
// ✅ Stack of elements
|
|
55
|
+
const stack = css({
|
|
56
|
+
display: 'flex',
|
|
57
|
+
flexDirection: 'column',
|
|
58
|
+
gap: 'md' // 16px between items
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// ✅ Grid layout
|
|
62
|
+
const grid = css({
|
|
63
|
+
display: 'grid',
|
|
64
|
+
gap: 'lg' // 24px between grid items
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// ✅ Container with padding
|
|
68
|
+
const container = css({
|
|
69
|
+
px: { base: 'md', lg: 'xl' }, // Responsive: 16px mobile, 32px desktop
|
|
70
|
+
py: 'lg' // 24px vertical
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### Margin Between Sections
|
|
75
|
+
```typescript
|
|
76
|
+
// ✅ Section spacing
|
|
77
|
+
const section = css({
|
|
78
|
+
mb: 'xxl' // 48px bottom margin
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// ✅ Page-level spacing
|
|
82
|
+
const page = css({
|
|
83
|
+
p: { base: 'lg', lg: 'xxl' } // 24px mobile, 48px desktop
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Common Use Cases
|
|
88
|
+
|
|
89
|
+
### Component Spacing
|
|
90
|
+
|
|
91
|
+
| Use Case | Token | Example |
|
|
92
|
+
|----------|-------|---------|
|
|
93
|
+
| Button padding (horizontal) | `md` | 16px |
|
|
94
|
+
| Button padding (vertical) | `sm` | 8px |
|
|
95
|
+
| Input padding | `md` | 16px |
|
|
96
|
+
| Card padding | `lg` or `xl` | 24-32px |
|
|
97
|
+
| Dialog padding | `lg` or `xl` | 24-32px |
|
|
98
|
+
| Icon margins | `xs` or `sm` | 4-8px |
|
|
99
|
+
|
|
100
|
+
### Layout Spacing
|
|
101
|
+
|
|
102
|
+
| Use Case | Token | Example |
|
|
103
|
+
|----------|-------|---------|
|
|
104
|
+
| Gap between form fields | `md` | 16px |
|
|
105
|
+
| Gap between cards | `lg` | 24px |
|
|
106
|
+
| Section margins | `xl` or `xxl` | 32-48px |
|
|
107
|
+
| Page margins | `xl` or `xxl` | 32-48px |
|
|
108
|
+
| Hero section padding | `xxxl` | 64px |
|
|
109
|
+
|
|
110
|
+
### Content Spacing
|
|
111
|
+
|
|
112
|
+
| Use Case | Token | Example |
|
|
113
|
+
|----------|-------|---------|
|
|
114
|
+
| Paragraph margins | `md` | 16px |
|
|
115
|
+
| List item spacing | `sm` or `md` | 8-16px |
|
|
116
|
+
| Icon-text gap | `xs` or `sm` | 4-8px |
|
|
117
|
+
| Button group gap | `sm` | 8px |
|
|
118
|
+
|
|
119
|
+
## Usage in Code
|
|
120
|
+
|
|
121
|
+
### Padding
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { css } from '@discourser/design-system/styled-system/css';
|
|
125
|
+
|
|
126
|
+
// All sides
|
|
127
|
+
const box = css({ p: 'md' }); // 16px all sides
|
|
128
|
+
|
|
129
|
+
// Individual sides
|
|
130
|
+
const box = css({
|
|
131
|
+
pt: 'lg', // padding-top: 24px
|
|
132
|
+
pr: 'md', // padding-right: 16px
|
|
133
|
+
pb: 'lg', // padding-bottom: 24px
|
|
134
|
+
pl: 'md' // padding-left: 16px
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Horizontal/Vertical
|
|
138
|
+
const box = css({
|
|
139
|
+
px: 'md', // padding-left + padding-right: 16px
|
|
140
|
+
py: 'sm' // padding-top + padding-bottom: 8px
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Margin
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// All sides
|
|
148
|
+
const box = css({ m: 'md' }); // 16px all sides
|
|
149
|
+
|
|
150
|
+
// Individual sides
|
|
151
|
+
const box = css({
|
|
152
|
+
mt: 'lg', // margin-top: 24px
|
|
153
|
+
mr: 'md', // margin-right: 16px
|
|
154
|
+
mb: 'lg', // margin-bottom: 24px
|
|
155
|
+
ml: 'md' // margin-left: 16px
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Horizontal/Vertical
|
|
159
|
+
const box = css({
|
|
160
|
+
mx: 'auto', // margin-left + margin-right: auto (centering)
|
|
161
|
+
my: 'lg' // margin-top + margin-bottom: 24px
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Gap (Flexbox/Grid)
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// Flex/Grid gap
|
|
169
|
+
const stack = css({
|
|
170
|
+
display: 'flex',
|
|
171
|
+
flexDirection: 'column',
|
|
172
|
+
gap: 'md' // 16px between all children
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Different horizontal/vertical gaps
|
|
176
|
+
const grid = css({
|
|
177
|
+
display: 'grid',
|
|
178
|
+
rowGap: 'lg', // 24px between rows
|
|
179
|
+
columnGap: 'md' // 16px between columns
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Responsive Spacing
|
|
184
|
+
|
|
185
|
+
Use different spacing values at different breakpoints:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
const container = css({
|
|
189
|
+
// Mobile: 16px padding
|
|
190
|
+
// Desktop: 32px padding
|
|
191
|
+
p: { base: 'md', lg: 'xl' }
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const section = css({
|
|
195
|
+
// Mobile: 24px margin
|
|
196
|
+
// Desktop: 48px margin
|
|
197
|
+
mb: { base: 'lg', lg: 'xxl' }
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## What NOT to Do
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
// ❌ NEVER use arbitrary pixel values
|
|
205
|
+
const wrong = css({ padding: '17px' });
|
|
206
|
+
const wrong = css({ margin: '13px' });
|
|
207
|
+
|
|
208
|
+
// ❌ NEVER use rem/em values directly
|
|
209
|
+
const wrong = css({ padding: '1.5rem' });
|
|
210
|
+
|
|
211
|
+
// ❌ NEVER use non-token spacing
|
|
212
|
+
const wrong = css({ gap: '20px' });
|
|
213
|
+
|
|
214
|
+
// ✅ ALWAYS use spacing tokens
|
|
215
|
+
const correct = css({ p: 'md', gap: 'lg' });
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Special Cases
|
|
219
|
+
|
|
220
|
+
### Zero Spacing
|
|
221
|
+
```typescript
|
|
222
|
+
// Reset spacing
|
|
223
|
+
const reset = css({
|
|
224
|
+
m: 'none', // margin: 0
|
|
225
|
+
p: 'none' // padding: 0
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Negative Margins
|
|
230
|
+
```typescript
|
|
231
|
+
// Negative margins (use sparingly)
|
|
232
|
+
const overlap = css({
|
|
233
|
+
mt: 'calc(var(--spacing-md) * -1)' // -16px
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Custom Spacing (Rare)
|
|
238
|
+
Only use custom spacing when absolutely necessary and none of the tokens fit:
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
// Last resort - custom spacing
|
|
242
|
+
const custom = css({
|
|
243
|
+
padding: 'calc(var(--spacing-sm) + var(--spacing-xs))' // 8px + 4px = 12px
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Accessibility
|
|
248
|
+
|
|
249
|
+
Proper spacing improves:
|
|
250
|
+
- **Touch targets**: Minimum 44x44px (use appropriate padding)
|
|
251
|
+
- **Readability**: Adequate spacing between text blocks
|
|
252
|
+
- **Scannability**: Clear visual separation between sections
|
|
253
|
+
- **Focus indicators**: Enough space for focus outlines
|
|
254
|
+
|
|
255
|
+
### Minimum Touch Target Spacing
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
// ✅ Adequate touch target (Button component handles this)
|
|
259
|
+
const button = css({
|
|
260
|
+
minHeight: '44px',
|
|
261
|
+
px: 'md',
|
|
262
|
+
py: 'sm'
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// ✅ Spacing between interactive elements
|
|
266
|
+
const buttonGroup = css({
|
|
267
|
+
display: 'flex',
|
|
268
|
+
gap: 'sm' // Minimum 8px between buttons
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Best Practices
|
|
273
|
+
|
|
274
|
+
1. **Prefer fewer, larger gaps** over many small gaps
|
|
275
|
+
2. **Use consistent spacing** within similar components
|
|
276
|
+
3. **Increase spacing** for more important separations
|
|
277
|
+
4. **Follow the 8px grid** whenever possible
|
|
278
|
+
5. **Test on mobile** to ensure adequate touch targets
|
|
279
|
+
6. **Use responsive spacing** for better mobile/desktop experiences
|
|
280
|
+
|
|
281
|
+
## Common Spacing Mistakes
|
|
282
|
+
|
|
283
|
+
| ❌ Wrong | ✅ Right | Why |
|
|
284
|
+
|---------|---------|-----|
|
|
285
|
+
| `gap: 'xs'` for cards | `gap: 'lg'` | Cards need breathing room |
|
|
286
|
+
| `p: 'xxxl'` for button | `px: 'md', py: 'sm'` | Too much padding overwhelms |
|
|
287
|
+
| `mb: 'sm'` for sections | `mb: 'xl'` or `'xxl'` | Sections need clear separation |
|
|
288
|
+
| `padding: '20px'` | `p: 'lg'` | Use tokens, not arbitrary values |
|
|
289
|
+
| `gap: 'lg'` between icons | `gap: 'xs'` or `'sm'` | Icons are small, need less space |
|