# Bridge design system > A design system run as code: W3C DTCG tokens (primitives, brand roles, light/dark semantic aliases, density and > component tiers) and 46 React components that read CSS variables only. The repo is the source of truth, not Figma. Generated from the repository on 2026-10-16. Source: https://github.com/peterbono/ai-design-system-demo Human version of the same content: the docs site pages /foundations/, /tokens/, /components/, /agents/ (Library) and /studio/settings/governance/. ## Library pages (read, latest release) - [Home](/): What's new and where to start - [Color](/foundations/color/): Palette ramps and the semantic roles in both themes - [Typography](/foundations/typography/): Type scale, weights and samples - [Spacing](/foundations/spacing/): The space scale - [Radius and borders](/foundations/radius/): Radius roles and border widths - [Elevation](/foundations/elevation/): Shadows for floating layers - [Accessibility](/foundations/accessibility/): Contrast grid of every gated color pair, in every brand and theme - [Explorer](/tokens/): Every token with its alias chain, values, contrast and usage - [Theme playground](/playground/): A sample product screen in every brand, theme and density - [Catalog](/components/): Every component with its guidance - [Install](/install/): Packages, fonts and the DS Bridge Figma plugin - [Agents](/agents/): MCP server, bridge CLI and llms.txt for AI agents - [Changelog](/changelog/): Release notes, ready to post ## Studio pages (change: drafts, branches, reviews, versions) - [Overview](/studio/): To review, health and activity - [Tokens](/studio/tokens/): Edit tokens into a draft, with live preview and guardrails - [Components](/studio/components/): Status, tokens, code and Figma links per component - [Drafts](/studio/drafts/): Token edits waiting in this browser - [Branches](/studio/branches/): Open branches with previews and snapshot packages - [Reviews](/studio/reviews/): Open pull requests with token diffs and contrast - [Versions](/studio/versions/): Releases and merged token changes, with revert - [Checks](/studio/checks/): Contrast gate and token validation - [Visual impact](/studio/visual-impact/): Screens and stories each pull request changes - [Adoption](/studio/adoption/): How much product code uses the design system ## Rules for agents (from AGENTS.md) ### Always - Work on a branch named agent/. One concern per PR. - Use semantic or component tokens for color, elevation and focus: var(--color-action-primary-bg), never a primitive (--color-orange-500) and never a literal. - Use the space, size, radius, type and motion scales. If a value is missing, stop and ask. - Accessible by default: native elements or Base UI parts, visible focus, labels wired, color never the only signal. - Write UI copy in the Bridge voice: sentence case, verbs on buttons, errors that say how to fix. No em dashes. - Add a changeset. Pick the bump with the rules in .changeset/README.md. - Fill the PR template, including the risk tier, and say the change is agent-authored. ### Ask before - Adding, renaming or removing any token (Tier 3, needs design and tech owner approval). - Adding a new component or a new variant or prop (Tier 2, open as draft PR). - Changing a public prop name, default or type (breaking change, major). - Adding a dependency, or upgrading @base-ui/react. - Touching .stylelintrc.json, .github/, AGENTS.md, CLAUDE.md, docs/GOVERNANCE.md or contrast.checks.json. ### Never - Never write hex, rgb(), named colors, raw box-shadow, px/rem lengths, ms/s durations, cubic-bezier() or raw z-index values in component CSS. - Never disable or loosen a lint rule or a contrast pair, add stylelint-disable, or use !important. - Never delete or skip tests or stories to make a check pass. - Never edit packages/tokens/dist/: it is generated. - Never treat a Figma file as the source of truth or copy values from it into code. Figma explores; tokens decide. - Never merge, push to main, or publish. Humans merge. ## Semantic tokens Use these in components for color, elevation and focus. Format: CSS variable | light | dark. ### color.bg Page backgrounds. default for the main canvas, subtle for side panels and zones that sit behind content. - `var(--color-bg-default)` | palette.neutral.50 (#f4f7fa) | palette.neutral.950 (#0d1826) | Page canvas behind panels. - `var(--color-bg-subtle)` | palette.neutral.100 (#edeff2) | palette.neutral.900 (#1c2735) | Recessed areas: table headers, code blocks, footers. - `var(--color-bg-muted)` | palette.neutral.200 (#dce3eb) | palette.neutral.800 (#333e4c) | Tracks, skeletons, neutral fills. ### color.surface Anything that sits on the page: cards, inputs, menus. raised is for surfaces that also carry elevation. - `var(--color-surface-default)` | palette.neutral.0 (#ffffff) | palette.neutral.900 (#1c2735) | Panels, cards, inputs. - `var(--color-surface-raised)` | palette.neutral.0 (#ffffff) | palette.neutral.800 (#333e4c) | Floating layers: popovers, menus, dialogs, toasts. - `var(--color-surface-hover)` | palette.neutral.100 (#edeff2) | palette.neutral.800 (#333e4c) | Hover fill for rows, menu items, ghost controls. - `var(--color-surface-hover-raised)` | palette.neutral.100 (#edeff2) | palette.neutral.700 (#4b5562) | Hover fill inside a raised surface. - `var(--color-surface-pressed)` | palette.neutral.200 (#dce3eb) | palette.neutral.700 (#4b5562) | Pressed fill. - `var(--color-surface-selected)` | palette.accent.50 (#fff4f1) | palette.accent.950 (#320400) | Selected rows and items. Paired with a checked control or a mark, never alone. - `var(--color-surface-inverse)` | palette.neutral.900 (#1c2735) | palette.neutral.100 (#edeff2) | Tooltips and inverse chips. ### color.text Text and icons. default for body copy, muted for secondary info, disabled only for disabled controls, inverse on dark or brand fills, on-solid on feedback solid fills. - `var(--color-text-default)` | palette.neutral.900 (#1c2735) | palette.neutral.50 (#f4f7fa) - `var(--color-text-muted)` | palette.neutral.600 (#636d79) | palette.neutral.400 (#a2aab5) | Secondary text. 4.5:1 on every background. - `var(--color-text-disabled)` | palette.neutral.400 (#a2aab5) | palette.neutral.600 (#636d79) | Disabled text. Exempt from contrast minimums; never the only cue. - `var(--color-text-inverse)` | palette.neutral.0 (#ffffff) | palette.neutral.950 (#0d1826) | Text on surface.inverse. - `var(--color-text-on-solid)` | palette.neutral.0 (#ffffff) | palette.neutral.0 (#ffffff) | Text and icons on feedback and danger solids. - `var(--color-text-link)` | palette.link.600 (#22749e) | palette.link.300 (#a7d5f1) - `var(--color-text-link-hover)` | palette.link.700 (#0a5b80) | palette.link.200 (#cae7f9) - `var(--color-text-signal)` | palette.accent.700 (#962600) | palette.accent.300 (#fdbcaa) | Signal-colored text (match highlights, current step). ### color.border Dividers and outlines. default for quiet separators, strong for control edges that must be found. - `var(--color-border-default)` | palette.neutral.200 (#dce3eb) | palette.neutral.800 (#333e4c) | Hairlines: card edges, dividers. Decorative, no contrast minimum. - `var(--color-border-subtle)` | palette.neutral.100 (#edeff2) | palette.neutral.900 (#1c2735) | Row separators inside tables and lists. - `var(--color-border-strong)` | palette.neutral.500 (#818a96) | palette.neutral.500 (#818a96) | Edges that identify a control (inputs, checkboxes, switches). 3:1 against surfaces (WCAG 1.4.11). - `var(--color-border-hover)` | palette.neutral.600 (#636d79) | palette.neutral.400 (#a2aab5) | Control edge on hover. ### color.action Interactive controls only. Never use action colors for decoration, or users will try to click it. - `var(--color-action-primary-bg)` | palette.primary.900 (#1c2735) | palette.primary.50 (#f4f7fa) | Primary action fill (steel ink in the bridge brand). - `var(--color-action-primary-bg-hover)` | palette.primary.800 (#333e4c) | palette.primary.200 (#dce3eb) - `var(--color-action-primary-bg-active)` | palette.primary.950 (#0d1826) | palette.primary.300 (#c7ced8) - `var(--color-action-primary-text)` | palette.neutral.0 (#ffffff) | palette.primary.950 (#0d1826) - `var(--color-action-secondary-bg)` | palette.neutral.0 (#ffffff) | palette.neutral.900 (#1c2735) - `var(--color-action-secondary-bg-hover)` | palette.neutral.100 (#edeff2) | palette.neutral.800 (#333e4c) - `var(--color-action-secondary-bg-active)` | palette.neutral.200 (#dce3eb) | palette.neutral.700 (#4b5562) - `var(--color-action-secondary-text)` | palette.neutral.900 (#1c2735) | palette.neutral.50 (#f4f7fa) - `var(--color-action-secondary-border)` | palette.neutral.300 (#c7ced8) | palette.neutral.600 (#636d79) - `var(--color-action-ghost-bg-hover)` | palette.neutral.100 (#edeff2) | palette.neutral.800 (#333e4c) - `var(--color-action-ghost-bg-active)` | palette.neutral.200 (#dce3eb) | palette.neutral.700 (#4b5562) - `var(--color-action-ghost-text)` | palette.neutral.800 (#333e4c) | palette.neutral.100 (#edeff2) - `var(--color-action-danger-bg)` | palette.danger.600 (#c3184c) | palette.danger.600 (#c3184c) - `var(--color-action-danger-bg-hover)` | palette.danger.700 (#9d0639) | palette.danger.700 (#9d0639) - `var(--color-action-danger-bg-active)` | palette.danger.800 (#760028) | palette.danger.800 (#760028) - `var(--color-action-danger-text)` | palette.neutral.0 (#ffffff) | palette.neutral.0 (#ffffff) - `var(--color-action-disabled-bg)` | palette.neutral.100 (#edeff2) | palette.neutral.800 (#333e4c) - `var(--color-action-disabled-text)` | palette.neutral.400 (#a2aab5) | palette.neutral.600 (#636d79) ### color.control - `var(--color-control-bg)` | palette.neutral.0 (#ffffff) | palette.neutral.900 (#1c2735) | Unchecked checkbox, radio, input fill. - `var(--color-control-checked-bg)` | palette.primary.900 (#1c2735) | palette.primary.50 (#f4f7fa) | Checked checkbox, radio, switch track. Selection is ink. - `var(--color-control-checked-bg-hover)` | palette.primary.800 (#333e4c) | palette.primary.200 (#dce3eb) - `var(--color-control-checked-fg)` | palette.neutral.0 (#ffffff) | palette.primary.950 (#0d1826) | Check mark, radio dot, switch thumb when checked. - `var(--color-control-track)` | palette.neutral.500 (#818a96) | palette.neutral.500 (#818a96) | Unchecked switch track and slider rail edge. 3:1 against surfaces. - `var(--color-control-thumb)` | palette.neutral.0 (#ffffff) | palette.neutral.50 (#f4f7fa) ### color.signal - `var(--color-signal-default)` | palette.accent.500 (#e64611) | palette.accent.400 (#fb8160) | The signal: active indicators, progress, slider range. - `var(--color-signal-subtle)` | palette.accent.100 (#ffeae4) | palette.accent.900 (#4a0e00) | Soft signal fill: highlighted search matches. ### color.overlay - `var(--color-overlay-scrim)` | color.alpha.steel-48 (rgb(13 24 37 / 0.48)) | color.alpha.black-64 (rgb(0 0 0 / 0.64)) | Backdrop behind dialogs and drawers. ### color.feedback Status and system messages. bg + text + border for inline messages, solid for compact labels like badges. Always pair with words. - `var(--color-feedback-success-bg)` | palette.success.50 (#eefaf6) | palette.success.950 (#001d16) - `var(--color-feedback-success-border)` | palette.success.200 (#c4ece1) | palette.success.800 (#06483c) - `var(--color-feedback-success-text)` | palette.success.700 (#036252) | palette.success.300 (#9eddcc) - `var(--color-feedback-success-icon)` | palette.success.600 (#007e6a) | palette.success.400 (#62bda7) - `var(--color-feedback-success-solid)` | palette.success.600 (#007e6a) | palette.success.600 (#007e6a) - `var(--color-feedback-warning-bg)` | palette.warning.50 (#fef6ea) | palette.warning.950 (#221400) - `var(--color-feedback-warning-border)` | palette.warning.200 (#f8deb6) | palette.warning.800 (#533700) - `var(--color-feedback-warning-text)` | palette.warning.700 (#6f4c03) | palette.warning.300 (#fad091) - `var(--color-feedback-warning-icon)` | palette.warning.600 (#8e6201) | palette.warning.400 (#e9b154) - `var(--color-feedback-warning-solid)` | palette.warning.600 (#8e6201) | palette.warning.600 (#8e6201) - `var(--color-feedback-danger-bg)` | palette.danger.50 (#fff4f4) | palette.danger.950 (#34000c) - `var(--color-feedback-danger-border)` | palette.danger.200 (#ffd6da) | palette.danger.800 (#760028) - `var(--color-feedback-danger-text)` | palette.danger.700 (#9d0639) | palette.danger.300 (#ffb8bf) - `var(--color-feedback-danger-icon)` | palette.danger.600 (#c3184c) | palette.danger.400 (#fd798e) - `var(--color-feedback-danger-solid)` | palette.danger.600 (#c3184c) | palette.danger.600 (#c3184c) - `var(--color-feedback-info-bg)` | palette.info.50 (#f0f8fe) | palette.info.950 (#001a29) - `var(--color-feedback-info-border)` | palette.info.200 (#cae7f9) | palette.info.800 (#03425f) - `var(--color-feedback-info-text)` | palette.info.700 (#0a5b80) | palette.info.300 (#a7d5f1) - `var(--color-feedback-info-icon)` | palette.info.600 (#22749e) | palette.info.400 (#73b2d8) - `var(--color-feedback-info-solid)` | palette.info.600 (#22749e) | palette.info.600 (#22749e) ### color.selection - `var(--color-selection-bg)` | palette.accent.100 (#ffeae4) | palette.accent.800 (#6e1c02) | Text selection highlight (::selection). ### focus Keyboard focus ring. Shared by every focusable component so focus looks the same everywhere. - `var(--focus-ring-color)` | palette.accent.500 (#e64611) | palette.accent.400 (#fb8160) | Keyboard focus ring. 3:1 against every surface. - `var(--focus-ring-width)` | border.width.2 (2px) | border.width.2 (2px) - `var(--focus-ring-offset)` | border.width.2 (2px) | border.width.2 (2px) ### elevation Shadows that lift surfaces. raised for cards, overlay for menus and dialogs. Stronger in dark mode so edges stay visible. - `var(--elevation-raised)` | shadow.1 (0px 1px 2px 0px rgb(13 24 37 / 0.06), 0px 2px 6px -1px rgb(13 24 37 / 0.08)) | shadow.dark-1 (0px 1px 2px 0px rgb(0 0 0 / 0.32), 0px 2px 6px -1px rgb(0 0 0 / 0.24)) | Interactive cards on hover, sticky headers. - `var(--elevation-overlay)` | shadow.2 (0px 2px 4px -1px rgb(13 24 37 / 0.08), 0px 12px 24px -6px rgb(13 24 37 / 0.14)) | shadow.dark-2 (0px 2px 4px -1px rgb(0 0 0 / 0.4), 0px 12px 24px -6px rgb(0 0 0 / 0.44)) | Popovers, menus, listboxes, tooltips. - `var(--elevation-modal)` | shadow.3 (0px 4px 8px -2px rgb(13 24 37 / 0.1), 0px 24px 48px -12px rgb(13 24 37 / 0.22)) | shadow.dark-3 (0px 4px 8px -2px rgb(0 0 0 / 0.48), 0px 24px 48px -12px rgb(0 0 0 / 0.56)) | Dialogs, drawers, toasts. ### motion - `var(--motion-duration-instant)` | duration.75 (75ms) | duration.75 (75ms) | Hover and press color changes. - `var(--motion-duration-fast)` | duration.150 (150ms) | duration.150 (150ms) | Tooltips, menus, popovers, checkbox ticks. - `var(--motion-duration-moderate)` | duration.200 (200ms) | duration.200 (200ms) | Dialogs, accordions, tab indicator. - `var(--motion-duration-slow)` | duration.300 (300ms) | duration.300 (300ms) | Drawers, toasts entering. - `var(--motion-duration-spin)` | duration.700 (700ms) | duration.700 (700ms) | One spinner rotation. - `var(--motion-duration-shimmer)` | duration.1400 (1400ms) | duration.1400 (1400ms) | One skeleton shimmer pass. - `var(--motion-easing-standard)` | easing.standard (cubic-bezier(0.2, 0, 0, 1)) | easing.standard (cubic-bezier(0.2, 0, 0, 1)) - `var(--motion-easing-enter)` | easing.expo-out (cubic-bezier(0.16, 1, 0.3, 1)) | easing.expo-out (cubic-bezier(0.16, 1, 0.3, 1)) - `var(--motion-easing-exit)` | easing.accel (cubic-bezier(0.4, 0, 1, 1)) | easing.accel (cubic-bezier(0.4, 0, 1, 1)) - `var(--motion-easing-linear)` | easing.linear (cubic-bezier(0, 0, 1, 1)) | easing.linear (cubic-bezier(0, 0, 1, 1)) ### layer - `var(--layer-sticky)` | z.sticky (100) | z.sticky (100) - `var(--layer-dropdown)` | z.dropdown (1000) | z.dropdown (1000) - `var(--layer-overlay)` | z.overlay (1100) | z.overlay (1100) - `var(--layer-modal)` | z.modal (1200) | z.modal (1200) - `var(--layer-popover)` | z.popover (1300) | z.popover (1300) - `var(--layer-toast)` | z.toast (1400) | z.toast (1400) - `var(--layer-tooltip)` | z.tooltip (1500) | z.tooltip (1500) ### icon - `var(--icon-size-xs)` | size.3 (12px) | size.3 (12px) - `var(--icon-size-sm)` | size.4 (16px) | size.4 (16px) - `var(--icon-size-md)` | size.5 (20px) | size.5 (20px) - `var(--icon-size-lg)` | size.6 (24px) | size.6 (24px) - `var(--icon-stroke)` | 1.75 | 1.75 | Lucide stroke width at every size, matched to Plex text weight. ### border-width - `var(--border-width-default)` | border.width.1 (1px) | border.width.1 (1px) - `var(--border-width-strong)` | border.width.2 (2px) | border.width.2 (2px) - `var(--border-width-indicator)` | border.width.2 (2px) | border.width.2 (2px) ## Scales (primitives, same in both themes) - Spacing: `--space-0` 0px, `--space-1` 4px, `--space-2` 8px, `--space-3` 12px, `--space-4` 16px, `--space-5` 20px, `--space-6` 24px, `--space-7` 28px, `--space-8` 32px, `--space-10` 40px, `--space-12` 48px, `--space-16` 64px, `--space-20` 80px, `--space-24` 96px, `--space-0-5` 2px, `--space-1-5` 6px - Radius: `--radius-none` 0px, `--radius-xs` 2px, `--radius-sm` 4px, `--radius-md` 6px, `--radius-lg` 10px, `--radius-xl` 14px, `--radius-2xl` 16px, `--radius-3xl` 20px, `--radius-full` 9999px - Border width: `--border-width-1` 1px, `--border-width-2` 2px, `--border-width-3` 3px - Font size: `--font-size-100` 12px, `--font-size-200` 14px, `--font-size-300` 17px, `--font-size-400` 20px, `--font-size-500` 24px, `--font-size-600` 29px, `--font-size-700` 35px, `--font-size-800` 42px - Font weight: `--font-weight-regular` 400, `--font-weight-medium` 500, `--font-weight-semibold` 600, `--font-weight-bold` 700 - Line height: `--font-line-height-none` 1, `--font-line-height-tight` 1.2, `--font-line-height-snug` 1.35, `--font-line-height-normal` 1.5, `--font-line-height-relaxed` 1.65 If a value is not in a scale, do not invent one: propose a token in the PR. ## Contrast Pairs are listed in packages/tokens/src/contrast.checks.json and checked against WCAG AA (4.5:1 text, 3:1 UI). All listed pairs pass AA in both themes. ## Components ### Icon Import: `import { Icon } from '@bridge-ds/react'` Props: - icon: LucideIcon. A Lucide icon component, for example `import { GitBranch } from 'lucide-react'`. - size?: 'xs' | 'sm' | 'md' | 'lg' = 'sm'. Token size: xs 12, sm 16, md 20, lg 24. Default `sm`, the size used inside controls. - label?: string. Accessible name. Omit for decorative icons next to text (they are hidden from assistive tech). Tokens used: --icon-size-lg, --icon-size-md, --icon-size-sm, --icon-size-xs, --icon-stroke ### VisuallyHidden Import: `import { VisuallyHidden } from '@bridge-ds/react'` Accepts all native attributes. Tokens used: --border-width-1 ### Button Triggers an action on the current screen: save, publish, approve, open a dialog. If it takes the user somewhere else, it is a Link, not a Button. Import: `import { Button } from '@bridge-ds/react'` Accepts all native