@codecademy/gamut 68.6.1-alpha.edab62.0 → 68.6.1-alpha.f6b2ce.0

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.
Files changed (46) hide show
  1. package/agent-tools/.cursor-plugin/plugin.json +1 -1
  2. package/agent-tools/DESIGN.Codecademy.md +466 -413
  3. package/agent-tools/DESIGN.LXStudio.md +350 -282
  4. package/agent-tools/DESIGN.Percipio.md +357 -279
  5. package/agent-tools/commands/gamut-review.md +176 -87
  6. package/agent-tools/guidelines/components/animations.md +74 -0
  7. package/agent-tools/guidelines/components/buttons.md +74 -23
  8. package/agent-tools/guidelines/components/card.md +19 -0
  9. package/agent-tools/guidelines/components/coachmark.md +21 -0
  10. package/agent-tools/guidelines/components/data-table.md +79 -0
  11. package/agent-tools/guidelines/components/forms.md +106 -0
  12. package/agent-tools/guidelines/components/loading-states.md +17 -0
  13. package/agent-tools/guidelines/components/menu.md +58 -0
  14. package/agent-tools/guidelines/components/overview.md +97 -17
  15. package/agent-tools/guidelines/components/radial-progress.md +13 -0
  16. package/agent-tools/guidelines/components/select.md +23 -0
  17. package/agent-tools/guidelines/components/tooltips.md +22 -0
  18. package/agent-tools/guidelines/components/video.md +29 -0
  19. package/agent-tools/guidelines/foundations/color.md +140 -58
  20. package/agent-tools/guidelines/foundations/modes.md +41 -17
  21. package/agent-tools/guidelines/foundations/spacing.md +78 -37
  22. package/agent-tools/guidelines/foundations/typography.md +69 -37
  23. package/agent-tools/guidelines/overview-icons.md +19 -0
  24. package/agent-tools/guidelines/overview-illustrations.md +7 -0
  25. package/agent-tools/guidelines/overview-patterns.md +7 -0
  26. package/agent-tools/guidelines/overview.md +71 -22
  27. package/agent-tools/guidelines/setup.md +59 -18
  28. package/agent-tools/rules/accessibility.mdc +22 -13
  29. package/agent-tools/skills/gamut-accessibility/SKILL.md +97 -112
  30. package/agent-tools/skills/gamut-color-mode/SKILL.md +91 -41
  31. package/agent-tools/skills/gamut-components/SKILL.md +46 -0
  32. package/agent-tools/skills/gamut-forms/SKILL.md +101 -0
  33. package/agent-tools/skills/gamut-style-utilities/SKILL.md +111 -0
  34. package/agent-tools/skills/gamut-system-props/SKILL.md +81 -29
  35. package/agent-tools/skills/gamut-testing/SKILL.md +106 -62
  36. package/agent-tools/skills/gamut-theming/SKILL.md +36 -86
  37. package/agent-tools/skills/gamut-typography/SKILL.md +36 -80
  38. package/bin/commands/plugin/install.mjs +96 -56
  39. package/bin/commands/plugin/list.mjs +11 -43
  40. package/bin/commands/plugin/remove.mjs +30 -38
  41. package/bin/commands/plugin/update.mjs +15 -5
  42. package/bin/gamut.mjs +17 -13
  43. package/bin/lib/design.mjs +71 -0
  44. package/bin/lib/io.mjs +14 -0
  45. package/package.json +6 -6
  46. package/bin/lib/figma.mjs +0 -49
@@ -4,344 +4,334 @@ name: Codecademy Design System
4
4
  description: Design tokens for codecademy.com
5
5
  colors:
6
6
  # palette — raw swatches; set once on :root and then always reference by token name, never use hex values directly in code
7
- hyper-500: "#3A10E5"
8
- hyper-400: "#5533FF"
9
- navy-900: "#0A0D1C"
10
- navy-800: "#10162F"
11
- navy-700: "#31374C"
12
- navy-600: "#4C5063"
13
- navy-500: "#686C7C"
14
- navy-300: "#BCBEC5"
15
- navy-200: "#E2E3E6"
16
- navy-100: "#F5F6F7"
17
- yellow-500: "#FFD300"
18
- yellow-400: "#CCA900"
19
- yellow-0: "#FFFAE5"
20
- yellow-900: "#211B00"
21
- green-700: "#008A27"
22
- green-400: "#AEE938"
23
- green-0: "#F5FFE3"
24
- green-900: "#151C07"
25
- red-600: "#BE1809"
26
- red-500: "#E91C11"
27
- red-400: "#DC5879"
28
- red-300: "#E85D7F"
29
- red-0: "#FBF1F0"
30
- red-900: "#280503"
31
- beige: "#FFF0E5"
32
- white: "#ffffff"
33
- black: "#000000"
7
+ hyper-500: '#3A10E5'
8
+ hyper-400: '#5533FF'
9
+ navy-900: '#0A0D1C'
10
+ navy-800: '#10162F'
11
+ navy-700: 'rgba(16, 22, 47, 0.86)'
12
+ navy-600: 'rgba(16, 22, 47, 0.75)'
13
+ navy-500: 'rgba(16, 22, 47, 0.63)'
14
+ navy-300: 'rgba(16, 22, 47, 0.28)'
15
+ navy-200: 'rgba(16, 22, 47, 0.12)'
16
+ navy-100: 'rgba(16, 22, 47, 0.04)'
17
+ yellow-500: '#FFD300'
18
+ yellow-400: '#CCA900'
19
+ yellow-0: '#FFFAE5'
20
+ yellow-900: '#211B00'
21
+ green-700: '#008A27'
22
+ green-400: '#AEE938'
23
+ green-0: '#F5FFE3'
24
+ green-900: '#151C07'
25
+ red-600: '#BE1809'
26
+ red-500: '#E91C11'
27
+ red-400: '#DC5879'
28
+ red-300: '#E85D7F'
29
+ red-0: '#FBF1F0'
30
+ red-900: '#280503'
31
+ beige: '#FFF0E5'
32
+ white: '#ffffff'
33
+ black: '#000000'
34
34
  # semantic aliases (light mode) — use these in code, not palette swatches or hex values
35
- text: "{colors.navy-800}"
36
- text-accent: "{colors.navy-900}"
37
- text-secondary: "{colors.navy-600}"
38
- text-disabled: "{colors.navy-500}"
39
- background: "{colors.white}"
40
- background-primary: "{colors.beige}"
41
- background-contrast: "{colors.white}"
42
- background-selected: "{colors.navy-100}"
43
- background-hover: "{colors.navy-200}"
44
- background-disabled: "{colors.navy-200}"
45
- background-success: "{colors.green-0}"
46
- background-warning: "{colors.yellow-0}"
47
- background-error: "{colors.red-0}"
48
- primary: "{colors.hyper-500}"
49
- primary-hover: "{colors.hyper-400}"
50
- primary-inverse: "{colors.yellow-500}"
51
- secondary: "{colors.navy-800}"
52
- secondary-hover: "{colors.navy-700}"
53
- interface: "{colors.hyper-500}"
54
- interface-hover: "{colors.hyper-400}"
55
- danger: "{colors.red-500}"
56
- danger-hover: "{colors.red-600}"
57
- feedback-error: "{colors.red-600}"
58
- feedback-success: "{colors.green-700}"
59
- feedback-warning: "{colors.yellow-500}"
60
- border-primary: "{colors.navy-800}"
61
- border-secondary: "{colors.navy-600}"
62
- border-tertiary: "{colors.navy-300}"
63
- border-disabled: "{colors.navy-500}"
64
- shadow-primary: "{colors.navy-800}"
65
- shadow-secondary: "{colors.navy-600}"
35
+ text: '{colors.navy-800}'
36
+ text-accent: '{colors.navy-900}'
37
+ text-secondary: '{colors.navy-600}'
38
+ text-disabled: '{colors.navy-500}'
39
+ background: '{colors.white}'
40
+ background-primary: '{colors.beige}'
41
+ background-contrast: '{colors.white}'
42
+ background-selected: '{colors.navy-100}'
43
+ background-hover: '{colors.navy-200}'
44
+ background-disabled: '{colors.navy-200}'
45
+ background-success: '{colors.green-0}'
46
+ background-warning: '{colors.yellow-0}'
47
+ background-error: '{colors.red-0}'
48
+ primary: '{colors.hyper-500}'
49
+ primary-hover: '{colors.hyper-400}'
50
+ primary-inverse: '{colors.yellow-500}'
51
+ secondary: '{colors.navy-800}'
52
+ secondary-hover: '{colors.navy-700}'
53
+ danger: '{colors.red-500}'
54
+ danger-hover: '{colors.red-600}'
55
+ feedback-error: '{colors.red-600}'
56
+ feedback-success: '{colors.green-700}'
57
+ feedback-warning: '{colors.yellow-500}'
58
+ border-primary: '{colors.navy-800}'
59
+ border-secondary: '{colors.navy-600}'
60
+ border-tertiary: '{colors.navy-300}'
61
+ border-disabled: '{colors.navy-500}'
62
+ shadow-primary: '{colors.navy-800}'
63
+ shadow-secondary: '{colors.navy-600}'
66
64
  typography:
67
65
  base:
68
66
  fontFamily: '"Apercu", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif'
69
- fontSize: "1rem"
70
- fontWeight: "400"
71
- lineHeight: "1.5"
67
+ fontSize: '1rem'
68
+ fontWeight: '400'
69
+ lineHeight: '1.5'
72
70
  accent:
73
71
  fontFamily: '"Suisse", "Apercu", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif'
74
- fontSize: "0.875rem"
75
- fontWeight: "400"
76
- lineHeight: "1.5"
72
+ fontSize: '0.875rem'
73
+ fontWeight: '400'
74
+ lineHeight: '1.5'
77
75
  title:
78
76
  fontFamily: '"Apercu", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif'
79
- fontSize: "2.125rem"
80
- fontWeight: "700"
81
- lineHeight: "1.2"
82
- hankenGrotesk:
83
- fontFamily: '"Hanken Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif'
77
+ fontSize: '2.125rem'
78
+ fontWeight: '700'
79
+ lineHeight: '1.2'
84
80
  monospace:
85
81
  fontFamily: 'Monaco, Menlo, "Ubuntu Mono", "Droid Sans Mono", Consolas, monospace'
86
- rounded:
87
- none: "0px"
88
- sm: "2px"
89
- md: "4px"
90
- lg: "8px"
91
- xl: "16px"
92
- full: "999px"
82
+ system:
83
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif'
84
+ '14':
85
+ fontSize: '0.875rem'
86
+ '16':
87
+ fontSize: '1rem'
88
+ '18':
89
+ fontSize: '1.125rem'
90
+ '20':
91
+ fontSize: '1.25rem'
92
+ '22':
93
+ fontSize: '1.375rem'
94
+ '26':
95
+ fontSize: '1.625rem'
96
+ '34':
97
+ fontSize: '2.125rem'
98
+ '44':
99
+ fontSize: '2.75rem'
100
+ '64':
101
+ fontSize: '4rem'
102
+ borderRadii:
103
+ none: '0px'
104
+ sm: '2px'
105
+ md: '4px'
106
+ lg: '8px'
107
+ xl: '16px'
108
+ full: '999px'
93
109
  spacing:
94
- "0": "0"
95
- "4": "0.25rem"
96
- "8": "0.5rem"
97
- "12": "0.75rem"
98
- "16": "1rem"
99
- "24": "1.5rem"
100
- "32": "2rem"
101
- "40": "2.5rem"
102
- "48": "3rem"
103
- "64": "4rem"
104
- "96": "6rem"
110
+ '0': '0'
111
+ '4': '0.25rem'
112
+ '8': '0.5rem'
113
+ '12': '0.75rem'
114
+ '16': '1rem'
115
+ '24': '1.5rem'
116
+ '32': '2rem'
117
+ '40': '2.5rem'
118
+ '48': '3rem'
119
+ '64': '4rem'
120
+ '96': '6rem'
105
121
  components:
106
122
  FillButton:
107
- backgroundColor: "{colors.primary}"
108
- textColor: "{colors.white}"
109
- rounded: "{rounded.md}"
123
+ backgroundColor: '{colors.primary}'
124
+ textColor: '{colors.white}'
125
+ borderRadii: '{borderRadii.md}'
110
126
  FillButton-hover:
111
- backgroundColor: "{colors.primary-hover}"
112
- textColor: "{colors.white}"
127
+ backgroundColor: '{colors.primary-hover}'
128
+ textColor: '{colors.white}'
113
129
  FillButton-danger:
114
- backgroundColor: "{colors.danger}"
115
- textColor: "{colors.white}"
116
- rounded: "{rounded.md}"
130
+ backgroundColor: '{colors.danger}'
131
+ textColor: '{colors.white}'
132
+ borderRadii: '{borderRadii.md}'
117
133
  FillButton-danger-hover:
118
- backgroundColor: "{colors.danger-hover}"
119
- textColor: "{colors.white}"
134
+ backgroundColor: '{colors.danger-hover}'
135
+ textColor: '{colors.white}'
120
136
  StrokeButton:
121
- backgroundColor: "transparent"
122
- textColor: "{colors.secondary}"
123
- rounded: "{rounded.md}"
137
+ backgroundColor: 'transparent'
138
+ textColor: '{colors.secondary}'
139
+ borderRadii: '{borderRadii.md}'
140
+ StrokeButton-hover:
141
+ textColor: '{colors.secondary-hover}'
142
+ IconButton:
143
+ backgroundColor: 'transparent'
144
+ textColor: '{colors.secondary}'
145
+ IconButton-hover:
146
+ textColor: '{colors.secondary-hover}'
124
147
  CTAButton:
125
- backgroundColor: "{colors.primary}"
126
- textColor: "{colors.white}"
127
- rounded: "{rounded.md}"
148
+ backgroundColor: '{colors.primary}'
149
+ textColor: '{colors.white}'
150
+ borderRadii: '{borderRadii.md}'
128
151
  CTAButton-inverse:
129
- backgroundColor: "{colors.primary-inverse}"
130
- textColor: "{colors.secondary}"
131
- rounded: "{rounded.md}"
152
+ backgroundColor: '{colors.primary-inverse}'
153
+ textColor: '{colors.secondary}'
154
+ borderRadii: '{borderRadii.md}'
132
155
  TextButton:
133
- backgroundColor: "transparent"
134
- textColor: "{colors.primary}"
156
+ backgroundColor: 'transparent'
157
+ textColor: '{colors.primary}'
135
158
  TextButton-hover:
136
- textColor: "{colors.primary-hover}"
159
+ textColor: '{colors.primary-hover}'
137
160
  Card:
138
- backgroundColor: "{colors.background}"
139
- rounded: "{rounded.none}"
161
+ backgroundColor: '{colors.background}'
162
+ borderRadii: '{borderRadii.none}'
140
163
  Card-interactive:
141
- rounded: "{rounded.md}"
164
+ borderRadii: '{borderRadii.md}'
142
165
  Card-elevated:
143
- backgroundColor: "{colors.background-primary}"
144
- rounded: "{rounded.lg}"
166
+ backgroundColor: '{colors.background-primary}'
167
+ borderRadii: '{borderRadii.lg}'
145
168
  Card-beige:
146
- backgroundColor: "{colors.beige}"
147
- rounded: "{rounded.lg}"
169
+ backgroundColor: '{colors.beige}'
170
+ borderRadii: '{borderRadii.lg}'
148
171
  Input:
149
- backgroundColor: "{colors.background}"
150
- textColor: "{colors.text}"
151
- rounded: "{rounded.md}"
172
+ backgroundColor: '{colors.background}'
173
+ textColor: '{colors.text}'
174
+ borderRadii: '{borderRadii.md}'
152
175
  Checkbox:
153
- backgroundColor: "{colors.interface}"
154
- rounded: "{rounded.sm}"
176
+ backgroundColor: '{colors.primary}'
177
+ borderRadii: '{borderRadii.sm}'
155
178
  Checkbox-hover:
156
- backgroundColor: "{colors.interface-hover}"
179
+ backgroundColor: '{colors.primary-hover}'
157
180
  Headline:
158
- textColor: "{colors.text-accent}"
159
- typography: "{typography.title}"
181
+ textColor: '{colors.text-accent}'
182
+ typography: '{typography.title}'
160
183
  Tag-success:
161
- backgroundColor: "{colors.feedback-success}"
162
- textColor: "{colors.white}"
163
- rounded: "{rounded.sm}"
184
+ backgroundColor: '{colors.feedback-success}'
185
+ textColor: '{colors.white}'
186
+ borderRadii: '{borderRadii.sm}'
164
187
  Tag-warning:
165
- backgroundColor: "{colors.feedback-warning}"
166
- textColor: "{colors.text}"
167
- rounded: "{rounded.sm}"
188
+ backgroundColor: '{colors.feedback-warning}'
189
+ textColor: '{colors.text}'
190
+ borderRadii: '{borderRadii.sm}'
168
191
  Alert-error:
169
- backgroundColor: "{colors.background-error}"
170
- textColor: "{colors.feedback-error}"
192
+ backgroundColor: '{colors.background-error}'
193
+ textColor: '{colors.feedback-error}'
171
194
  Alert-success:
172
- backgroundColor: "{colors.background-success}"
173
- textColor: "{colors.text}"
195
+ backgroundColor: '{colors.background-success}'
196
+ textColor: '{colors.text}'
174
197
  Alert-warning:
175
- backgroundColor: "{colors.background-warning}"
176
- textColor: "{colors.text}"
198
+ backgroundColor: '{colors.background-warning}'
199
+ textColor: '{colors.text}'
177
200
  ---
178
201
 
179
202
  # Codecademy
180
203
 
181
204
  This file defines the visual design tokens for codecademy.com, implemented using the Gamut design system (`@codecademy/gamut`, `@codecademy/gamut-styles`). Gamut ships 52 components with Figma ↔ code mappings via Figma Code Connect.
182
205
 
183
- **Figma file**: https://www.figma.com/design/ReGfRNillGABAj5SlITalN/📐-Gamut
184
- **Storybook**: https://gamut.codecademy.com
206
+ Figma file: https://www.figma.com/design/ReGfRNillGABAj5SlITalN/📐-Gamut
207
+ Storybook: https://gamut.codecademy.com
185
208
 
186
209
  ---
187
210
 
188
- ## Visual Theme & Atmosphere
211
+ ## Overview
189
212
 
190
- Codecademy communicates **logic with personality** — structured and trustworthy enough for a learning platform, with creative moments that feel engaging and human. The design voice is: *"we are ruled by logic, but are creative and a bit unexpected as well."*
213
+ Codecademy communicates logic with personality — structured and trustworthy enough for a learning platform, with creative moments that feel engaging and human. The design voice is: _"we are ruled by logic, but are creative and a bit unexpected as well."_
191
214
 
192
- **Density**: Medium. Information-dense layouts use careful whitespace and strong typographic hierarchy to stay readable. Avoid cramped or overly airy layouts.
215
+ Density: Medium. Information-dense layouts use careful whitespace and strong typographic hierarchy to stay readable. Avoid cramped or overly airy layouts.
216
+
217
+ Design philosophy:
193
218
 
194
- **Design philosophy**:
195
219
  - Components are color mode–aware by default — never hardcode hex values for adaptive UI
196
220
  - Every component works across all themes without modification
197
221
  - Mobile-first responsive design built on a 12-column grid
198
- - Accessibility is guaranteed by design: semantic color tokens meet contrast requirements per mode automatically
199
-
200
- ---
201
-
202
- ## Themes
203
-
204
- Codecademy products use one of four Gamut themes, all sharing the same core visual identity. Token aliases resolve to the right values per theme automatically — components require no modification.
205
-
206
- | Theme | Use case | Base font | Dark mode |
207
- |---|---|---|---|
208
- | **Core** | Codecademy (default) | Apercu | ✓ light + dark |
209
- | **Admin** | Codecademy admin tools | Apercu | ✓ light + dark |
210
- | **Platform** | Codecademy learning environment | Apercu | ✓ light + dark |
211
- | **LX Studio** | LX Studio application | Hanken Grotesk | light only |
212
-
213
- The active theme is set at the app root via `<GamutProvider>`. When designing, know which theme your screen targets — it affects primary colors, font families, and available color weights.
214
-
215
- **Font licensing**: Apercu is licensed for codecademy.com only. LX Studio uses Hanken Grotesk.
222
+ - Use semantic tokens (`background-primary`, not raw `beige`) so colors adapt across themes and modes
216
223
 
217
- For Percipio projects, use `DESIGN.Percipio.md` from the same package instead.
224
+ ### Themes
218
225
 
219
- ### LX Studio theme overrides
226
+ Codecademy products use three Gamut themes, all sharing the same core visual identity. Token aliases resolve to the right values per theme automatically — components require no modification.
220
227
 
221
- LX Studio extends Core with these differences:
228
+ | Theme | Use case | Base font | Dark mode |
229
+ | -------- | ------------------------------- | --------- | -------------- |
230
+ | Core | Codecademy (default) | Apercu | ✓ light + dark |
231
+ | Admin | Codecademy admin tools | Apercu | ✓ light + dark |
232
+ | Platform | Codecademy learning environment | Apercu | ✓ light + dark |
222
233
 
223
- **Font**: All families `"Hanken Grotesk"` (no Apercu, no Suisse).
234
+ Set the active theme at the app root via `<GamutProvider theme={theme}>` (or `adminTheme` / `platformTheme`). Install this file in app repos: `gamut plugin install cursor --theme core` (copies to `./DESIGN.md`).
224
235
 
225
- **Border radii** (all values shift up one step):
226
-
227
- | Token | Core | LX Studio |
228
- |---|---|---|
229
- | `sm` | 2px | 4px |
230
- | `md` | 4px | 8px |
231
- | `lg` | 8px | 12px |
232
-
233
- **Semantic color overrides (light mode)**:
234
-
235
- | Token | Core value | LX Studio value |
236
- |---|---|---|
237
- | `primary` | hyper-500 `#3A10E5` | `#5628FE` (lxStudioPurple) |
238
- | `primary-hover` | hyper-400 `#5533FF` | `#7955FC` (lxStudioPurpleHover) |
239
- | `interface` | hyper-500 | hyper-500 (unchanged) |
240
- | `feedback-success` | green-700 `#008A27` | `#06844F` (lxStudioSuccess) |
241
- | `background-primary` | beige `#FFF0E5` | `#FAFBFC` (lxStudioBgPrimary) |
242
- | `shadow-primary` | navy-800 | navy-200 |
243
- | `border-primary` | navy-800 | navy-400 |
244
- | `border-disabled` | navy-500 | navy-300 |
236
+ For other Skillsoft products: `gamut plugin install cursor --theme percipio` or `--theme lxstudio` (see `DESIGN.Percipio.md`, `DESIGN.LXStudio.md`).
245
237
 
246
238
  ---
247
239
 
248
- ## Semantic Color Aliases
240
+ ## Colors
241
+
242
+ Use semantic token names in code and designs. They resolve to the correct raw value for the active theme and color mode automatically. Never hardcode hex values for anything that needs to adapt across modes. Never hardcode core-theme values like `beige` — use `background-primary` and other semantic aliases.
249
243
 
250
- Use these token names when specifying colors in designs. They resolve to the correct raw value for the active theme and color mode automatically. **Never hardcode hex values** for anything that needs to adapt across modes.
244
+ For dark/light regions, use `<ColorMode>` or `<Background>` from `@codecademy/gamut-styles` never swap colors manually with custom CSS.
251
245
 
252
246
  ### Text
253
247
 
254
- | Token | Light | Dark | Use for |
255
- |---|---|---|---|
256
- | `text` | navy-800 `#10162F` at 100% | white `#ffffff` | Default body and UI text |
257
- | `text-accent` | navy-900 `#0A0D1C` | beige `#FFF0E5` | Stronger emphasis text |
258
- | `text-secondary` | navy-800 at 75% | white at 65% | Supporting / secondary copy |
259
- | `text-disabled` | navy-800 at 63% | white at 50% | Disabled state labels |
248
+ | Token | Light | Dark | Use for |
249
+ | ---------------- | -------------------------- | --------------- | --------------------------- |
250
+ | `text` | navy-800 `#10162F` at 100% | white `#ffffff` | Default body and UI text |
251
+ | `text-accent` | navy-900 `#0A0D1C` | beige `#FFF0E5` | Stronger emphasis text |
252
+ | `text-secondary` | navy-800 at 75% | white at 65% | Supporting / secondary copy |
253
+ | `text-disabled` | navy-800 at 63% | white at 50% | Disabled state labels |
260
254
 
261
255
  ### Background
262
256
 
263
- | Token | Light | Dark | Use for |
264
- |---|---|---|---|
265
- | `background` | white `#ffffff` | navy-800 `#10162F` | Default page/component background |
266
- | `background-primary` | beige `#FFF0E5` | navy-900 `#0A0D1C` | Slightly elevated surfaces |
267
- | `background-contrast` | white | black `#000000` | Maximum contrast surface |
268
- | `background-selected` | navy-800 at 4% | white at 4% | Selected row / item |
269
- | `background-hover` | navy-800 at 12% | white at 9% | Hover state overlay |
270
- | `background-disabled` | navy-800 at 12% | white at 9% | Disabled surface |
271
- | `background-success` | green-0 `#F5FFE3` | green-900 `#151C07` | Success state container |
272
- | `background-warning` | yellow-0 `#FFFAE5` | yellow-900 `#211B00` | Warning state container |
273
- | `background-error` | red-0 `#FBF1F0` | red-900 `#280503` | Error state container |
257
+ | Token | Light | Dark | Use for |
258
+ | --------------------- | ------------------ | -------------------- | --------------------------------- |
259
+ | `background` | white `#ffffff` | navy-800 `#10162F` | Default page/component background |
260
+ | `background-primary` | beige `#FFF0E5` | navy-900 `#0A0D1C` | Slightly elevated surfaces |
261
+ | `background-contrast` | white | black `#000000` | Maximum contrast surface |
262
+ | `background-selected` | navy-800 at 4% | white at 4% | Selected row / item |
263
+ | `background-hover` | navy-800 at 12% | white at 9% | Hover state overlay |
264
+ | `background-disabled` | navy-800 at 12% | white at 9% | Disabled surface |
265
+ | `background-success` | green-0 `#F5FFE3` | green-900 `#151C07` | Success state container |
266
+ | `background-warning` | yellow-0 `#FFFAE5` | yellow-900 `#211B00` | Warning state container |
267
+ | `background-error` | red-0 `#FBF1F0` | red-900 `#280503` | Error state container |
274
268
 
275
269
  ### Interactive
276
270
 
277
- | Token | Light | Dark | Use for |
278
- |---|---|---|---|
279
- | `primary` | hyper-500 `#3A10E5` | yellow-500 `#FFD300` | Primary CTA, links, focus rings |
280
- | `primary-hover` | hyper-400 `#5533FF` | yellow-400 `#CCA900` | Hover state of primary interactive |
281
- | `primary-inverse` | yellow-500 `#FFD300` | hyper-500 `#3A10E5` | Primary on a colored background |
282
- | `secondary` | navy-800 `#10162F` | white `#ffffff` | Secondary CTA, ghost buttons |
283
- | `secondary-hover` | navy-800 at 86% | white at 80% | Hover state of secondary interactive |
284
- | `interface` | hyper-500 `#3A10E5` | yellow-500 `#FFD300` | UI affordances (checkboxes, toggles, sliders) |
285
- | `interface-hover` | hyper-400 `#5533FF` | yellow-400 `#CCA900` | Hover on interface elements |
286
- | `danger` | red-500 `#E91C11` | red-300 `#E85D7F` | Destructive actions, error states |
287
- | `danger-hover` | red-600 `#BE1809` | red-400 `#DC5879` | Hover on danger interactive |
271
+ | Token | Light | Dark | Use for |
272
+ | ----------------- | -------------------- | -------------------- | ------------------------------------ |
273
+ | `primary` | hyper-500 `#3A10E5` | yellow-500 `#FFD300` | Primary CTA, links, focus rings |
274
+ | `primary-hover` | hyper-400 `#5533FF` | yellow-400 `#CCA900` | Hover state of primary interactive |
275
+ | `primary-inverse` | yellow-500 `#FFD300` | hyper-500 `#3A10E5` | Primary on a colored background |
276
+ | `secondary` | navy-800 `#10162F` | white `#ffffff` | Secondary CTA, ghost buttons |
277
+ | `secondary-hover` | navy-800 at 86% | white at 80% | Hover state of secondary interactive |
278
+ | `danger` | red-500 `#E91C11` | red-300 `#E85D7F` | Destructive actions, error states |
279
+ | `danger-hover` | red-600 `#BE1809` | red-400 `#DC5879` | Hover on danger interactive |
288
280
 
289
281
  ### Border
290
282
 
291
- | Token | Light | Dark | Use for |
292
- |---|---|---|---|
293
- | `border-primary` | navy-800 `#10162F` | white `#ffffff` | Strong borders, dividers |
294
- | `border-secondary` | navy-800 at 75% | white at 65% | Medium-weight borders |
295
- | `border-tertiary` | navy-800 at 28% | white at 20% | Subtle borders, separators |
296
- | `border-disabled` | navy-800 at 63% | white at 50% | Disabled input borders |
283
+ | Token | Light | Dark | Use for |
284
+ | ------------------ | ------------------ | --------------- | -------------------------- |
285
+ | `border-primary` | navy-800 `#10162F` | white `#ffffff` | Strong borders, dividers |
286
+ | `border-secondary` | navy-800 at 75% | white at 65% | Medium-weight borders |
287
+ | `border-tertiary` | navy-800 at 28% | white at 20% | Subtle borders, separators |
288
+ | `border-disabled` | navy-800 at 63% | white at 50% | Disabled input borders |
297
289
 
298
290
  ### Feedback
299
291
 
300
- | Token | Light | Dark | Use for |
301
- |---|---|---|---|
302
- | `feedback-error` | red-600 `#BE1809` | red-300 `#E85D7F` | Error messages, validation |
303
- | `feedback-success` | green-700 `#008A27` | green-400 `#AEE938` | Success messages, confirmations |
304
- | `feedback-warning` | yellow `#FFD300` | yellow-0 `#FFFAE5` | Warning messages, caution states |
292
+ | Token | Light | Dark | Use for |
293
+ | ------------------ | ------------------- | ------------------- | -------------------------------- |
294
+ | `feedback-error` | red-600 `#BE1809` | red-300 `#E85D7F` | Error messages, validation |
295
+ | `feedback-success` | green-700 `#008A27` | green-400 `#AEE938` | Success messages, confirmations |
296
+ | `feedback-warning` | yellow `#FFD300` | yellow-0 `#FFFAE5` | Warning messages, caution states |
305
297
 
306
298
  ### Shadow
307
299
 
308
- | Token | Light | Dark |
309
- |---|---|---|
310
- | `shadow-primary` | navy-800 | white |
300
+ | Token | Light | Dark |
301
+ | ------------------ | --------------- | ------------ |
302
+ | `shadow-primary` | navy-800 | white |
311
303
  | `shadow-secondary` | navy-800 at 75% | white at 65% |
312
304
 
313
305
  ---
314
306
 
315
- ## Raw Color Palette
316
-
317
- All colors available as static tokens regardless of color mode. Use these only when a color should be **fixed** and not adapt to dark mode.
318
-
319
- ### Core Palette
320
-
321
- | Name | Weights available | Notes |
322
- |---|---|---|
323
- | `navy` | 100–900 | 100–700 are rgba transparencies of `#10162F`; 800 = `#10162F`; 900 = `#0A0D1C` |
324
- | `white` | 100–700 | rgba transparencies of `#ffffff` (no solid white weight — use `white` for `#fff`) |
325
- | `blue` | 0, 100, 300, 400, 500, 800 | 500 = `#1557FF` |
326
- | `hyper` | 400, 500 | 500 = `#3A10E5` (purple-blue), 400 = `#5533FF` |
327
- | `green` | 0, 100, 400, 700, 900 | 700 = `#008A27` |
328
- | `yellow` | 0, 400, 500, 900 | 500 = `#FFD300` |
329
- | `red` | 0, 300, 400, 500, 600, 900 | 500 = `#E91C11` |
330
- | `gray` | 100, 200, 300, 600, 800, 900 | |
331
- | `pink` | 0, 400 | 400 = `#F966FF` |
332
- | `orange` | 100, 500 | 500 = `#FF8C00` |
333
- | `beige` | 100 (alias: `beige`) | `#FFF0E5` |
334
- | `black` | — | `#000000` |
335
- | `white` (solid) | — | `#ffffff` |
336
-
337
- **Named aliases** (shorthand for common weights):
307
+ ### Raw color palette
308
+
309
+ All colors available as static tokens regardless of color mode. Use these only when a color should be fixed and not adapt to dark mode (e.g. `<Background bg="navy">` on Codecademy-branded surfaces).
310
+
311
+ #### Core palette
312
+
313
+ | Name | Weights available | Notes |
314
+ | --------------- | ---------------------------- | --------------------------------------------------------------------------------- |
315
+ | `navy` | 100–900 | 100–700 are rgba transparencies of `#10162F`; 800 = `#10162F`; 900 = `#0A0D1C` |
316
+ | `white` | 100–700 | rgba transparencies of `#ffffff` (no solid white weight — use `white` for `#fff`) |
317
+ | `blue` | 0, 100, 300, 400, 500, 800 | 500 = `#1557FF` |
318
+ | `hyper` | 400, 500 | 500 = `#3A10E5` (purple-blue), 400 = `#5533FF` |
319
+ | `green` | 0, 100, 400, 700, 900 | 700 = `#008A27` |
320
+ | `yellow` | 0, 400, 500, 900 | 500 = `#FFD300` |
321
+ | `red` | 0, 300, 400, 500, 600, 900 | 500 = `#E91C11` |
322
+ | `gray` | 100, 200, 300, 600, 800, 900 | |
323
+ | `pink` | 0, 400 | 400 = `#F966FF` |
324
+ | `orange` | 100, 500 | 500 = `#FF8C00` |
325
+ | `beige` | 100 (alias: `beige`) | `#FFF0E5` |
326
+ | `black` | — | `#000000` |
327
+ | `white` (solid) | — | `#ffffff` |
328
+
329
+ Named aliases (shorthand for common weights):
338
330
  `beige`, `blue`, `green`, `hyper`, `lightBlue`, `lightGreen`, `navy`, `orange`, `paleBlue`, `paleGreen`, `palePink`, `paleRed`, `paleYellow`, `pink`, `red`, `yellow`, `black`, `white`
339
331
 
340
- ### Platform-only additions
341
- `lightBeige` (`#FFFBF8`), `gold` (`#8A7300`), `teal` (`#006D82`), `purple` (`#B3CCFF`)
332
+ #### Platform-only additions
342
333
 
343
- ### LX Studio additions
344
- `lxStudioPurple` (`#5628FE`), `lxStudioPurpleHover` (`#7955FC`), `lxStudioSuccess` (`#06844F`)
334
+ `lightBeige` (`#FFFBF8`), `gold` (`#8A7300`), `teal` (`#006D82`), `purple` (`#B3CCFF`)
345
335
 
346
336
  ---
347
337
 
@@ -349,52 +339,50 @@ All colors available as static tokens regardless of color mode. Use these only w
349
339
 
350
340
  ### Typefaces
351
341
 
352
- | Token | Core / Admin / Platform | LX Studio | Use for |
353
- |---|---|---|---|
354
- | `base` | Apercu Pro (CSS: `Apercu`) | Hanken Grotesk | All default UI text, headlines, body copy |
355
- | `accent` | Suisse Intl Mono (CSS: `Suisse`); falls back to `Apercu` | Hanken Grotesk | Code, captions, labels, lists, technical context |
356
- | `monospace` | Monaco, Menlo, Ubuntu Mono, Droid Sans Mono, Consolas | Monaco, Menlo, Ubuntu Mono, Droid Sans Mono, Consolas | Code editor contexts |
357
- | `system` | System UI fonts | System UI fonts | Performance-critical surfaces |
358
-
359
- **Apercu is licensed for codecademy.com only.** LX Studio uses Hanken Grotesk for both `base` and `accent`.
342
+ | Token | Font | Use for |
343
+ | ----------- | -------------------------------------------------------- | ------------------------------------------------ |
344
+ | `base` | Apercu Pro (CSS: `Apercu`) | All default UI text, headlines, body copy |
345
+ | `accent` | Suisse Intl Mono (CSS: `Suisse`); falls back to `Apercu` | Code, captions, labels, lists, technical context |
346
+ | `monospace` | Monaco, Menlo, Ubuntu Mono, Droid Sans Mono, Consolas | Code editor contexts |
347
+ | `system` | System UI fonts | Performance-critical surfaces |
360
348
 
361
349
  ### Rules
362
350
 
363
- - **Apercu Bold** for headlines, sub-headlines, CTAs, and buttons.
364
- - **Apercu Regular** for body text, UI labels, and menu items.
365
- - **Apercu Italic** to emphasize text within a Regular paragraph — not Bold.
366
- - **Suisse** sparingly: code snippets, enumerated items, quotations, captions. Reads 10–15% large for its point size — size down relative to Apercu (14px Suisse ≈ 16px Apercu visually).
367
- - Text is **left-aligned** by default. Center-align only for short marketing headlines. Never right-align.
351
+ - Apercu Bold for headlines, sub-headlines, CTAs, and buttons.
352
+ - Apercu Regular for body text, UI labels, and menu items.
353
+ - Apercu Italic to emphasize text within a Regular paragraph — not Bold.
354
+ - Suisse sparingly: code snippets, enumerated items, quotations, captions. Reads 10–15% large for its point size — size down relative to Apercu (14px Suisse ≈ 16px Apercu visually).
355
+ - Text is left-aligned by default. Center-align only for short marketing headlines. Never right-align.
368
356
  - Do not adjust letter-spacing.
369
357
 
370
358
  ### Font size scale
371
359
 
372
- | Token key | Size | Common use |
373
- |---|---|---|
374
- | `64` | 64px | Hero / display |
375
- | `44` | 44px | Page titles |
376
- | `34` | 34px | Section titles |
377
- | `26` | 26px | Sub-section titles |
378
- | `22` | 22px | Card titles, large UI labels |
379
- | `20` | 20px | Secondary titles |
380
- | `18` | 18px | Large body, intro text |
381
- | `16` | 16px | Default body text |
382
- | `14` | 14px | Small body, captions, labels |
360
+ | Token key | Size | Common use |
361
+ | --------- | ---- | ---------------------------- |
362
+ | `64` | 64px | Hero / display |
363
+ | `44` | 44px | Page titles |
364
+ | `34` | 34px | Section titles |
365
+ | `26` | 26px | Sub-section titles |
366
+ | `22` | 22px | Card titles, large UI labels |
367
+ | `20` | 20px | Secondary titles |
368
+ | `18` | 18px | Large body, intro text |
369
+ | `16` | 16px | Default body text |
370
+ | `14` | 14px | Small body, captions, labels |
383
371
 
384
372
  ### Font weight scale
385
373
 
386
- | Token | Value | Use |
387
- |---|---|---|
388
- | `base` | 400 | Body text, UI labels |
389
- | `title` | 700 | Headlines, CTAs, buttons |
374
+ | Token | Value | Use |
375
+ | ------- | ----- | ------------------------ |
376
+ | `base` | 400 | Body text, UI labels |
377
+ | `title` | 700 | Headlines, CTAs, buttons |
390
378
 
391
379
  ### Line height scale
392
380
 
393
- | Token | Value | Use |
394
- |---|---|---|
395
- | `base` | 1.5 | Body text (150% — aim for 150–175% of font size) |
396
- | `spacedTitle` | 1.3 | Sub-headlines and medium titles |
397
- | `title` | 1.2 | Large headlines (aim for 100–110% of font size) |
381
+ | Token | Value | Use |
382
+ | ------------- | ----- | ------------------------------------------------ |
383
+ | `base` | 1.5 | Body text (150% — aim for 150–175% of font size) |
384
+ | `spacedTitle` | 1.3 | Sub-headlines and medium titles |
385
+ | `title` | 1.2 | Large headlines (aim for 100–110% of font size) |
398
386
 
399
387
  ### Line length
400
388
 
@@ -402,27 +390,90 @@ Target 45–85 characters per line; 66 characters is ideal for web body text. Ma
402
390
 
403
391
  ---
404
392
 
405
- ## Spacing Scale
393
+ ## Layout
406
394
 
407
- All spacing is multiples of 4px, placed on an 8px grid.
395
+ ### Spacing scale
396
+
397
+ All spacing is multiples of 4px, placed on an 8px grid. Use only these values for padding, margin, gap, width, and height — snap to the nearest token if a design specifies an off-scale value.
408
398
 
409
399
  | Token | Value |
410
- |---|---|
411
- | `0` | 0 |
412
- | `4` | 4px |
413
- | `8` | 8px |
414
- | `12` | 12px |
415
- | `16` | 16px |
416
- | `24` | 24px |
417
- | `32` | 32px |
418
- | `40` | 40px |
419
- | `48` | 48px |
420
- | `64` | 64px |
421
- | `96` | 96px |
400
+ | ----- | ----- |
401
+ | `0` | 0 |
402
+ | `4` | 4px |
403
+ | `8` | 8px |
404
+ | `12` | 12px |
405
+ | `16` | 16px |
406
+ | `24` | 24px |
407
+ | `32` | 32px |
408
+ | `40` | 40px |
409
+ | `48` | 48px |
410
+ | `64` | 64px |
411
+ | `96` | 96px |
412
+
413
+ Use multiples of 8px for block-element spacing; 4px only for inline or typographic relationships.
414
+
415
+ ### System props
416
+
417
+ Never use inline `style` attributes. Use system props from `@codecademy/gamut-styles` with shorthand names:
418
+
419
+ | Long form | Shorthand |
420
+ | --------------- | --------- |
421
+ | `margin` | `m` |
422
+ | `marginTop` | `mt` |
423
+ | `marginBottom` | `mb` |
424
+ | `marginLeft` | `ml` |
425
+ | `marginRight` | `mr` |
426
+ | `marginX` | `mx` |
427
+ | `marginY` | `my` |
428
+ | `padding` | `p` |
429
+ | `paddingTop` | `pt` |
430
+ | `paddingBottom` | `pb` |
431
+ | `paddingLeft` | `pl` |
432
+ | `paddingRight` | `pr` |
433
+ | `paddingX` | `px` |
434
+ | `paddingY` | `py` |
435
+
436
+ Use `mb={16}`, not `marginBottom={16}`. Color and border props: `bg`, `color` / `textColor`, `borderColor`, `borderRadius` — values must be Gamut tokens, never raw hex.
437
+
438
+ ### Responsive behavior
439
+
440
+ Mobile-first. Apply styles from the named breakpoint and up.
441
+
442
+ | Token | Min-width | Screen dimensions | Max content | Fold height |
443
+ | -------- | --------- | ----------------- | ----------- | ----------- |
444
+ | _(base)_ | 0 | 320×480 | 288px | 440px |
445
+ | `xs` | 480px | 480×900 | 448px | 440px |
446
+ | `sm` | 768px | 768×1024 | 704px | 680px |
447
+ | `md` | 1024px | 1024×768 | 896px | 680px |
448
+ | `lg` | 1200px | 1200×900 | 1072px | 680px |
449
+ | `xl` | 1440px | 1440×900 | 1248px | 680px |
450
+
451
+ Container query variants (`c_xs` through `c_xl`) mirror these values but trigger on component container size, not viewport.
452
+
453
+ Grid: 12-column grid at all breakpoints.
454
+
455
+ | Usage | Recommended values |
456
+ | --------------------- | ------------------------------------------------ |
457
+ | Horizontal margins | 64px (lg+), 48px (md), 32px (sm/xs), 16px (base) |
458
+ | Column gaps (gutters) | 32px (lg+), 24px (md), 16px (sm/xs), 8px (base) |
459
+ | Row gaps | 32px (lg+), 24px (md), 16px (sm/xs), 8px (base) |
460
+
461
+ Minimum interactive touch target: 44×44px on mobile breakpoints.
462
+
463
+ - Begin design work at 1440px (XL), then adapt down.
464
+ - Wider multi-column layouts collapse to fewer columns — do not stretch or squish.
465
+ - Avoid dense or small components in the base (mobile) breakpoint.
466
+
467
+ ### Global layout tokens
468
+
469
+ | Token | Value | Use |
470
+ | -------------- | -------------------------------------- | ------------------------------- |
471
+ | `headerHeight` | 4rem (64px) base, 5rem (80px) at `md`+ | Global page header height |
472
+ | `headerZ` | 15 | Z-index for global page headers |
422
473
 
423
474
  ---
424
475
 
425
- ## Depth & Elevation
476
+ ## Elevation & Depth
426
477
 
427
478
  Gamut uses border-based and shadow-based depth cues rather than a rigid z-elevation tier system.
428
479
 
@@ -437,77 +488,41 @@ box-shadow: 0 0 4px rgba(0,0,0,.15) → Subtle ambient shadow
437
488
 
438
489
  ### Card shadow variants
439
490
 
440
- | Variant | Effect | Use for |
441
- |---|---|---|
442
- | `none` (default) | No shadow | Static / non-interactive cards |
443
- | `outline` | Solid shadow on bottom + left/right using border color | Standard clickable cards |
444
- | `patternLeft` | Decorative checker pattern on bottom + left | Stylized content cards |
445
- | `patternRight` | Decorative checker pattern on bottom + right | Stylized content cards |
491
+ | Variant | Effect | Use for |
492
+ | ---------------- | ------------------------------------------------------ | ------------------------------ |
493
+ | `none` (default) | No shadow | Static / non-interactive cards |
494
+ | `outline` | Solid shadow on bottom + left/right using border color | Standard clickable cards |
495
+ | `patternLeft` | Decorative checker pattern on bottom + left | Stylized content cards |
496
+ | `patternRight` | Decorative checker pattern on bottom + right | Stylized content cards |
446
497
 
447
498
  Interactive cards (`isInteractive` prop) gain a shadow on hover and `borderRadius: md`. Cards with a pattern drop the pattern on hover.
448
499
 
449
500
  ### Z-index
450
501
 
451
- | Token | Value | Use |
452
- |---|---|---|
453
- | `headerZ` | 15 | Global page header |
454
-
455
- ---
456
-
457
- ## Border Radius Scale
458
-
459
- | Token | Value | Use |
460
- |---|---|---|
461
- | `none` | 0px | Square / non-interactive elements |
462
- | `sm` | 2px | Subtle rounding, tags |
463
- | `md` | 4px | Default buttons, inputs, interactive cards |
464
- | `lg` | 8px | Cards, panels |
465
- | `xl` | 16px | Large cards, modals |
466
- | `full` | 999px | Pills, avatars, circular elements |
502
+ | Token | Value | Use |
503
+ | --------- | ----- | ------------------ |
504
+ | `headerZ` | 15 | Global page header |
467
505
 
468
506
  ---
469
507
 
470
- ## Responsive Behavior
471
-
472
- Mobile-first. Apply styles from the named breakpoint and up.
473
-
474
- ### Breakpoints & screen sizes
475
-
476
- | Token | Min-width | Screen dimensions | Max content | Fold height |
477
- |---|---|---|---|---|
478
- | _(base)_ | 0 | 320×480 | 288px | 440px |
479
- | `xs` | 480px | 480×900 | 448px | 440px |
480
- | `sm` | 768px | 768×1024 | 704px | 680px |
481
- | `md` | 1024px | 1024×768 | 896px | 680px |
482
- | `lg` | 1200px | 1200×900 | 1072px | 680px |
483
- | `xl` | 1440px | 1440×900 | 1248px | 680px |
484
-
485
- Container query variants (`c_xs` through `c_xl`) mirror these values but trigger on component container size, not viewport.
508
+ ## Shapes
486
509
 
487
- ### Grid
510
+ Border radius tokens from `borderRadii` in `@codecademy/gamut-styles`. No custom radius values.
488
511
 
489
- 12-column grid at all breakpoints. The designer specifies how many columns a section spans per breakpoint.
490
-
491
- | Usage | Recommended values |
492
- |---|---|
493
- | Horizontal margins | 64px (lg+), 48px (md), 32px (sm/xs), 16px (base) |
494
- | Column gaps (gutters) | 32px (lg+), 24px (md), 16px (sm/xs), 8px (base) |
495
- | Row gaps | 32px (lg+), 24px (md), 16px (sm/xs), 8px (base) |
496
-
497
- ### Touch targets
498
-
499
- Minimum interactive touch target: **44×44px** on mobile breakpoints.
500
-
501
- ### Collapsing strategies
502
-
503
- - Begin design work at 1440px (XL), then adapt to smaller sizes.
504
- - Wider multi-column layouts collapse to fewer columns — do not simply stretch or squish.
505
- - Elements not in an explicit lockup (e.g., catalog cards) should align on one axis (usually left) rather than fill column widths.
506
- - Avoid dense or small components in the base (mobile) breakpoint.
512
+ | Token | Value | Use |
513
+ | ------ | ----- | ------------------------------------------ |
514
+ | `none` | 0px | Square / non-interactive elements |
515
+ | `sm` | 2px | Subtle rounding, tags, checkboxes |
516
+ | `md` | 4px | Default buttons, inputs, interactive cards |
517
+ | `lg` | 8px | Cards, panels |
518
+ | `xl` | 16px | Large cards, modals |
519
+ | `full` | 999px | Pills, avatars, circular elements |
507
520
 
508
521
  ---
509
522
 
510
- ## Component Library
523
+ ## Components
524
+
525
+ ### Component catalog
511
526
 
512
527
  Components are organized into three tiers:
513
528
 
@@ -527,39 +542,68 @@ ContentContainer, GridContainer, Layout, LayoutGrid
527
542
 
528
543
  #### Buttons
529
544
 
530
- | Variant | Component | Use for |
531
- |---|---|---|
532
- | Primary action | `FillButton` | Solid fill, high-emphasis CTA |
533
- | Secondary action | `StrokeButton` | Outlined, secondary CTA |
534
- | Marketing CTA | `CTAButton` | High-visibility promotional actions |
535
- | Tertiary / inline | `TextButton` | Low-emphasis, inline text actions |
536
- | Icon-only | `IconButton` | Compact actions with icon only |
545
+ There is no generic `Button` component. Use the variant that matches intent:
537
546
 
538
- All button variants support sizes: `small`, `normal` (default), `large`. They accept an `icon` prop (leading or trailing) and a `disabled` prop. Passing `href` renders the button as an `<a>` tag.
547
+ | Variant | Component | Use for |
548
+ | ----------------- | -------------- | ----------------------------------- |
549
+ | Primary action | `FillButton` | Solid fill, high-emphasis CTA |
550
+ | Secondary action | `StrokeButton` | Outlined, secondary CTA |
551
+ | Marketing CTA | `CTAButton` | High-visibility promotional actions |
552
+ | Tertiary / inline | `TextButton` | Low-emphasis, inline text actions |
553
+ | Icon-only | `IconButton` | Compact actions with icon only |
539
554
 
540
- **States**: default → hover (`primary-hover` / `secondary-hover`) active disabled (`text-disabled` + `background-disabled`).
555
+ - `IconButton` requires `tip` for screen reader accessibility.
556
+ - Never set `mode` on buttons — they inherit color context from parent wrappers.
557
+ - Sizes: `small`, `normal` (default), `large`. Support `icon`, `disabled`, and `href` (renders as `<a>`).
558
+ - States: default → hover (`primary-hover` / `secondary-hover`) → active → disabled (`text-disabled` + `background-disabled`).
541
559
 
542
560
  #### Cards
543
561
 
544
- Cards support:
545
- - **Background variants**: `default` (ColorMode-responsive), `white`, `yellow`, `beige` (light contexts), `navy`, `hyper` (dark contexts)
546
- - **Shadow variants**: `none` (default), `outline`, `patternLeft`, `patternRight`
547
- - **Interaction**: wrap in `<Anchor>` and add `isInteractive` for hover shadow + `borderRadius: md`
548
- - **Border radius**: defaults to `none` (non-interactive); override with the `borderRadius` prop as needed
562
+ - Valid `variant` values: `default`, `white`, `yellow`, `beige`, `navy`, `hyper` — never invent compound names (invalid values crash `parseToHsl()`).
563
+ - Defaults: `shadow="none"`, `isInteractive={false}`.
564
+ - Set `isInteractive` only when the card is a link or has click/hover interaction (e.g. wrapped in `<Anchor>`); interactive cards get hover shadow and `borderRadius: md`.
565
+ - Shadow variants: `none` (default), `outline`, `patternLeft`, `patternRight`.
549
566
 
550
567
  #### Color-aware components
551
568
 
552
- - **`<ColorMode mode="light|dark|system">`**wraps a subtree in an explicit color mode.
553
- - **`<Background bg="<color>">`**applies a background color and automatically switches the color mode inside to maintain accessible contrast. Prefer this over setting a raw `bg` prop on any content-bearing surface.
569
+ - `<ColorMode mode="light|dark|system">`explicit mode when you know which mode a region should use.
570
+ - `<Background bg="<color>">`dynamic background; Gamut picks contrast-safe inner mode. Prefer over raw `bg` on content-bearing surfaces.
554
571
 
555
- ---
572
+ ### Gamut implementation guardrails
573
+
574
+ #### Component discovery
575
+
576
+ 1. Inspect `@codecademy/gamut` exports before building custom UI for a pattern.
577
+ 2. Prefer Gamut components (`Menu`, `DataTable`, `Tabs`, `Video`, etc.) over raw HTML.
578
+ 3. Read component TypeScript definitions before using `variant` / color props.
579
+ 4. If no Gamut component exists: `{/* No Gamut component available for [pattern] — using custom markup */}`
580
+
581
+ #### Forms
582
+
583
+ - Submit/save flows (validation, bundled fields, dirty tracking): use `GridForm` or `ConnectedForm`.
584
+ - Live filters / standalone controls (no submit step): use atoms (`Input`, `Select`, `Checkbox`, `Radio`, `TextArea`, `FormGroup`) directly.
585
+ - Always provide `defaultValues`; use `validation="onChange"` when the submit button should stay disabled until valid.
586
+ - Set `hideLabel: true` on checkbox, radio, or toggle fields without a meaningful `label`.
587
+
588
+ #### DataTable / DataList
589
+
590
+ - `sortable: true` on a column requires `query`, `onQueryChange`, and client-sorted `rows` — Gamut does not sort data internally.
591
+
592
+ #### Menu
556
593
 
557
- ## Global Elements
594
+ - Always set `variant` explicitly: `fixed` + `as="nav"` for persistent navigation (sidebars, primary nav); `popover` for overflow/action menus.
595
+ - Do not let flex-stretching ancestors expand `Menu` to fill vertical space — wrap in intrinsic-height containers.
558
596
 
559
- | Token | Value | Use |
560
- |---|---|---|
561
- | `headerHeight` | 4rem (64px) base, 5rem (80px) at `md`+ | Global page header height |
562
- | `headerZ` | 15 | Z-index for global page headers |
597
+ #### Accessibility
598
+
599
+ - Meet WCAG contrast and 44×44px minimum touch targets on mobile.
600
+ - Use `FocusTrap` inside modals, dialogs, drawers, and other focus-confined regions.
601
+
602
+ #### Assets
603
+
604
+ - Icons: `@codecademy/gamut-icons` — verify icons exist; do not trust stale Figma layer names.
605
+ - Illustrations: `@codecademy/gamut-illustrations`
606
+ - Patterns: `@codecademy/gamut-patterns`
563
607
 
564
608
  ---
565
609
 
@@ -567,34 +611,42 @@ Cards support:
567
611
 
568
612
  ### Colors
569
613
 
570
- - **Do** use semantic color aliases (`primary`, `text`, `background`, etc.) for any UI that must adapt to color mode or theme.
571
- - **Do** use `<Background bg="...">` when setting a section background — it adjusts the inner color mode for contrast automatically.
572
- - **Don't** hardcode hex values for anything adaptive.
573
- - **Don't** use navy or white semi-transparent swatches where they may overlap unpredictably.
614
+ - Do use semantic color aliases (`primary`, `text`, `background`, etc.) for any UI that must adapt to color mode or theme.
615
+ - Do use `<Background bg="...">` when setting a section background — it adjusts the inner color mode for contrast automatically.
616
+ - Don't hardcode hex values for anything adaptive.
617
+ - Don't use navy or white semi-transparent swatches where they may overlap unpredictably.
574
618
 
575
619
  ### Typography
576
620
 
577
- - **Do** use `title` weight (700) for headlines, CTAs, and buttons.
578
- - **Do** keep body text at 150–175% line height for readability.
579
- - **Do** use Suisse sparingly — as an accent for code, captions, and lists only.
580
- - **Don't** use Apercu Bold to emphasize text *within* a paragraph — use Italic instead.
581
- - **Don't** adjust letter-spacing.
582
- - **Don't** right-align text in normal circumstances.
583
- - **Don't** center-align body paragraphs with long line lengths.
621
+ - Do use `title` weight (700) for headlines, CTAs, and buttons.
622
+ - Do keep body text at 150–175% line height for readability.
623
+ - Do use Suisse sparingly — as an accent for code, captions, and lists only.
624
+ - Don't use Apercu Bold to emphasize text _within_ a paragraph — use Italic instead.
625
+ - Don't adjust letter-spacing.
626
+ - Don't right-align text in normal circumstances.
627
+ - Don't center-align body paragraphs with long line lengths.
584
628
 
585
629
  ### Layout & Spacing
586
630
 
587
- - **Do** use multiples of 8px for block-element spacing (4px only for inline / typographic relationships).
588
- - **Do** begin design work at 1440px (XL), then adapt down to each breakpoint.
589
- - **Do** align elements to the 12-column grid.
590
- - **Don't** stretch elements to fill wider space — maintain proper line lengths and component widths.
631
+ - Do use multiples of 8px for block-element spacing (4px only for inline / typographic relationships).
632
+ - Do begin design work at 1440px (XL), then adapt down to each breakpoint.
633
+ - Do align elements to the 12-column grid.
634
+ - Don't stretch elements to fill wider space — maintain proper line lengths and component widths.
591
635
 
592
636
  ### Components
593
637
 
594
- - **Do** use `FillButton` for primary actions and `StrokeButton` for secondary actions.
595
- - **Do** add `isInteractive` to any `Card` that is wrapped in an `<Anchor>`.
596
- - **Don't** use `CTAButton` for standard UI actions reserve it for marketing/high-visibility promotions.
597
- - **Don't** use `<Background>` without an actual color value — it's not a neutral wrapper.
638
+ - Do use `FillButton` for primary actions and `StrokeButton` for secondary actions.
639
+ - Do add `isInteractive` only to `Card` components that are links or otherwise interactive.
640
+ - Don't import a generic `Button` it does not exist in Gamut.
641
+ - Don't use `CTAButton` for standard UI actionsreserve it for marketing/high-visibility promotions.
642
+ - Don't use `<Background>` without an actual color value — it's not a neutral wrapper.
643
+ - Don't use bare form atoms for functional forms — use `GridForm` or `ConnectedForm`.
644
+
645
+ ### Pre-ship validation
646
+
647
+ Before considering UI output final, run `/gamut-review` from the app repository root (the directory that contains `DESIGN.md`). Install the plugin first if needed: Cursor — `gamut plugin install cursor --theme core`; Claude Code — `gamut plugin install claude --theme core` (use `admin` or `platform` for those Codecademy themes).
648
+
649
+ The command performs automated checks (dependencies, `GamutProvider`, imports, hex colors, tests, component guardrails) and prints a manual pre-ship checklist keyed to this product's theme. Fix all errors before shipping. Full procedure: [`commands/gamut-review.md`](commands/gamut-review.md) in `@codecademy/gamut` agent-tools (installed as a slash command with the Gamut plugin).
598
650
 
599
651
  ---
600
652
 
@@ -602,17 +654,17 @@ Cards support:
602
654
 
603
655
  Quick color/token reference for generating or specifying UI:
604
656
 
605
- | Scenario | Tokens |
606
- |---|---|
607
- | Primary button (light) | `bg: primary (#3A10E5)`, `color: white`, `hover: primary-hover (#5533FF)` |
608
- | Primary button (dark) | `bg: primary (#FFD300)`, `color: navy-800`, `hover: primary-hover (#CCA900)` |
609
- | Body text | `color: text`, `font: base (Apercu Pro)`, `size: 16px`, `weight: 400`, `lineHeight: base (1.5)` |
610
- | Headline | `color: text-accent`, `font: base`, `size: 34–64px`, `weight: title (700)`, `lineHeight: title (1.2)` |
611
- | Caption / label | `color: text-secondary`, `font: accent (Suisse Int'l Mono)`, `size: 14px` |
612
- | Card default | `bg: background`, `borderRadius: none` — add `isInteractive` for hover shadow + `borderRadius: md` |
613
- | Error state | `color: feedback-error`, `bg: background-error`, `border: danger` |
614
- | Success state | `color: feedback-success`, `bg: background-success` |
615
- | Disabled state | `color: text-disabled`, `bg: background-disabled`, `border: border-disabled` |
657
+ | Scenario | Tokens |
658
+ | ---------------------- | ----------------------------------------------------------------------------------------------------- |
659
+ | Primary button (light) | `bg: primary (#3A10E5)`, `color: white`, `hover: primary-hover (#5533FF)` |
660
+ | Primary button (dark) | `bg: primary (#FFD300)`, `color: navy-800`, `hover: primary-hover (#CCA900)` |
661
+ | Body text | `color: text`, `font: base (Apercu Pro)`, `size: 16px`, `weight: 400`, `lineHeight: base (1.5)` |
662
+ | Headline | `color: text-accent`, `font: base`, `size: 34–64px`, `weight: title (700)`, `lineHeight: title (1.2)` |
663
+ | Caption / label | `color: text-secondary`, `font: accent (Suisse Int'l Mono)`, `size: 14px` |
664
+ | Card default | `bg: background`, `borderRadius: none` — add `isInteractive` for hover shadow + `borderRadius: md` |
665
+ | Error state | `color: feedback-error`, `bg: background-error`, `border: danger` |
666
+ | Success state | `color: feedback-success`, `bg: background-success` |
667
+ | Disabled state | `color: text-disabled`, `bg: background-disabled`, `border: border-disabled` |
616
668
 
617
669
  ### Component token cheatsheet
618
670
 
@@ -638,6 +690,7 @@ Background → <Background bg="hyper"> — auto-flips color mode for contra
638
690
  Figma layer names use emojis as visual shorthand (e.g. `✏️ label`, `👁 leading icon`, `↳ trailing icon`). These map to named props in the React components.
639
691
 
640
692
  To publish updated code snippets after changing a component:
693
+
641
694
  ```
642
695
  npx figma connect publish --token <your-figma-token>
643
696
  ```