@codecademy/gamut
72.0.272.0.3-alpha.67eeac.0
+
Added (4 files)
~
Modified (16 files)
Index: package/package.json
===================================================================
--- package/package.json
+++ package/package.json
@@ -1,16 +1,16 @@
{
"name": "@codecademy/gamut",
"description": "Styleguide & Component library for Codecademy",
- "version": "72.0.2",
+ "version": "72.0.3-alpha.67eeac.0",
"author": "Codecademy Engineering <[email protected]>",
"bin": "./bin/gamut.mjs",
"dependencies": {
- "@codecademy/gamut-icons": "9.57.9",
- "@codecademy/gamut-illustrations": "0.58.15",
- "@codecademy/gamut-patterns": "0.10.34",
- "@codecademy/gamut-styles": "20.0.2",
- "@codecademy/variance": "0.26.1",
+ "@codecademy/gamut-icons": "9.57.10-alpha.67eeac.0",
+ "@codecademy/gamut-illustrations": "0.58.16-alpha.67eeac.0",
+ "@codecademy/gamut-patterns": "0.10.35-alpha.67eeac.0",
+ "@codecademy/gamut-styles": "20.0.3-alpha.67eeac.0",
+ "@codecademy/variance": "0.26.2-alpha.67eeac.0",
"@formatjs/intl-locale": "5.3.1",
"@react-aria/interactions": "3.25.0",
"@types/marked": "^4.0.8",
"@vidstack/react": "^1.12.12",
@@ -54,8 +54,9 @@
"scripts": {
"build": "nx build @codecademy/gamut",
"build:watch": "yarn build && onchange ./src -- yarn build",
"compile": "babel ./src --out-dir ./dist --extensions \".ts,.tsx\"",
+ "test:bin": "node --test bin/__tests__/design.test.mjs",
"verify": "tsc --noEmit && tsc --project tsconfig.bin.json"
},
"sideEffects": [
"**/*.css", Index: package/agent-tools/.claude-plugin/plugin.json
===================================================================
--- package/agent-tools/.claude-plugin/plugin.json
+++ package/agent-tools/.claude-plugin/plugin.json
@@ -1,7 +1,7 @@
{
"name": "gamut-design-system",
- "version": "0.0.1",
+ "version": "0.0.2",
"description": "Gamut design system agent tools: skills and rules for AI-assisted development.",
"license": "MIT",
"keywords": ["codecademy", "gamut", "design-system", "agent-skills"]
} Index: package/agent-tools/.cursor-plugin/plugin.json
===================================================================
--- package/agent-tools/.cursor-plugin/plugin.json
+++ package/agent-tools/.cursor-plugin/plugin.json
@@ -1,7 +1,7 @@
{
"name": "gamut-design-system",
"displayName": "Gamut Design System",
- "version": "0.0.1",
+ "version": "0.0.2",
"description": "Gamut design system agent tools: skills and rules for AI-assisted development.",
"keywords": ["codecademy", "gamut", "design-system", "agent-skills"]
} Index: package/agent-tools/skills/gamut-accessibility/SKILL.md
===================================================================
--- package/agent-tools/skills/gamut-accessibility/SKILL.md
+++ package/agent-tools/skills/gamut-accessibility/SKILL.md
@@ -1,7 +1,7 @@
---
name: gamut-accessibility
-description: Deep Gamut accessibility reference (component matrix, overlays, tips, live regions, checklists). Form wiring and validation UX live in `gamut-forms`. Universal HTML/ARIA/focus/color rules: always-loaded `accessibility.mdc` — read that first; this skill does not duplicate them.
+description: Use this skill when implementing accessibility for a specific Gamut component, building a custom overlay or composite widget, or auditing component usage against WCAG — complements the always-loaded `accessibility.mdc` with Gamut component-specific patterns. Form wiring lives in `gamut-forms`.
---
# Gamut Accessibility Index: package/agent-tools/skills/gamut-buttons/SKILL.md
===================================================================
--- package/agent-tools/skills/gamut-buttons/SKILL.md
+++ package/agent-tools/skills/gamut-buttons/SKILL.md
@@ -4,9 +4,9 @@
---
# Gamut Buttons
-Which button component and which `variant` to use. Colors are wired inside each atom — consumers do not pass `color`, `bg`, hex, or semantic token names on stock buttons.
+Which button component and which `variant` to use.
See also: [`gamut-color-mode`](../gamut-color-mode/SKILL.md) — semantic tokens for custom styled controls only, not stock button atoms; ColorMode / `<Background>` when placing buttons on colored surfaces. [`gamut-accessibility`](../gamut-accessibility/SKILL.md) — universal action and naming rules.
Storybook:
@@ -86,8 +86,12 @@
- `href` + `disabled`: `ButtonBase` (internal) drops `href` and renders a `<button disabled>` — link-style buttons cannot stay anchors while disabled.
- `IconButton`: provide an accessible name via `tip` (used as `aria-label` when `aria-label` is omitted). See ToolTip / IconButton Storybook pages.
- `ButtonBase` is not exported from `@codecademy/gamut` (only the `ButtonBaseElements` type is). Prefer stock atoms; custom button styling belongs in Gamut itself or via `css` / `variant` from `gamut-styles`, not by importing `ButtonBase`.
+## Focus management — buttons with ToolTip
+
+When a button uses `placement="floating"` and the click does not naturally move DOM focus elsewhere, the tooltip may linger after a keyboard-triggered click. See [TOOLTIP_FOCUS.md](./TOOLTIP_FOCUS.md) for the pattern, when to apply `.blur()`, and when it is not needed.
+
## Rules
- Use `FillButton` for primary actions and `StrokeButton` for secondary — do not use both at equal weight on the same screen.
- Reserve `CTAButton` for marketing / high-visibility promotions; do not use it for standard UI actions. Index: package/agent-tools/skills/gamut-color-mode/SKILL.md
===================================================================
--- package/agent-tools/skills/gamut-color-mode/SKILL.md
+++ package/agent-tools/skills/gamut-color-mode/SKILL.md
@@ -121,105 +121,18 @@
// Boolean from window.matchMedia('(prefers-color-scheme: dark)')
const prefersDark = usePrefersDarkMode();
```
-## Decision guide
-
-| Need | Use |
-| ------------------------------------------------------------- | ---------------------------------- | ---- | --------- |
-| Set a page or section to a specific mode | `<ColorMode mode="light | dark | system">` |
-| Place content on a colored background with automatic contrast | `<Background bg="...">` |
-| Read the current mode in JavaScript | `useCurrentMode()` |
-| Access all modes, variables, and resolve raw colors | `useColorModes()` |
-| Detect OS dark mode preference | `usePrefersDarkMode()` |
-| Access full emotion theme | `useTheme()` from `@emotion/react` |
-
## Common mistakes to avoid
- Do not use raw color tokens (e.g. `color: 'navy-400'`) for text, backgrounds, or borders that need to be accessible across modes — use semantic aliases instead.
- Do not use a raw `bg` prop for colored section backgrounds that need static, ColorMode-agnostic, background colors — use `<Background>` so mode selection is handled for you.
- Do not manually set `ColorMode`'s `mode` from `usePrefersDarkMode()` when `mode="system"` is enough. The hook is still useful for non-mode concerns (e.g. choosing a decorative `bg` in Storybook demos).
-## Semantic aliases (theme-stable names)
+## Semantic aliases
-These tokens describe roles. Actual colors come from the active theme + ColorMode. Never assume Codecademy Core hex when advising another product.
+These tokens describe roles; actual colors come from the active theme + ColorMode. Full alias tables: [Foundations / ColorMode](https://gamut.codecademy.com/?path=/docs-foundations-colormode--page) — and per-theme breakdowns at [Core](https://gamut.codecademy.com/?path=/docs-foundations-theme-core-theme--docs) · [Admin](https://gamut.codecademy.com/?path=/docs-foundations-theme-admin-theme--docs) · [Platform](https://gamut.codecademy.com/?path=/docs-foundations-theme-platform-theme--docs) · [Percipio](https://gamut.codecademy.com/?path=/docs-foundations-theme-percipio-theme--docs) · [LX Studio](https://gamut.codecademy.com/?path=/docs-foundations-theme-lx-studio-theme--docs). Source: [`packages/gamut-styles/src/themes`](https://github.com/Codecademy/gamut/tree/main/packages/gamut-styles/src/themes).
-### Text
-
-| Token | Use for |
-| ---------------- | --------------------------- |
-| `text` | Default body and UI text |
-| `text-accent` | Stronger emphasis text |
-| `text-secondary` | Supporting / secondary copy |
-| `text-disabled` | Disabled state labels |
-
-### Background
-
-| Token | Use for |
-| --------------------- | --------------------------------- |
-| `background` | Default page/component background |
-| `background-primary` | Slightly elevated surfaces |
-| `background-contrast` | Maximum contrast surface |
-| `background-selected` | Selected row / item |
-| `background-hover` | Hover state overlay |
-| `background-disabled` | Disabled surface |
-| `background-success` | Success state container |
-| `background-warning` | Warning state container |
-| `background-error` | Error state container |
-
-### Interactive
-
-| Token | Use for |
-| ----------------- | ----------------------------------------- |
-| `primary` | Primary CTA, links, focus accents |
-| `primary-hover` | Hover on primary interactive |
-| `primary-inverse` | Accent on top of primary-colored surfaces |
-| `secondary` | Secondary CTA, ghost buttons |
-| `secondary-hover` | Hover on secondary interactive |
-| `danger` | Destructive actions, error emphasis |
-| `danger-hover` | Hover on danger interactive |
-
-### Border
-
-| Token | Use for |
-| ------------------ | -------------------------- |
-| `border-primary` | Strong borders, dividers |
-| `border-secondary` | Medium-weight borders |
-| `border-tertiary` | Subtle borders, separators |
-| `border-disabled` | Disabled input borders |
-
-### Feedback
-
-| Token | Use for |
-| ------------------ | -------------------------- |
-| `feedback-error` | Error messages, validation |
-| `feedback-success` | Success messages |
-| `feedback-warning` | Warning messages |
-
-## Where resolved colors are documented
-
-- Storybook [ColorMode](https://gamut.codecademy.com/?path=/docs-foundations-colormode--page)
-- [Core](https://gamut.codecademy.com/?path=/docs-foundations-theme-core-theme--docs) · [Admin](https://gamut.codecademy.com/?path=/docs-foundations-theme-admin-theme--docs) · [Platform](https://gamut.codecademy.com/?path=/docs-foundations-theme-platform-theme--docs) · [Percipio](https://gamut.codecademy.com/?path=/docs-foundations-theme-percipio-theme--docs) · [LX Studio](https://gamut.codecademy.com/?path=/docs-foundations-theme-lx-studio-theme--docs) theme pages
-- Root `DESIGN.md` from agent-tools (`DESIGN.Codecademy.md`, `DESIGN.Percipio.md`, `DESIGN.LXStudio.md`)
-- Source: [`packages/gamut-styles/src/themes`](https://github.com/Codecademy/gamut/tree/main/packages/gamut-styles/src/themes)
-
-## Codecademy Core — illustrative light/dark hex only
-
-Not valid for Percipio, LX Studio, or other themes. Quick mental model for Core defaults only.
-
-| Token | Light | Dark |
-| ---------------- | --------- | --------- |
-| `text` | `#10162F` | `#ffffff` |
-| `text-accent` | `#0A0D1C` | `#FFF0E5` |
-| `background` | `#ffffff` | `#10162F` |
-| `primary` | `#3A10E5` | `#FFD300` |
-| `primary-hover` | `#5533FF` | `#CCA900` |
-| `secondary` | `#10162F` | `#ffffff` |
-| `danger` | `#E91C11` | `#E85D7F` |
-| `feedback-error` | `#BE1809` | `#E85D7F` |
-
-Full tables: Storybook theme pages or audit with [`gamut-review`](../gamut-review/SKILL.md) Appendix A/B for hex triage.
-
## Raw palette (Core-centric reference)
Raw tokens name fixed swatches for `<Background bg="…">`, illustration, and surfaces. Confirm allowed keys in the active theme or `DESIGN.md` before using in non-Core apps. Index: package/agent-tools/skills/gamut-forms/SKILL.md
===================================================================
--- package/agent-tools/skills/gamut-forms/SKILL.md
+++ package/agent-tools/skills/gamut-forms/SKILL.md
@@ -1,7 +1,7 @@
---
name: gamut-forms
-description: Implementing or auditing Gamut forms — FormGroup, ConnectedForm, ConnectedFormGroup, GridForm, react-hook-form wiring, labels, and accessible error/description regions. Pair with `gamut-accessibility` for non-form widgets and `accessibility.mdc` for universal HTML/ARIA rules.
+description: Use this skill when implementing or auditing Gamut forms — FormGroup, ConnectedForm, ConnectedFormGroup, GridForm, react-hook-form wiring, labels, and accessible error/description regions. Pair with `gamut-accessibility` for non-form widgets and `accessibility.mdc` for universal HTML/ARIA rules.
---
# Gamut forms Index: package/agent-tools/skills/gamut-layout/SKILL.md
===================================================================
--- package/agent-tools/skills/gamut-layout/SKILL.md
+++ package/agent-tools/skills/gamut-layout/SKILL.md
@@ -1,7 +1,7 @@
---
name: gamut-layout
-description: Use this skill when applying Gamut spacing scale, border radii, viewport or container breakpoints, or page layout grid (LayoutGrid vs GridBox) — complements gamut-system-props for system.space and responsive props.
+description: Use this skill when applying Gamut spacing scale, border radii, responsive breakpoints (viewport or container), or the page layout grid (LayoutGrid vs GridBox) — including responsive prop patterns and mobile-first design. Complements gamut-system-props for system.space and responsive props.
---
# Gamut Layout Index: package/agent-tools/skills/gamut-list/SKILL.md
===================================================================
--- package/agent-tools/skills/gamut-list/SKILL.md
+++ package/agent-tools/skills/gamut-list/SKILL.md
@@ -8,9 +8,9 @@
Structured, repeating layouts built from `List`, `ListRow`, `ListCol`, and `TableHeader`. Colors, borders, and spacing are wired through the `variant` and `spacing` props — consumers do not override these with raw CSS values.
Source: `@codecademy/gamut` — [List.tsx](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/List/List.tsx)
-See also: [`gamut-accessibility`](../gamut-accessibility/SKILL.md) — ARIA, focus, and keyboard interaction rules. [`gamut-layout`](../gamut-layout/SKILL.md) — spacing tokens and system props.
+See also: [`gamut-datatable`](../gamut-datatable/SKILL.md) — use instead of List when data needs sorting, filtering, or query state. [`gamut-datalist`](../gamut-datalist/SKILL.md) — use instead of List when rows need expansion or selection. [`gamut-accessibility`](../gamut-accessibility/SKILL.md) — ARIA, focus, and keyboard interaction rules. [`gamut-layout`](../gamut-layout/SKILL.md) — spacing tokens and system props.
Storybook:
- [Organisms / Lists & Tables / List](https://gamut.codecademy.com/?path=/docs-organisms-lists-tables-list-list--docs)
@@ -31,10 +31,18 @@
| `TableHeader` | Sticky header row; use only with `List as="table"` |
## When to use List
+List is for **fully custom, manually composed layouts**. Reach for a higher-level component first:
+
+- Data that needs **sorting, filtering, or query state** → use [`DataTable`](../gamut-datatable/SKILL.md) or [`DataList`](../gamut-datalist/SKILL.md) instead of wiring these up manually in List.
+- Rows that need **expansion or row selection** → use [`DataList`](../gamut-datalist/SKILL.md).
+
+Use List directly when:
+
+- You need fully custom row/column composition that DataTable or DataList cannot accommodate.
- Displaying repetitive content where individual rows may contain interactive elements, metrics, or controls — use List, not Card.
-- Comparing data across rows — use `variant="table"` (or `as="table"`) rather than a plain `<table>`.
+- Comparing data across rows with a fully custom layout — use `variant="table"` (or `as="table"`) rather than a plain `<table>`.
- Needing numbered rows — use `as="ol"`.
- **Needing multiple expandable/disclosure-style items** — use List's expandable row pattern (see [Expandable rows](#expandable-rows)), not multiple standalone `Disclosure` components.
## Variants Index: package/agent-tools/skills/gamut-review/SKILL.md
===================================================================
--- package/agent-tools/skills/gamut-review/SKILL.md
+++ package/agent-tools/skills/gamut-review/SKILL.md
@@ -1,7 +1,7 @@
---
name: gamut-review
-description: Use this skill when auditing existing code for Gamut usage — dependencies, GamutProvider, deep imports, hardcoded hex colors, and test patterns — and you need a consolidated report with pointers to matching Gamut skills.
+description: Use this skill when auditing existing code for Gamut usage and you need a consolidated report — checks dependencies, setup, import patterns, hardcoded colors, and test setup, with pointers to remediation skills.
---
# Gamut Review
@@ -10,9 +10,9 @@
When `DESIGN.md` is present at the audit root, use it as the authoritative reference for product design intent, token names, and component patterns. It is copied from `DESIGN.Codecademy.md`, `DESIGN.Percipio.md`, or `DESIGN.LXStudio.md` in `@codecademy/gamut` agent-tools (via `gamut plugin install --theme <name>`). When a finding maps to a skill, note it in the report so the developer knows where to get remediation guidance.
Run Check 0 first, then Checks 1–5, then print a single consolidated report using the format at the end of this file.
-Remediation skills: [`gamut-theming`](../gamut-theming/SKILL.md) · [`gamut-color-mode`](../gamut-color-mode/SKILL.md) · [`gamut-testing`](../gamut-testing/SKILL.md)
+Remediation skills: [`gamut-theming`](../gamut-theming/SKILL.md) · [`gamut-color-mode`](../gamut-color-mode/SKILL.md) · [`gamut-system-props`](../gamut-system-props/SKILL.md) · [`gamut-style-utilities`](../gamut-style-utilities/SKILL.md) · [`gamut-typography`](../gamut-typography/SKILL.md) · [`gamut-testing`](../gamut-testing/SKILL.md)
---
## Check 0 — DESIGN.md present
@@ -29,13 +29,14 @@
## Check 1 — Dependencies
Read `package.json` at the audit root. Inspect `dependencies`, `devDependencies`, and `peerDependencies` combined.
-| Package | Expectation |
-| -------------------------- | ------------------------------------------------------- |
-| `@codecademy/gamut` | Required — core component library |
-| `@codecademy/gamut-styles` | Recommended — design tokens and theme primitives |
-| `@codecademy/variance` | Recommended — style-prop system used by Gamut internals |
+| Package | Expectation |
+| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `@codecademy/gamut` | Required — core component library |
+| `@codecademy/gamut-styles` | Recommended — design tokens and theme primitives |
+| `@codecademy/variance` | Recommended — style-prop system used by Gamut internals |
+| `@codecademy/gamut-kit` | Acceptable alternative meta-package — re-exports `@codecademy/gamut`, `@codecademy/gamut-styles`, `@codecademy/variance`, and more. Treat its presence as satisfying the three rows above; do not separately flag those packages as missing. **Caveat:** requires npm or yarn with `nodeLinker: node-modules`; not compatible with yarn Plug'n'Play. Flag as ⚠ warning if `.yarnrc.yml` shows `nodeLinker: pnp`. |
---
## Check 2 — Setup
@@ -52,8 +53,10 @@
| `declare module '@emotion/react'` | **Required if TypeScript** — `Theme` must be augmented with the active theme type (e.g. `CoreTheme`) so scale props type-check correctly; grep `.d.ts` and `.ts`/`.tsx` source files for this string. **Recommended if not TypeScript** — note that adopting TypeScript is recommended and that `theme.d.ts` will be needed when it is. |
For each found symbol report the first file path where it appears.
+**Conditional — `StyleProps` with `states()`/`variant()`**: If source files contain `states(` or `variant(` from `@codecademy/gamut-styles`, check whether component prop interfaces use `StyleProps<typeof ...>` from `@codecademy/variance`. When `states()`/`variant()` are present but `StyleProps` is absent from associated component interfaces, report as ⚠ warning: `StyleProps not used — state/variant props may be untyped`. Remediation: `import { StyleProps } from '@codecademy/variance'` and add `extends StyleProps<typeof myStates>` to the component interface. Skill reference: [`gamut-style-utilities`](../gamut-style-utilities/SKILL.md).
+
---
## Check 3 — Import patterns
@@ -69,8 +72,110 @@
Report each violation as `file:line`.
---
+## Check 3b — SCSS/CSS module imports and className on Gamut components
+
+Gamut components are styled via the variance system (system props, `css()`, `variant()`, `states()` from `@codecademy/gamut-styles`). Importing SCSS/CSS modules and passing `className` to Gamut components bypasses this system entirely, breaks ColorMode token propagation, and prevents system props from composing correctly.
+
+**Step 1 — SCSS/CSS module imports**
+
+Grep source files (`.ts`, `.tsx`, `.js`, `.jsx`) for:
+
+```
+import .* from '.*\.(scss|css)'
+```
+
+Skip `node_modules`, `dist`. Each match is an error unless:
+
+- The import targets a third-party stylesheet (e.g. a carousel or date-picker vendor sheet that cannot be replaced) — flag as ⚠ warning with note "third-party vendor styles".
+- The file is a global reset or application shell (not a component) — flag as ⚠ warning.
+
+Report the count and list of files. If there are more than 5 files, group by directory and report totals rather than listing every file.
+
+**Step 2 — className on Gamut components**
+
+Grep source files for `className=` appearing on any of the core Gamut component names in the same JSX element opening tag. The known Gamut components to check:
+
+```
+Box, FlexBox, Column, LayoutGrid, GridBox, Card, Text, Anchor,
+FillButton, StrokeButton, TextButton, CTAButton, IconButton, Toggle,
+List, ListRow, ListCol, Background, Disclosure
+```
+
+Pattern (grep, case-sensitive):
+
+```
+<(Box|FlexBox|Column|LayoutGrid|GridBox|Card|Text|Anchor|FillButton|StrokeButton|TextButton|CTAButton|IconButton|Toggle|List|ListRow|ListCol|Background|Disclosure)\b[^>]*\bclassName=
+```
+
+Each match is an error. Report as `file:line <ComponentName className={...}>`.
+
+Severity note: `className` is not always forbidden — some Gamut components accept it for integration with third-party tools (e.g. passing a class to an external drag-and-drop library). Downgrade to ⚠ warning only when the usage is clearly an integration seam, not styling.
+
+Remediation: replace SCSS module rules with system props directly on the Gamut component — use semantic ColorMode tokens as values (`color="text"`, `bg="background"`, `borderColor="border-primary"`, etc.) rather than hardcoded hex or palette names; use `css()`, `variant()`, or `states()` from `@codecademy/gamut-styles` (with `styled` from `@emotion/styled`) for styles not expressible as system props; delete the SCSS file when all rules are migrated.
+
+Skill references: [`gamut-system-props`](../gamut-system-props/SKILL.md) · [`gamut-style-utilities`](../gamut-style-utilities/SKILL.md) · [`gamut-color-mode`](../gamut-color-mode/SKILL.md)
+
+---
+
+## Check 3c — Nested selectors
+
+Nested selectors inside styled-component or Emotion template literals cause hard-to-isolate side effects and make consistent updates difficult. The [Gamut Best Practices](https://gamut.codecademy.com/?path=/docs-meta-best-practices--page) page flags two kinds as "at your own risk": tag selectors and Gamut component selectors.
+
+**Step 1 — Tag selectors**
+
+Grep source files (`.ts`, `.tsx`, `.js`, `.jsx`) for bare HTML tag names appearing as CSS selector lines inside styled-component or Emotion template literals. Skip `node_modules`, `dist`, `.next`, `build`, `.turbo`.
+
+- Pattern A (named tags):
+ ```
+ ^\s*(div|span|p|ul|li|ol|a|img|h[1-6]|table|thead|tbody|tr|td|th|form|section|header|footer|nav|main)\s*\{
+ ```
+- Pattern B (universal selector):
+ ```
+ ^\s*\*\s*\{
+ ```
+ False-positive risk: `*` appears in JSDoc comment bodies (` * {`). Post-filter matches where the line is a comment (starts with `//` or matches `\s*\*\s`). For remaining matches, verify context before marking as a violation.
+
+Do NOT include SVG primitive tag names (`path`, `rect`, `circle`, `line`, `polyline`, `svg`) — styled SVG primitives in icon and form components are normal and not the target of this rule.
+
+Exemptions (downgrade to `ℹ note`, not warning):
+
+- Files that import `Global` from `@emotion/react` — intentional global reset/injection stylesheets.
+- Files whose name matches `*reboot*`, `*reset*`, `*global*`, or `*base-styles*`.
+
+**Step 2 — Gamut component selectors**
+
+Rather than enumerating every Gamut component by name (brittle, misses new additions), scope to files that already import from `@codecademy/gamut`, then grep those files for any PascalCase identifier used as a CSS selector:
+
+1. Find files that contain `from '@codecademy/gamut'`.
+2. In those files, grep for:
+ ```
+ \$\{[A-Z][A-Za-z]+\}[^{]*\{
+ ```
+ This matches any `${PascalCaseName}` followed by a rule block — i.e., a component used as a CSS child selector.
+
+Each match means a component is being targeted from a parent styled wrapper rather than styled directly. Report as `file:line ${ComponentName} { ... }`.
+
+Severity note: `&:pseudo ${ComponentName}` (pseudo-class combinator preceding the interpolation) is lower risk — downgrade those to ⚠ warning with a note to verify scope. Bare `${ComponentName} { }` selector blocks are the primary target.
+
+**Severity:** ⚠ warning for all matches (per Best Practices: "you may still do so, but at your own risk").
+
+**Remediation:**
+
+_Tag selectors_ — plain HTML elements do not need to become Gamut components. Two valid paths:
+
+- Use `Box`, `FlexBox`, or `GridBox` with the `as` prop to render as the intended element — no extra DOM node needed: `<Box as="section" p={16} color="text">`, `<FlexBox as="nav" gap={8}>`.
+- Style in place with `styled.div(css({ color: 'text', p: 16 }))` using semantic ColorMode tokens from `@codecademy/gamut-styles` — keeps the element but brings it into the design system token graph.
+
+Replace the parent's nested selector rule with one of the above and remove the selector block.
+
+_Gamut component selectors_ — pass system props directly to the component (`alignSelf`, `mt`, etc.) rather than targeting it from a parent wrapper. Where dynamic behavior spans multiple children, prefer `css()` with `variant()` or `states()` from `@codecademy/gamut-styles` keyed to data attributes or boolean props on the parent.
+
+Skill references: [`gamut-system-props`](../gamut-system-props/SKILL.md) · [`gamut-style-utilities`](../gamut-style-utilities/SKILL.md)
+
+---
+
## Check 4 — Hardcoded colors (semantic-first)
Rule: Inline hex literals in application UI code are violations. Remediation is not “replace hex with `navy-800`” — prefer semantic ColorMode tokens (`text`, `background`, `primary`, …) so light/dark and theme switches stay correct. Reserve raw palette tokens for colors that must stay fixed and for `bg` on `<Background>` from `@codecademy/gamut-styles` (section surfaces with content).
@@ -184,8 +289,29 @@
| `#ca00d1` | `pink-800` |
| `#006d82` | `teal-500` / `teal` |
| `#b3ccff` | `purple-300` / `purple` |
+### Step 2 — Non-Gamut CSS custom properties (SCSS/CSS/Less files)
+
+After the hex scan, grep `.scss`, `.css`, and `.less` files for `var(--` occurrences. For each custom property name found, classify it:
+
+_Gamut-issued variables_ — skip these:
+
+- `--color-*` (ColorMode semantic aliases)
+- `--space*` (spacing scale)
+- `--font*` (font-size scale)
+- `--lineHeight*` (line-height scale)
+- `--borderWidth*` (border-width tokens)
+- `--fontFamily*` (font-family tokens)
+- `--fontWeight*` (font-weight tokens)
+
+_Non-Gamut variables_ — flag these:
+Any other name — especially camel-cased semantic names like `--darkNeutralColor`, `--whiteColor`, `--lightPrimaryColor`, `--colorNavy800`, `--borderGreyColor` — indicates a parallel token system (Skillsoft/Percipio globals, legacy design tokens, or ad-hoc project variables). These variables are NOT set by `GamutProvider`/`ColorMode` and will be undefined inside a Gamut-scoped tree unless the host shell also loads them.
+
+Severity: ✗ error for color-semantic variables (invisible in tests/Storybook without the host stylesheet); ⚠ warning for spacing/sizing variables that duplicate Gamut scale tokens.
+
+Reporting: count unique non-Gamut variable names and list the top offenders with frequency. Do not enumerate every call-site — just the variable names and usage counts. Suggest the nearest Gamut semantic alias where obvious (e.g. `--darkNeutralColor` → `--color-text`, `--whiteColor` → `--color-background`).
+
---
## Check 5 — Test setup
@@ -197,8 +323,9 @@
| `jest.mock\(.*@codecademy/gamut-styles` | Error | Same issue as above — mocking gamut-styles breaks token resolution |
| `from '@codecademy/gamut-tests'` | Good — report count of files using it | Correct import for `setupRtl` and `MockGamutProvider` |
| `from 'component-test-setup'` (without gamut-tests) | Warning | Should import `setupRtl` from `@codecademy/gamut-tests`, not directly from `component-test-setup` — the gamut-tests wrapper adds `MockGamutProvider` automatically |
| `new GamutProvider` or `<GamutProvider` in test files | Warning | Prefer `setupRtl`; use `MockGamutProvider` (sets `useCache={false}`, `useGlobals={false}`) in harnesses or stories, not `GamutProvider` directly |
+| `jest.mock\(.*[Gg]amut[Pp]rovider` | Warning | Mocking any file whose path contains `GamutProvider` (including project-internal wrappers) strips Emotion/theme context; prefer `setupRtl` from `@codecademy/gamut-tests` |
Skill reference for remediation: [`gamut-testing`](../gamut-testing/SKILL.md)
---
@@ -230,12 +357,29 @@
✗ Deep src imports 2 occurrences
src/Thing.tsx:7
src/Other.tsx:12
+SCSS modules & className [→ gamut-system-props] [→ gamut-style-utilities]
+ ✗ SCSS/CSS imports 14 files — migrate to system props and css()/variant()
+ src/components/Card/Card.scss
+ src/components/Nav/Nav.scss (+ 12 more)
+ ✗ className on Gamut components 9 occurrences
+ src/components/Card/Card.tsx:14 <Box className={styles.wrapper}>
+ src/components/Nav/Nav.tsx:7 <Text className={styles.title}>
+
+Nested selectors [→ gamut-system-props] [→ gamut-style-utilities]
+ ⚠ Tag selectors 3 occurrences — replace with system props or layout components (FlexBox, GridBox)
+ src/components/Nav/Nav.tsx:18 div { ... }
+ src/components/Hero/Hero.tsx:9 * { ... } (verify scope — may be JSDoc false positive)
+ ⚠ Gamut component selectors 1 occurrence — use system props directly instead
+ src/components/Layout/Layout.tsx:12 ${Box} { align-self: start; }
+ (or: ✓ none found)
+
Hardcoded colors [→ gamut-color-mode]
✗ src/Card.tsx:22 '#10162F' → semantic: text | palette: navy-800 | note: Core light body copy
⚠ src/Hero.tsx:14 '#1557FF' → semantic: primary (if link/CTA) | palette: blue-500 | note: no exact semantic; confirm theme
⚠ src/Nav.tsx:8 '#BADA55' → semantic: (n/a) | palette: — | note: no Gamut token
+ ✗ Non-Gamut CSS vars --darkNeutralColor (8 uses), --whiteColor (5 uses) → --color-text, --color-background
Test setup [→ gamut-testing]
✓ @codecademy/gamut-tests used in 12 test files
✗ jest.mock(@codecademy/gamut) 2 occurrences — remove; prefer setupRtl (or harness + setupRtl) Index: package/agent-tools/skills/gamut-style-utilities/SKILL.md
===================================================================
--- package/agent-tools/skills/gamut-style-utilities/SKILL.md
+++ package/agent-tools/skills/gamut-style-utilities/SKILL.md
@@ -6,9 +6,9 @@
# Gamut style utilities
Source: `@codecademy/gamut-styles` — [`variance/props.ts`](https://github.com/Codecademy/gamut/blob/main/packages/gamut-styles/src/variance/props.ts) (`css`, `variant`, `states` built on `PROPERTIES.all`).
-See also: [`gamut-theming`](../gamut-theming/SKILL.md) (which theme, `GamutProvider`, new themes). [`gamut-system-props`](../gamut-system-props/SKILL.md) (`system.*`, responsive props, `Box`). [`gamut-color-mode`](../gamut-color-mode/SKILL.md) (semantic color, `<ColorMode>`, `<Background>`). [Best practices](../../../../styleguide/src/lib/Meta/Best%20practices.mdx) and [system compose](https://gamut.codecademy.com/?path=/docs-foundations-system-compose--page).
+See also: [`gamut-theming`](../gamut-theming/SKILL.md) (which theme, `GamutProvider`, new themes). [`gamut-system-props`](../gamut-system-props/SKILL.md) (`system.*`, responsive props, `Box`). [`gamut-color-mode`](../gamut-color-mode/SKILL.md) (semantic color, `<ColorMode>`, `<Background>`). [Best practices](https://gamut.codecademy.com/?path=/docs-meta-best-practices--page) and [system compose](https://gamut.codecademy.com/?path=/docs-foundations-system-compose--page).
## Overview
Use `css()`, `variant()`, and `states()` from `@codecademy/gamut-styles` for typed, token-scaled style objects (same scales as composed `system.*` props). Prefer semantic color keys so styles track ColorMode and theme.
@@ -98,10 +98,4 @@
const theme = useTheme();
return <path strokeWidth={theme.spacing[4]} d="M0 0 L10 10" />;
};
```
-
-## Key principles
-
-- Prefer semantic color keys in `css` / `variant` / `states` so ColorMode and theme switches apply; see `gamut-color-mode`.
-- Never hardcode hex in component styles — use tokens / semantic aliases.
-- Prefer `variant` / `states` for modes and toggles instead of ad-hoc `theme` interpolation in template literals. Index: package/agent-tools/skills/gamut-system-props/SKILL.md
===================================================================
--- package/agent-tools/skills/gamut-system-props/SKILL.md
+++ package/agent-tools/skills/gamut-system-props/SKILL.md
@@ -1,14 +1,14 @@
---
name: gamut-system-props
-description: Use this skill when building or refactoring styled Gamut components that need layout, spacing, color, border, background, typography, positioning, grid, flex, shadow, list styles, or responsive values from @codecademy/gamut-styles — including composing system prop groups with variance.
+description: 'Use this skill when composing system prop groups (`system.*`) on styled components, selecting which group covers a CSS property, or building responsive props with `variance.compose()` — not for `css()`, `variant()`, or `states()` (see gamut-style-utilities).'
---
# Gamut System Props
Source: `@codecademy/gamut-styles` — [`variance/config.ts`](https://github.com/Codecademy/gamut/blob/main/packages/gamut-styles/src/variance/config.ts) (definitions) and [`variance/props.ts`](https://github.com/Codecademy/gamut/blob/main/packages/gamut-styles/src/variance/props.ts) (`variance.create` groups). `Box`, `FlexBox`, and `GridBox` compose the same groups in [`packages/gamut/src/Box/props.ts`](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Box/props.ts).
-See also: [`gamut-style-utilities`](../gamut-style-utilities/SKILL.md) (`css`, `variant`, `states`, `StyleProps`). [Styleguide — Best practices](../../../../styleguide/src/lib/Meta/Best%20practices.mdx) (semantic colors, responsive examples) and Storybook [Responsive properties](https://gamut.codecademy.com/storybook/?path=/docs-foundations-system-responsive-properties--page).
+See also: [`gamut-style-utilities`](../gamut-style-utilities/SKILL.md) (`css`, `variant`, `states`, `StyleProps`). [Styleguide — Best practices](https://gamut.codecademy.com/?path=/docs-meta-best-practices--page) (semantic colors, responsive examples) and Storybook [Responsive properties](https://gamut.codecademy.com/storybook/?path=/docs-foundations-system-responsive-properties--page).
## Overview
System props are strongly-typed, theme-connected CSS prop groups from `@codecademy/gamut-styles`. They give styled components a consistent, responsive API. All props are built on top of `@codecademy/variance`.
@@ -191,13 +191,4 @@
// Semantic color (adapts to color mode)
const Text = styled.div(css({ color: 'primary', p: 4 }));
```
-
-## Key principles
-
-- Compose `system.*` groups via `variance.compose()` — don't apply multiple groups by chaining `styled.div(system.a)(system.b)`.
-- Prefer semantic color names on `system.color` (e.g. `bg="background"`, `textColor="text"`) so values track ColorMode; use raw palette keys only when the design should stay fixed across modes.
-- Use `bg` with semantic tokens for most mode-aware surfaces; use `<Background>` from `@codecademy/gamut-styles` when you need its contrast- and mode-aware behavior, not for every tinted panel.
-- Use `system.space` values on the spacing scale rather than arbitrary pixel strings to keep rhythm consistent.
-- For background images/patterns use `system.background`; for solid fills use `system.color` / semantic `bg` (or `Background` when that component’s behavior is required).
-- For reusable variants or boolean states on styled primitives, use `variant` / `states` from `@codecademy/gamut-styles` and expose props with `StyleProps<typeof …>` from `@codecademy/variance` — see [Best practices](../../../../styleguide/src/lib/Meta/Best%20practices.mdx). Index: package/agent-tools/skills/gamut-testing/SKILL.md
===================================================================
--- package/agent-tools/skills/gamut-testing/SKILL.md
+++ package/agent-tools/skills/gamut-testing/SKILL.md
@@ -8,10 +8,8 @@
Source: `@codecademy/gamut-tests` — [`index.tsx`](https://github.com/Codecademy/gamut/blob/main/packages/gamut-tests/src/index.tsx)
---
----
-
## What `MockGamutProvider` does (under `setupRtl`)
`MockGamutProvider` forwards to `GamutProvider` with: Index: package/agent-tools/skills/gamut-theming/SKILL.md
===================================================================
--- package/agent-tools/skills/gamut-theming/SKILL.md
+++ package/agent-tools/skills/gamut-theming/SKILL.md
@@ -105,11 +105,4 @@
## Creating a new theme
See [Creating Themes](https://gamut.codecademy.com/?path=/docs-foundations-theme-creating-themes--docs) in Storybook. Themes are defined in `@codecademy/gamut-styles` and must extend the base theme shape with all required token keys.
-
-## Key principles
-
-- Pick the correct theme export for the product so tokens and fonts match design intent.
-- Align `theme.d.ts` / `Theme extends …` with the same theme interface you pass to `GamutProvider`.
-- 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`.
-- `GamutProvider` wires theme, color mode, and logical-properties settings at the root; individual components should not hard-code which org theme is active. Index: package/agent-tools/skills/gamut-typography/SKILL.md
===================================================================
--- package/agent-tools/skills/gamut-typography/SKILL.md
+++ package/agent-tools/skills/gamut-typography/SKILL.md
@@ -1,7 +1,7 @@
---
name: gamut-typography
-description: Use this skill when creating or reviewing UI text in Gamut apps — headlines, body, captions, labels, code snippets, or text-heavy layouts. Covers theme-specific stacks (Core Apercu/Suisse vs Percipio/LX Skillsoft), fontSize / lineHeight tokens, semantic fontWeight title (700 vs 500), line length, and alignment for Codecademy-branded surfaces.
+description: Use this skill when applying Gamut typography tokens — font family, font size, line height, or semantic font weight (including the `fontWeight="title"` difference between Core 700 and Percipio/LX 500). See `gamut-system-props` for system.typography props.
---
# Gamut Typography Index: package/bin/lib/design.mjs
===================================================================
--- package/bin/lib/design.mjs
+++ package/bin/lib/design.mjs
@@ -1,5 +1,5 @@
-import { copyFile, stat } from 'node:fs/promises';
+import { readFile, stat, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
/** @type {Record<string, { sourceFile: string, label: string }>} */
const THEME_ALIASES = {
@@ -49,9 +49,9 @@
* @param {{ force?: boolean }} [options]
* @returns {Promise<{ dest: string, label: string }>}
*/
export async function installDesignMd(sourceRoot, cwd, theme, options = {}) {
- const { sourceFile, label } = resolveTheme(theme);
+ const { sourceFile, label, alias } = resolveTheme(theme);
const src = join(sourceRoot, sourceFile);
const dest = join(cwd, 'DESIGN.md');
const srcStat = await stat(src).catch(() => null);
@@ -65,7 +65,17 @@
`DESIGN.md already exists at ${dest}. Use --force to overwrite, or remove it first.`
);
}
- await copyFile(src, dest);
+ const version = await readFile(join(sourceRoot, '..', 'package.json'), 'utf8')
+ .then((raw) => JSON.parse(raw).version ?? 'unknown')
+ .catch(() => 'unknown');
+
+ const header =
+ `<!-- Generated by @codecademy/gamut@${version}.\n` +
+ ` Do not edit this file directly — to update, re-run:\n` +
+ ` gamut plugin install --theme ${alias} --force -->\n`;
+
+ const content = await readFile(src, 'utf8');
+ await writeFile(dest, header + content, 'utf8');
return { dest, label };
}