# @filigran/design-system - Full LLM Context This file is a standalone reference for the `@filigran/design-system` repository. ## Identity - Package: `@filigran/design-system` - Version: `1.5.1` - License: Apache-2.0 - Repository: `XTM-Foundation/filigran-design-system` - Figma source: `https://www.figma.com/design/0PmhuZzF9XcaIEfMMW2a51/Design_System_2026` - Scope: Shared design system for OpenCTI, OpenAEV, XTM CRQ, XTM One, XTM Hub ## Core Principles - AI-first: every component includes machine-readable metadata via `.meta.ts`. - Accessibility-first: WCAG 2.1 AA and Section 508 are **hard requirements**, not best-effort. A component is not done until both are fully satisfied. - Token-first: no hardcoded color, spacing, typography, or radius values anywhere. - Tailwind-native ergonomics: developers use standard utility names mapped to Filigran tokens. ## Accessibility Contract This file does not duplicate the accessibility contract — it is a **pointer**, never a copy (a stale copy of this exact section is what the 2026-07-07 hygiene audit found contradicting decided conventions). The binding, maintained specification is `AGENTS.md` → "Accessibility Compliance (Non-Negotiable Gate)". On any conflict between a summary here and that section, `AGENTS.md` wins. Summary for external tools that only fetch this file: WCAG 2.1 AA + Section 508 are hard gates, not best-effort — contrast (4.5:1 normal text / 3:1 large text and UI components, every gradient stop independently, both light and dark mode), full keyboard operability, visible focus rings, and correct accessible names/roles/states are required before a component can be exported or marked `stable`. See `AGENTS.md` for the full checklist and the STABLE/EXPORT gate rules. ## Monorepo Layout ``` filigran-design-system/ ├── package.json root scripts (build, typecheck, gates — see AGENTS.md) ├── pnpm-workspace.yaml workspaces: ["packages/*", "docs"] ├── AGENTS.md AI operating contract (read-first rule) ├── README.md human-readable overview ├── llms.txt compact LLM entry point (generated) ├── llms-full.txt this file — full machine context (generated) ├── ROADMAP.json / ROADMAP.md component coverage manifest (md generated) ├── process/ project context, process memory, dated analyses, PLAYBOOK.md ├── scripts/ executable gates and artifact generators ├── .github/ │ ├── workflows/ CI (conformity pipeline + token-diff gate) │ └── prompts/ AI prompt commands (see llms-full.txt "Prompt Command Reference") ├── packages/ │ └── filigran-design-system/ published package (@filigran/design-system) │ ├── package.json │ ├── tsup.config.ts │ ├── src/ │ │ ├── index.ts │ │ ├── lib/cn.ts │ │ ├── types/meta.ts │ │ ├── tokens/ │ │ │ ├── theme.css SOURCE OF TRUTH │ │ │ └── index.css Tailwind CSS entrypoint │ │ └── components/ one folder per component (see "Component Inventory" below) └── docs/ Next.js docs app ├── app/layout.tsx layout shell (nav, header, theming) ├── app/page.tsx generated component index (registry-driven) ├── app/components/[name]/ GENERATED per-component pages (shared template) ├── app/status/ generated Implementation Status dashboard ├── app/llms.txt, app/llms-full.txt routes serving the root files ├── components/ docs-only building blocks (NavLinks, StatusBadge, …) └── lib/ component-registry.ts + implementation-status.json (GENERATED) ``` ## Build Pipeline ``` src/index.ts ──tsup──► dist/index.js (CJS) dist/index.mjs (ESM) dist/index.d.ts / dist/index.d.mts src/tokens/index.css ──tailwindcss CLI──► dist/index.css (minified) @import "tailwindcss/theme" /* Tailwind default @theme variables */ @import "./theme.css" /* Filigran tokens — BEFORE utilities, so Tailwind sees the @theme {} mappings before generating them */ @import "tailwindcss/utilities" /* utilities (preflight intentionally omitted) */ ``` Build command: `pnpm --filter @filigran/design-system build` The docs app uses Strategy B (see "CSS Import Strategy" below): `docs/app/layout.tsx` imports `./globals.css`, which imports `@filigran/design-system/tokens/theme.css` (resolved to `dist/tokens/theme.css` via the package's `exports` map) plus its own `tailwindcss` entrypoint — not `dist/index.css`. TypeScript resolves the package source directly via `docs/tsconfig.json` path alias. ## Token Architecture Single source of truth: `packages/filigran-design-system/src/tokens/theme.css` Rules: - This file is generated by an external Figma export script. Do not hand-edit token values (see AGENTS.md "Tailwind and Token Rules"). - The repository must not invent token values. - Tailwind v4 `@theme {}` block makes tokens available as Tailwind utility classes. - All `var(--token-name)` references chain through the CSS custom property system. - Dark mode defaults live in `@theme {}`. Light mode overrides in `.light {}`. Dark overrides also in `.dark {}`. - Elevation context classes (`.layer-0`, `.layer-1`, `.layer-2`, `.layer-3`) reroute elevation shortcuts. ## Token Pipeline: theme.css → CSS Variables → Tailwind ``` Figma export → src/tokens/theme.css (single source of truth — do not edit manually) │ ├─ pnpm run build │ └─ tailwindcss CLI compiles utilities → dist/index.css │ └─ cp src/tokens/theme.css → dist/tokens/theme.css │ └─ docs/app/globals.css imports dist/tokens/theme.css ``` See `AGENTS.md` "Token pipeline — source of truth and build flow" and "dist/tokens/theme.css — @theme blocks and Vite consumers" for the full consumer-facing rules (Vite + CSS-in-JS caveat, no-preflight rule). ## Technology Stack - React ^19.0.0 - TypeScript ^5.9.0 - Radix UI primitives ^1.3.3 (`@radix-ui/react-slot`) - Tailwind CSS ^4.3.1 - class-variance-authority (CVA) ^0.7.1 - tailwind-merge ^3.3.1 - clsx ^2.1.1 - lucide-react 1.23.0 (icon glyphs, via the Icon component only — see AGENTS.md "Icons") - tsup ^8.5.0 (JS/DTS build) - @tailwindcss/cli ^4.3.1 (CSS build) - concurrently ^10.0.3 (dev watch mode) - Next.js ^15.0.0 (docs app) - pnpm workspaces ## Component Inventory Source: `packages/filigran-design-system/src/components/*/*.meta.ts`. Full per-prop contract lives in each component's `.meta.ts` — this table is a summary, not a substitute. | Component | Status | WCAG | Radix primitive | Variants | Sizes | Props | Exported | |---|---|---|---|---|---|---|---| | Alert | beta | pass | none | error, info, success, warning, ia | — | 7 | yes | | Avatar | beta | pass | @radix-ui/react-avatar | default | xs, sm, md, lg | 13 | yes | | Badge | beta | pass | none | brand, error, success, neutral | — | 10 | yes | | Banner | beta | pass | none | default, warning, error, success | — | 11 | yes | | Breadcrumbs | beta | pass | none | unlinked, linked, hover, current | — | 5 | yes | | Button | beta | pass | @radix-ui/react-slot | default, destructive, ia, highlight | sm, md | 11 | yes | | ButtonGroup | beta | fail | none | integrated, isolated, default, selected, disabled, selected + disabled | sm, md | 13 | yes | | Card | beta | pass | none | default, interactive, selected, disabled | — | 13 | yes | | CardContent | beta | pass | none | default | — | 2 | yes | | CardFooter | beta | pass | none | default | — | 1 | yes | | CardHeader | beta | pass | none | default | — | 3 | yes | | CardMetaBar | beta | pass | none | default | — | 1 | yes | | CardSubtitle | beta | pass | none | default | — | 2 | yes | | CardTitle | beta | pass | none | default | — | 3 | yes | | Checkbox | beta | pass | @radix-ui/react-checkbox | default | — | 13 | yes | | Chip | beta | fail | none | neutral, info, low, medium, high, critical, analyses, cases, events, observations, all-threats, arsenal, techniques, victimology, location, ee | md, sm | 12 | yes | | ColorPicker | beta | pass | @radix-ui/react-popover + @radix-ui/react-slider | default, error, disabled | — | 18 | yes | | Combobox | beta | pass | @radix-ui/react-popover | default | md | 42 | yes | | CustomizePanel | beta | pass | @radix-ui/react-popover | pin, show | — | 14 | yes | | DatePicker | beta | pass | @radix-ui/react-popover | default, with-time, error, disabled | — | 41 | yes | | Dialog | beta | pass | @radix-ui/react-dialog | default | sm, md, lg | 10 | yes | | Drawer | beta | pass | @radix-ui/react-dialog | default | sm, md, lg, xl | 18 | yes | | FileSelect | beta | pass | none | default, error, select, multi-select, loading, disabled | md | 23 | yes | | Header | beta | pass | none | default | — | 12 | yes | | Hero | beta | pass | none | default, header-only, body-only, dense | — | 10 | yes | | HeroBody | beta | pass | none | default | — | 3 | yes | | HeroHeader | beta | pass | none | default | — | 4 | yes | | HeroStat | beta | pass | none | neutral, info, low, medium, high, critical | — | 8 | yes | | Icon | stable | pass | none | default, gradient-ia, gradient-focus | 16, 20, 24 | 6 | yes | | IconButton | stable | pass | none | default, destructive, ia | sm, md | 7 | yes | | Input | beta | pass | none | default, error, success, disabled | md | 23 | yes | | MarkdownField | beta | fail | @radix-ui/react-popover | default, hover, focus, error, disabled, preview, drop-valid, limit-warning, limit-exceeded, toolbar-collapsed | — | 30 | yes | | Menu | beta | pass | @radix-ui/react-dropdown-menu | default, hover, focus, disabled, selected, group + label, separator, link item | — | 20 | yes | | Navbar | stable | pass | none | expanded, collapsed | — | 10 | yes | | NavbarItem | stable | pass | @radix-ui/react-slot | default, hover, focus, selected, disabled | — | 8 | yes | | NavbarSubmenu | stable | pass | @radix-ui/react-accordion, @radix-ui/react-dropdown-menu | default, hover, selected, disabled, expanded (accordion), collapsed (flyout) | — | 17 | yes | | Paper | beta | pass | none | default | — | 7 | yes | | ProductSwitcher | stable | pass | @radix-ui/react-dropdown-menu | default, hover, focus, external link item, internal link item, logo link trigger | — | 13 | yes | | ProgressBar | beta | pass | none | determinate, tone=default, tone=success, tone=error | md, lg | 7 | yes | | Radio | beta | pass | @radix-ui/react-radio-group | default | — | 15 | yes | | RichTextField | beta | pass | @radix-ui/react-popover | default, hover, focus, error, disabled, read-only, fullscreen, source-html, html-removed, image-rejected, generating, unsaved-changes, toolbar-sm, toolbar-xs | — | 33 | yes | | SearchField | stable | pass | none | default | sm, md | 13 | yes | | Select | beta | pass | @radix-ui/react-select | default | md | 25 | yes | | Skeleton | beta | pass | none | rectangle, circle | — | 7 | yes | | Slider | beta | pass | @radix-ui/react-slider | default | — | 21 | yes | | Snackbar | beta | pass | @radix-ui/react-toast | error, info, success, warning, ia | — | 12 | yes | | Spinner | beta | pass | none | brand, inherit | sm, md, lg, xl | 5 | yes | | Switch | beta | fail | @radix-ui/react-switch | default | — | 11 | yes | | Tabs | stable | pass | @radix-ui/react-tabs | active, default, disabled | — | 28 | yes | | Text | stable | pass | none | title-2xl, title-xl, title-lg, title-md, title-sm, title-xs, title-jumbo, content-base, content-base-bold, content-base-medium, content-base-link, content-compact, content-compact-bold, content-compact-medium, content-compact-link, content-caption, content-highlight, content-button, content-code | — | 4 | yes | | Textarea | beta | pass | none | default, error, disabled | — | 15 | yes | | Thumbnail | beta | pass | none | default | — | 3 | yes | | TimePicker | beta | pass | @radix-ui/react-popover | default, twelve-hour, error, disabled | — | 32 | yes | | Tooltip | stable | pass | @radix-ui/react-tooltip | default | — | 4 | yes | ## Design Token Reference Source: `packages/filigran-design-system/src/tokens/theme.css` — regenerate this section by re-running `pnpm generate:artifacts` after any Figma export (see `process/PLAYBOOK.md` "Updating tokens"). ### Base tokens (dark defaults) (`@theme`) #### Disable Tailwind default color palette — Colors Primitive — _other ```css --gray-50: #f4f4f6; --gray-100: #f2f2f3; --gray-150: #e4e5e7; --gray-200: #cacbce; --gray-300: #afb0b6; --gray-400: #95969d; --gray-500: #7a7c85; --gray-600: #62636a; --gray-700: #494a50; --gray-800: #313235; --gray-900: #18191b; --gray-1000: #00020c; --grayblue-50: #eff3fa; --grayblue-100: #c8d6ee; --grayblue-150: #a0b4e3; --grayblue-200: #7a9cd6; --grayblue-300: #3a5bbb; --grayblue-400: #2b4f8d; --grayblue-500: #1f3965; --grayblue-600: #182a4e; --grayblue-700: #13213e; --grayblue-800: #101b33; --grayblue-850: #0d172b; --grayblue-900: #0c1527; --grayblue-1000: #070d18; --darkblue-100: #dbe0ff; --darkblue-200: #a8b3ff; --darkblue-300: #7587ff; --darkblue-400: #425aff; --darkblue-500: #0f2dff; --darkblue-600: #001bdb; --darkblue-700: #0015a8; --darkblue-800: #000f75; --darkblue-900: #000842; --purple-100: #e3d6fa; --purple-200: #c3a8f5; --purple-300: #a47af0; --purple-400: #854ceb; --purple-500: #651fe5; --purple-600: #5015bc; --purple-700: #3c108c; --purple-800: #290b60; --purple-900: #160632; --blue-100: #dbf5ff; --blue-200: #a8e7ff; --blue-300: #75d9ff; --blue-400: #42caff; --blue-500: #0fbcff; --blue-600: #009edb; --blue-700: #0079a8; --blue-800: #005575; --blue-900: #003042; --turquoise-100: #bdffed; --turquoise-200: #80ffdd; --turquoise-300: #52ffd1; --turquoise-400: #1fffc3; --turquoise-500: #00f0bc; --turquoise-600: #00bd94; --turquoise-700: #009474; --turquoise-750: #006e56; --turquoise-800: #005744; --turquoise-900: #00241c; --green-100: #c8f9ca; --green-200: #91f396; --green-300: #72e978; --green-400: #41e149; --green-500: #20cb28; --green-600: #17ab1f; --green-700: #117916; --green-800: #094e0b; --green-900: #083a0a; --red-100: #fbcbc5; --red-200: #f8958c; --red-300: #f57266; --red-400: #f14337; --red-500: #e51e10; --red-600: #b8180a; --red-700: #881106; --red-800: #570a05; --red-900: #3b0602; --orange-100: #fbddc1; --orange-200: #f8c08c; --orange-300: #f5ab66; --orange-400: #f2933a; --orange-500: #e6700f; --orange-600: #b8550a; --orange-700: #884106; --orange-800: #572a05; --orange-900: #3b1b02; --yellow-100: #fbecc5; --yellow-200: #f8d98c; --yellow-300: #f6ce6a; --yellow-400: #f2be3a; --yellow-500: #eaa510; --yellow-600: #b8870a; --yellow-700: #886606; --yellow-800: #573e05; --yellow-900: #3a2903; --black-100: #0000000d; --black-200: #0000001a; --black-300: #00000033; --black-400: #00000066; --black-500: #000000b2; --black-600: #000000cc; --black-700: #000000d9; --black-800: #000000e5; --black-900: #000000f2; --black-1000: #000000; --white-100: #ffffff0d; --white-200: #ffffff1a; --white-300: #ffffff33; --white-400: #ffffff66; --white-500: #ffffffb2; --white-600: #ffffffcc; --white-700: #ffffffd9; --white-800: #ffffffe5; --white-900: #fffffff2; --white-1000: #ffffff; ``` #### Colors Alias — Filigran ```css --color-filigran-brand-primary: var(--blue-400); --color-filigran-brand-primary-transparency-55: color-mix(in srgb, var(--color-filigran-brand-primary) 55%, transparent); --color-filigran-brand-primary-transparency-10: color-mix(in srgb, var(--color-filigran-brand-primary) 10%, transparent); --color-filigran-brand-secondary: var(--blue-200); --color-filigran-brand-tertiary: var(--blue-600); --color-filigran-tonic-primary: var(--turquoise-500); --color-filigran-tonic-accent: var(--turquoise-500); --color-filigran-tonic-accent-transparency-20: color-mix(in srgb, var(--color-filigran-tonic-accent) 20%, transparent); --color-filigran-tonic-secondary: var(--turquoise-100); --color-filigran-tonic-tertiary: var(--turquoise-700); --color-filigran-ia-primary: var(--purple-300); --color-filigran-ia-secondary: var(--purple-100); --color-filigran-ia-secondary-transparency-10: color-mix(in srgb, var(--color-filigran-ia-secondary) 10%, transparent); --color-filigran-ia-tertiary: var(--purple-500); ``` #### Feedback ```css --color-feedback-info-primary: var(--blue-400); --color-feedback-info-secondary: var(--blue-700); --color-feedback-info-secondary-transparency-30: color-mix(in srgb, var(--color-feedback-info-secondary) 30%, transparent); --color-feedback-info-secondary-transparency-20: color-mix(in srgb, var(--color-feedback-info-secondary) 20%, transparent); --color-feedback-info-tertiary: var(--blue-200); --color-feedback-success-primary: var(--green-600); --color-feedback-success-secondary: var(--green-800); --color-feedback-success-secondary-transparency-30: color-mix(in srgb, var(--color-feedback-success-secondary) 30%, transparent); --color-feedback-success-secondary-transparency-20: color-mix(in srgb, var(--color-feedback-success-secondary) 20%, transparent); --color-feedback-success-tertiary: var(--green-200); --color-feedback-alert-primary: var(--yellow-400); --color-feedback-alert-secondary: var(--yellow-600); --color-feedback-alert-secondary-transparency-30: color-mix(in srgb, var(--color-feedback-alert-secondary) 30%, transparent); --color-feedback-alert-secondary-transparency-20: color-mix(in srgb, var(--color-feedback-alert-secondary) 20%, transparent); --color-feedback-alert-tertiary: var(--yellow-200); --color-feedback-warning-primary: var(--orange-500); --color-feedback-warning-secondary: var(--orange-700); --color-feedback-warning-secondary-transparency-30: color-mix(in srgb, var(--color-feedback-warning-secondary) 30%, transparent); --color-feedback-warning-secondary-transparency-20: color-mix(in srgb, var(--color-feedback-warning-secondary) 20%, transparent); --color-feedback-warning-tertiary: var(--orange-200); --color-feedback-error-primary: var(--red-400); --color-feedback-error-secondary: var(--red-700); --color-feedback-error-secondary-transparency-30: color-mix(in srgb, var(--color-feedback-error-secondary) 30%, transparent); --color-feedback-error-secondary-transparency-20: color-mix(in srgb, var(--color-feedback-error-secondary) 20%, transparent); --color-feedback-error-tertiary: var(--red-300); --color-feedback-contrast-primary: var(--white-300); --color-feedback-contrast-secondary: var(--white-200); --color-feedback-contrast-tertiary: var(--white-100); --color-feedback-neutral-primary: var(--gray-400); --color-feedback-neutral-secondary: var(--gray-600); --color-feedback-neutral-secondary-transparency-30: color-mix(in srgb, var(--color-feedback-neutral-secondary) 30%, transparent); --color-feedback-neutral-tertiary: var(--gray-200); --color-feedback-neutral-off: var(--gray-100); ``` #### Entities ```css --color-entities-analyses: #70b23b; --color-entities-analyses-transparency-20: color-mix(in srgb, var(--color-entities-analyses) 20%, transparent); --color-entities-cases: #ea80fc; --color-entities-cases-transparency-20: color-mix(in srgb, var(--color-entities-cases) 20%, transparent); --color-entities-events: #f96c9b; --color-entities-events-transparency-20: color-mix(in srgb, var(--color-entities-events) 20%, transparent); --color-entities-observations: #ff6f42; --color-entities-observations-transparency-20: color-mix(in srgb, var(--color-entities-observations) 20%, transparent); --color-entities-all-threats: #ff9800; --color-entities-all-threats-transparency-20: color-mix(in srgb, var(--color-entities-all-threats) 20%, transparent); --color-entities-arsenal: #f0b60a; --color-entities-arsenal-transparency-20: color-mix(in srgb, var(--color-entities-arsenal) 20%, transparent); --color-entities-techniques: #d3e157; --color-entities-techniques-transparency-20: color-mix(in srgb, var(--color-entities-techniques) 20%, transparent); --color-entities-victimology: #ba88ff; --color-entities-victimology-transparency-20: color-mix(in srgb, var(--color-entities-victimology) 20%, transparent); --color-entities-location: #05acc1; --color-entities-location-transparency-20: color-mix(in srgb, var(--color-entities-location) 20%, transparent); ``` #### Radius ```css --radius-sm: 4px; --radius-md: 12px; --radius-lg: 20px; --radius-full: 9999px; ``` #### Depth / Shadow ```css --depth-sm: 4px; --depth-md: 8px; --depth-lg: 16px; --depth-xl: 20px; ``` #### Width ```css --width-overlay-sm: 420px; --width-overlay-md: 640px; --width-overlay-lg: 960px; --width-overlay-xl: 1360px; ``` #### Blur ```css --blur-sm: 4px; --blur-md: 8px; --blur-lg: 16px; --blur-xl: 24px; ``` #### Typography Tokens ```css --font-sans-geologica: Geologica; --font-sans-plex: "IBM Plex Sans"; --font-mono-plex: "IBM Plex Mono"; --text-1: 10px; --text-2: 12px; --text-3: 14px; --text-4: 16px; --text-5: 20px; --text-6: 24px; --text-7: 28px; --text-8: 32px; --text-9: 36px; --text-10: 42px; --text-11: 52px; --text-12: 64px; --font-weight-regular: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; --leading-tight: 120%; --leading-snug: 135%; --leading-normal: 150%; --leading-relaxed: 170%; --tracking-tight: 0.005em; --tracking-normal: 0.0075em; --tracking-relaxed: 0.01em; ``` #### Typography Styles — title-lg ```css --font-title-lg: var(--font-sans-geologica); --text-title-lg: var(--text-6); --font-weight-title-lg: var(--font-weight-medium); --leading-title-lg: var(--leading-normal); --tracking-title-lg: var(--tracking-normal); ``` #### title-xs ```css --font-title-xs: var(--font-sans-geologica); --text-title-xs: var(--text-3); --font-weight-title-xs: var(--font-weight-semibold); --leading-title-xs: var(--leading-normal); --tracking-title-xs: var(--tracking-normal); ``` #### title-sm ```css --font-title-sm: var(--font-sans-geologica); --text-title-sm: var(--text-4); --font-weight-title-sm: var(--font-weight-bold); --leading-title-sm: var(--leading-normal); --tracking-title-sm: var(--tracking-normal); ``` #### title-2xl ```css --font-title-2xl: var(--font-sans-geologica); --text-title-2xl: var(--text-8); --font-weight-title-2xl: var(--font-weight-medium); --leading-title-2xl: var(--leading-normal); --tracking-title-2xl: var(--tracking-normal); ``` #### title-jumbo ```css --font-title-jumbo: var(--font-sans-geologica); --text-title-jumbo: var(--text-10); --font-weight-title-jumbo: var(--font-weight-medium); --leading-title-jumbo: var(--leading-normal); --tracking-title-jumbo: 0.0179em; ``` #### title-xl ```css --font-title-xl: var(--font-sans-geologica); --text-title-xl: var(--text-7); --font-weight-title-xl: var(--font-weight-medium); --leading-title-xl: var(--leading-normal); --tracking-title-xl: var(--tracking-normal); ``` #### title-md ```css --font-title-md: var(--font-sans-geologica); --text-title-md: var(--text-5); --font-weight-title-md: var(--font-weight-medium); --leading-title-md: var(--leading-normal); --tracking-title-md: var(--tracking-normal); ``` #### content-compact-link ```css --font-content-compact-link: var(--font-sans-plex); --text-content-compact-link: var(--text-2); --font-weight-content-compact-link: var(--font-weight-medium); --leading-content-compact-link: var(--leading-normal); --tracking-content-compact-link: var(--tracking-normal); --text-decoration-content-compact-link: underline; ``` #### content-base-bold ```css --font-content-base-bold: var(--font-sans-plex); --text-content-base-bold: var(--text-3); --font-weight-content-base-bold: var(--font-weight-semibold); --leading-content-base-bold: var(--leading-normal); --tracking-content-base-bold: var(--tracking-normal); ``` #### content-button ```css --font-content-button: var(--font-sans-plex); --text-content-button: var(--text-3); --font-weight-content-button: var(--font-weight-semibold); --leading-content-button: var(--leading-normal); --tracking-content-button: var(--tracking-normal); ``` #### content-compact ```css --font-content-compact: var(--font-sans-plex); --text-content-compact: var(--text-2); --font-weight-content-compact: var(--font-weight-regular); --leading-content-compact: var(--leading-normal); --tracking-content-compact: var(--tracking-normal); ``` #### content-caption ```css --font-content-caption: var(--font-sans-plex); --text-content-caption: var(--text-1); --font-weight-content-caption: var(--font-weight-regular); --leading-content-caption: var(--leading-normal); --tracking-content-caption: var(--tracking-normal); ``` #### content-highlight ```css --font-content-highlight: var(--font-sans-plex); --text-content-highlight: var(--text-3); --font-weight-content-highlight: var(--font-weight-semibold); --leading-content-highlight: var(--leading-normal); --tracking-content-highlight: var(--tracking-normal); ``` #### content-base-medium ```css --font-content-base-medium: var(--font-sans-plex); --text-content-base-medium: var(--text-3); --font-weight-content-base-medium: var(--font-weight-medium); --leading-content-base-medium: var(--leading-normal); --tracking-content-base-medium: var(--tracking-normal); ``` #### content-base-link ```css --font-content-base-link: var(--font-sans-plex); --text-content-base-link: var(--text-3); --font-weight-content-base-link: var(--font-weight-medium); --leading-content-base-link: var(--leading-normal); --tracking-content-base-link: var(--tracking-normal); --text-decoration-content-base-link: underline; ``` #### content-compact-bold ```css --font-content-compact-bold: var(--font-sans-plex); --text-content-compact-bold: var(--text-2); --font-weight-content-compact-bold: var(--font-weight-semibold); --leading-content-compact-bold: var(--leading-normal); --tracking-content-compact-bold: var(--tracking-normal); ``` #### content-compact-medium ```css --font-content-compact-medium: var(--font-sans-plex); --text-content-compact-medium: var(--text-2); --font-weight-content-compact-medium: var(--font-weight-medium); --leading-content-compact-medium: var(--leading-normal); --tracking-content-compact-medium: var(--tracking-normal); ``` #### content-code ```css --font-content-code: var(--font-mono-plex); --text-content-code: var(--text-2); --font-weight-content-code: var(--font-weight-regular); --leading-content-code: var(--leading-normal); --tracking-content-code: var(--tracking-normal); ``` #### content-base ```css --font-content-base: var(--font-sans-plex); --text-content-base: var(--text-3); --font-weight-content-base: var(--font-weight-regular); --leading-content-base: var(--leading-normal); --tracking-content-base: var(--tracking-normal); ``` #### Figma Annotations — Typography (from MCP node 2495-15295) Extracted from annotation text node `#5438:99678` on the Typo canvas. Verbatim designer rules. Coverage: node 2495-15295 is the "Typo" documentation canvas — typography compound styles received use-case captions and designer rules this way; all other token groups (color, spacing, radius, shadow, gradient) returned no variable descriptions from this node (would require the `/v1/files/:key/variables/local` REST endpoint — see `process/AI-BACKLOG.md` "Token Annotations"). - **RULE-01 — Scale groups:** Two exclusive groups: `Header` (display + title levels: jumbo, heading-2xl → heading-xs) and `Content` (body + utility: body-base, body-compact, button, caption, highlight). - **RULE-02 — Weight variants:** `body-base` and `body-compact` each have three weight variants: default (regular), `medium`, `bold`. Link variants (`body-base-link`, `body-compact-link`) are **typographic style variants, not interactive component states** — they define the text style for inline hyperlinks. - **RULE-03 — `jumbo` usage:** `Header/jumbo` is reserved for **large numeric or single-element display** (KPIs, counters, dashboard metrics). **Must not be used for editorial titles.** - **RULE-04 — `highlight`:** `Content/highlight` is reserved for **important notifications or emphasis** in body text. **Must not be used as a heading substitute.** - **RULE-05 — `button`:** `Content/button` is the **exclusive text style for all interactive CTA elements** (buttons, primary actions). **Must not be applied to non-interactive text.** - **RULE-06 — Font family:** All styles consume font-family tokens from the `Typo primitive` collection. Do not override font-family at component level. - **RULE-07 — Line-height:** Line-height `%` variable support not yet fully implemented in Figma. Values driven by the typo table declaration (e.g. `heading-xl` → `line-height-normal` = 150%). - **RULE-08 — Letter-spacing:** Same limitation as line-height. Values driven by the typo table (e.g. `body-base-medium` → `letter-spacing-normal` = 0.75%). ##### Designer Intent by style (Figma, node 2495-15295) | Style name | Designer Intent (Figma) | |---|---| | `header-jumbo` | Use case: Big number — KPIs, counters, dashboard metrics. NOT for editorial titles (RULE-03) | | `header-heading-2xl` | Use case: Title — display and title levels (RULE-01) | | `header-heading-xl` | Use case: Title — display and title levels (RULE-01) | | `header-heading-lg` | Use case: Title — display and title levels (RULE-01) | | `header-heading-md` | Use case: Title — display and title levels (RULE-01) | | `header-heading-sm` | Use case: Title — display and title levels (RULE-01) | | `header-heading-xs` | Use case: Title — smallest title level (RULE-01) | | `content-highlight` | Use case: Important notification — emphasis in body text. NOT a heading substitute (RULE-04) | | `content-button` | Use case: Button — exclusive for interactive CTAs. NOT for non-interactive text (RULE-05) | | `content-body-base` | Use case: Content text — base body text, regular weight default (RULE-02) | | `content-body-base-medium` | Use case: Content text emphasis — medium weight body text variant (RULE-02) | | `content-body-base-bold` | Use case: Content text highlight — bold weight body text variant (RULE-02) | | `content-body-base-link` | Use case: Content text link — inline hyperlink style. NOT an interactive component state (RULE-02) | | `content-body-compact` | Use case: Secondary text — compact body text at 12px, regular weight default (RULE-02) | | `content-body-compact-medium` | Use case: Secondary text emphasis — medium weight compact variant (RULE-02) | | `content-body-compact-bold` | Use case: Secondary text highlight — bold weight compact variant (RULE-02) | | `content-body-compact-link` | Use case: Secondary text link — compact inline hyperlink. NOT an interactive state (RULE-02) | | `content-caption` | Use case: Tertiary text, legend — smallest size (10px) in the scale | No other token group (color, spacing, radius, shadow, gradient) has designer-intent annotations yet — see `process/AI-BACKLOG.md` "Token Annotations" for how to extend coverage. Do not infer intent for ungrouped tokens; state "No annotation" instead. #### Color Styles — Effect Styles ```css --shadow-global-shadow: 0px 2px 4px 0px #00000066; ``` ### Light mode overrides (`.light`) #### Colors Alias — Bg ```css --bg-elevation-default-layer-0: var(--gray-100); --bg-elevation-default-layer-0-transparency-70: color-mix(in srgb, var(--bg-elevation-default-layer-0) 70%, transparent); --bg-elevation-default-layer-0-gradient: var(--white-1000); --bg-elevation-default-layer-1: var(--white-1000); --bg-elevation-default-layer-2: var(--gray-50); --bg-elevation-default-layer-3: var(--gray-150); --bg-elevation-heading-layer-0: var(--gray-100); --bg-elevation-heading-layer-1: var(--gray-50); --bg-elevation-heading-layer-2: var(--gray-200); --bg-elevation-heading-layer-3: var(--gray-300); --bg-elevation-highlight-layer-0: var(--gray-150); --bg-elevation-highlight-layer-1: var(--gray-100); --bg-elevation-highlight-layer-2: var(--gray-150); --bg-elevation-highlight-layer-3: var(--gray-50); --bg-elevation-hover-layer-0: var(--gray-200); --bg-elevation-hover-layer-1: var(--gray-150); --bg-elevation-hover-layer-2: var(--gray-200); --bg-elevation-hover-layer-3: var(--gray-200); --bg-elevation-disabled-layer-0: var(--gray-200); --bg-elevation-disabled-layer-1: var(--gray-200); --bg-elevation-disabled-layer-2: var(--gray-200); --bg-elevation-disabled-layer-3: var(--gray-300); ``` #### Border ```css --border-elevation-subtle-soft-layer-0: var(--gray-400); --border-elevation-subtle-soft-layer-0-transparency-15: color-mix(in srgb, var(--border-elevation-subtle-soft-layer-0) 15%, transparent); --border-elevation-subtle-soft-layer-1: var(--gray-400); --border-elevation-subtle-soft-layer-1-transparency-15: color-mix(in srgb, var(--border-elevation-subtle-soft-layer-1) 15%, transparent); --border-elevation-subtle-soft-layer-2: var(--gray-400); --border-elevation-subtle-soft-layer-2-transparency-15: color-mix(in srgb, var(--border-elevation-subtle-soft-layer-2) 15%, transparent); --border-elevation-subtle-soft-layer-3: var(--gray-400); --border-elevation-subtle-soft-layer-3-transparency-15: color-mix(in srgb, var(--border-elevation-subtle-soft-layer-3) 15%, transparent); --border-elevation-disabled-layer-0: var(--gray-500); --border-elevation-disabled-layer-1: var(--gray-500); --border-elevation-disabled-layer-2: var(--gray-500); --border-elevation-disabled-layer-3: var(--gray-500); --border-elevation-subtle-layer-0: var(--gray-200); --border-elevation-subtle-layer-1: var(--gray-200); --border-elevation-subtle-layer-2: var(--gray-200); --border-elevation-subtle-layer-3: var(--gray-300); --border-elevation-default-layer-0: var(--gray-500); --border-elevation-default-layer-1: var(--gray-300); --border-elevation-default-layer-2: var(--gray-400); --border-elevation-default-layer-3: var(--gray-500); ``` #### Text ```css --text-default-primary: var(--gray-900); --text-default-secondary: var(--gray-700); --text-default-disabled: var(--gray-600); --text-negative-primary: var(--gray-100); --text-negative-secondary: var(--gray-300); --text-negative-disabled: var(--gray-400); ``` #### Filigran ```css --color-filigran-brand-primary: var(--darkblue-700); --color-filigran-brand-primary-transparency-55: color-mix(in srgb, var(--color-filigran-brand-primary) 55%, transparent); --color-filigran-brand-primary-transparency-10: color-mix(in srgb, var(--color-filigran-brand-primary) 10%, transparent); --color-filigran-brand-secondary: var(--darkblue-300); --color-filigran-brand-tertiary: var(--darkblue-900); --color-filigran-tonic-primary: var(--turquoise-700); --color-filigran-tonic-accent: var(--turquoise-800); --color-filigran-tonic-accent-transparency-20: color-mix(in srgb, var(--color-filigran-tonic-accent) 20%, transparent); --color-filigran-tonic-secondary: var(--turquoise-900); --color-filigran-tonic-tertiary: var(--turquoise-200); --color-filigran-ia-primary: var(--purple-500); --color-filigran-ia-secondary: var(--purple-700); --color-filigran-ia-secondary-transparency-10: color-mix(in srgb, var(--color-filigran-ia-secondary) 10%, transparent); --color-filigran-ia-tertiary: var(--purple-100); ``` #### Feedback ```css --color-feedback-info-primary: var(--blue-700); --color-feedback-info-secondary: var(--blue-400); --color-feedback-info-secondary-transparency-30: color-mix(in srgb, var(--color-feedback-info-secondary) 30%, transparent); --color-feedback-info-secondary-transparency-20: color-mix(in srgb, var(--color-feedback-info-secondary) 20%, transparent); --color-feedback-info-tertiary: var(--blue-800); --color-feedback-success-primary: var(--green-700); --color-feedback-success-secondary: var(--green-300); --color-feedback-success-secondary-transparency-30: color-mix(in srgb, var(--color-feedback-success-secondary) 30%, transparent); --color-feedback-success-secondary-transparency-20: color-mix(in srgb, var(--color-feedback-success-secondary) 20%, transparent); --color-feedback-success-tertiary: var(--green-800); --color-feedback-alert-primary: var(--yellow-400); --color-feedback-alert-secondary: var(--yellow-200); --color-feedback-alert-secondary-transparency-30: color-mix(in srgb, var(--color-feedback-alert-secondary) 30%, transparent); --color-feedback-alert-secondary-transparency-20: color-mix(in srgb, var(--color-feedback-alert-secondary) 20%, transparent); --color-feedback-alert-tertiary: var(--yellow-800); --color-feedback-warning-primary: var(--orange-600); --color-feedback-warning-secondary: var(--orange-200); --color-feedback-warning-secondary-transparency-30: color-mix(in srgb, var(--color-feedback-warning-secondary) 30%, transparent); --color-feedback-warning-secondary-transparency-20: color-mix(in srgb, var(--color-feedback-warning-secondary) 20%, transparent); --color-feedback-warning-tertiary: var(--orange-800); --color-feedback-error-primary: var(--red-600); --color-feedback-error-secondary: var(--red-300); --color-feedback-error-secondary-transparency-30: color-mix(in srgb, var(--color-feedback-error-secondary) 30%, transparent); --color-feedback-error-secondary-transparency-20: color-mix(in srgb, var(--color-feedback-error-secondary) 20%, transparent); --color-feedback-error-tertiary: var(--red-700); --color-feedback-contrast-primary: var(--black-300); --color-feedback-contrast-secondary: var(--black-200); --color-feedback-contrast-tertiary: var(--black-100); --color-feedback-neutral-primary: var(--gray-600); --color-feedback-neutral-secondary: var(--gray-400); --color-feedback-neutral-secondary-transparency-30: color-mix(in srgb, var(--color-feedback-neutral-secondary) 30%, transparent); --color-feedback-neutral-tertiary: var(--gray-700); --color-feedback-neutral-off: var(--gray-500); ``` #### Entities ```css --color-entities-analyses: #468315; --color-entities-analyses-transparency-20: color-mix(in srgb, var(--color-entities-analyses) 20%, transparent); --color-entities-cases: #c10fdf; --color-entities-cases-transparency-20: color-mix(in srgb, var(--color-entities-cases) 20%, transparent); --color-entities-events: #dc2c67; --color-entities-events-transparency-20: color-mix(in srgb, var(--color-entities-events) 20%, transparent); --color-entities-observations: #ce4914; --color-entities-observations-transparency-20: color-mix(in srgb, var(--color-entities-observations) 20%, transparent); --color-entities-all-threats: #a06b00; --color-entities-all-threats-transparency-20: color-mix(in srgb, var(--color-entities-all-threats) 20%, transparent); --color-entities-arsenal: #817800; --color-entities-arsenal-transparency-20: color-mix(in srgb, var(--color-entities-arsenal) 20%, transparent); --color-entities-techniques: #667f00; --color-entities-techniques-transparency-20: color-mix(in srgb, var(--color-entities-techniques) 20%, transparent); --color-entities-victimology: #6600f5; --color-entities-victimology-transparency-20: color-mix(in srgb, var(--color-entities-victimology) 20%, transparent); --color-entities-location: #008292; --color-entities-location-transparency-20: color-mix(in srgb, var(--color-entities-location) 20%, transparent); ``` ### Dark mode overrides (`.dark`) #### Colors Alias — Bg ```css --bg-elevation-default-layer-0: var(--grayblue-1000); --bg-elevation-default-layer-0-transparency-70: color-mix(in srgb, var(--bg-elevation-default-layer-0) 70%, transparent); --bg-elevation-default-layer-0-gradient: var(--grayblue-900); --bg-elevation-default-layer-1: var(--grayblue-850); --bg-elevation-default-layer-2: var(--grayblue-700); --bg-elevation-default-layer-3: var(--grayblue-500); --bg-elevation-heading-layer-0: var(--grayblue-1000); --bg-elevation-heading-layer-1: var(--grayblue-1000); --bg-elevation-heading-layer-2: var(--grayblue-800); --bg-elevation-heading-layer-3: var(--grayblue-600); --bg-elevation-highlight-layer-0: var(--grayblue-700); --bg-elevation-highlight-layer-1: var(--grayblue-600); --bg-elevation-highlight-layer-2: var(--grayblue-900); --bg-elevation-highlight-layer-3: var(--grayblue-700); --bg-elevation-hover-layer-0: var(--grayblue-850); --bg-elevation-hover-layer-1: var(--grayblue-800); --bg-elevation-hover-layer-2: var(--grayblue-1000); --bg-elevation-hover-layer-3: var(--grayblue-850); --bg-elevation-disabled-layer-0: var(--gray-800); --bg-elevation-disabled-layer-1: var(--gray-700); --bg-elevation-disabled-layer-2: var(--gray-700); --bg-elevation-disabled-layer-3: var(--gray-600); ``` #### Border ```css --border-elevation-subtle-soft-layer-0: var(--grayblue-400); --border-elevation-subtle-soft-layer-0-transparency-15: color-mix(in srgb, var(--border-elevation-subtle-soft-layer-0) 15%, transparent); --border-elevation-subtle-soft-layer-1: var(--grayblue-400); --border-elevation-subtle-soft-layer-1-transparency-15: color-mix(in srgb, var(--border-elevation-subtle-soft-layer-1) 15%, transparent); --border-elevation-subtle-soft-layer-2: var(--grayblue-200); --border-elevation-subtle-soft-layer-2-transparency-15: color-mix(in srgb, var(--border-elevation-subtle-soft-layer-2) 15%, transparent); --border-elevation-subtle-soft-layer-3: var(--grayblue-200); --border-elevation-subtle-soft-layer-3-transparency-15: color-mix(in srgb, var(--border-elevation-subtle-soft-layer-3) 15%, transparent); --border-elevation-disabled-layer-0: var(--gray-600); --border-elevation-disabled-layer-1: var(--gray-500); --border-elevation-disabled-layer-2: var(--gray-500); --border-elevation-disabled-layer-3: var(--gray-400); --border-elevation-subtle-layer-0: var(--grayblue-500); --border-elevation-subtle-layer-1: var(--grayblue-400); --border-elevation-subtle-layer-2: var(--grayblue-400); --border-elevation-subtle-layer-3: var(--grayblue-400); --border-elevation-default-layer-0: var(--grayblue-300); --border-elevation-default-layer-1: var(--grayblue-200); --border-elevation-default-layer-2: var(--grayblue-200); --border-elevation-default-layer-3: var(--grayblue-200); ``` #### Text ```css --text-default-primary: var(--gray-100); --text-default-secondary: var(--gray-300); --text-default-disabled: var(--gray-400); --text-negative-primary: var(--gray-900); --text-negative-secondary: var(--gray-700); --text-negative-disabled: var(--gray-600); ``` #### Filigran ```css --color-filigran-brand-primary: var(--blue-400); --color-filigran-brand-primary-transparency-55: color-mix(in srgb, var(--color-filigran-brand-primary) 55%, transparent); --color-filigran-brand-primary-transparency-10: color-mix(in srgb, var(--color-filigran-brand-primary) 10%, transparent); --color-filigran-brand-secondary: var(--blue-200); --color-filigran-brand-tertiary: var(--blue-600); --color-filigran-tonic-primary: var(--turquoise-500); --color-filigran-tonic-accent: var(--turquoise-500); --color-filigran-tonic-accent-transparency-20: color-mix(in srgb, var(--color-filigran-tonic-accent) 20%, transparent); --color-filigran-tonic-secondary: var(--turquoise-100); --color-filigran-tonic-tertiary: var(--turquoise-700); --color-filigran-ia-primary: var(--purple-300); --color-filigran-ia-secondary: var(--purple-100); --color-filigran-ia-secondary-transparency-10: color-mix(in srgb, var(--color-filigran-ia-secondary) 10%, transparent); --color-filigran-ia-tertiary: var(--purple-500); ``` #### Feedback ```css --color-feedback-info-primary: var(--blue-400); --color-feedback-info-secondary: var(--blue-700); --color-feedback-info-secondary-transparency-30: color-mix(in srgb, var(--color-feedback-info-secondary) 30%, transparent); --color-feedback-info-secondary-transparency-20: color-mix(in srgb, var(--color-feedback-info-secondary) 20%, transparent); --color-feedback-info-tertiary: var(--blue-200); --color-feedback-success-primary: var(--green-600); --color-feedback-success-secondary: var(--green-800); --color-feedback-success-secondary-transparency-30: color-mix(in srgb, var(--color-feedback-success-secondary) 30%, transparent); --color-feedback-success-secondary-transparency-20: color-mix(in srgb, var(--color-feedback-success-secondary) 20%, transparent); --color-feedback-success-tertiary: var(--green-200); --color-feedback-alert-primary: var(--yellow-400); --color-feedback-alert-secondary: var(--yellow-600); --color-feedback-alert-secondary-transparency-30: color-mix(in srgb, var(--color-feedback-alert-secondary) 30%, transparent); --color-feedback-alert-secondary-transparency-20: color-mix(in srgb, var(--color-feedback-alert-secondary) 20%, transparent); --color-feedback-alert-tertiary: var(--yellow-200); --color-feedback-warning-primary: var(--orange-500); --color-feedback-warning-secondary: var(--orange-700); --color-feedback-warning-secondary-transparency-30: color-mix(in srgb, var(--color-feedback-warning-secondary) 30%, transparent); --color-feedback-warning-secondary-transparency-20: color-mix(in srgb, var(--color-feedback-warning-secondary) 20%, transparent); --color-feedback-warning-tertiary: var(--orange-200); --color-feedback-error-primary: var(--red-400); --color-feedback-error-secondary: var(--red-700); --color-feedback-error-secondary-transparency-30: color-mix(in srgb, var(--color-feedback-error-secondary) 30%, transparent); --color-feedback-error-secondary-transparency-20: color-mix(in srgb, var(--color-feedback-error-secondary) 20%, transparent); --color-feedback-error-tertiary: var(--red-300); --color-feedback-contrast-primary: var(--white-300); --color-feedback-contrast-secondary: var(--white-200); --color-feedback-contrast-tertiary: var(--white-100); --color-feedback-neutral-primary: var(--gray-400); --color-feedback-neutral-secondary: var(--gray-600); --color-feedback-neutral-secondary-transparency-30: color-mix(in srgb, var(--color-feedback-neutral-secondary) 30%, transparent); --color-feedback-neutral-tertiary: var(--gray-200); --color-feedback-neutral-off: var(--gray-100); ``` #### Entities ```css --color-entities-analyses: #70b23b; --color-entities-analyses-transparency-20: color-mix(in srgb, var(--color-entities-analyses) 20%, transparent); --color-entities-cases: #ea80fc; --color-entities-cases-transparency-20: color-mix(in srgb, var(--color-entities-cases) 20%, transparent); --color-entities-events: #f96c9b; --color-entities-events-transparency-20: color-mix(in srgb, var(--color-entities-events) 20%, transparent); --color-entities-observations: #ff6f42; --color-entities-observations-transparency-20: color-mix(in srgb, var(--color-entities-observations) 20%, transparent); --color-entities-all-threats: #ff9800; --color-entities-all-threats-transparency-20: color-mix(in srgb, var(--color-entities-all-threats) 20%, transparent); --color-entities-arsenal: #f0b60a; --color-entities-arsenal-transparency-20: color-mix(in srgb, var(--color-entities-arsenal) 20%, transparent); --color-entities-techniques: #d3e157; --color-entities-techniques-transparency-20: color-mix(in srgb, var(--color-entities-techniques) 20%, transparent); --color-entities-victimology: #ba88ff; --color-entities-victimology-transparency-20: color-mix(in srgb, var(--color-entities-victimology) 20%, transparent); --color-entities-location: #05acc1; --color-entities-location-transparency-20: color-mix(in srgb, var(--color-entities-location) 20%, transparent); ``` ### Elevation layer-0 overrides (`.layer-0`) #### Default ```css --bg-elevation-default: var(--bg-elevation-default-layer-0); --border-elevation-default: var(--border-elevation-default-layer-0); ``` #### Disabled ```css --bg-elevation-disabled: var(--bg-elevation-disabled-layer-0); --border-elevation-disabled: var(--border-elevation-disabled-layer-0); ``` #### Heading ```css --bg-elevation-heading: var(--bg-elevation-heading-layer-0); ``` #### Highlight ```css --bg-elevation-highlight: var(--bg-elevation-highlight-layer-0); ``` #### Hover ```css --bg-elevation-hover: var(--bg-elevation-hover-layer-0); ``` #### Subtle ```css --border-elevation-subtle: var(--border-elevation-subtle-layer-0); --border-elevation-subtle-soft: var(--border-elevation-subtle-soft-layer-0-transparency-15); ``` #### Layer-dependent aliases (L133) ```css --bg-input-default: var(--bg-elevation-highlight); --bg-input-disabled: var(--bg-elevation-disabled); --bg-input-hover: var(--bg-elevation-hover); ``` ### Elevation layer-1 overrides (`.layer-1`) #### Default ```css --bg-elevation-default: var(--bg-elevation-default-layer-1); --border-elevation-default: var(--border-elevation-default-layer-1); ``` #### Disabled ```css --bg-elevation-disabled: var(--bg-elevation-disabled-layer-1); --border-elevation-disabled: var(--border-elevation-disabled-layer-1); ``` #### Heading ```css --bg-elevation-heading: var(--bg-elevation-heading-layer-1); ``` #### Highlight ```css --bg-elevation-highlight: var(--bg-elevation-highlight-layer-1); ``` #### Hover ```css --bg-elevation-hover: var(--bg-elevation-hover-layer-1); ``` #### Subtle ```css --border-elevation-subtle: var(--border-elevation-subtle-layer-1); --border-elevation-subtle-soft: var(--border-elevation-subtle-soft-layer-1-transparency-15); ``` #### Layer-dependent aliases (L133) ```css --bg-input-default: var(--bg-elevation-highlight); --bg-input-disabled: var(--bg-elevation-disabled); --bg-input-hover: var(--bg-elevation-hover); ``` ### Elevation layer-2 overrides (`.layer-2`) #### Default ```css --bg-elevation-default: var(--bg-elevation-default-layer-2); --border-elevation-default: var(--border-elevation-default-layer-2); ``` #### Disabled ```css --bg-elevation-disabled: var(--bg-elevation-disabled-layer-2); --border-elevation-disabled: var(--border-elevation-disabled-layer-2); ``` #### Heading ```css --bg-elevation-heading: var(--bg-elevation-heading-layer-2); ``` #### Highlight ```css --bg-elevation-highlight: var(--bg-elevation-highlight-layer-2); ``` #### Hover ```css --bg-elevation-hover: var(--bg-elevation-hover-layer-2); ``` #### Subtle ```css --border-elevation-subtle: var(--border-elevation-subtle-layer-2); --border-elevation-subtle-soft: var(--border-elevation-subtle-soft-layer-2-transparency-15); ``` #### Layer-dependent aliases (L133) ```css --bg-input-default: var(--bg-elevation-highlight); --bg-input-disabled: var(--bg-elevation-disabled); --bg-input-hover: var(--bg-elevation-hover); ``` ### Elevation layer-3 overrides (`.layer-3`) #### Default ```css --bg-elevation-default: var(--bg-elevation-default-layer-3); --border-elevation-default: var(--border-elevation-default-layer-3); ``` #### Disabled ```css --bg-elevation-disabled: var(--bg-elevation-disabled-layer-3); --border-elevation-disabled: var(--border-elevation-disabled-layer-3); ``` #### Heading ```css --bg-elevation-heading: var(--bg-elevation-heading-layer-3); ``` #### Highlight ```css --bg-elevation-highlight: var(--bg-elevation-highlight-layer-3); ``` #### Hover ```css --bg-elevation-hover: var(--bg-elevation-hover-layer-3); ``` #### Subtle ```css --border-elevation-subtle: var(--border-elevation-subtle-layer-3); --border-elevation-subtle-soft: var(--border-elevation-subtle-soft-layer-3-transparency-15); ``` #### Layer-dependent aliases (L133) ```css --bg-input-default: var(--bg-elevation-highlight); --bg-input-disabled: var(--bg-elevation-disabled); --bg-input-hover: var(--bg-elevation-hover); ``` ### Utility classes (`@utility` blocks) The only agent-extendable zone of theme.css — utilities only, never token declarations (see AGENTS.md "Tailwind and Token Rules"). - `.text-default-primary`: ```css color: var(--text-default-primary); ``` - `.text-default-secondary`: ```css color: var(--text-default-secondary); ``` - `.text-default-disabled`: ```css color: var(--text-default-disabled); ``` - `.text-negative-primary`: ```css color: var(--text-negative-primary); ``` - `.text-negative-secondary`: ```css color: var(--text-negative-secondary); ``` - `.text-negative-disabled`: ```css color: var(--text-negative-disabled); ``` - `.border-input-focus`: ```css border-color: var(--border-input-focus); ``` - `.border-input-hover`: ```css border-color: var(--border-input-hover); ``` - `.border-input-error`: ```css border-color: var(--border-input-error); ``` - `.text-input-label`: ```css color: var(--text-input-label); ``` - `.text-input-value`: ```css color: var(--text-input-value); ``` - `.text-input-placeholder`: ```css color: var(--text-input-placeholder); ``` - `.text-input-helper`: ```css color: var(--text-input-helper); ``` - `.text-input-disabled`: ```css color: var(--text-input-disabled); ``` - `.text-input-error`: ```css color: var(--text-input-error); ``` - `.text-input-required`: ```css color: var(--text-input-required); ``` - `.bg-input-default`: ```css background-color: var(--bg-input-default); ``` - `.bg-input-disabled`: ```css background-color: var(--bg-input-disabled); ``` - `.bg-input-hover`: ```css background-color: var(--bg-input-hover); ``` - `.fill-default`: ```css fill: var(--icon-default); ``` - `.stroke-default`: ```css stroke: var(--icon-default); ``` - `.text-icon-default`: ```css color: var(--icon-default); ``` - `.bg-icon-default`: ```css background-color: var(--icon-default); ``` - `.fill-subtle`: ```css fill: var(--icon-subtle); ``` - `.stroke-subtle`: ```css stroke: var(--icon-subtle); ``` - `.text-icon-subtle`: ```css color: var(--icon-subtle); ``` - `.bg-icon-subtle`: ```css background-color: var(--icon-subtle); ``` - `.fill-negative`: ```css fill: var(--icon-negative); ``` - `.stroke-negative`: ```css stroke: var(--icon-negative); ``` - `.text-icon-negative`: ```css color: var(--icon-negative); ``` - `.bg-icon-negative`: ```css background-color: var(--icon-negative); ``` - `.fill-highlight`: ```css fill: var(--icon-highlight); ``` - `.stroke-highlight`: ```css stroke: var(--icon-highlight); ``` - `.text-icon-highlight`: ```css color: var(--icon-highlight); ``` - `.bg-icon-highlight`: ```css background-color: var(--icon-highlight); ``` - `.fill-disabled`: ```css fill: var(--icon-disabled); ``` - `.stroke-disabled`: ```css stroke: var(--icon-disabled); ``` - `.text-icon-disabled`: ```css color: var(--icon-disabled); ``` - `.bg-icon-disabled`: ```css background-color: var(--icon-disabled); ``` - `.fill-info`: ```css fill: var(--icon-info); ``` - `.stroke-info`: ```css stroke: var(--icon-info); ``` - `.text-icon-info`: ```css color: var(--icon-info); ``` - `.bg-icon-info`: ```css background-color: var(--icon-info); ``` - `.fill-success`: ```css fill: var(--icon-success); ``` - `.stroke-success`: ```css stroke: var(--icon-success); ``` - `.text-icon-success`: ```css color: var(--icon-success); ``` - `.bg-icon-success`: ```css background-color: var(--icon-success); ``` - `.fill-warning`: ```css fill: var(--icon-warning); ``` - `.stroke-warning`: ```css stroke: var(--icon-warning); ``` - `.text-icon-warning`: ```css color: var(--icon-warning); ``` - `.bg-icon-warning`: ```css background-color: var(--icon-warning); ``` - `.fill-alert`: ```css fill: var(--icon-alert); ``` - `.stroke-alert`: ```css stroke: var(--icon-alert); ``` - `.text-icon-alert`: ```css color: var(--icon-alert); ``` - `.bg-icon-alert`: ```css background-color: var(--icon-alert); ``` - `.fill-error`: ```css fill: var(--icon-error); ``` - `.stroke-error`: ```css stroke: var(--icon-error); ``` - `.text-icon-error`: ```css color: var(--icon-error); ``` - `.bg-icon-error`: ```css background-color: var(--icon-error); ``` - `.fill-neutral`: ```css fill: var(--icon-neutral); ``` - `.stroke-neutral`: ```css stroke: var(--icon-neutral); ``` - `.text-icon-neutral`: ```css color: var(--icon-neutral); ``` - `.bg-icon-neutral`: ```css background-color: var(--icon-neutral); ``` - `.fill-off`: ```css fill: var(--icon-off); ``` - `.stroke-off`: ```css stroke: var(--icon-off); ``` - `.text-icon-off`: ```css color: var(--icon-off); ``` - `.bg-icon-off`: ```css background-color: var(--icon-off); ``` - `.border-alert-error`: ```css border-color: var(--border-alert-error); ``` - `.border-alert-info`: ```css border-color: var(--border-alert-info); ``` - `.border-alert-success`: ```css border-color: var(--border-alert-success); ``` - `.border-alert-warning`: ```css border-color: var(--border-alert-warning); ``` - `.border-alert-alert`: ```css border-color: var(--border-alert-alert); ``` - `.text-alert`: ```css color: var(--text-alert); ``` - `.bg-alert-info`: ```css background-color: var(--color-feedback-info-secondary-transparency-30); ``` - `.bg-alert-success`: ```css background-color: var(--color-feedback-success-secondary-transparency-30); ``` - `.bg-alert-alert`: ```css background-color: var(--color-feedback-alert-secondary-transparency-30); ``` - `.bg-alert-warning`: ```css background-color: var(--color-feedback-warning-secondary-transparency-30); ``` - `.bg-alert-error`: ```css background-color: var(--color-feedback-error-secondary-transparency-30); ``` - `.bg-elevation-default`: ```css background-color: var(--bg-elevation-default); ``` - `.bg-elevation-disabled`: ```css background-color: var(--bg-elevation-disabled); ``` - `.bg-elevation-heading`: ```css background-color: var(--bg-elevation-heading); ``` - `.bg-elevation-highlight`: ```css background-color: var(--bg-elevation-highlight); ``` - `.bg-elevation-hover`: ```css background-color: var(--bg-elevation-hover); ``` - `.border-elevation-subtle`: ```css border-color: var(--border-elevation-subtle); ``` - `.border-elevation-subtle-soft`: ```css border-color: var(--border-elevation-subtle-soft); ``` - `.border-elevation-default`: ```css border-color: var(--border-elevation-default); ``` - `.border-elevation-disabled`: ```css border-color: var(--border-elevation-disabled); ``` - `.ring-focus`: ```css --tw-ring-color: var(--color-filigran-brand-primary); ``` - `.ring-offset-focus`: ```css --tw-ring-offset-color: var(--bg-elevation-default); ``` - `.outline-focus`: ```css outline-color: var(--color-filigran-brand-primary); ``` - `.border-bg-elevation-default`: ```css border-color: var(--bg-elevation-default); ``` - `.from-fade-overlay`: ```css --tw-gradient-from: color-mix(in srgb, var(--bg-elevation-default-layer-0) 20%, transparent); --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); ``` - `.bg-tooltip`: ```css background-color: var(--border-elevation-subtle); ``` - `.w-overlay-sm`: ```css width: var(--width-overlay-sm); max-width: calc(100vw - var(--spacing) * 8); ``` - `.w-overlay-md`: ```css width: var(--width-overlay-md); max-width: calc(100vw - var(--spacing) * 8); ``` - `.w-overlay-lg`: ```css width: var(--width-overlay-lg); max-width: calc(100vw - var(--spacing) * 8); ``` - `.w-overlay-xl`: ```css width: var(--width-overlay-xl); max-width: calc(100vw - var(--spacing) * 8); ``` - `.title-lg`: ```css font-family: var(--font-title-lg); font-size: var(--text-title-lg); font-weight: var(--font-weight-title-lg); line-height: var(--leading-title-lg); letter-spacing: var(--tracking-title-lg); ``` - `.title-xs`: ```css font-family: var(--font-title-xs); font-size: var(--text-title-xs); font-weight: var(--font-weight-title-xs); line-height: var(--leading-title-xs); letter-spacing: var(--tracking-title-xs); ``` - `.title-sm`: ```css font-family: var(--font-title-sm); font-size: var(--text-title-sm); font-weight: var(--font-weight-title-sm); line-height: var(--leading-title-sm); letter-spacing: var(--tracking-title-sm); ``` - `.title-2xl`: ```css font-family: var(--font-title-2xl); font-size: var(--text-title-2xl); font-weight: var(--font-weight-title-2xl); line-height: var(--leading-title-2xl); letter-spacing: var(--tracking-title-2xl); ``` - `.title-jumbo`: ```css font-family: var(--font-title-jumbo); font-size: var(--text-title-jumbo); font-weight: var(--font-weight-title-jumbo); line-height: var(--leading-title-jumbo); letter-spacing: var(--tracking-title-jumbo); ``` - `.title-xl`: ```css font-family: var(--font-title-xl); font-size: var(--text-title-xl); font-weight: var(--font-weight-title-xl); line-height: var(--leading-title-xl); letter-spacing: var(--tracking-title-xl); ``` - `.title-md`: ```css font-family: var(--font-title-md); font-size: var(--text-title-md); font-weight: var(--font-weight-title-md); line-height: var(--leading-title-md); letter-spacing: var(--tracking-title-md); ``` - `.content-compact-link`: ```css font-family: var(--font-content-compact-link); font-size: var(--text-content-compact-link); font-weight: var(--font-weight-content-compact-link); line-height: var(--leading-content-compact-link); letter-spacing: var(--tracking-content-compact-link); text-decoration: var(--text-decoration-content-compact-link); ``` - `.content-base-bold`: ```css font-family: var(--font-content-base-bold); font-size: var(--text-content-base-bold); font-weight: var(--font-weight-content-base-bold); line-height: var(--leading-content-base-bold); letter-spacing: var(--tracking-content-base-bold); ``` - `.content-button`: ```css font-family: var(--font-content-button); font-size: var(--text-content-button); font-weight: var(--font-weight-content-button); line-height: var(--leading-content-button); letter-spacing: var(--tracking-content-button); ``` - `.content-compact`: ```css font-family: var(--font-content-compact); font-size: var(--text-content-compact); font-weight: var(--font-weight-content-compact); line-height: var(--leading-content-compact); letter-spacing: var(--tracking-content-compact); ``` - `.content-caption`: ```css font-family: var(--font-content-caption); font-size: var(--text-content-caption); font-weight: var(--font-weight-content-caption); line-height: var(--leading-content-caption); letter-spacing: var(--tracking-content-caption); ``` - `.content-highlight`: ```css font-family: var(--font-content-highlight); font-size: var(--text-content-highlight); font-weight: var(--font-weight-content-highlight); line-height: var(--leading-content-highlight); letter-spacing: var(--tracking-content-highlight); ``` - `.content-base-medium`: ```css font-family: var(--font-content-base-medium); font-size: var(--text-content-base-medium); font-weight: var(--font-weight-content-base-medium); line-height: var(--leading-content-base-medium); letter-spacing: var(--tracking-content-base-medium); ``` - `.content-base-link`: ```css font-family: var(--font-content-base-link); font-size: var(--text-content-base-link); font-weight: var(--font-weight-content-base-link); line-height: var(--leading-content-base-link); letter-spacing: var(--tracking-content-base-link); text-decoration: var(--text-decoration-content-base-link); ``` - `.content-compact-bold`: ```css font-family: var(--font-content-compact-bold); font-size: var(--text-content-compact-bold); font-weight: var(--font-weight-content-compact-bold); line-height: var(--leading-content-compact-bold); letter-spacing: var(--tracking-content-compact-bold); ``` - `.content-compact-medium`: ```css font-family: var(--font-content-compact-medium); font-size: var(--text-content-compact-medium); font-weight: var(--font-weight-content-compact-medium); line-height: var(--leading-content-compact-medium); letter-spacing: var(--tracking-content-compact-medium); ``` - `.content-code`: ```css font-family: var(--font-content-code); font-size: var(--text-content-code); font-weight: var(--font-weight-content-code); line-height: var(--leading-content-code); letter-spacing: var(--tracking-content-code); ``` - `.content-base`: ```css font-family: var(--font-content-base); font-size: var(--text-content-base); font-weight: var(--font-weight-content-base); line-height: var(--leading-content-base); letter-spacing: var(--tracking-content-base); ``` - `.bg-gradient-ia`: ```css background-image: var(--gradient-ia); ``` - `.text-gradient-ia`: ```css background-image: var(--gradient-ia); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; & > * { -webkit-text-fill-color: currentColor; } ``` - `.bg-gradient-success`: ```css background-image: var(--gradient-success); ``` - `.text-gradient-success`: ```css background-image: var(--gradient-success); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; & > * { -webkit-text-fill-color: currentColor; } ``` - `.bg-gradient-error`: ```css background-image: var(--gradient-error); ``` - `.text-gradient-error`: ```css background-image: var(--gradient-error); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; & > * { -webkit-text-fill-color: currentColor; } ``` - `.bg-gradient-warning`: ```css background-image: var(--gradient-warning); ``` - `.text-gradient-warning`: ```css background-image: var(--gradient-warning); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; & > * { -webkit-text-fill-color: currentColor; } ``` - `.bg-gradient-default`: ```css background-image: var(--gradient-default); ``` - `.text-gradient-default`: ```css background-image: var(--gradient-default); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; & > * { -webkit-text-fill-color: currentColor; } ``` - `.bg-gradient-focus`: ```css background-image: var(--gradient-focus); ``` - `.text-gradient-focus`: ```css background-image: var(--gradient-focus); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; & > * { -webkit-text-fill-color: currentColor; } ``` - `.scrollbar-hide`: ```css scrollbar-width: none; -ms-overflow-style: none; &::-webkit-scrollbar { display: none; } ``` - `.scrollbar-thin-input`: ```css scrollbar-width: thin; scrollbar-color: var(--text-default-secondary) transparent; &::-webkit-scrollbar { width: 4px; } &::-webkit-scrollbar-track { background-color: transparent; } &::-webkit-scrollbar-thumb { background-color: var(--text-default-secondary); border-radius: var(--radius-full); } ``` - `.appearance-textfield`: ```css -moz-appearance: textfield; appearance: textfield; &::-webkit-outer-spin-button, &::-webkit-inner-spin-button { appearance: none; margin: 0; } ``` ## Tailwind v4 Native Class Reference Hand-authored — not derivable from theme.css (explicit DO/DON'T guidance, not just data). Preserved verbatim from the pre-generation llms-full.txt (2026-06-30) during the migration to generation. All compound typography tokens are mapped in `@theme {}` and are available as native Tailwind v4 utility classes. Do NOT use arbitrary bracket syntax for these — native class names are always correct. ### Native typography class names | Token group | Class name | CSS property | Resolved value | |---|---|---|---| | Button text | `text-content-button` | font-size | 14px | | Button weight | `font-content-button` | font-family + font-weight | IBM Plex Sans, 600 | | Button leading | `leading-content-button` | line-height | 115% | | Button tracking | `tracking-content-button` | letter-spacing | 0.75% | | Body compact | `text-content-compact` | font-size | 12px | | Body compact weight | `font-content-compact` | font-family + font-weight | IBM Plex Sans, 400 | | Body default | `text-content-base` | font-size | 14px | | Caption | `text-content-caption` | font-size | 10px | | Heading XL | `text-title-xl` | font-size | 28px | | Heading 2XL | `text-title-2xl` | font-size | 32px | | Jumbo | `text-title-jumbo` | font-size | 42px | ### Rules - **NEVER** use `text-[length:var(--...)]` or `font-[number:var(--...)]` — these are Tailwind v3 arbitrary patterns. They are **obsolete** and wrong in this codebase. - **NEVER** use arbitrary bracket syntax (`[...]`) for any token that has a named `@theme` mapping. - Native classes like `text-content-button`, `rounded-md`, `bg-primary`, `text-default-primary` are always preferred. ### Gradient backgrounds — special case `background-image` has no Tailwind v4 namespace. `theme.css` defines 8 gradient `@utility` classes (`bg-gradient-{ia,focus,default,warning}` and their `text-gradient-*` counterparts for gradient text-fill), but only 3 of the `bg-gradient-*` ones are explicitly safelisted (`@source inline(...)`) in `packages/filigran-design-system/src/tokens/index.css` — those are the only ones GUARANTEED present when a consumer imports the precompiled `dist/index.css` (Strategy A). The rest exist in `theme.css` and work immediately under Strategy B (consumer runs its own Tailwind v4, which generates utilities from actual usage); under Strategy A they're only present if something in the library's own component source already uses them (picked up by the package build's own `@source` scan). ```tsx // ✅ Use @utility class names directly — safelisted in dist/index.css (Strategy A): className="bg-gradient-ia" className="bg-gradient-focus" className="bg-gradient-default" // Not safelisted — works under Strategy B, or Strategy A only if a library // component already uses it (check dist/index.css if depending on this): className="bg-gradient-warning" className="text-gradient-ia" // and the other text-gradient-* variants // ✅ If a gradient token has NO @utility defined yet, use bg-[image:var(...)] className="bg-[image:var(--some-custom-gradient)]" // ❌ NEVER — bg-[var(...)] targets background-color, not background-image // className="bg-[var(--gradient-ia)]" // ❌ NEVER — [--token] without var() is invalid CSS // className="bg-[--gradient-ia]" ``` ### Full Button typography example ```tsx // All four Button typography classes together: className="text-content-button font-content-button leading-content-button tracking-content-button" ``` ## ComponentMeta TypeScript Interface Source: `packages/filigran-design-system/src/types/meta.ts` — this type is the contract; if any prose elsewhere in this repo ever disagrees with it, the type wins. ```typescript import type { ReactNode } from "react"; /** * Functional, DX-first taxonomy used to group components in the docs-site * navbar (arbitrated by @fimow, 2026-08-20). A CLOSED union: a component whose * `category` is missing or misspelled fails `pnpm typecheck`/`pnpm build` * rather than silently dropping out of the navigation. The human-readable * label and the fixed display order live on the docs side * (docs/lib/categorized-registry.ts), not here — this type is the controlled * vocabulary only. */ export type ComponentCategory = "inputs" | "data-display" | "feedback" | "navigation" | "surface"; export interface ComponentMeta { name: string; description: string; status: "stable" | "beta" | "deprecated" | "planned"; /** Functional docs-navbar grouping (controlled vocabulary — see ComponentCategory). */ category: ComponentCategory; version: string; radixPrimitive: string; /** Full Figma URL of the component frame (including the ?node-id=… parameter) */ figmaLink?: string; /** * RAW Figma node ID in colon form, e.g. "1234:5678" — never a URL. * (Figma URLs display it with a dash; convert to the colon form used by * the Figma API. Decided 2026-07-06, audit Q6.) */ figmaNodeId?: string; /** * @deprecated No meta sets this any more, and nothing reads it: the docs * demo is `.demo.tsx` exporting `Demo`, outside this contract * and outside the documentation entry (AGENTS.md "Docs demos"). Kept as an * optional member so the published type does not break; removed at the * next major. `examples` is what an agent reads; a demo is a picture. */ render?: () => ReactNode; /** * Copy-paste JSX usage snippets. Agents implementing the component in a * product copy from examples first — keep them minimal, idiomatic and * token-compliant. Shown on the component's docs page. */ examples?: string[]; accessibility: { wcag: string; wcagStatus: "pass" | "fail" | "pending"; contrastRatios?: { [variant: string]: { light: number; dark: number; }; }; /** * Machine-verifiable contrast declarations. Each pair names the fg/bg * TOKENS (e.g. "--text-default-primary"; bg may be a gradient * token — every stop is then checked independently) and the WCAG * threshold (4.5 for normal text, 3 for large text / UI components). * CI resolves the tokens from theme.css in both modes and computes the * real ratios (scripts/check-contrast.ts) — declared numbers in * `contrastRatios`/`notes` are cross-checked against computed values. */ contrastPairs?: Array<{ /** Human id; when it matches a `contrastRatios` key, values are cross-checked */ id: string; /** Foreground (text/icon/ring) token name */ fg: string; /** Background token name, or a gradient token */ bg: string; /** Minimum WCAG ratio: 4.5 (normal text) or 3 (large text / UI) */ minRatio: number; /** * Restrict this pair to a single theme mode. Use ONLY for a * combination that genuinely renders in one mode only — e.g. a * colour replaced by another, independently-covered token in the * other mode via a `light:`/`.dark`-scoped class (see Banner warning: * its ink is --text-negative-primary in dark and --text-default-primary * in light). Omit to check BOTH modes (the default, and the safe * choice). */ mode?: "light" | "dark"; }>; /** * Machine-verifiable declaration for a component that paints a DATA * colour — a hex that comes from the data, not from the design system, * so no token exists for it and `contrastPairs` structurally cannot * describe it (see Chip's `color` prop). * * `check:contrast` Rule 6 reads this and COMPUTES the worst case over the * whole hex space rather than sampling a few colours: for a fixed alpha * and backdrop, compositing is affine and monotone per channel and WCAG * luminance is monotone in each channel, so the extreme surfaces are the * corners of the sRGB cube — the rule evaluates all eight and takes the * minimum ratio. It also verifies the precondition that makes that * argument valid (the `fg` token's luminance lies OUTSIDE the range the * wash can reach), and that the effective alpha stays under `ceiling`. */ dataColorWash?: { /** Human id used in the report, e.g. "chip-data-wash". */ id: string; /** Alpha of the resting wash, 0-1. */ restAlpha: number; /** * How many times the wash composites in the component's busiest state. * The effective alpha is DERIVED as 1-(1-restAlpha)^stackedLayers — it * is never declared, so it cannot drift from the mechanism. */ stackedLayers: number; /** Per-mode ceiling the effective alpha must not exceed. */ ceiling: { dark: number; light: number }; /** System token the component pins on the wash (label and icon). */ fg: string; /** Opaque surface token the wash composites over. */ backdrop: string; /** Minimum WCAG ratio: 4.5 (normal text) or 3 (large text / UI). */ minRatio: number; }; /** * Background tokens painted on an element hidden from assistive * technology that carries no information (a loading placeholder). * `check:contrast` Rule 5 then skips their `bg-*` uses instead of * demanding a contrastPair, after checking the token is a `--bg-*` the * component really renders and that its source sets `aria-hidden` * (scripts/lib/decorative-fill.ts; Skeleton.rfc.md §9 Q1, Q10). */ decorativeFills?: Array<{ /** Background token, e.g. "--bg-elevation-highlight". */ token: string; /** Why the fill carries no information. */ reason: string; }>; /** * Icon tokens tinting a glyph hidden from assistive technology, when what * the tint suggests is stated in text elsewhere (HeroStat's severity tint). * `check:contrast` Rule 5 then skips their `text-*` uses instead of * demanding a contrastPair, after checking the token is an `--icon-*` the * component really renders and that its source sets `aria-hidden` * (scripts/lib/decorative-tint.ts; HeroStat.rfc.md §9 Q1). */ decorativeTints?: Array<{ /** Icon token, e.g. "--icon-alert". */ token: string; /** Why the tint carries no information of its own. */ reason: string; }>; notes: string; }; variants: string[]; sizes: string[]; props: Record; /** * Groups several distinct, independently-exported components onto ONE * docs-site page with a Tabs switcher between them (fidelity re-pass #2 * point 9 — e.g. Navbar/NavbarItem/NavbarSubmenu/ProductSwitcher). Purely * a docs-PRESENTATION concern: the components themselves stay separate * files/exports; only `docs/app/components/[name]/page.tsx` reads this to * decide whether to render a single body or a `Tabs` of every entry * sharing the same `id`. Every member of a group must use the same `id`; * `label`/`tabLabel` may differ per member (used for that member's own * `TabsTrigger` text and — for the entry whose slug equals `id` — the * page's canonical/landing tab). */ docsGroup?: { /** Shared identifier for the whole group — must match across all members. */ id: string; /** Group-level label, currently unused by the generator but kept for * forward compatibility (e.g. a future shared group heading). */ label: string; /** This member's own `TabsTrigger` text (e.g. "Navbar Item"). */ tabLabel: string; }; } // ComponentMeta type definition (source of truth) ``` ## Prompt Command Reference Task-oriented guidance (which prompt for which task, human checkpoints, exit checks): `process/PLAYBOOK.md`. This list is a directory, not a substitute. - **`.github/prompts/add-component.prompt.md`** — Add Component — RFC → Design System: Call this prompt and provide: - The component name (must match a ROADMAP.json entry) - Optional comments or constraints - **`.github/prompts/audit-accessibility.prompt.md`** — Audit Accessibility - WCAG 2.1 AA + Section 508 (Compliance Gate): Use this command to audit all components in the design system package. This audit is a **shipping gate**: a component with a FAIL verdict is not shippable until fixed. - **`.github/prompts/generate-rfc.prompt.md`** — Generate RFC — Pre-fill a Component Brief: Call this prompt and provide: - The **component name** (must match a ROADMAP.json entry) - The **Figma node ID** and URL (from the designer) - Optional constraints or focus areas - **`.github/prompts/implement-tokens-product.prompt.md`** — Implement Tokens in a Product: Use this command to wire `@filigran/design-system` tokens (colors, typography, spacing) into a product's existing MUI theme, without touching component code. This prompt is written to be run **standalone, in a fresh session, by an agent with no prior context** beyond what's written here and what it reads from the repos as instructed below. - **`.github/prompts/reconcile-product.prompt.md`** — Reconcile a Product's fds-migration State: Use this command when a product's `fds/tokens-implementation` (or any other `fds/*`) branch needs to catch up with that product's fast-moving default branch — repos are live, teams push continuously, and their work can silently overwrite or bypass the design-system wiring done via `implement-tokens-product.prompt.md`. Run this at the start of any `fds/*` session, and periodically (daily during an active pilot, weekly afterwards) or right after a `filigran-design-system` token update. - **`.github/prompts/review-pr.prompt.md`** — Review a PR — Reusable Protocol: Use this command to review any pull request in this repo — yours, a teammate's, or an external contributor's — at the standard this repo expects. Written from lived review rounds on this repo (a single-file docs fix, a full component API surface with ARIA/keyboard/contrast gates, and a three-round, three-rebase token-nomenclature reorganisation touching every consumer in two sibling products) — every step below is something that actually mattered at least once here, not theory imported from elsewhere. - **`.github/prompts/update-component.prompt.md`** — Update Component — Targeted Edit or RFC-driven Rework: Use this prompt to modify an existing component. Supports two modes: - **`.github/prompts/update-docs-site.prompt.md`** — Update Docs Site — Generated-First Documentation: Use this command for any change to the docs app (`docs/`): layout, navigation, shared page templates, docs-only components, new documentation features. - **`.github/prompts/update-tokens.prompt.md`** — Update Tokens - theme.css Sync: Use this command when `packages/filigran-design-system/src/tokens/theme.css` is updated by the Figma export pipeline. ## Local Development Commands Source: `package.json` (repo root). Task-oriented guidance (when to run which command, human checkpoints): `process/PLAYBOOK.md`. | Script | Command | |---|---| | `pnpm prepare` | `cd packages/filigran-design-system && tsup && node scripts/rewrite-relative-extensions.mjs && tailwindcss -i src/tokens/index.css -o dist/index.css --minify && node -e "const fs=require('node:fs'); fs.mkdirSync('dist/tokens',{recursive:true}); fs.copyFileSync('src/tokens/theme.css','dist/tokens/theme.css'); fs.mkdirSync('dist/styles',{recursive:true}); fs.copyFileSync('src/styles/gradient-helpers.css','dist/styles/gradient-helpers.css'); fs.copyFileSync('src/styles/motion.css','dist/styles/motion.css'); fs.copyFileSync('../../llms.txt','dist/llms.txt'); fs.copyFileSync('../../llms-full.txt','dist/llms-full.txt'); fs.copyFileSync('src/assets/icons/icon-mapping.json','dist/icon-mapping.json');"` | | `pnpm prepack` | `cd packages/filigran-design-system && tsup && node scripts/rewrite-relative-extensions.mjs && tailwindcss -i src/tokens/index.css -o dist/index.css --minify && node -e "const fs=require('node:fs'); fs.mkdirSync('dist/tokens',{recursive:true}); fs.copyFileSync('src/tokens/theme.css','dist/tokens/theme.css'); fs.mkdirSync('dist/styles',{recursive:true}); fs.copyFileSync('src/styles/gradient-helpers.css','dist/styles/gradient-helpers.css'); fs.copyFileSync('src/styles/motion.css','dist/styles/motion.css'); fs.copyFileSync('../../llms.txt','dist/llms.txt'); fs.copyFileSync('../../llms-full.txt','dist/llms-full.txt'); fs.copyFileSync('src/assets/icons/icon-mapping.json','dist/icon-mapping.json');"` | | `pnpm build` | `pnpm -r build` | | `pnpm typecheck` | `pnpm -r typecheck && tsc --noEmit -p scripts` | | `pnpm dev` | `pnpm --filter docs dev` | | `pnpm check:wcag` | `tsx scripts/check-wcag-gate.ts` | | `pnpm check:contrast` | `tsx scripts/check-contrast.ts` | | `pnpm check:tokens` | `tsx scripts/check-token-usage.ts` | | `pnpm check:docs` | `tsx scripts/check-docs-completeness.ts` | | `pnpm check:lint` | `pnpm --filter @filigran/design-system lint` | | `pnpm check:manifest-parity` | `tsx scripts/check-manifest-parity.ts` | | `pnpm check:playbook` | `tsx scripts/check-playbook.ts` | | `pnpm check:release-pr` | `tsx scripts/check-release-pr.ts` | | `pnpm check:language` | `tsx scripts/check-language.ts` | | `pnpm check:language:metadata` | `tsx scripts/check-language-metadata.ts` | | `pnpm check:icons` | `tsx scripts/check-icons.ts` | | `pnpm generate:artifacts` | `tsx scripts/generate-artifacts.ts && tsx scripts/generate-icon-registry.ts && tsx scripts/generate-product-logos.ts && tsx scripts/generate-primitive-colors.ts && tsx scripts/generate-roadmap.ts && tsx scripts/generate-migration-map.ts && tsx scripts/generate-implementation-status.ts && tsx scripts/generate-llms-full.ts` | | `pnpm generate:migration-map` | `tsx scripts/generate-migration-map.ts` | | `pnpm generate:icons` | `tsx scripts/generate-icon-registry.ts` | | `pnpm generate:product-logos` | `tsx scripts/generate-product-logos.ts` | | `pnpm generate:primitive-colors` | `tsx scripts/generate-primitive-colors.ts` | | `pnpm generate:roadmap` | `tsx scripts/generate-roadmap.ts` | | `pnpm generate:adoption` | `tsx scripts/generate-adoption.ts` | | `pnpm scan:products` | `tsx scripts/scan-products.ts` | | `pnpm generate:mui-bridge` | `tsx scripts/generate-mui-bridge.ts` | | `pnpm generate:fds-migration` | `tsx scripts/generate-fds-migration.ts` | | `pnpm check:artifacts` | `tsx scripts/check-artifacts.ts` | | `pnpm check:zones` | `tsx scripts/check-zones.ts` | | `pnpm check:skills` | `tsx scripts/check-skills.ts` | | `pnpm check:bundle-size` | `tsx scripts/check-bundle-size.ts` | | `pnpm test` | `pnpm --filter @filigran/design-system test` | | `pnpm test:smoke` | `tsx scripts/scoped-tests.ts smoke` | | `pnpm test:unit` | `tsx scripts/scoped-tests.ts vitest` | | `pnpm ci:scope` | `tsx scripts/scoped-tests.ts print` | | `pnpm test:scripts` | `tsx --test scripts/lib/legacy-usage.test.ts scripts/lib/status-guards.test.ts scripts/lib/tsx-classnames.test.ts scripts/check-figma-fidelity.test.ts scripts/lib/style-block-rules.test.ts scripts/lib/overlay-width-viewport-safe.test.ts scripts/lib/search-field-native-clear.test.ts scripts/lib/gradient-default-angle.test.ts scripts/lib/text-gradient-child-fill.test.ts scripts/lib/typography-line-height-tracking.test.ts scripts/lib/chip-data-color.test.ts scripts/lib/product-switcher-dropdown-visuals.test.ts scripts/lib/navbaritem-icon-color.test.ts scripts/lib/navbarsubmenu-icon-color.test.ts scripts/lib/navbar-label-overflow.test.ts scripts/lib/menu-focus-ring.test.ts scripts/lib/spacing-scale-emission.test.ts scripts/lib/token-utility-safelist.test.ts scripts/lib/fds-conformity-component-usage.test.ts scripts/lib/fds-conformity-legacy-motif.test.ts scripts/lib/fds-conformity-bridge-freshness.test.ts scripts/lib/navbar-list-top-padding.test.ts scripts/lib/product-switcher-chevron-color.test.ts scripts/lib/product-logos.test.ts scripts/lib/docs-completeness.test.ts scripts/lib/rfc-rule-references.test.ts scripts/lib/docs-navbar-icon-color.test.ts scripts/lib/docs-component-search.test.ts scripts/lib/docs-header-adoption.test.ts scripts/lib/header-fixed-only.test.ts scripts/lib/header-glass-showcase.test.ts scripts/lib/manifest-parity.test.ts scripts/lib/excluded-variable-ids.test.ts scripts/lib/product-ci-guard.test.ts scripts/check-language.test.ts scripts/check-icons.test.ts scripts/lib/token-diff.test.ts scripts/lib/meta-entry-separation.test.ts scripts/lib/demos-out-of-meta.test.ts scripts/lib/rfc-template.test.ts scripts/lib/rfc-class-claims.test.ts scripts/lib/scaffold-meta-insert.test.ts scripts/lib/bundle-size-verdict.test.ts scripts/lib/asset-slot-widths-documented.test.ts scripts/lib/figma-no-source.test.ts scripts/lib/navbar-flyout-anchor.test.ts scripts/lib/navbar-row-paint-surface.test.ts scripts/lib/breadcrumbs-width-basis.test.ts scripts/lib/disabled-gating.test.ts scripts/lib/test-registry-complete.test.ts scripts/lib/token-family-ramp.test.ts scripts/lib/ua-defaults.test.ts scripts/lib/card-surface-delegation.test.ts scripts/lib/card-state-couple.test.ts scripts/lib/combobox-named-style-composite.test.ts scripts/lib/token-alias-resolution.test.ts scripts/lib/layer-dependent-aliases.test.ts scripts/lib/icon-utility-generation.test.ts scripts/lib/combobox-start-icon-iso-searchfield.test.ts scripts/lib/rfc-category-enum.test.ts scripts/lib/migration-type-only-exclusion.test.ts scripts/lib/release-pr-guard.test.ts scripts/lib/release-regeneration.test.ts scripts/lib/migration-shipped-not-frozen.test.ts scripts/lib/homemade-usage.test.ts scripts/lib/library-exports.test.ts scripts/lib/root-version-sync.test.ts scripts/lib/release-commit.test.ts scripts/lib/light-variant-parity.test.ts scripts/lib/pr-ready-covers-ci.test.ts scripts/lib/ci-scope.test.ts scripts/lib/skills-gate.test.ts scripts/lib/decorative-fill.test.ts scripts/lib/decorative-tint.test.ts` | | `pnpm report:conformity` | `tsx scripts/generate-conformity-report.ts` | | `pnpm check:figma-fidelity` | `tsx scripts/check-figma-fidelity.ts` | | `pnpm validate:theme` | `tsx scripts/validate-theme.ts` | | `pnpm diff:tokens` | `tsx scripts/diff-tokens.ts` | | `pnpm generate:tokens` | `node scripts/extract-tokens/extract-tokens.mjs && node scripts/extract-tokens/normalize-w3c.mjs && node scripts/extract-tokens/transform-tailwind.mjs && node scripts/extract-tokens/sync-output.mjs` | | `pnpm generate:tokens:sync` | `pnpm generate:tokens && pnpm validate:theme && pnpm diff:tokens && pnpm --filter @filigran/design-system build` | | `pnpm scaffold:component` | `tsx scripts/scaffold-component.ts` | | `pnpm promote:component` | `tsx scripts/promote-component.ts` | | `pnpm sync:root-version` | `tsx scripts/sync-root-version.ts` | | `pnpm release:regenerate` | `tsx scripts/sync-root-version.ts && pnpm generate:artifacts` | | `pnpm check:release-regeneration` | `tsx scripts/check-release-regeneration.ts` | | `pnpm pr:ready` | `tsx scripts/pr-ready.ts` | | `pnpm format` | `prettier --write .` | | `pnpm format:check` | `prettier --check .` | | `pnpm check:token-families` | `tsx scripts/check-token-families.ts` | | `pnpm check:ua-defaults` | `tsx scripts/check-ua-defaults.ts` | | `pnpm generate:mui-regression-policy` | `tsx scripts/generate-mui-regression-policy.ts` | ## Consumers (product integration — host responsibilities) - Theme activation: set `.light` or `.dark` on the root element. No class = dark defaults. No `prefers-color-scheme`. Products with other conventions (MUI palette mode, `data-theme`) need a bridge — see AGENTS.md "Consumers". - Fonts: the library ships NO font files. The host must load Geologica + IBM Plex Sans, or typography silently falls back to system fonts. - Missing component: FLAG the gap (issue / process/AI-BACKLOG.md), NEVER create a local fork or approximation. - Install: `npm install @filigran/design-system` from the public registry. Releases are cut by a manually triggered workflow for now. A pinned git SHA still resolves, but only to try a commit that has not been released. - CSS import: Vite + MUI/emotion apps import only `dist/index.css` (complete on its own). Tailwind v4 hosts import `tokens/theme.css` AND must also import `@filigran/design-system/styles/gradient-helpers.css` — `theme.css` is tokens only and does not define the `gradient-border-*` / `icon-gradient-mask*` mechanics. They also declare `@custom-variant dark (&:is(.dark *), &:is(.dark));` and `@custom-variant light (&:is(.light *), &:is(.light));`, or the library's `light:` utilities compile to nothing. ## Consumer Integration Guide Hand-authored — not derivable from theme.css/package.json (concrete setup steps, code samples, and a non-obvious Tailwind v4 gotcha). Preserved verbatim from the pre-generation llms-full.txt (2026-06-30) during the migration to generation; update here if it goes stale, never let it drift back into a hand-copied duplicate elsewhere. ### Font Requirements Fonts are NOT bundled with `@filigran/design-system`. The package declares CSS custom properties for font families but does not ship the font files. The consuming app must load them explicitly. **Required fonts:** | Font | CSS variable | Weights used | |---|---|---| | Geologica | `--font-sans-geologica` | 500 | | IBM Plex Sans | `--font-sans-plex` | 400, 500, 600 | **Next.js 15 App Router (recommended):** ```tsx // app/layout.tsx import { Geologica, IBM_Plex_Sans } from "next/font/google"; const geologica = Geologica({ subsets: ["latin"], weight: ["500"], variable: "--font-sans-geologica", }); const ibmPlexSans = IBM_Plex_Sans({ subsets: ["latin"], weight: ["400", "500", "600"], variable: "--font-sans-plex", }); export default function RootLayout({ children }) { return ( {children} ); } ``` **Alternative — Google Fonts ``:** ```html ``` Then in CSS: ```css :root { --font-sans-geologica: "Geologica", sans-serif; --font-sans-plex: "IBM Plex Sans", sans-serif; } ``` ### Theme Mode The design system uses class-based theming, NOT `prefers-color-scheme` media queries. **Setup:** apply `.dark` or `.light` to the `` element. Default (no class applied) = dark mode (governed by `@theme {}` defaults). ```html ``` **Tailwind v4 configuration (consuming app):** if your app runs its own Tailwind v4, add these two lines to your global CSS file to enable the `dark:` and `light:` utility variants: ```css @custom-variant dark (&:is(.dark *), &:is(.dark)); @custom-variant light (&:is(.light *), &:is(.light)); ``` Without the `dark` line, Tailwind's `dark:` utilities will use `prefers-color-scheme` and produce incorrect results with this design system. Without the `light` line, every `light:` utility of the library compiles to nothing: the components that paint differently in light mode lose it silently (Banner's `warning` keeps a white ink on its orange gradient, Alert loses its severity wash). `dist/index.css` declares both already, so Strategy A needs neither line. **Elevation context:** `layer-0` is the DEFAULT — the base `@theme {}` block already resolves the elevation tokens (`--bg-elevation-default`, borders, surfaces) to their layer-0 values, so `bg-gradient-default` and friends work with no elevation class at all. Only apply a `layer-N` class when you need to OVERRIDE the context for a nested region (e.g. a card that should render at layer-1): ```html
``` Available elevation classes: `layer-0` (rarely needed — same as the default; useful only for explicitness), `layer-1`, `layer-2`, `layer-3` ### CSS Import Strategy Choose ONE of the following strategies based on your app setup. **Strategy A — Pre-compiled CSS (recommended for most apps).** Use when: your app does NOT run Tailwind v4, OR you want zero Tailwind configuration overhead. ```css /* app/globals.css or equivalent */ @import "@filigran/design-system/dist/index.css"; ``` Or in `layout.tsx`: ```ts import "@filigran/design-system/dist/index.css"; ``` What you get: all tokens, all component styles — pre-compiled and ready to use. Gradient utilities are a partial exception: only the 3 safelisted `bg-gradient-*` classes (`bg-gradient-default`, `bg-gradient-ia`, `bg-gradient-focus`) are guaranteed present; see "Gradient backgrounds — special case" in the Tailwind Class Reference above for the rest. **Strategy B — Raw tokens + your own Tailwind v4.** Use when: your app runs Tailwind v4 and you want to extend its theme with Filigran tokens (enables design system tokens in your own components via Tailwind utilities). ```css /* app/globals.css */ @import "tailwindcss"; /* Filigran @theme tokens + @utility definitions */ @import "@filigran/design-system/tokens/theme.css"; /* Gradient-border and gradient-icon mechanics — NOT in theme.css; required for IA/highlight gradient borders and gradient icons (dist/index.css bundles these already, so Strategy A does not need this line) */ @import "@filigran/design-system/styles/gradient-helpers.css"; /* Scan the compiled design system bundle for CVA class strings */ @source "../node_modules/@filigran/design-system/dist/index.mjs"; /* Class-based dark and light modes — both required */ @custom-variant dark (&:is(.dark *), &:is(.dark)); @custom-variant light (&:is(.light *), &:is(.light)); ``` What you get: all Filigran tokens available as Tailwind utilities in your own components, plus full design system component styles. ⚠️ Do NOT import both `dist/index.css` and `tokens/theme.css` — this will duplicate all token definitions. **Export map reference:** | Export path | Use case | |---|---| | `@filigran/design-system/dist/index.css` | Strategy A | | `@filigran/design-system/tokens/theme.css` | Strategy B | | `@filigran/design-system/styles/gradient-helpers.css` | Strategy B — gradient-border / gradient-icon mechanics (bundled into `dist/index.css` for Strategy A) | | `@filigran/design-system` | JS components (both strategies) | ### Overlay Stacking Order Every floating surface (Dialog panel and scrim, Tooltip, Select, Menu and submenus, Combobox option panel, ProductSwitcher panel, Navbar submenu flyouts) reads its stacking order from one custom property, defaulting to 50: ```css z-index: var(--fds-z-overlay, 50); ``` The default lives only in that fallback — `--fds-z-overlay` is deliberately NOT declared in `tokens/theme.css` (that file carries Figma-generated design values; this is a technical integration hook). Raise it when host chrome outranks 50. MUI's `AppBar` defaults to `1100`, so without an override a Tooltip or Select near the top of the page renders underneath it — and a form hosted in a MUI `Drawer` (1200) or `Dialog` (1300) is worse than a paint problem: the modal's backdrop can own the pixel, leaving a non-modal surface (Combobox, Tooltip, NavbarSubmenu flyout) visible-looking but unreachable by a real pointer: ```css :root { --fds-z-overlay: 1400; /* above MUI AppBar (1100) and Drawer (1200) */ } ``` ⚠️ Set it on `:root`, not on an app wrapper — portalled surfaces mount on `document.body`, outside your React tree, so a scoped variable never reaches them. ## AI Agent Rules — pointer, not a copy The binding operating contract is `AGENTS.md`. This file never copies its rules — a stale copy is exactly what the 2026-07-07 hygiene audit found and removed. Read `AGENTS.md` first in every session.