@codecademy/gamut 68.5.2-alpha.f0a056.0 → 68.6.1-alpha.edab62.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.
- package/agent-tools/.claude-plugin/marketplace.json +16 -0
- package/agent-tools/.claude-plugin/plugin.json +7 -0
- package/agent-tools/.cursor-plugin/plugin.json +7 -0
- package/agent-tools/DESIGN.Codecademy.md +643 -0
- package/agent-tools/DESIGN.LXStudio.md +444 -0
- package/agent-tools/DESIGN.Percipio.md +435 -0
- package/agent-tools/DESIGN.md +1 -0
- package/agent-tools/agents/.gitkeep +0 -0
- package/agent-tools/commands/gamut-review.md +170 -0
- package/agent-tools/guidelines/components/buttons.md +44 -0
- package/agent-tools/guidelines/components/overview.md +44 -0
- package/agent-tools/guidelines/foundations/color.md +86 -0
- package/agent-tools/guidelines/foundations/modes.md +45 -0
- package/agent-tools/guidelines/foundations/spacing.md +66 -0
- package/agent-tools/guidelines/foundations/typography.md +50 -0
- package/agent-tools/guidelines/overview.md +35 -0
- package/agent-tools/guidelines/setup.md +42 -0
- package/agent-tools/rules/accessibility.mdc +69 -0
- package/agent-tools/skills/gamut-accessibility/SKILL.md +239 -0
- package/agent-tools/skills/gamut-color-mode/SKILL.md +99 -0
- package/agent-tools/skills/gamut-system-props/SKILL.md +173 -0
- package/agent-tools/skills/gamut-testing/SKILL.md +181 -0
- package/agent-tools/skills/gamut-theming/SKILL.md +113 -0
- package/agent-tools/skills/gamut-typography/SKILL.md +123 -0
- package/bin/commands/plugin/install.mjs +173 -0
- package/bin/commands/plugin/list.mjs +105 -0
- package/bin/commands/plugin/remove.mjs +116 -0
- package/bin/commands/plugin/update.mjs +49 -0
- package/bin/gamut.mjs +92 -0
- package/bin/lib/claude.mjs +52 -0
- package/bin/lib/cursor.mjs +40 -0
- package/bin/lib/figma.mjs +49 -0
- package/bin/lib/resolve-plugin-dir.mjs +38 -0
- package/bin/lib/run-command.mjs +22 -0
- package/package.json +11 -8
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Components
|
|
2
|
+
|
|
3
|
+
52 components have Figma ↔ code mappings via Figma Code Connect (`packages/code-connect/`). Live code snippets appear in Figma's inspect panel when you select a component.
|
|
4
|
+
|
|
5
|
+
## Atoms — foundational, single-purpose
|
|
6
|
+
|
|
7
|
+
Badge, Button (FillButton, StrokeButton, CTAButton, TextButton, IconButton), ButtonBase, Card, Checkbox, CodeBlock, ColorMode, Drawer, FlexBox, FormGroup, GridBox, HiddenText, Icon, Input, Label, Loader, Radio, Select, Spinner, Tag, TextArea, Toggle, Tooltip
|
|
8
|
+
|
|
9
|
+
## Molecules — composed of atoms
|
|
10
|
+
|
|
11
|
+
Alert, Anchor, Breadcrumbs, Coachmark, Disclosure, GridForm, Markdown, Menu, Modal, Pagination, Popover, ProgressBar, Table, Tabs, Toast, Toaster, Video
|
|
12
|
+
|
|
13
|
+
## Organisms — page-level compositions
|
|
14
|
+
|
|
15
|
+
ContentContainer, GridContainer, Layout, LayoutGrid
|
|
16
|
+
|
|
17
|
+
## Key patterns
|
|
18
|
+
|
|
19
|
+
### Buttons
|
|
20
|
+
See [buttons.md](buttons.md) for full reference. Use `FillButton` for primary actions, `StrokeButton` for secondary.
|
|
21
|
+
|
|
22
|
+
### Cards
|
|
23
|
+
- **Background variants**: `default` (ColorMode-responsive), `white`, `yellow`, `beige`, `navy`, `hyper`
|
|
24
|
+
- **Shadow variants**: `none` (default), `outline`, `patternLeft`, `patternRight`
|
|
25
|
+
- Add `isInteractive` when wrapping in `<Anchor>` — enables hover shadow + `borderRadius: md`
|
|
26
|
+
- Default `borderRadius` is `none`; override with `borderRadius` prop
|
|
27
|
+
|
|
28
|
+
### Color-aware components
|
|
29
|
+
- `<ColorMode mode="light|dark|system">` — scopes a subtree to an explicit color mode
|
|
30
|
+
- `<Background bg="<color>">` — applies background color + auto-switches inner color mode for contrast
|
|
31
|
+
|
|
32
|
+
### Alerts
|
|
33
|
+
| Variant | Tokens |
|
|
34
|
+
|---|---|
|
|
35
|
+
| Error | `feedback-error` + `background-error` |
|
|
36
|
+
| Success | `feedback-success` + `background-success` |
|
|
37
|
+
| Warning | `feedback-warning` + `background-warning` |
|
|
38
|
+
|
|
39
|
+
## Global tokens
|
|
40
|
+
|
|
41
|
+
| Token | Value | Use |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| `headerHeight` | 64px (base), 80px (md+) | Global page header height |
|
|
44
|
+
| `headerZ` | 15 | Z-index for global page header |
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Color
|
|
2
|
+
|
|
3
|
+
Use **semantic aliases** for all UI that adapts to color mode or theme. Use raw palette tokens only when a color must stay fixed regardless of mode.
|
|
4
|
+
|
|
5
|
+
## Semantic aliases
|
|
6
|
+
|
|
7
|
+
### Text
|
|
8
|
+
|
|
9
|
+
| Token | Light | Dark | Use for |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| `text` | `#10162F` | `#ffffff` | Default body and UI text |
|
|
12
|
+
| `text-accent` | `#0A0D1C` | `#FFF0E5` | Stronger emphasis text |
|
|
13
|
+
| `text-secondary` | navy-800 75% | white 65% | Supporting / secondary copy |
|
|
14
|
+
| `text-disabled` | navy-800 63% | white 50% | Disabled state labels |
|
|
15
|
+
|
|
16
|
+
### Background
|
|
17
|
+
|
|
18
|
+
| Token | Light | Dark | Use for |
|
|
19
|
+
|---|---|---|---|
|
|
20
|
+
| `background` | `#ffffff` | `#10162F` | Default page/component background |
|
|
21
|
+
| `background-primary` | `#FFF0E5` | `#0A0D1C` | Slightly elevated surfaces |
|
|
22
|
+
| `background-contrast` | white | black | Maximum contrast surface |
|
|
23
|
+
| `background-selected` | navy-800 4% | white 4% | Selected row / item |
|
|
24
|
+
| `background-hover` | navy-800 12% | white 9% | Hover state overlay |
|
|
25
|
+
| `background-disabled` | navy-800 12% | white 9% | Disabled surface |
|
|
26
|
+
| `background-success` | `#F5FFE3` | `#151C07` | Success state container |
|
|
27
|
+
| `background-warning` | `#FFFAE5` | `#211B00` | Warning state container |
|
|
28
|
+
| `background-error` | `#FBF1F0` | `#280503` | Error state container |
|
|
29
|
+
|
|
30
|
+
### Interactive
|
|
31
|
+
|
|
32
|
+
| Token | Light | Dark | Use for |
|
|
33
|
+
|---|---|---|---|
|
|
34
|
+
| `primary` | `#3A10E5` | `#FFD300` | Primary CTA, links, focus rings |
|
|
35
|
+
| `primary-hover` | `#5533FF` | `#CCA900` | Hover on primary interactive |
|
|
36
|
+
| `primary-inverse` | `#FFD300` | `#3A10E5` | Primary on a colored background |
|
|
37
|
+
| `secondary` | `#10162F` | `#ffffff` | Secondary CTA, ghost buttons |
|
|
38
|
+
| `secondary-hover` | navy-800 86% | white 80% | Hover on secondary interactive |
|
|
39
|
+
| `interface` | `#3A10E5` | `#FFD300` | Checkboxes, toggles, sliders |
|
|
40
|
+
| `interface-hover` | `#5533FF` | `#CCA900` | Hover on interface elements |
|
|
41
|
+
| `danger` | `#E91C11` | `#E85D7F` | Destructive actions, error states |
|
|
42
|
+
| `danger-hover` | `#BE1809` | `#DC5879` | Hover on danger interactive |
|
|
43
|
+
|
|
44
|
+
### Border
|
|
45
|
+
|
|
46
|
+
| Token | Light | Dark | Use for |
|
|
47
|
+
|---|---|---|---|
|
|
48
|
+
| `border-primary` | `#10162F` | `#ffffff` | Strong borders, dividers |
|
|
49
|
+
| `border-secondary` | navy-800 75% | white 65% | Medium-weight borders |
|
|
50
|
+
| `border-tertiary` | navy-800 28% | white 20% | Subtle borders, separators |
|
|
51
|
+
| `border-disabled` | navy-800 63% | white 50% | Disabled input borders |
|
|
52
|
+
|
|
53
|
+
### Feedback
|
|
54
|
+
|
|
55
|
+
| Token | Light | Dark | Use for |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| `feedback-error` | `#BE1809` | `#E85D7F` | Error messages, validation |
|
|
58
|
+
| `feedback-success` | `#008A27` | `#AEE938` | Success messages |
|
|
59
|
+
| `feedback-warning` | `#FFD300` | `#FFFAE5` | Warning messages |
|
|
60
|
+
|
|
61
|
+
## Raw palette
|
|
62
|
+
|
|
63
|
+
Use raw tokens only when color must be **fixed** (not adaptive).
|
|
64
|
+
|
|
65
|
+
| Name | Weights | Key values |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `navy` | 100–900 | 800 = `#10162F`, 900 = `#0A0D1C` |
|
|
68
|
+
| `hyper` | 400, 500 | 500 = `#3A10E5`, 400 = `#5533FF` |
|
|
69
|
+
| `yellow` | 0, 400, 500, 900 | 500 = `#FFD300` |
|
|
70
|
+
| `red` | 0, 300, 400, 500, 600, 900 | 500 = `#E91C11` |
|
|
71
|
+
| `green` | 0, 100, 400, 700, 900 | 700 = `#008A27` |
|
|
72
|
+
| `blue` | 0, 100, 300, 400, 500, 800 | 500 = `#1557FF` |
|
|
73
|
+
| `beige` | — | `#FFF0E5` |
|
|
74
|
+
| `pink` | 0, 400 | 400 = `#F966FF` |
|
|
75
|
+
| `orange` | 100, 500 | 500 = `#FF8C00` |
|
|
76
|
+
|
|
77
|
+
Named shorthand aliases: `beige`, `blue`, `green`, `hyper`, `navy`, `orange`, `pink`, `red`, `yellow`, `black`, `white`
|
|
78
|
+
|
|
79
|
+
## Decision guide
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
Coloring UI text or backgrounds?
|
|
83
|
+
└─ Needs to adapt to light/dark or theme? → use semantic alias (text, background, primary, …)
|
|
84
|
+
└─ Must be fixed regardless of mode? → use raw token (navy-800, yellow-500, …)
|
|
85
|
+
└─ Setting a section background with content inside? → use <Background bg="…"> (see modes.md)
|
|
86
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Color Modes
|
|
2
|
+
|
|
3
|
+
Gamut uses **semantic color aliases** so components adapt to light/dark mode without configuration. See [color.md](color.md) for the full alias reference.
|
|
4
|
+
|
|
5
|
+
## `<ColorMode>`
|
|
6
|
+
|
|
7
|
+
Wraps a subtree in an explicit color mode. Nest to create scoped mode regions.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { ColorMode } from '@codecademy/gamut-styles';
|
|
11
|
+
|
|
12
|
+
<ColorMode mode="light">{children}</ColorMode> // force light
|
|
13
|
+
<ColorMode mode="dark">{children}</ColorMode> // force dark
|
|
14
|
+
<ColorMode mode="system">{children}</ColorMode> // follows OS preference
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Props**: `mode="light" | "dark" | "system"`
|
|
18
|
+
|
|
19
|
+
## `<Background>`
|
|
20
|
+
|
|
21
|
+
Use `<Background>` — not a raw `bg` prop — whenever setting a colored background on a section that contains text or interactive elements. It automatically switches the color mode inside to maintain accessible contrast.
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { Background } from '@codecademy/gamut-styles';
|
|
25
|
+
|
|
26
|
+
<Background bg="hyper">{children}</Background>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Nesting is supported — each `<Background>` creates its own accessible color context.
|
|
30
|
+
|
|
31
|
+
## Hooks
|
|
32
|
+
|
|
33
|
+
| Hook | Returns | Use |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `useCurrentMode()` | `"light" \| "dark"` | Read active mode in JS |
|
|
36
|
+
| `useColorMode()` | `[modeKey, modeColors, allModes]` | Access all mode data |
|
|
37
|
+
| `usePrefersDarkMode()` | `boolean` | Read OS dark preference only |
|
|
38
|
+
|
|
39
|
+
Import from `@codecademy/gamut-styles`.
|
|
40
|
+
|
|
41
|
+
## Common mistakes
|
|
42
|
+
|
|
43
|
+
- Do not use raw color tokens (`navy-400`, `white`) for text/backgrounds that must be accessible across modes — use semantic aliases.
|
|
44
|
+
- Do not use a raw `bg` prop on colored sections containing content — use `<Background>` so contrast is guaranteed.
|
|
45
|
+
- Do not manually toggle modes from `usePrefersDarkMode()` — use `<ColorMode mode="system">` instead.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Spacing, Border Radius & Layout
|
|
2
|
+
|
|
3
|
+
## Spacing scale
|
|
4
|
+
|
|
5
|
+
All spacing is multiples of 4px on an 8px grid.
|
|
6
|
+
|
|
7
|
+
| Token | Value |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `0` | 0 |
|
|
10
|
+
| `4` | 4px |
|
|
11
|
+
| `8` | 8px |
|
|
12
|
+
| `12` | 12px |
|
|
13
|
+
| `16` | 16px |
|
|
14
|
+
| `24` | 24px |
|
|
15
|
+
| `32` | 32px |
|
|
16
|
+
| `40` | 40px |
|
|
17
|
+
| `48` | 48px |
|
|
18
|
+
| `64` | 64px |
|
|
19
|
+
| `96` | 96px |
|
|
20
|
+
|
|
21
|
+
Use multiples of 8px for block-element spacing. Use 4px only for inline or typographic relationships.
|
|
22
|
+
|
|
23
|
+
## Border radius
|
|
24
|
+
|
|
25
|
+
| Token | Value | Use |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `none` | 0px | Square / non-interactive elements |
|
|
28
|
+
| `sm` | 2px | Subtle rounding, tags |
|
|
29
|
+
| `md` | 4px | Buttons, inputs, interactive cards |
|
|
30
|
+
| `lg` | 8px | Cards, panels |
|
|
31
|
+
| `xl` | 16px | Large cards, modals |
|
|
32
|
+
| `full` | 999px | Pills, avatars, circular elements |
|
|
33
|
+
|
|
34
|
+
## Breakpoints
|
|
35
|
+
|
|
36
|
+
Mobile-first. Styles apply from the named breakpoint and up.
|
|
37
|
+
|
|
38
|
+
| Token | Min-width | Max content width |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| _(base)_ | 0 | 288px |
|
|
41
|
+
| `xs` | 480px | 448px |
|
|
42
|
+
| `sm` | 768px | 704px |
|
|
43
|
+
| `md` | 1024px | 896px |
|
|
44
|
+
| `lg` | 1200px | 1072px |
|
|
45
|
+
| `xl` | 1440px | 1248px |
|
|
46
|
+
|
|
47
|
+
Container query variants (`c_xs`–`c_xl`) mirror these values but trigger on component width.
|
|
48
|
+
|
|
49
|
+
## Grid
|
|
50
|
+
|
|
51
|
+
12-column grid at all breakpoints.
|
|
52
|
+
|
|
53
|
+
| Property | xl/lg | md | sm/xs | base |
|
|
54
|
+
|---|---|---|---|---|
|
|
55
|
+
| Horizontal margins | 64px | 48px | 32px | 16px |
|
|
56
|
+
| Column gutters | 32px | 24px | 16px | 8px |
|
|
57
|
+
| Row gaps | 32px | 24px | 16px | 8px |
|
|
58
|
+
|
|
59
|
+
Minimum touch target on mobile: **44×44px**.
|
|
60
|
+
|
|
61
|
+
## Responsive rules
|
|
62
|
+
|
|
63
|
+
- Begin design work at 1440px (XL), then adapt down.
|
|
64
|
+
- Multi-column layouts collapse to fewer columns — do not stretch or squish.
|
|
65
|
+
- Catalog cards and non-lockup elements should align on one axis (usually left), not fill column widths.
|
|
66
|
+
- Avoid dense or small components at the base (mobile) breakpoint.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Typography
|
|
2
|
+
|
|
3
|
+
## Typefaces
|
|
4
|
+
|
|
5
|
+
| Token | Codecademy font | Non-Codecademy | Use for |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| `base` | Apercu Pro | Hanken Grotesk | All UI text, headlines, body copy |
|
|
8
|
+
| `accent` | Suisse Intl Mono | Hanken Grotesk | Code, captions, labels, technical context |
|
|
9
|
+
| `monospace` | Monaco / Menlo / Consolas | Monaco / Menlo / Consolas | Code editor contexts |
|
|
10
|
+
|
|
11
|
+
Percipio overrides all families to Roboto. Apercu is licensed for codecademy.com only.
|
|
12
|
+
|
|
13
|
+
## Font size scale
|
|
14
|
+
|
|
15
|
+
| Token | Size | Common use |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| `64` | 64px | Hero / display |
|
|
18
|
+
| `44` | 44px | Page titles |
|
|
19
|
+
| `34` | 34px | Section titles |
|
|
20
|
+
| `26` | 26px | Sub-section titles |
|
|
21
|
+
| `22` | 22px | Card titles, large UI labels |
|
|
22
|
+
| `20` | 20px | Secondary titles |
|
|
23
|
+
| `18` | 18px | Large body, intro text |
|
|
24
|
+
| `16` | 16px | Default body text |
|
|
25
|
+
| `14` | 14px | Small body, captions, labels |
|
|
26
|
+
|
|
27
|
+
## Font weight
|
|
28
|
+
|
|
29
|
+
| Token | Value | Use |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `base` | 400 | Body text, UI labels |
|
|
32
|
+
| `title` | 700 | Headlines, CTAs, buttons |
|
|
33
|
+
|
|
34
|
+
## Line height
|
|
35
|
+
|
|
36
|
+
| Token | Value | Use |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `base` | 1.5 | Body text |
|
|
39
|
+
| `spacedTitle` | 1.3 | Sub-headlines, medium titles |
|
|
40
|
+
| `title` | 1.2 | Large headlines |
|
|
41
|
+
|
|
42
|
+
Target 45–85 characters per line (66 ideal for web body text).
|
|
43
|
+
|
|
44
|
+
## Rules
|
|
45
|
+
|
|
46
|
+
- Use `title` weight (700) for headlines, CTAs, and buttons.
|
|
47
|
+
- Use Apercu Italic to emphasize within a Regular paragraph — not Bold.
|
|
48
|
+
- Use `accent` (Suisse) sparingly: code snippets, captions, enumerated items. Suisse reads ~10–15% large — size it down relative to Apercu equivalents.
|
|
49
|
+
- Left-align text by default. Center-align only for short marketing headlines. Never right-align.
|
|
50
|
+
- Do not adjust letter-spacing.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Gamut Design System
|
|
2
|
+
|
|
3
|
+
Gamut is the Codecademy / Skillsoft design system — React component library (`@codecademy/gamut`), design tokens (`@codecademy/gamut-styles`), and Figma components with live code previews via Figma Code Connect.
|
|
4
|
+
|
|
5
|
+
**Design voice**: "Ruled by logic, but creative and a bit unexpected." Structured and trustworthy for a learning platform, with engaging personality. Medium density — information-rich layouts with strong typographic hierarchy. Never cramped or overly airy.
|
|
6
|
+
|
|
7
|
+
**Core principles**:
|
|
8
|
+
- Components are color mode–aware by default — never hardcode hex values for adaptive UI
|
|
9
|
+
- All components work across all themes without modification
|
|
10
|
+
- Mobile-first, 12-column grid
|
|
11
|
+
- Semantic color tokens guarantee WCAG AA contrast automatically
|
|
12
|
+
|
|
13
|
+
## Themes
|
|
14
|
+
|
|
15
|
+
| Theme | Product | Base font | Dark mode |
|
|
16
|
+
|---|---|---|---|
|
|
17
|
+
| **Core** | Codecademy (default) | Apercu | ✓ |
|
|
18
|
+
| **Admin** | Codecademy admin tools | Apercu | ✓ |
|
|
19
|
+
| **Platform** | Codecademy learning environment | Apercu | ✓ |
|
|
20
|
+
| **LX Studio** | LX Studio application | Hanken Grotesk | — |
|
|
21
|
+
| **Percipio** | Skillsoft Percipio | Roboto | — |
|
|
22
|
+
|
|
23
|
+
Set the theme at the app root via `<GamutProvider theme={...}>`.
|
|
24
|
+
|
|
25
|
+
## Reading order
|
|
26
|
+
|
|
27
|
+
| File | What it covers |
|
|
28
|
+
|---|---|
|
|
29
|
+
| [setup.md](setup.md) | Packages, GamutProvider, theme selection |
|
|
30
|
+
| [foundations/color.md](foundations/color.md) | Semantic aliases, raw palette, decision guide |
|
|
31
|
+
| [foundations/modes.md](foundations/modes.md) | Light/dark ColorMode, Background component |
|
|
32
|
+
| [foundations/typography.md](foundations/typography.md) | Typefaces, font scale, rules |
|
|
33
|
+
| [foundations/spacing.md](foundations/spacing.md) | Spacing, border radius, responsive grid |
|
|
34
|
+
| [components/overview.md](components/overview.md) | Full component catalog |
|
|
35
|
+
| [components/buttons.md](components/buttons.md) | Button variants, props, decision tree |
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Setup
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install @codecademy/gamut-kit
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
`gamut-kit` bundles `gamut`, `gamut-icons`, `gamut-illustrations`, `gamut-patterns`, `gamut-styles`, `variance`, and `gamut-tests`.
|
|
10
|
+
|
|
11
|
+
## Required wrapper
|
|
12
|
+
|
|
13
|
+
Wrap the app root in `<GamutProvider>`. This wires up the theme, color mode, and logical properties for all child components.
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { GamutProvider } from '@codecademy/gamut';
|
|
17
|
+
import { theme } from '@codecademy/gamut-styles';
|
|
18
|
+
|
|
19
|
+
const App = () => (
|
|
20
|
+
<GamutProvider theme={theme}>
|
|
21
|
+
{/* app content */}
|
|
22
|
+
</GamutProvider>
|
|
23
|
+
);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Theme selection
|
|
27
|
+
|
|
28
|
+
| Product | Theme to import |
|
|
29
|
+
|---|---|
|
|
30
|
+
| Codecademy public | `coreTheme` (default `theme`) |
|
|
31
|
+
| Codecademy admin | `adminTheme` |
|
|
32
|
+
| Codecademy platform | `platformTheme` |
|
|
33
|
+
| LX Studio | `lxStudioTheme` |
|
|
34
|
+
| Percipio | `percipioTheme` |
|
|
35
|
+
|
|
36
|
+
All themes are exported from `@codecademy/gamut-styles`.
|
|
37
|
+
|
|
38
|
+
## Font licensing
|
|
39
|
+
|
|
40
|
+
**Apercu Pro** is licensed for codecademy.com only. Non-Codecademy products must use their theme's approved typeface:
|
|
41
|
+
- LX Studio → Hanken Grotesk
|
|
42
|
+
- Percipio → Roboto
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Apply these guardrails when editing Gamut UI in TS/JS/TSX/JSX. Mirrors the General rules in the `gamut-accessibility` skill; see `skills/gamut-accessibility/SKILL.md` in this plugin for full component patterns and checklists. Loaded automatically for matched files.
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
globs: ["*.tsx", "*.ts", "*.jsx", "*.js"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Gamut Accessibility Rules
|
|
8
|
+
|
|
9
|
+
## Prefer HTML over ARIA
|
|
10
|
+
|
|
11
|
+
Unnecessary ARIA can cause harm. If a native HTML element or attribute with the semantics and behavior you need already exists, use it. Reach for ARIA only when native HTML is genuinely insufficient for the pattern.
|
|
12
|
+
|
|
13
|
+
## A Role is a Promise
|
|
14
|
+
|
|
15
|
+
ARIA roles modify the accessibility tree and _imply_ behavior. Always ensure that the implied keyboard behavior, focusability, and interactivity exists when a role is used.
|
|
16
|
+
|
|
17
|
+
## ARIA can both cloak and enhance
|
|
18
|
+
|
|
19
|
+
ARIA can augment native semantics (`aria-pressed` on a `<button>`) or override them entirely (`role="menuitem"` on an `<a>`). Both capabilities are powerful and dangerous. Override only when native HTML genuinely doesn't fit the pattern; when augmenting, don't contradict the native semantics.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## Align accessible names with visible copy
|
|
23
|
+
|
|
24
|
+
Prefer wiring names through visible text and native `<label>` / control text / `alt` over using `aria-label`. Point `aria-labelledby` at the visible heading or label that should define the name if it's not possible to name elements from their content. Use bare `aria-label` when there is no suitable visible label.
|
|
25
|
+
|
|
26
|
+
## Treat missing visible labels as a design smell
|
|
27
|
+
|
|
28
|
+
When there is no visible text for a nameable element, consider this a sign that the content design could be improved, but not a requirement that it is changed. This is not an accessibility violation.
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<!-- smell: this list has no conceptual name, so we have to create one using ARIA -->
|
|
32
|
+
<ul aria-label="List heading">
|
|
33
|
+
<li>...</li>
|
|
34
|
+
</ul>
|
|
35
|
+
|
|
36
|
+
<!-- better: the list's name is visible and can be used for its accessible name -->
|
|
37
|
+
<h2 id="list-name">List heading</h2>
|
|
38
|
+
<ul aria-labelledby="list-name">
|
|
39
|
+
<li>...</li>
|
|
40
|
+
</ul>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Use Gamut components — don't reimplement what they already solve
|
|
44
|
+
|
|
45
|
+
- `<Button>` not `<div onClick>` or `<span role="button">`
|
|
46
|
+
- `<Tabs>` / `<Tab>` / `<TabList>` / `<TabPanel>` — arrow key, Home, End navigation is automatic
|
|
47
|
+
- `<Dialog>` / `<Modal>` — always provide an accessible name; **prefer `aria-labelledby`** to a visible title when one exists, otherwise `aria-label`. Focus lock and Escape are handled by the components.
|
|
48
|
+
|
|
49
|
+
## Every interactive control needs an accessible name
|
|
50
|
+
|
|
51
|
+
- **`<IconButton>`** — provide `tip` (becomes the accessible name for icon-only)
|
|
52
|
+
- **`<InfoTip>`** — provide `ariaLabel` or `ariaLabelledby`; there is no automatic fallback
|
|
53
|
+
- Icon SVGs next to visible text — add `aria-hidden="true"` to decorative icons
|
|
54
|
+
|
|
55
|
+
## Form label association
|
|
56
|
+
|
|
57
|
+
Match `htmlFor` on `<FormGroupLabel>` with the `id` on the input. `<FormGroup>` auto-wires `aria-live` for `error` and `description` — do not add redundant live regions manually.
|
|
58
|
+
|
|
59
|
+
## Screen-reader-only text
|
|
60
|
+
|
|
61
|
+
Use `<Text screenreader>` for visually hidden but announced content. `<HiddenText>` is deprecated.
|
|
62
|
+
|
|
63
|
+
## Color and contrast
|
|
64
|
+
|
|
65
|
+
Do not hardcode hex values for adaptive UI. Gamut semantic color tokens through `ColorMode` are built for WCAG AA; see the `gamut-color-mode` skill for token usage. For non-text contrast, focus order, and ARIA beyond color, see `gamut-accessibility`.
|
|
66
|
+
|
|
67
|
+
## Focus visibility
|
|
68
|
+
|
|
69
|
+
Never suppress focus indicators with `outline: none` or `outline: 0` without a visible replacement. Gamut's focus styles are intentional and meet WCAG 2.4.7.
|