@codecademy/gamut 68.6.1-alpha.e6c390.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 (47) hide show
  1. package/agent-tools/.cursor-plugin/plugin.json +1 -1
  2. package/agent-tools/DESIGN.Codecademy.md +239 -191
  3. package/agent-tools/DESIGN.LXStudio.md +236 -184
  4. package/agent-tools/DESIGN.Percipio.md +232 -182
  5. package/agent-tools/DESIGN.md +1 -1
  6. package/agent-tools/commands/gamut-review.md +176 -87
  7. package/agent-tools/guidelines/components/animations.md +74 -0
  8. package/agent-tools/guidelines/components/buttons.md +74 -23
  9. package/agent-tools/guidelines/components/card.md +19 -0
  10. package/agent-tools/guidelines/components/coachmark.md +21 -0
  11. package/agent-tools/guidelines/components/data-table.md +79 -0
  12. package/agent-tools/guidelines/components/forms.md +106 -0
  13. package/agent-tools/guidelines/components/loading-states.md +17 -0
  14. package/agent-tools/guidelines/components/menu.md +58 -0
  15. package/agent-tools/guidelines/components/overview.md +97 -17
  16. package/agent-tools/guidelines/components/radial-progress.md +13 -0
  17. package/agent-tools/guidelines/components/select.md +23 -0
  18. package/agent-tools/guidelines/components/tooltips.md +22 -0
  19. package/agent-tools/guidelines/components/video.md +29 -0
  20. package/agent-tools/guidelines/foundations/color.md +140 -58
  21. package/agent-tools/guidelines/foundations/modes.md +39 -17
  22. package/agent-tools/guidelines/foundations/spacing.md +78 -37
  23. package/agent-tools/guidelines/foundations/typography.md +69 -37
  24. package/agent-tools/guidelines/overview-icons.md +19 -0
  25. package/agent-tools/guidelines/overview-illustrations.md +7 -0
  26. package/agent-tools/guidelines/overview-patterns.md +7 -0
  27. package/agent-tools/guidelines/overview.md +69 -23
  28. package/agent-tools/guidelines/setup.md +59 -18
  29. package/agent-tools/rules/accessibility.mdc +22 -13
  30. package/agent-tools/skills/gamut-accessibility/SKILL.md +97 -112
  31. package/agent-tools/skills/gamut-color-mode/SKILL.md +79 -29
  32. package/agent-tools/skills/gamut-components/SKILL.md +46 -0
  33. package/agent-tools/skills/gamut-forms/SKILL.md +101 -0
  34. package/agent-tools/skills/gamut-style-utilities/SKILL.md +111 -0
  35. package/agent-tools/skills/gamut-system-props/SKILL.md +70 -26
  36. package/agent-tools/skills/gamut-testing/SKILL.md +106 -62
  37. package/agent-tools/skills/gamut-theming/SKILL.md +34 -86
  38. package/agent-tools/skills/gamut-typography/SKILL.md +36 -80
  39. package/bin/commands/plugin/install.mjs +96 -56
  40. package/bin/commands/plugin/list.mjs +11 -43
  41. package/bin/commands/plugin/remove.mjs +30 -38
  42. package/bin/commands/plugin/update.mjs +15 -5
  43. package/bin/gamut.mjs +17 -13
  44. package/bin/lib/design.mjs +71 -0
  45. package/bin/lib/io.mjs +14 -0
  46. package/package.json +6 -6
  47. package/bin/lib/figma.mjs +0 -49
@@ -1,48 +1,46 @@
1
1
  ---
2
2
  name: gamut-testing
3
- description: Use this skill when writing or fixing unit tests for React components that use Gamut — setupRtl, MockGamutProvider, ColorMode in tests, emotion matchers, or removing jest.mock of @codecademy/gamut / gamut-styles.
3
+ description: Unit tests for Gamut UI — setupRtl, MockGamutProvider, no jest.mock of @codecademy/gamut. When invoked, see commands/gamut-review.md Check 5 for test guardrails.
4
4
  ---
5
5
 
6
6
  # Gamut Testing
7
7
 
8
- Source: `@codecademy/gamut-tests` — [`index.tsx`](https://github.com/Codecademy/gamut/blob/main/packages/gamut-tests/src/index.tsx)
9
-
10
- ---
8
+ ## Read first
11
9
 
12
- ## Core rule: never mock Gamut components
10
+ When this skill applies, skim [`commands/gamut-review.md`](../../commands/gamut-review.md) Check 5 (test setup) for blocking patterns before changing tests.
13
11
 
14
- Do not use `jest.mock('@codecademy/gamut')` or manually mock individual Gamut components in test files. This silently bypasses emotion's theme context, produces tests that can't catch real rendering failures, and requires every test file to maintain its own fragile mock list.
12
+ Source: `@codecademy/gamut-tests` [`index.tsx`](https://github.com/Codecademy/gamut/blob/main/packages/gamut-tests/src/index.tsx)
15
13
 
16
- Use `MockGamutProvider` or `setupRtl` — both are designed for exactly this purpose.
14
+ ---
17
15
 
18
16
  ---
19
17
 
20
- ## What `MockGamutProvider` does
18
+ ## What `MockGamutProvider` does (under `setupRtl`)
21
19
 
22
- `MockGamutProvider` wraps `GamutProvider` with test-safe settings:
20
+ `MockGamutProvider` forwards to `GamutProvider` with:
23
21
 
24
- - `useCache={false}` — disables emotion's style injection cache so styles are predictable across tests
25
- - `useGlobals={false}` — disables global CSS (Reboot, Typography, CSS Variables) to avoid side effects between test files
26
- - `theme={coreTheme}` — provides the full Gamut token set so styled components resolve correctly
22
+ - `useCache={false}` — stable Emotion output across tests
23
+ - `useGlobals={false}` — no global Reboot/Typography bleed between files
24
+ - `theme={theme}` — full token theme for styled components
25
+ - Optional `useLogicalProperties` — forwarded for logical vs physical CSS in variance
27
26
 
28
- You should never construct a `GamutProvider` manually in a test. Use `MockGamutProvider`.
27
+ You normally do not import `MockGamutProvider` for plain component tests; `setupRtl` already wraps the component under test once. Import it inside a harness when the SUT needs a non-default provider flag or extra wrappers (see below).
29
28
 
30
29
  ---
31
30
 
32
31
  ## Decision guide
33
32
 
34
- | Scenario | Use |
35
- |---|---|
36
- | Standard component unit test | `setupRtl` from `@codecademy/gamut-tests` |
37
- | Test that needs to vary `useLogicalProperties` | `render` + `MockGamutProvider` directly |
38
- | Test that needs `ColorMode` context | `render` + `MockGamutProvider` + `<ColorMode>` |
39
- | Visual test mock / Storybook wrapper | `MockGamutProvider` directly |
33
+ | Scenario | Prefer |
34
+ | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
35
+ | Default unit test for a Gamut (or app) component | `setupRtl(Component, defaultProps)` once per file / describe |
36
+ | Vary `useLogicalProperties` across cases | Harness that accepts `useLogicalProperties` and wraps `MockGamutProvider`, then `setupRtl(Harness, defaults)`; pass overrides per `it` / `describe.each` |
37
+ | Need `ColorMode` (or other context) around the SUT | Harness with `<ColorMode>` inside the tree, then `setupRtl(Harness)` no need for raw `render` unless you are testing the provider itself |
38
+ | `dir` / RTL behavior (e.g. mirrored layout, `useElementDir`) | Keep using `setupRtl` for the component; set `document.documentElement.setAttribute('dir', 'rtl' \| 'ltr')` (and scroll/viewport stubs if needed) in `beforeEach` / `afterEach`; reset `dir` after tests so suites do not leak |
39
+ | Storybook-only mock, chromatic-style wrapper, or non-RTL harness | `MockGamutProvider` (± `ColorMode`) in the exported wrapper component |
40
40
 
41
41
  ---
42
42
 
43
- ## `setupRtl` — preferred pattern
44
-
45
- `setupRtl` from `@codecademy/gamut-tests` wraps `setupRtl` from `component-test-setup` with `MockGamutProvider` automatically. You do not need to add `MockGamutProvider` yourself.
43
+ ## `setupRtl` — primary pattern
46
44
 
47
45
  ```tsx
48
46
  import { setupRtl } from '@codecademy/gamut-tests';
@@ -66,13 +64,21 @@ it('accepts prop overrides', () => {
66
64
  ```
67
65
 
68
66
  `renderView` returns `{ view, props, update }`:
69
- - `view` — RTL `RenderResult` (getByRole, getByText, etc.)
70
- - `props` — the resolved props passed to the component (useful for asserting on jest.fn() mocks)
71
- - `update` — re-render with updated props without remounting
67
+
68
+ - `view` — RTL `RenderResult` (`getByRole`, `getByLabelText`, `getByText`, )
69
+ - `props` — resolved props (handy for `jest.fn()` assertions)
70
+ - `update` — re-render with new props without remounting
71
+
72
+ ### Query and interaction habits (RTL)
73
+
74
+ - Prefer `getByRole`, `getByLabelText`, and accessible names over CSS selectors or snapshotting class strings unless you are explicitly testing styling.
75
+ - Prefer `@testing-library/user-event` over `fireEvent` when simulating real input (import `userEvent` from `@testing-library/user-event` in current major versions).
72
76
 
73
77
  ### Accessing mock functions via `props`
74
78
 
75
79
  ```tsx
80
+ import userEvent from '@testing-library/user-event';
81
+
76
82
  it('calls onClick when clicked', async () => {
77
83
  const { view, props } = renderView();
78
84
  await userEvent.click(view.getByRole('button'));
@@ -82,50 +88,84 @@ it('calls onClick when clicked', async () => {
82
88
 
83
89
  ---
84
90
 
85
- ## `MockGamutProvider` directly when you need more control
91
+ ## Harness + `setupRtl` when the wrapper is not default
92
+
93
+ `setupRtl` always wraps with `MockGamutProvider` with default props. To vary `useLogicalProperties`, add `ColorMode`, or compose other providers, define a small harness and pass `setupRtl` that harness — still one `renderView` factory, still `props` / `update` ergonomics.
86
94
 
87
- Use `render` + `MockGamutProvider` when `setupRtl` doesn't give enough control over the wrapper — for example, when testing both logical and physical CSS property modes, or when adding `ColorMode`.
95
+ ### Varying `useLogicalProperties` (logical vs physical CSS)
88
96
 
89
97
  ```tsx
90
- import { MockGamutProvider } from '@codecademy/gamut-tests';
91
- import { render } from '@testing-library/react';
92
-
93
- it('uses logical properties when enabled', () => {
94
- const { container } = render(
95
- <MockGamutProvider useLogicalProperties>
96
- <MyComponent width="200px" />
97
- </MockGamutProvider>
98
- );
99
- // assert on inlineSize rather than width
100
- });
101
- ```
98
+ import { MockGamutProvider, setupRtl } from '@codecademy/gamut-tests';
102
99
 
103
- ### Testing both logical and physical property modes
100
+ import { MyComponent } from '../MyComponent';
101
+
102
+ type HarnessProps = React.ComponentProps<typeof MyComponent> & {
103
+ useLogicalProperties?: boolean;
104
+ };
105
+
106
+ const MyHarness = ({ useLogicalProperties, ...rest }: HarnessProps) => (
107
+ <MockGamutProvider useLogicalProperties={useLogicalProperties}>
108
+ <MyComponent {...rest} />
109
+ </MockGamutProvider>
110
+ );
111
+
112
+ const renderView = setupRtl(MyHarness, { width: '200px' });
104
113
 
105
- ```tsx
106
114
  describe.each([
107
- { useLogicalProperties: true, widthProp: 'inlineSize' },
108
- { useLogicalProperties: false, widthProp: 'width' },
115
+ { useLogicalProperties: true as const, widthProp: 'inlineSize' as const },
116
+ { useLogicalProperties: false as const, widthProp: 'width' as const },
109
117
  ])(
110
118
  'useLogicalProperties=$useLogicalProperties',
111
119
  ({ useLogicalProperties, widthProp }) => {
112
120
  it(`uses ${widthProp}`, () => {
113
- const { container } = render(
114
- <MockGamutProvider useLogicalProperties={useLogicalProperties}>
115
- <MyComponent width="200px" />
116
- </MockGamutProvider>
117
- );
118
- expect(container.firstChild).toHaveStyle({ [widthProp]: '200px' });
121
+ const { view } = renderView({ useLogicalProperties });
122
+ expect(view.getByTestId('my-component-root')).toHaveStyle({
123
+ [widthProp]: '200px',
124
+ });
119
125
  });
120
126
  }
121
127
  );
122
128
  ```
123
129
 
130
+ The outer `setupRtl` wrapper adds a default `MockGamutProvider`; the harness’s inner `MockGamutProvider` sets `useLogicalProperties` for the subtree under test (nested `GamutProvider` / theme is the nearest one Emotion and variance see).
131
+
132
+ ### `ColorMode` without abandoning `setupRtl`
133
+
134
+ ```tsx
135
+ import { ColorMode } from '@codecademy/gamut-styles';
136
+ import { setupRtl } from '@codecademy/gamut-tests';
137
+
138
+ const DarkHarness = (props: React.ComponentProps<typeof MyComponent>) => (
139
+ <ColorMode mode="dark">
140
+ <MyComponent {...props} />
141
+ </ColorMode>
142
+ );
143
+
144
+ const renderDark = setupRtl(DarkHarness, { title: 'Hi' });
145
+ ```
146
+
147
+ Use `MockGamutProvider` only inside the harness if you also need a non-default Gamut flag and `ColorMode` in the same tree; otherwise `setupRtl(DarkHarness)` is enough.
148
+
149
+ ---
150
+
151
+ ## Raw `render` + `MockGamutProvider` — rare
152
+
153
+ Reserve `render` from `@testing-library/react` + manual `MockGamutProvider` for cases where a harness would be more obscure than a single inline tree (e.g. highly dynamic one-off trees). If the same wrapper appears more than once, switch to a harness + `setupRtl`.
154
+
155
+ ---
156
+
157
+ ## RTL / `dir` and document-level behavior
158
+
159
+ Some components (e.g. overlays that call `useElementDir`) resolve direction from `document.documentElement` when there is no real target node. For those tests:
160
+
161
+ - Set `document.documentElement.setAttribute('dir', 'rtl')` (or `'ltr'`) around the scenario, `unmount` between LTR and RTL assertions when re-rendering, and restore `dir` in `afterEach` so other tests start clean.
162
+ - Combine with the harness pattern above when `useLogicalProperties` affects which longhand wins (`left` vs `insetInlineStart`, etc.).
163
+
124
164
  ---
125
165
 
126
166
  ## Emotion style assertions
127
167
 
128
- Install `@emotion/jest` matchers once per test file to enable CSS-in-JS assertions:
168
+ Install `@emotion/jest` matchers if you absolutely need to enable CSS-in-JS assertions:
129
169
 
130
170
  ```tsx
131
171
  import { matchers } from '@emotion/jest';
@@ -140,7 +180,7 @@ expect(element).toHaveStyle({ borderRadius: '2px' });
140
180
  expect(element).toHaveStyleRule('padding', '1rem');
141
181
  ```
142
182
 
143
- Use `theme` from `@codecademy/gamut-styles` to avoid hardcoding token values:
183
+ Use `theme` from `@codecademy/gamut-styles` instead of hardcoding token strings:
144
184
 
145
185
  ```tsx
146
186
  import { theme } from '@codecademy/gamut-styles';
@@ -150,15 +190,17 @@ expect(element).toHaveStyle({ columnGap: theme.spacing[40] });
150
190
 
151
191
  ---
152
192
 
153
- ## Visual test wrappers
193
+ ## Visual test wrappers and Storybook
154
194
 
155
- When creating mock components for visual tests or Storybook, wrap with `MockGamutProvider` and `ColorMode`:
195
+ Exported mocks and stories may wrap with `MockGamutProvider` and `ColorMode` explicitly (no `setupRtl` in Storybook):
156
196
 
157
197
  ```tsx
158
198
  import { MockGamutProvider } from '@codecademy/gamut-tests';
159
199
  import { ColorMode } from '@codecademy/gamut-styles';
160
200
 
161
- export const MyComponentMock: React.FC<ComponentProps<typeof MyComponent>> = (props) => (
201
+ export const MyComponentMock: React.FC<ComponentProps<typeof MyComponent>> = (
202
+ props
203
+ ) => (
162
204
  <MockGamutProvider>
163
205
  <ColorMode mode="light">
164
206
  <MyComponent {...props} />
@@ -171,11 +213,13 @@ export const MyComponentMock: React.FC<ComponentProps<typeof MyComponent>> = (pr
171
213
 
172
214
  ## Common anti-patterns
173
215
 
174
- | Anti-pattern | Fix |
175
- |---|---|
176
- | `jest.mock('@codecademy/gamut', () => ({ ... }))` | Remove mock; use `setupRtl` or `MockGamutProvider` |
177
- | `jest.mock('@codecademy/gamut-styles', ...)` | Remove mock; `MockGamutProvider` handles theme context |
178
- | Wrapping with `GamutProvider` directly in tests | Use `MockGamutProvider` it sets `useCache={false}` and `useGlobals={false}` |
179
- | Repeating `render(<MockGamutProvider>...</MockGamutProvider>)` in every test | Extract with `setupRtl`; define `renderView` once above the `describe` block |
180
- | One `setupRtl` call per `it` block | Define `renderView` once outside `describe`, call it inside each `it` |
181
- | Asserting on raw CSS token strings | Import `theme` from `@codecademy/gamut-styles` and use `theme.spacing[n]`, `theme.fontSize`, etc. |
216
+ | Anti-pattern | Fix |
217
+ | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
218
+ | `jest.mock('@codecademy/gamut', () => ({ ... }))` | Remove; use `setupRtl` (or harness + `setupRtl`) |
219
+ | `jest.mock('@codecademy/gamut-styles', ...)` | Remove; `MockGamutProvider` / `setupRtl` supplies theme |
220
+ | `GamutProvider` in test files | Use `MockGamutProvider` only when building a harness or story; default tests go through `setupRtl` |
221
+ | `import { setupRtl } from 'component-test-setup'` in Gamut / apps | Import `setupRtl` from `@codecademy/gamut-tests` so `MockGamutProvider` is applied |
222
+ | Repeated `render(<MockGamutProvider>…` | Harness + `setupRtl`, or a shared `renderView` factory |
223
+ | One `setupRtl` call per `it` | Define `renderView` once outside `describe`, call it inside each `it` |
224
+ | Asserting raw CSS strings for tokens | Use `theme` from `@codecademy/gamut-styles` |
225
+ | Leaking `dir="rtl"` between tests | Reset `document.documentElement` in `afterEach` |
@@ -1,115 +1,63 @@
1
1
  ---
2
2
  name: gamut-theming
3
- description: Use this skill when working with GamutProvider themes, Emotion theme tokens, or behavior that differs across Core, Admin, Platform, LX Studio, and Percipio including new themes, token access in styled components, or debugging theme-specific styles.
3
+ description: Gamut themes, GamutProvider, theme.d.ts, CreatingThemes. When invoked, read guidelines/setup.md and repo DESIGN.md first. Not for css/variant/states (see gamut-style-utilities).
4
4
  ---
5
5
 
6
6
  # Gamut Theming
7
7
 
8
+ ## Read first
9
+
10
+ When this skill applies, read before writing code:
11
+
12
+ - [`guidelines/setup.md`](../../guidelines/setup.md)
13
+ - Root `DESIGN.md` in the app repo (product tokens and patterns)
14
+
8
15
  Source: `@codecademy/gamut-styles`
9
16
 
17
+ See also: [`gamut-style-utilities`](../gamut-style-utilities/SKILL.md) (`css`, `variant`, `states`, `StyleProps`, `useTheme` escape hatch). [`gamut-color-mode`](../gamut-color-mode/SKILL.md) (semantic color, `<ColorMode>`, `<Background>`). [`gamut-system-props`](../gamut-system-props/SKILL.md) (`system.*`, responsive `Box` props).
18
+
10
19
  ## Overview
11
20
 
12
- Gamut uses Emotion's theme system. All styled components have access to a typed theme object containing every design token. Themes are org-specific collections of tokens; components work across all themes without modification as long as they use token aliases rather than hardcoded values.
21
+ Gamut uses Emotion's theme system. Themes are org-specific token bundles (colors, typography, spacing, etc.). The active theme is set at the app root with `<GamutProvider theme={...}>`; child styled components read tokens through Emotion context.
22
+
23
+ For authoring component styles (`css`, `variant`, `states`, system props, ColorMode), use the skills linked above and the styleguide [Best practices](../../../../styleguide/src/lib/Meta/Best%20practices.mdx).
24
+
25
+ ## Infer theme from the repo
26
+
27
+ Do not hardcode a product theme in generic guidance. In an app repo:
13
28
 
14
- **See also:** [`gamut-color-mode`](../gamut-color-mode/SKILL.md) for `<ColorMode>`, `<Background>`, `useColorModes`, and contrast-safe surfaces read that before wiring light/dark or colored sections. Storybook: [ColorMode](https://gamut.codecademy.com/?path=/docs-foundations-colormode--page), [Best practices](https://gamut.codecademy.com/?path=/docs-meta-best-practices--page).
29
+ 1. Read root `DESIGN.md` (installed via `gamut plugin install --theme <name>`) for semantic tokens, fonts, and product patterns.
30
+ 2. Confirm `<GamutProvider theme={...}>` matches that product (`coreTheme`, `percipioTheme`, `lxStudioTheme`, etc.).
31
+ 3. Use Storybook Foundations / Theme stories for the active product when verifying hex ↔ semantic mappings.
15
32
 
16
33
  ## Available themes
17
34
 
18
35
  | Theme | Used for |
19
36
  | --------- | ---------------------------------------------------------- |
20
- | Core | Codecademy default (public-facing products) |
37
+ | Core | Codecademy default |
21
38
  | Admin | Codecademy admin tools |
22
39
  | Platform | Codecademy learning environment / shared platform surfaces |
23
40
  | LX Studio | Learning Experience Studio |
24
41
  | Percipio | Skillsoft Percipio platform |
25
42
 
26
- The active theme is set at the app level via `<GamutProvider>`. Components inside receive the current theme via Emotion's context.
27
-
28
- ## Accessing tokens in styled components
29
-
30
- ### Via `css()` utility (recommended for static styles)
31
-
32
- ```tsx
33
- import { css } from '@codecademy/gamut-styles';
34
- import styled from '@emotion/styled';
35
-
36
- // Static color token
37
- const Box = styled.div(css({ bg: 'navy-400', p: 4 }));
38
-
39
- // Semantic color alias (adapts to color mode and theme)
40
- const Text = styled.div(css({ color: 'primary', p: 4 }));
41
- ```
42
-
43
- ### Via `theme` prop (for dynamic styles)
44
-
45
- Every Emotion styled component receives `theme` as a prop:
46
-
47
- ```tsx
48
- import styled from '@emotion/styled';
49
-
50
- const DynamicBox = styled.div`
51
- color: ${({ theme }) => theme.colors.blue};
52
- font-size: ${({ theme }) => theme.fontSize[16]};
53
- `;
54
- ```
55
-
56
- ### Via imported `theme` object (outside styled components)
57
-
58
- ```tsx
59
- import { css as emotionCss } from '@emotion/react';
60
- import { theme } from '@codecademy/gamut-styles';
61
-
62
- // For use in plain CSS-in-JS outside of styled components
63
- const myStyles = emotionCss`
64
- font-size: ${theme.fontSize[14]};
65
- color: ${theme.colors['navy-400']};
66
- `;
67
- ```
68
-
69
- ### Via `useTheme()` hook
70
-
71
- ```tsx
72
- import { useTheme } from '@emotion/react';
73
-
74
- const MyComponent = () => {
75
- const theme = useTheme();
76
- return <div style={{ color: theme.colors.primary }} />;
77
- };
78
- ```
79
-
80
- ## System props as the primary token API
81
-
82
- For most styling needs, use **system props** (see the `gamut-system-props` skill) rather than accessing the theme directly. System props are the idiomatic way to use design tokens in Gamut components:
83
-
84
- ```tsx
85
- import { variance } from '@codecademy/variance';
86
- import { system } from '@codecademy/gamut-styles';
87
-
88
- const Card = styled.div(
89
- variance.compose(system.layout, system.space, system.color)
90
- );
91
-
92
- // token scale values are validated at the type level
93
- <Card bg="navy-400" p={16} width="100%" />;
94
- ```
95
-
96
- ## Theme-aware vs. color-mode-aware
43
+ Product-level import names and `theme.d.ts` patterns live in [setup.md](../../guidelines/setup.md).
97
44
 
98
- | Concern | Tool |
99
- | ---------------------------------------------- | ------------------------------------------ |
100
- | Tokens change per theme (colors, sizes, fonts) | Access via `theme.*` or system props |
101
- | Colors change per light/dark mode | Use semantic color aliases + `<ColorMode>` |
102
- | Background contrast | Use `<Background>` from ColorMode |
45
+ ## Theme vs color mode vs style API
103
46
 
104
- Semantic aliases like `primary`, `secondary`, `text`, `background` serve double duty: they resolve to theme-specific values **and** switch between light/dark variants automatically.
47
+ | Concern | Where to read |
48
+ | ------------------------------------------------------- | -------------------------------------------------- |
49
+ | Which `theme` object to pass to `GamutProvider` | This skill + [setup.md](../../guidelines/setup.md) |
50
+ | Light / dark semantic colors, `ColorMode`, `Background` | `gamut-color-mode` |
51
+ | `css` / `variant` / `states`, `useTheme` for non-CSS JS | `gamut-style-utilities` |
52
+ | Composed `system.*` props on styled primitives / `Box` | `gamut-system-props` |
105
53
 
106
54
  ## Creating a new theme
107
55
 
108
- See `CreatingThemes.mdx` in the styleguide (`packages/styleguide/src/lib/Foundations/Theme/CreatingThemes.mdx`) for the full guide. Themes are defined in `@codecademy/gamut-styles` and must extend the base theme shape with all required token keys.
56
+ See `CreatingThemes.mdx` in the styleguide (`packages/styleguide/src/lib/Foundations/Theme/CreatingThemes.mdx`). Themes are defined in `@codecademy/gamut-styles` and must extend the base theme shape with all required token keys.
109
57
 
110
58
  ## Key principles
111
59
 
112
- - Prefer semantic token aliases over raw token values when the style needs to respond to color mode changes.
113
- - Use raw tokens (e.g. `navy-400`) only for styles that should be fixed regardless of mode.
114
- - Never hardcode hex values in styled components always go through the theme/system props.
115
- - The `GamutProvider` at the app root wires up the theme, color mode, and logical properties settings; components should not need to know which theme is active.
60
+ - Pick the correct theme export for the product (`coreTheme`, `adminTheme`, `platformTheme`, `lxStudioTheme`, `percipioTheme`, etc.) so tokens and fonts match `DESIGN.md` and design intent.
61
+ - Align `theme.d.ts` / `Theme extends …` with the same theme interface you pass to `GamutProvider` (see [setup.md](../../guidelines/setup.md)).
62
+ - Components stay portable across themes when they use token and semantic aliases rather than one-off hex; authoring rules live in `gamut-style-utilities` and `gamut-color-mode`.
63
+ - `GamutProvider` wires theme, color mode, and logical-properties settings at the root; individual components should not hard-code which org theme is active.
@@ -1,123 +1,79 @@
1
1
  ---
2
2
  name: gamut-typography
3
- description: Use this skill when creating or reviewing UI text in Gamut apps — headlines, body, captions, labels, code snippets, or text-heavy layouts — even if the user does not name fonts or tokens. Covers Apercu Pro, Suisse Intl Mono, scale, line heights, line length, and alignment.
3
+ description: Gamut UI text — headlines, body, fontSize, lineHeight, fontWeight title. When invoked, read guidelines/foundations/typography.md and DESIGN.md for the active theme.
4
4
  ---
5
5
 
6
6
  # Gamut Typography
7
7
 
8
- > **Scope**: This skill covers typography for **Codecademy products** using the Core, Admin, or Platform themes (Apercu + Suisse). Percipio uses Roboto for all type — see `DESIGN.md` for Percipio-specific guidance. LX Studio uses Hanken Grotesk in place of both Apercu and Suisse.
8
+ ## Read first
9
9
 
10
- ## Typefaces
10
+ When this skill applies, read [`guidelines/foundations/typography.md`](../../guidelines/foundations/typography.md) before writing code. Confirm stacks and `fontWeight.title` against root `DESIGN.md`.
11
11
 
12
- Codecademy products use two typefaces:
12
+ Implementation source of truth: [`packages/gamut-styles/src/variables/typography.ts`](https://github.com/Codecademy/gamut/blob/main/packages/gamut-styles/src/variables/typography.ts) and themes under [`packages/gamut-styles/src/themes`](https://github.com/Codecademy/gamut/tree/main/packages/gamut-styles/src/themes).
13
13
 
14
- ### Apercu Pro (`fontFamily: "base"`)
14
+ ## Scope by theme
15
15
 
16
- The primary typeface. Geometric-ish, humanist sans-serif. Use for:
17
- - Headlines (Bold weight)
18
- - Body / paragraph text (Regular weight)
19
- - UI labels, menu items (Regular weight)
20
- - Emphasis within body copy (Italic — **not** Bold)
16
+ | Themes | Fonts | `fontWeight.title` |
17
+ | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
18
+ | Core, Admin, Platform | `base` → Apercu stack; `accent` → Suisse + Apercu stack | 700 |
19
+ | Percipio, LX Studio | `base` → Skillsoft Text; `accent` → Skillsoft Sans; Percipio `monospace` → Roboto Mono; LX `monospace` matches Core stack per theme file | 500 |
21
20
 
22
- **Rules:**
23
- - Do not use Bold to emphasize text within a Regular-weight paragraph. Use Italic instead.
24
- - Set with generous line-height for body text: **150–175%** of the type size (e.g. 16px type → 24–28px line-height).
25
- - Headlines should use **100–110%** line-height to appear intentional and grouped.
26
- - Text should be **left-aligned** by default.
27
-
28
- ### Suisse Intl Mono (`fontFamily: "accent"`)
29
-
30
- Monospace accent typeface. Use sparingly for:
31
- - Code snippets and inline code
32
- - Numbers, figures, and statistics
33
- - Captions and labels that reference technical/engineering context
34
- - Enumerated lists
35
- - Quotations in a technical voice
36
-
37
- **Rules:**
38
- - Every character is the same width — avoid long paragraph-length prose in Suisse.
39
- - It reads large for its point size: **reduce the size by ~10–15%** relative to Apercu text at the same visual scale (e.g. 14px Suisse ≈ 16px Apercu visually).
40
- - Requires extra line-height to remain readable.
21
+ Use `fontWeight="title"` for headlines / emphasis roles — never hardcode `700` on Percipio/LX unless SPECIFICALLY noted in Figma designs.
41
22
 
42
23
  ## Font size scale (`fontSize`)
43
24
 
44
- Sizes are accessed via the theme's `fontSize` scale. Common keys:
25
+ Theme keys: `64`, `44`, `34`, `26`, `22`, `20`, `18`, `16`, `14`.
45
26
 
46
27
  ```tsx
47
28
  import { css } from '@codecademy/gamut-styles';
48
29
  import styled from '@emotion/styled';
49
-
50
- // Via system props
51
30
  import { system } from '@codecademy/gamut-styles';
52
- const Text = styled.p(system.typography);
53
- <Text fontSize={16} />;
54
31
 
55
- // Via css() utility
56
- const Box = styled.div(css({ fontSize: 14 }));
32
+ const Paragraph = styled.p(system.typography);
33
+ <Paragraph fontSize={16} lineHeight="base" />;
57
34
 
58
- // Via theme directly (outside styled components)
59
- import { theme } from '@codecademy/gamut-styles';
60
- const size = theme.fontSize[16];
35
+ const Styled = styled.div(css({ fontSize: 14, fontFamily: 'base' }));
61
36
  ```
62
37
 
63
- ## Line heights (`lineHeight`)
38
+ ## Line height (`lineHeight`)
64
39
 
65
- Line heights are limited to **multiples of 4px**. Type boxes are placed on an **8px placement grid**.
66
-
67
- Guidelines:
68
- - Body text: 150–175% of font size
69
- - Headlines: 100–110% of font size
40
+ Tokens: `base` (1.5), `spacedTitle` (1.3), `title` (1.2). Prefer tokens over raw decimals. Only specify `lineHeight` when specified by design.
70
41
 
71
42
  ## Line length
72
43
 
73
- Controlling line length is essential for readability:
74
-
75
- | Context | Target |
76
- |---|---|
77
- | Single-column body text | ~66 characters (max 85) |
78
- | Multi-column layouts | ≤50 characters per line |
79
- | Minimum | 45 characters |
80
-
81
- **How to control line length**: Start with the right text style for the design, then adjust the width or column count of the text container.
82
-
83
- ## Alignment
84
-
85
- - **Left-align** paragraphs by default — this is easiest to read and supports grid alignment.
86
- - **Center-align** only for short marketing headlines or specific interface components with brief text.
87
- - **Never right-align** text in normal circumstances (exceptions: numbers, equations).
88
- - Do not adjust letter-spacing.
44
+ | Context | Target |
45
+ | ------------------ | ------------------------ |
46
+ | Single-column body | ~66 characters (max ~85) |
47
+ | Multi-column | ≤50 characters per line |
48
+ | Minimum | ~45 characters |
89
49
 
90
50
  ## Accessing typography tokens
91
51
 
92
52
  ```tsx
93
- // System props (recommended for styled components)
94
53
  import { system } from '@codecademy/gamut-styles';
95
54
  import { variance } from '@codecademy/variance';
96
55
 
97
- const Heading = styled.h2(
98
- variance.compose(system.typography, system.space)
99
- );
56
+ const Heading = styled.h2(variance.compose(system.typography, system.space));
100
57
 
101
- <Heading fontSize={24} fontFamily="base" fontWeight="bold" lineHeight={1.1} mb={8} />;
58
+ <Heading
59
+ fontSize={26}
60
+ fontFamily="base"
61
+ fontWeight="title"
62
+ lineHeight="title"
63
+ mb={8}
64
+ />;
102
65
 
103
- // css() utility (recommended for static styles)
104
66
  import { css } from '@codecademy/gamut-styles';
105
67
 
106
68
  const Caption = styled.span(
107
- css({ fontFamily: 'accent', fontSize: 12, color: 'secondary' })
69
+ css({ fontFamily: 'accent', fontSize: 14, color: 'text-secondary' })
108
70
  );
109
-
110
- // Theme object (outside styled components)
111
- import { theme } from '@codecademy/gamut-styles';
112
- import { css as emotionCss } from '@emotion/react';
113
-
114
- const myStyles = emotionCss`
115
- font-size: ${theme.fontSize[14]};
116
- `;
117
71
  ```
118
72
 
119
- ## Semantic vs. visual sizing
73
+ Prefer `<Text>` from `@codecademy/gamut` with `variant` / `as` — see Storybook [Typography / Text](https://gamut.codecademy.com/?path=/docs-typography-text--docs).
74
+
75
+ ## Semantic vs visual headings
120
76
 
121
- - The term **"Title"** distinguishes visual size from semantic HTML hierarchy (H1–H6).
122
- - A visually large title may use `<h2>` or `<p>` semantically visual scale and semantic meaning are independent in Gamut.
123
- - Choose HTML heading levels for document structure, choose font size for visual hierarchy.
77
+ - `<Text as="h1">` … `<Text as="h6">` gets default heading styles: each tag maps to the same scale as `variant="title-xxl"` … `variant="title-xs"` (`h1` largest through `h6` smallest). Plain `<Text>` defaults to `as="span"` (inherits font size).
78
+ - Use `variant` plus `fontSize` / `fontWeight` / `lineHeight` (and other system props) to override element defaults when the outline needs one heading level but the UI needs another visual weight — e.g. `<Text as="h2" variant="title-sm">`.
79
+ - Still pick `h1`–`h6` for document structure and assistive tech; overrides are for intentional divergence between semantics and appearance.