diff --git a/docs/design-system.md b/docs/design-system.md index ce134f0a..2421d658 100644 --- a/docs/design-system.md +++ b/docs/design-system.md @@ -32,66 +32,33 @@ Native window decorations and pre-WebView launch color are **not** controlled by CSS attribute. An attended packaged-app check is still needed before claiming native chrome/relaunch parity; no broad Tauri capability or CSP expansion was added here. -## Tokens are the shared contract - -`src/shared/styles/tokens.css` is the only built-in color palette. Both modes define -every paint role. `globals.css` exposes the roles through Tailwind 4, base elements, -and small shared component classes. CSS modules and Tailwind use the same values. - -| Roles | Use | -| --- | --- | -| `--workspace`, `--shell-image`, `--shell-dot` | App canvas and decorative shell art | -| `--surface`, `--surface-elevated` | Card and dialog/popover surfaces | -| `--surface-accent`, `--surface-control`, `--surface-input`, `--surface-hover` | Subtle, control, input and hover fills | -| `--text`, `--text-muted` | Primary and secondary text; do not lower text opacity to simulate muted text | -| `--border`, `--border-input`, `--focus` | Decorative separators, visible input boundaries, keyboard focus | -| `--primary` / `--on-primary`, `--action` / `--on-action` | Primary controls and the lavender composer action | -| `--selected` / `--on-selected` | Selected controls; also expose selection semantically | -| `--link`, `--danger`, `--warning`, `--success` | Meaningful foregrounds; pair status fills intentionally and include readable copy | -| `--overlay`, `--elevation-*` | Backdrops and card/popover/dialog/dock shadows | -| `--radius-card`, `--radius-card-compact`, `--radius-control` | Shared curvature; Tailwind `rounded-3xl` / `rounded-xl` map to card/control | - -Existing `ink`, `muted`, `line`, `soft`, `shell` utilities remain compatible. New -`surface`, `elevated`, `primary`, `on-primary`, `input-line`, `focus`, `overlay` and -status utilities avoid literal palette colors. **Legacy `--accent` is a foreground**; -do not reinterpret it as shadcn's accent background. If adding shadcn components, -map their paired roles explicitly. No shadcn/Radix dependency was needed for this -slice: native radios, buttons, fields and the existing dialogs supply the behavior. - -Typography uses the shared Inter/system sans stack with Tailwind's existing type -scale: `text-sm` controls, `text-base` body/labels, `text-lg` section headings and -`text-3xl` page headings. Existing conversation type sizes remain unchanged at 100%. Spacing -uses Tailwind's 4px rhythm; preserve established responsive card gutters. Avoid -creating new scales for the same values. Motion is optional and respects reduced -motion; theme changes must not fade through the old mode's foreground/background. -Existing shell/panel layering stays local to its owner; native modal dialogs use -the browser top layer, not ever-increasing global z-index values. - -## Shared UI rules - -- `.ui-card` is the shared surface recipe; `.ui-choice` is a labeled native-radio - selection with hover, selected and focus-within states. The existing `.notice`, - `.error`, `.danger`, `.actions` and shell classes use the same palette. -- Defaults/hover/focus/disabled come from base rules. Domain-specific components own - pressed/busy/error behavior. Disabled controls must not act; busy guards are not - replaced by CSS. A selected style must agree with ARIA (`aria-current="page"` - requires `aria-[current=page]:`, not Tailwind's boolean `aria-current:` variant). -- Shared React components should be extracted for actual repeated behavior, not - empty wrappers around every native element. Do not migrate all dialogs merely to - add a component-library badge. Use an accessible headless primitive when the next - complex interaction warrants one, and test keyboard/focus behavior in context. -- Do not invert images. Media/brand art retains its colors. All host-owned surfaces, - including loading/recovery, must inherit the mode. CSS variables inherit into - portals and shadow hosts; third-party Shadow DOM/canvas widgets may additionally - require an explicit mode adapter. Theme mode changes must not reset widget input. -- `/tests/fixtures/design-system.html` now hosts the design system this app is - moving to, with its own components, tokens and documentation. It uses no relay - or identity services. The earlier offline diagnostic at that URL — one - Appearance section, some native controls, profile fields and a conversation - row — was replaced by it. The rules above still govern the styling that ships - today; verify those in the running app and its browser journeys. Surfaces move - onto the new system incrementally, and its documentation is the reference for - anything already on it. +## Tokens and shared controls + +`src/shared/design-system/styles/tokens.css` owns the public palette and semantic +roles. `src/shared/styles/tokens.css` is a compatibility bridge for older callers, +not a second palette. The app imports one Tailwind reset and keeps its existing +appearance service, storage and startup ownership. + +| Group | Examples | Purpose | +| --- | --- | --- | +| Surface | `--surface-base`, `--surface-panel`, `--surface-popover` | Page, card, popup | +| Text | `--text-standard`, `--text-subtle`, `--text-inverse`, `--text-danger` | Meaning and emphasis | +| Border | `--border-standard`, `--border-prominent`, `--border-focus` | Edges and keyboard focus | +| Affordance | `--affordance-prominent`, `--affordance-subtle`, `--affordance-danger` | Controls and actions | + +Shared components consume these roles; features consume shared components. +Base UI owns focus, keyboard interaction, selection, portals and dismissal. +Buzz owns visual styles and product behavior. Build missing shared components +from Base UI rather than copying private components or wrapping another library. + +Use complete type roles with Inter and JetBrains Mono. Do not import proprietary +fonts, private packages or internal business examples. Default, hover, pressed, +focus, selected, disabled and loading states are shared component decisions. +Loading must prevent repeated actions while preserving the label footprint. + +The standalone design viewer imports the real shared controls without app startup, +identity or relay services. Check the actual app as well as specimens, in both +themes and at narrow, intermediate and wide widths with enlarged text. ## Future theme contributions (design boundary, not implemented API) diff --git a/scripts/design-system/check-color.mjs b/scripts/design-system/check-color.mjs index cb483fd2..45577d2b 100644 --- a/scripts/design-system/check-color.mjs +++ b/scripts/design-system/check-color.mjs @@ -316,66 +316,27 @@ function auditLayers() { } } - // 2. THE ROLES THAT REMAIN MUST BE MODE-ASYMMETRIC. - // - // This replaces four checks that audited the identity families — that each - // referenced a palette step, that none was missing, that no two shared a step, - // that dark never restated one. All four are gone because their subject is: - // nineteen roles were deleted once palette steps became reachable as classes, - // and the ones left are the four surfaces plus emphasis. - // - // The invariant now worth enforcing is the TEST FOR WHETHER A ROLE IS EARNED. - // A surface role exists precisely because light and dark take *different* ramp - // steps, so no single class can express it. If someone adds a surface role - // whose two modes agree, the name is doing nothing and a class would say it — - // that is the mistake this catches, in the same shape it already happened. - // - // Two reasons earn a name whose modes agree, per DESIGN.md § When a name is - // earned. Each entry states which one, so the list reads as decisions rather - // than as accumulated exceptions. - // - // • the name enforces a rule a ramp cannot state — there are three levels - // of text and one border weight; - // • a pattern repeated across screens has been named for the pattern. - // - // What this still catches is the mistake it was written for: a role invented - // by symmetry, restating one step, that no design asked for. - const NAME_IS_EARNED = new Map([ - [ - "--border-control", - "An input boundary must clear 3:1 against its surface, unlike a decorative divider.", - ], - ["--text-primary", "Three text levels, enforced by name."], - ["--text-secondary", "Three text levels, enforced by name."], - ["--text-tertiary", "Three text levels, enforced by name."], - ["--text-disabled", "Unavailability is a rule, not a fourth level."], - ["--border-primary", "One shared border weight, enforced by name."], - ["--text-on-accent", "Paired text follows its fill, not the mode."], - ]); - - const roleNames = [ - ...new Set( - [...modes.light.matchAll(/^\s*(--(?:bg|text|border)-[a-z0-9-]+):/gm)].map( - (m) => m[1], - ), + // Semantic names describe purpose even when both themes choose the same step. + // Require every new semantic role to declare both modes and reference a token. + const roles = [ + ...modes.light.matchAll( + /^\s*(--(?:surface|affordance|text|border)-[a-z0-9-]+):/gm, ), ]; - - for (const name of roleNames) { - if (NAME_IS_EARNED.has(name)) continue; - // Glass materials are a bundled treatment, not a surface step, and `bg-app` - // swaps a whole gradient rather than a step. - if (name.includes("glass") || name === "--bg-app") continue; - - const light = read(modes.light, name); - const dark = read(modes.dark, name); - if (dark && dark !== light) continue; // earns its name - - failures.push({ - at: rel, - found: `${name}: ${light}`, - why: `Same value in both modes. Write ${light?.replace(/var\(--(.+)\)/, "$1") ?? "the step"} where it is used — unless this name is earned, in which case add it to NAME_IS_EARNED with its reason: a rule a ramp cannot state, or a pattern repeated across screens that is now named for the pattern.`, - }); + for (const [, name] of roles) { + for (const [mode, code] of Object.entries(modes)) { + if (name === "--text-on-accent") continue; // Fixed white paired with the accent fill; measured by check-contrast. + const value = + read(code, name) ?? + (mode === "dark" ? read(modes.light, name) : undefined); + if (!value || !/^var\(--[a-z0-9-]+\)$/.test(value)) { + failures.push({ + at: rel, + found: `${name} (${mode}): ${value ?? "missing"}`, + why: "Semantic roles must reference a shared token in both modes.", + }); + } + } } } diff --git a/scripts/design-system/check-contrast.mjs b/scripts/design-system/check-contrast.mjs index abf3ff18..c4ef893f 100644 --- a/scripts/design-system/check-contrast.mjs +++ b/scripts/design-system/check-contrast.mjs @@ -48,7 +48,7 @@ const EXCEPTIONS = new Map([ ]); /** Roles measured at the meta target rather than the body target. */ -const META_ROLES = new Set(["--text-tertiary"]); +const META_ROLES = new Set(["--text-tertiary", "--text-metadata"]); /** * Neutral surfaces any text may sit on. @@ -57,7 +57,15 @@ const META_ROLES = new Set(["--text-tertiary"]); * its step here. It still belongs in this list: a row under the cursor is a * surface text sits on, whatever it is called. */ -const SURFACES = ["--bg-panel", "--bg-float", "--neutral-2", "--neutral-4"]; +const SURFACES = [ + "--surface-base", + "--surface-panel", + "--surface-popover", + "--surface-inset", + "--affordance-subtle", + "--affordance-selected", + "--neutral-4", +]; /** * Text that must be readable on every neutral surface. @@ -74,6 +82,13 @@ const SURFACES = ["--bg-panel", "--bg-float", "--neutral-2", "--neutral-4"]; * does — and it is the step that fails, at 59.7 on a dark panel. */ const TEXT_ROLES = [ + "--text-standard", + "--text-subtle", + "--text-metadata", + "--text-danger", + "--text-warning", + "--text-success", + "--text-accent", "--text-primary", "--text-secondary", "--text-tertiary", @@ -94,6 +109,18 @@ const TEXT_ROLES = [ * every fill it can actually sit on, and hover is one of them. */ const PAIRS = [ + ...["subtle", "subtle-hover", "subtle-pressed"].map((state) => [ + "--text-standard", + `--affordance-${state}`, + ]), + ...["prominent", "prominent-hover", "prominent-pressed"].map((state) => [ + "--text-inverse", + `--affordance-${state}`, + ]), + ...["danger", "danger-hover", "danger-pressed"].map((state) => [ + "--text-danger", + `--affordance-${state}`, + ]), ["--text-on-accent", "--purple-9"], ["--text-on-accent", "--purple-10"], // `bg-neutral-11` with `text-neutral-1` — the inverse pair, written as steps diff --git a/src/shared/design-system/AGENTS.md b/src/shared/design-system/AGENTS.md index 1d6fec34..c35d8627 100644 --- a/src/shared/design-system/AGENTS.md +++ b/src/shared/design-system/AGENTS.md @@ -3,7 +3,8 @@ This is the app's design system. New UI and surfaces moving off the existing styles should use it. This initial port does not migrate existing surfaces; that is a boundary of the PR, not a prohibition on adoption. Read DESIGN.md and MAINTAINING_DESIGN_SYSTEM.md before editing. -Use authored ramps and named type roles; keep Base UI behavior and Tabler icons. +Use semantic color roles and complete type roles; keep Base UI behavior and Tabler icons. +Block UI is the visual target. Palette steps belong in shared token definitions, not new component recipes. Preserve keyboard-only focus and test light/dark and narrow/intermediate/wide views. Components live in ui/, values in styles/, documentation metadata in tokens/ and ui/registry.ts. The standalone viewer lives in tests/fixtures/design-system and imports the real shared components. diff --git a/src/shared/design-system/DESIGN.md b/src/shared/design-system/DESIGN.md index 292dd476..b0d82d3d 100644 --- a/src/shared/design-system/DESIGN.md +++ b/src/shared/design-system/DESIGN.md @@ -1,5 +1,27 @@ # DESIGN.md +## Direction + +Buzz adopts Block UI's visual language and semantic color grammar. Base UI +remains the behavior layer; Buzz owns styles, composition and product semantics. +Use Inter and JetBrains Mono, public dependencies and generic examples. Do not +copy private packages, proprietary fonts, internal product data or private source. + +## Semantic colors + +Use `color / purpose / emphasis / state`: surface, text, border and affordance. +An affordance is a control or action color. CSS spells these `--surface-panel`, +`--text-standard`, `--border-prominent` and `--affordance-subtle-hover`. +Tailwind utilities include `bg-surface-panel`, `text-standard`, +`border-prominent` and `bg-affordance-subtle-hover`. Text and border registrations +stay in separate namespaces so they cannot accidentally share a value. + +Components choose roles, not palette steps. The palette is an implementation +detail, even when a role uses the same step in both themes. Add a role only for +an actual use, document it in the registry and measure its intended pairings. +Legacy utilities and host aliases remain while their callers migrate. Do not +add new uses. Whole materials such as glass still travel as one shared recipe. + ## Foundations The interface uses shared color, type, spacing and shape roles. Primary actions @@ -40,7 +62,7 @@ Buzz is a place where people build together and bring their agents into the room - **Panels sit on the backdrop; the backdrop is a gradient.** Everything else is a panel in a different place. The navigation column is not a special kind of surface. - **A region is separated by a soft fill, not by an outline.** Reach for `bg-inset` before reaching for a border. A bordered box announces its own edges; a filled one lets the content sit in a place. Grouping is the common case, so the quiet treatment is the default one. -- **A border is for a genuine boundary, and there is one weight: `border-primary`.** It is `neutral-4` in light and `neutral-6` in dark. A hairline needs more separation on a dark surface than the same step number supplies: `neutral-4` measured 1.08:1 on a dark panel and 1.09:1 on the floating composer — drawn, and effectively invisible. `neutral-5` looked like the obvious one-step move but is `bg-float` in dark, so a border there would be 1.00:1: the same colour as the surface it is meant to bound. `neutral-6` clears the actual surfaces at 1.35–1.43 on panel/inset and 1.14 on float. **That mode difference is the reason this is a role rather than `border-neutral-4`.** A second weight arrives only with the design that proves a different boundary needs it. Text and borders still hold different values — text at the dark end of the neutral ramp, borders at the light end — so if a divider looks like text, it is pointed at the wrong role. +- **Use border-standard for quiet separators, border-prominent for controls and border-focus for keyboard focus.** Error and warning boundaries have their own roles. Measure real surfaces in both themes. - **No page-wide gradient behind documentation or dense reading.** The gradient is the product's backdrop for chrome and panels. Behind a column of prose it fights the text and makes contrast position-dependent — such surfaces sit on `bg-panel`. - **Shadows stay at the threshold of perception.** If a shadow is obvious, it is too strong. The two elevation values are the whole vocabulary. - **Elevation is carried by shadow in light mode and by lightness in dark mode.** On a near-black background there is nothing darker for a shadow to cast, so a floating surface becomes a step lighter instead. Never reach for a stronger shadow to make something float in dark mode. @@ -55,7 +77,7 @@ Buzz is a place where people build together and bring their agents into the room ## State -- **The interface has three states, plus disabled where it matters: default, hover, selected.** There is no pressed state: pressed is too fleeting to read and makes an interface feel jumpy. +- **Design default, hover, pressed, focus, selected, disabled and loading states where they apply.** Pressed changes fill without moving the control. Loading keeps the label footprint and prevents repeated activation; CSS alone cannot enforce it. - **Hover means one step more contrast, in whichever direction that surface needs.** A light row darkens, a dark chip lightens. Direction lives in the value. - **Selected is a persistent statement, not a stronger hover.** It should be legible without a cursor present. - **A selected item in a toggle group is not interactive.** Clicking it does nothing, so it gets no hover. @@ -66,7 +88,7 @@ Buzz is a place where people build together and bring their agents into the room - **Three levels of text: normal, lesser, really lesser.** If a fourth seems necessary, the thing wants a different size, weight, or position instead of a fourth colour. - **Two text colours do most of the work.** Treat the third level as genuinely for metadata. -- **Borders use the same three levels, and they mean the same thing.** Learn the ramp once. +- **Borders describe their job:** a quiet edge, a control boundary or focus. - **Weight and size carry hierarchy before colour does.** Reaching for a louder colour to fix hierarchy usually means the size relationship is wrong. ## Type @@ -101,9 +123,8 @@ Values scale with the host text-size preference. - Preserve text preferences and browser zoom. Author values in scaled rem and keep layout geometry independent of text scaling. -Typography provenance: the ramp and role settings derive from the pinned -[Block UI typography specification](https://github.com/squareup/design-blockinterface/blob/eff766161ba8aaee3258ca107f0d904dd542c708/blockUI/docs/type.resolution.draft.json). -The values documented above define this system, including the 12px xsmall role. +Typography follows the adopted Block UI scale, using public fonts. The values +documented above define this system, including the 12px xsmall role. ## Both modes @@ -399,18 +420,11 @@ it is the rule a generated theme is measured against. ## Growing the system -Need something the system doesn't have? **Add it, mark it `proposed`, keep working.** There is no gate and no separate mechanism for one-offs — the moment the legal path is slower than writing a raw value, the system starts being bypassed. - -1. Search the component list, then the role list, by intent — not by colour. -2. If the decision is one ramp step in both modes, write that step directly. `bg-purple-3`, `text-red-12`, `border-purple-8`: the steps are public and mode-aware. -3. A state of an existing role — add the `-hover`, `-selected`, or `-disabled` sibling with both values, only if that state cannot be one step in both modes. -4. A material variant — add a named utility that carries its inseparable parts together. Glass is the example: its fill is deliberately not reachable alone, because fill without blur, rim, and lift is not glass. -5. A new role using existing words — only where one step cannot express both modes, or where the name enforces a rule. Add the name, both values, a one-sentence description, and an owner. -6. A new hue — generate its ramp. Never write a raw literal in a component; the palette is where literals live. -7. A new vocabulary word — allowed, but it is the thing the audit reports on its own line, so use an existing word if one fits. -8. **If none fit, stop and ask.** The answer is a proposed decision, not a raw value or an undocumented local exception. - -Every addition lands in `src/shared/design-system/tokens/registry.ts` in the same change that needed it. Promotion from `proposed` to `core` is a metadata change, not a rename. +1. Use an existing component before assembling its appearance yourself. +2. Choose a semantic role by purpose. Add a state sibling when the actual control needs it. +3. Keep each new role paired in both modes, document it in the token registry, and check contrast. +4. Fix shared decisions in their owner. Do not cancel shared styles from a feature stylesheet. +5. Keep layout, media geometry, editor semantics and data behavior with their product owner. ## Components @@ -426,12 +440,6 @@ Every addition lands in `src/shared/design-system/tokens/registry.ts` in the sam - **Use an existing component before creating one, and an existing role before adding one.** - **A new visual treatment that repeats belongs in the system, not in the feature.** - **If a shared role fails in a real context, repair the role — never work around it locally.** A documentation specimen frame needed a border but `border-primary` was neutral-4 in both modes, which measured 1.08:1 on the dark page. The wrong response was the one we made first: name `neutral-6` directly and call documentation furniture a special case. The right response was to ask whether the one shared boundary role was wrong, measure it on every surface it reaches, and make it `neutral-4` light / `neutral-6` dark. The frame then returned to `border-primary`, and every product divider improved with it. **A local exception is evidence the shared decision is incomplete, not a licence to bypass it.** -- **When choosing a colour, surface, or boundary, use this order.** - 1. **Is there already a component for the thing?** Use it. Its variants are the decisions already made. Do not assemble its fill, border, shadow, or states yourself. - 2. **If the component has no variant for its background, add a variant rather than a sibling component.** `Tabs` is `chrome` (glass pill on the gradient) or `panel` (underline on a plain surface): same behaviour, different appearance. A second component would duplicate its keyboard and accessibility contract just to change styling. - 3. **If building a surface directly, choose its job before its colour.** Backdrop → `bg-app`; opaque region on it → `bg-panel`; region pushed in → `bg-inset`; thing floating above → `bg-float`; something the backdrop should show through → `glass-primary` or `glass-secondary`. Do not use a border to do the work of an inset fill. - 4. **If drawing a boundary, use `border-primary`.** It is the one shared hairline, already authored for both modes. If it does not read in the actual context, measure that context and fix this role — do not name a neutral step at the call site. - 5. **If choosing an accent or status colour, choose a ramp step and measure it.** `bg-purple-9`, `bg-purple-3`, `text-red-12`, `border-purple-8`: every step responds to mode. Do not invent a semantic name for one choice; a repeated *pattern* earns a name when Morgan says it does. - 6. **If the choice cannot be expressed by one step in both modes, make a proposed role with both values and a one-sentence job.** If the name only restates one step, it has not earned a role. If it is a whole treatment — glass is the example — make a utility that carries every inseparable part together. - 7. **If none of this feels clearly right, stop and ask.** Choosing a raw literal or a local exception is never the escape hatch. The system is deliberately allowed to grow; uncertainty is evidence of a missing decision, not a prompt to hide one. +- **Choose the job first.** Page → surface-base; card → surface-panel; popup → surface-popover; recessed region → surface-inset. Controls use affordance roles; labels use text roles; edges use border roles. +- **A role is useful because it names a purpose.** It does not need different palette steps in each theme to earn its name. - **If a screen looks right but breaks these rules, the rules are probably wrong — say so.** This document is meant to be argued with, not worked around. diff --git a/src/shared/design-system/MAINTAINING_DESIGN_SYSTEM.md b/src/shared/design-system/MAINTAINING_DESIGN_SYSTEM.md index 22e4e704..23fd53b3 100644 --- a/src/shared/design-system/MAINTAINING_DESIGN_SYSTEM.md +++ b/src/shared/design-system/MAINTAINING_DESIGN_SYSTEM.md @@ -12,30 +12,16 @@ Build the screen or interaction you are trying to make. Use the existing colors, A repeated need is evidence. One-off work is evidence too: it may reveal that a ramp step is wrong, a component needs another supported state, or a rule no longer reflects the product. -## Color: use the ramps first +## Color: use semantic roles -Each color ramp has a version for light mode and a version for dark mode. A step has the same job in both: a quiet neutral surface, a selected row, a strong accent, or readable colored text. That lets a screen use a normal ramp step such as `neutral-2` and still work in both modes. +Choose a name by what the color does: surface, text, border or affordance. +The shared palette supplies values to those roles in light and dark mode. +Components should not choose a palette step directly, even when both modes use +the same step. A role lets us adjust that job without editing every caller. -A semantic color name is earned when a plain ramp step cannot tell the truth: - -- Light and dark need different steps for the same job. A panel, for example, needs a different relationship to its surroundings in each mode. -- A name protects a real rule. The three text levels and the single shared border weight are examples. -- A pattern has appeared enough times that giving it one name makes the product easier to understand and keep consistent. - -Do not create a semantic name just to avoid choosing a ramp step. If a name points to the same step in both modes, it is usually hiding a decision rather than recording one. - -## Refine the ramp when the product teaches us something - -The ramps are not sacred. They began as a useful starting point and should change when real product work gives us better evidence. - -The lighter search-field neutral is a good example. A real prototype showed that the old value was too heavy, so we corrected the neutral ramp instead of adding a search-only color or a fractional stop. That improvement now benefits every quiet compact surface that uses the same step. - -Before adding a new step or a new role, ask: - -1. Is an existing step already the right answer? -2. If not, is the ramp itself missing the right relationship? -3. Does light mode need one step and dark mode another? If so, give that relationship a semantic name. -4. Will people see this pattern again, or is it one moment in one screen? +Keep roles grounded in real controls. Add the required hover, pressed or disabled +state beside the base role and check its paired text. Update the registry and +viewer in the same change. Legacy names exist only to support staged migration. ## Components grow from real repetition @@ -51,7 +37,7 @@ Do not build a catalogue in advance. Small, proven components are more flexible ## Keep the important states visible -Every interactive piece should have a clear default, hover, selected state where it applies, and disabled state where it matters. Selection is a lasting statement, not just a stronger hover. Keyboard focus should be visible, and the same action should work with a pointer or keyboard. +Every interactive piece should have a clear default, hover, pressed, selected state where it applies, and disabled state where it matters. Selection is a lasting statement, not just a stronger hover. Keyboard focus should be visible, and the same action should work with a pointer or keyboard. Check narrow, medium, and wide layouts. Check light and dark mode together. A decision that works only in a component specimen is not finished. diff --git a/src/shared/design-system/styles/tokens.css b/src/shared/design-system/styles/tokens.css index 1960588e..ad971f0c 100644 --- a/src/shared/design-system/styles/tokens.css +++ b/src/shared/design-system/styles/tokens.css @@ -301,6 +301,7 @@ --neutral-9: #666666; /* text-tertiary */ --neutral-10: #525252; /* text-secondary */ --neutral-11: #191919; /* inverse fill, paired with neutral-1 */ + --neutral-action-pressed: #525252; --neutral-12: #000000; /* text-primary */ /* --- Glass. A translucency ramp of fills. ---------------- */ @@ -396,6 +397,7 @@ --neutral-9: #808080; --neutral-10: #a4a4a4; --neutral-11: #c7c7c7; + --neutral-action-pressed: #b8b8b8; --neutral-12: #ffffff; /* Every coloured family is absent here on purpose. A family points at a @@ -478,6 +480,87 @@ --gradient-4: var(--gradient-ultraviolet); } +/* Semantic colors: color / purpose / emphasis / state. + Components consume roles; palette steps stay inside this shared layer. */ +:root { + --surface-base: var(--neutral-2); + --surface-panel: var(--neutral-1); + --surface-popover: var(--neutral-1); + --surface-inset: var(--neutral-2); + --text-standard: var(--neutral-12); + --text-subtle: var(--neutral-10); + --text-metadata: var(--neutral-9); + --text-inverse: var(--neutral-1); + --text-unavailable: var(--neutral-7); + --text-danger: var(--red-12); + --text-warning: var(--amber-12); + --text-success: var(--green-12); + --text-accent: var(--purple-12); + --border-standard: var(--neutral-3); + --border-prominent: var(--neutral-8); + --border-focus: var(--neutral-12); + --border-danger: var(--red-8); + --border-warning: var(--amber-8); + --affordance-prominent: var(--neutral-12); + --affordance-prominent-hover: var(--neutral-11); + --affordance-prominent-pressed: var(--neutral-action-pressed); + --affordance-subtle: var(--neutral-2); + --affordance-subtle-hover: var(--neutral-3); + --affordance-subtle-pressed: var(--neutral-4); + --affordance-selected: var(--neutral-3); + --affordance-disabled: var(--neutral-3); + --affordance-danger: var(--red-3); + --affordance-danger-hover: var(--red-4); + --affordance-danger-pressed: var(--red-5); + --affordance-warning: var(--amber-3); + --affordance-success: var(--green-3); + --affordance-accent: var(--purple-3); + --affordance-accent-hover: var(--purple-4); + --affordance-accent-prominent: var(--purple-9); + --affordance-accent-prominent-hover: var(--purple-10); + --border-accent: var(--purple-8); +} + +:root[data-color-mode="dark"], +.dark { + --surface-base: var(--neutral-1); + --surface-panel: var(--neutral-3); + --surface-popover: var(--neutral-5); + --surface-inset: var(--neutral-2); + --text-standard: var(--neutral-12); + --text-subtle: var(--neutral-11); + --text-metadata: var(--neutral-10); + --text-inverse: var(--neutral-1); + --text-unavailable: var(--neutral-7); + --text-danger: var(--red-12); + --text-warning: var(--amber-12); + --text-success: var(--green-12); + --text-accent: var(--purple-12); + --border-standard: var(--neutral-4); + --border-prominent: var(--neutral-8); + --border-focus: var(--neutral-12); + --border-danger: var(--red-8); + --border-warning: var(--amber-8); + --affordance-prominent: var(--neutral-12); + --affordance-prominent-hover: var(--neutral-11); + --affordance-prominent-pressed: var(--neutral-action-pressed); + --affordance-subtle: var(--neutral-5); + --affordance-subtle-hover: var(--neutral-6); + --affordance-subtle-pressed: var(--neutral-7); + --affordance-selected: var(--neutral-5); + --affordance-disabled: var(--neutral-3); + --affordance-danger: var(--red-3); + --affordance-danger-hover: var(--red-4); + --affordance-danger-pressed: var(--red-5); + --affordance-warning: var(--amber-3); + --affordance-success: var(--green-3); + --affordance-accent: var(--purple-3); + --affordance-accent-hover: var(--purple-4); + --affordance-accent-prominent: var(--purple-9); + --affordance-accent-prominent-hover: var(--purple-10); + --border-accent: var(--purple-8); +} + /* ============================================================ LAYER 2 — PUBLIC ROLES The only layer a screen may use. Every value below points at @@ -486,102 +569,30 @@ ============================================================ */ :root { - /* --- Structural surfaces. --------------------------------- - THE ROLES THAT EXIST BECAUSE A RAMP STEP CANNOT SAY THEM. - Each one takes a DIFFERENT step per mode, so no class like - `bg-neutral-1` is correct in both. That is the whole test for - whether a name is earned; everything that passed it is here. - - `bg-app` qualifies for the same reason in a different shape: - the gradient itself is swapped per mode. */ --bg-app: var(--gradient-1); - --bg-panel: var(--neutral-1); /* dark: neutral-3 */ - --bg-float: var(--neutral-1); /* dark: neutral-5 */ + --bg-panel: var(--surface-panel); /* dark: neutral-3 */ + --bg-float: var(--surface-popover); /* dark: neutral-5 */ --bg-scrim: #00000060; - --border-control: var(--neutral-8); + --border-control: var(--border-prominent); /* Emoji artwork scales with text without becoming a readable type role. */ --size-emoji-display: calc(var(--type-rem) * 2.625); --size-emoji-line: calc(var(--type-rem) * 3); --size-emoji-inline: calc(var(--type-rem) * 1.375); - /* `bg-hover` was here and is gone. It was neutral-4 in both modes, so - `bg-neutral-4` says it exactly. Its five call sites now do. - - Worth knowing before recreating it: hover means "one step more - contrast than whatever is underneath", which is a relationship, not - a value — hover on a panel and hover on an inset are different - numbers. A single token could never express that, so the name was - promising something it did not deliver. */ - - /* --- Emphasis. ------------------------------------------- - These are the exception to the rule above: every one is the - same step in both modes, so a class would work. They stay - because the NAME enforces something — there are deliberately - three levels of text and one border weight, and a ramp cannot - say "three". Reaching for `text-neutral-11` because it looks - right is how a fourth level appears without anyone deciding. */ - --text-primary: var(--neutral-12); - --text-secondary: var(--neutral-10); - --text-tertiary: var(--neutral-9); + --text-primary: var(--text-standard); + --text-secondary: var(--text-subtle); + --text-tertiary: var(--text-metadata); /* Deliberately below the contrast targets: low contrast is the signal that a control is unavailable, and WCAG exempts inactive controls for the same reason. Raising it to Lc 60 would make disabled read as enabled. Never put information here that a person needs. */ - --text-disabled: var(--neutral-7); + --text-disabled: var(--text-unavailable); /* One border, not three. `primary` names the sole deliberate weight, and neutral-4 keeps it at the threshold of perception over both the panel and the atmospheric backdrop. A second weight arrives only with a design that proves a different boundary needs one. */ - --border-primary: var(--neutral-3); - - /* --- Identity: deliberately absent. ---------------------- - There were six accent roles and every one held the same purple - step in both modes, so each was a name in front of a number — - `bg-accent` was `purple-9` and nothing else. The screens now say - `bg-purple-9`, `bg-purple-3`, `text-purple-12`, `border-purple-8`. - - `bg-inverse` went the same way (`neutral-11`), and so did its - paired text (`neutral-1`). - - The argument FOR naming them was that accent is a slot whose hue - could change. It is a real argument and it lost to a simpler fact: - the hue already changed once — the accent moved to Tailwind purple - — and it was five values in the ramp, not a rename. The ramp is - the slot. A role in front of it only adds a hop. - - When a repeated accent PATTERN appears — not a colour, a pattern, - like a tinted callout that turns up on four screens — that earns a - name, and it will be named for the pattern. */ - - /* --- Status: deliberately absent. ------------------------ - There were twenty roles here — danger, success, warning, info, five - each, the accent's shape copied four times. Nineteen of the twenty - had no reader outside the /design pages that displayed them, and the - one real consumer was a 8px presence dot. - - They were invented by symmetry rather than by need, and the symmetry - was actively misleading: the red ramp alone offers red-3, red-8, - red-9 and red-12 for "an error", and a set of pre-made names hid - that choice instead of making it. `text-danger` shipped pointing at - the wrong red for months (see the registration note below) and - nothing noticed, because no design had ever looked at it. - - Status colour gets designed when there is a screen to design it on. - Until then the ramps are right there: an error message picks its - step, and once two screens pick the same one it earns a name. The - hues stay in the palette — nothing was deleted from layer 0. - - Two things briefly had names here — `text-error` and - `bg-agent-running` — and they went too, for the same reason as the - accent: both were one step, identical in both modes. Error text is - `text-red-12` and the running dot is `bg-green-9`, written where - they are used. - - The measurement that chose red-12 is the part worth keeping, and it - lives in DESIGN.md rather than in a token: red-11 is the more - obviously red choice and fails the Lc 60 body target on a dark panel - (59.7) and the dark composer (57.5). */ + --border-primary: var(--border-standard); /* --- Material. Translucency and blur live inside the value. A component never assembles them. @@ -600,16 +611,6 @@ --bg-glass-secondary-hover: var(--glass-5); --bg-chrome-selected: var(--neutral-1); - /* --- Focus: `purple-8`, written directly. ----------------- - Same step in both modes, so it was a name in front of a number too. - The judgement that survives is not the value: purple-8 is legible on - every neutral surface (WCAG 3.0–3.5:1) but measures 1.1:1 on - `purple-9` and disappears. **A focused primary button needs the ring - separated from the fill** — an offset in the surface colour, not a - different ring hue, so one focus treatment works everywhere. That - belongs to whichever component layer introduces focus. It is in - DESIGN.md, which is where a rule lives; a token could not state it. */ - /* --- Geometry. Repeated relationships from Messages. ----- */ --space-workspace-inset: var(--space-4); --space-panel-gap: var(--space-2); @@ -647,14 +648,6 @@ --easing-state: var(--ease-state); --easing-settle: var(--ease-settle); - /* ========================================================== - DELIBERATE EXCEPTIONS — the entire list. - ========================================================== - - 1. Paired text. Computed from its fill's lightness rather - than fixed, because white is readable on a blue or - purple fill and unreadable on yellow or lime. Until the - computation lands, these hold literals. */ --text-on-accent: #ffffff; /* `text-on-inverse` was `var(--neutral-1)` in both modes and went with @@ -689,20 +682,20 @@ :root[data-color-mode="dark"], .dark { - --bg-panel: var(--neutral-3); - --bg-float: var(--neutral-5); + --bg-panel: var(--surface-panel); + --bg-float: var(--surface-popover); --bg-scrim: #000000a6; - --border-control: var(--neutral-8); + --border-control: var(--border-prominent); --bg-chrome-selected: var(--neutral-5); /* BlockUI's darker secondary/tertiary grays fall below Buzz's APCA targets on raised panels. Keep the BlockUI ramp, selecting its lighter steps for readable text: secondary >= 60, metadata >= 45 on every opaque surface. */ - --text-secondary: var(--neutral-11); - --text-tertiary: var(--neutral-10); + --text-secondary: var(--text-subtle); + --text-tertiary: var(--text-metadata); /* BlockUI subtle border: E8E8E8 in light, 232323 in dark. */ - --border-primary: var(--neutral-4); + --border-primary: var(--border-standard); --rim-lit: #ffffff2e; --rim-shade: #ffffff0f; @@ -716,27 +709,47 @@ @theme inline { --color-*: initial; - - /* --------------------------------------------------------------- - THE RAMPS, REACHABLE DIRECTLY. - - `bg-neutral-4`, `text-red-12`, `border-purple-8`. This is the layer - a screen is built from now, and it is a deliberate reversal: the - colour guard used to reject exactly these classes. - - The reason it is safe here and was not safe in Tailwind's own - palette is that **every step is authored per mode.** `neutral-11` - is one value in light and another in dark, so a component naming a - step still behaves in both. Tailwind's `neutral-800` is a single - literal, which is why reaching for it used to break dark mode and - why a semantic name was the only correct move. - - What a semantic role is still FOR is the case a step cannot - express — see the four surface roles above, where light and dark - take different steps. Everything else was a name in front of a - number, and a name in front of a number hides the choice instead - of recording it. - --------------------------------------------------------------- */ + /* Semantic utilities. Text and border stay in their own namespaces. */ + --color-surface-base: var(--surface-base); + --color-surface-panel: var(--surface-panel); + --color-surface-popover: var(--surface-popover); + --color-surface-inset: var(--surface-inset); + --text-color-standard: var(--text-standard); + --text-color-subtle: var(--text-subtle); + --text-color-metadata: var(--text-metadata); + --text-color-inverse: var(--text-inverse); + --text-color-unavailable: var(--text-unavailable); + --text-color-danger: var(--text-danger); + --text-color-warning: var(--text-warning); + --text-color-success: var(--text-success); + --text-color-accent: var(--text-accent); + --border-color-standard: var(--border-standard); + --border-color-prominent: var(--border-prominent); + --border-color-focus: var(--border-focus); + --border-color-danger: var(--border-danger); + --border-color-warning: var(--border-warning); + --color-affordance-prominent: var(--affordance-prominent); + --color-affordance-prominent-hover: var(--affordance-prominent-hover); + --color-affordance-prominent-pressed: var(--affordance-prominent-pressed); + --color-affordance-subtle: var(--affordance-subtle); + --color-affordance-subtle-hover: var(--affordance-subtle-hover); + --color-affordance-subtle-pressed: var(--affordance-subtle-pressed); + --color-affordance-selected: var(--affordance-selected); + --color-affordance-disabled: var(--affordance-disabled); + --color-affordance-danger: var(--affordance-danger); + --color-affordance-danger-hover: var(--affordance-danger-hover); + --color-affordance-danger-pressed: var(--affordance-danger-pressed); + --color-affordance-warning: var(--affordance-warning); + --color-affordance-success: var(--affordance-success); + --color-affordance-accent: var(--affordance-accent); + --color-affordance-accent-hover: var(--affordance-accent-hover); + --color-affordance-accent-prominent: var(--affordance-accent-prominent); + --color-affordance-accent-prominent-hover: var( + --affordance-accent-prominent-hover + ); + --border-color-accent: var(--border-accent); + + /* Palette utilities remain for the palette viewer and staged migration. */ /* neutral */ --color-neutral-1: var(--neutral-1); --color-neutral-2: var(--neutral-2); diff --git a/src/shared/design-system/styles/typography.css b/src/shared/design-system/styles/typography.css index 61d0d55a..b6c7a51c 100644 --- a/src/shared/design-system/styles/typography.css +++ b/src/shared/design-system/styles/typography.css @@ -1,5 +1,5 @@ /* Block UI size ramp and resolved role settings, rendered in Inter and JetBrains Mono. - Source: https://github.com/squareup/design-blockinterface/blob/eff766161ba8aaee3258ca107f0d904dd542c708/blockUI/docs/type.resolution.draft.json + Uses the adopted type scale with open-source Inter and JetBrains Mono. The upstream spec is provisional. Values use rem to preserve host text scaling. */ :root { --font-system-mono: diff --git a/src/shared/design-system/tokens/registry.ts b/src/shared/design-system/tokens/registry.ts index 624f9e3d..e7a26cd1 100644 --- a/src/shared/design-system/tokens/registry.ts +++ b/src/shared/design-system/tokens/registry.ts @@ -293,11 +293,292 @@ export const RAMPS: Ramp[] = [ */ export const ROLE_GROUPS: RoleGroup[] = [ + { + id: "semantic-surface", + name: "Surface", + description: "Shared surface roles, with values for both themes.", + roles: [ + { + token: "bg-surface-base", + variable: "--surface-base", + pointsAt: "neutral-2 light / neutral-1 dark", + use: "The plain background behind panels.", + status: "core", + }, + { + token: "bg-surface-panel", + variable: "--surface-panel", + pointsAt: "neutral-1 light / neutral-3 dark", + use: "A content panel or card.", + status: "core", + }, + { + token: "bg-surface-popover", + variable: "--surface-popover", + pointsAt: "neutral-1 light / neutral-5 dark", + use: "A menu, dialog or other raised surface.", + status: "core", + }, + { + token: "bg-surface-inset", + variable: "--surface-inset", + pointsAt: "neutral-2 light / neutral-2 dark", + use: "A recessed region inside a panel.", + status: "core", + }, + ], + }, + { + id: "semantic-text", + name: "Text", + description: "Shared text roles, with values for both themes.", + roles: [ + { + token: "text-standard", + variable: "--text-standard", + pointsAt: "neutral-12 light / neutral-12 dark", + use: "Normal reading text.", + status: "core", + }, + { + token: "text-subtle", + variable: "--text-subtle", + pointsAt: "neutral-10 light / neutral-11 dark", + use: "Supporting text that must remain readable.", + status: "core", + }, + { + token: "text-metadata", + variable: "--text-metadata", + pointsAt: "neutral-9 light / neutral-10 dark", + use: "Nonessential metadata.", + status: "core", + }, + { + token: "text-inverse", + variable: "--text-inverse", + pointsAt: "neutral-1 light / neutral-1 dark", + use: "Text on prominent actions.", + status: "core", + }, + { + token: "text-unavailable", + variable: "--text-unavailable", + pointsAt: "neutral-7 light / neutral-7 dark", + use: "Unavailable controls only.", + status: "core", + }, + { + token: "text-danger", + variable: "--text-danger", + pointsAt: "red-12 light / red-12 dark", + use: "Error text and destructive action labels.", + status: "core", + }, + { + token: "text-warning", + variable: "--text-warning", + pointsAt: "amber-12 light / amber-12 dark", + use: "Warning text.", + status: "core", + }, + { + token: "text-success", + variable: "--text-success", + pointsAt: "green-12 light / green-12 dark", + use: "Success text.", + status: "core", + }, + { + token: "text-accent", + variable: "--text-accent", + pointsAt: "purple-12 light / purple-12 dark", + use: "Linked or selected identity text.", + status: "core", + }, + ], + }, + { + id: "semantic-border", + name: "Border", + description: "Shared border roles, with values for both themes.", + roles: [ + { + token: "border-standard", + variable: "--border-standard", + pointsAt: "neutral-3 light / neutral-4 dark", + use: "A quiet separator or panel edge.", + status: "core", + }, + { + token: "border-prominent", + variable: "--border-prominent", + pointsAt: "neutral-8 light / neutral-8 dark", + use: "A visible input or choice boundary.", + status: "core", + }, + { + token: "border-focus", + variable: "--border-focus", + pointsAt: "neutral-12 light / neutral-12 dark", + use: "Keyboard focus.", + status: "core", + }, + { + token: "border-danger", + variable: "--border-danger", + pointsAt: "red-8 light / red-8 dark", + use: "Invalid field boundary.", + status: "core", + }, + { + token: "border-warning", + variable: "--border-warning", + pointsAt: "amber-8 light / amber-8 dark", + use: "Warning boundary.", + status: "core", + }, + { + token: "border-accent", + variable: "--border-accent", + pointsAt: "purple-8 light / purple-8 dark", + use: "An identity accent boundary.", + status: "core", + }, + ], + }, + { + id: "semantic-affordance", + name: "Affordance", + description: "Shared affordance roles, with values for both themes.", + roles: [ + { + token: "bg-affordance-prominent", + variable: "--affordance-prominent", + pointsAt: "neutral-12 light / neutral-12 dark", + use: "The main action fill.", + status: "core", + }, + { + token: "bg-affordance-prominent-hover", + variable: "--affordance-prominent-hover", + pointsAt: "neutral-11 light / neutral-11 dark", + use: "The main action under a pointer.", + status: "core", + }, + { + token: "bg-affordance-prominent-pressed", + variable: "--affordance-prominent-pressed", + pointsAt: "neutral-action-pressed", + use: "The main action while pressed.", + status: "core", + }, + { + token: "bg-affordance-subtle", + variable: "--affordance-subtle", + pointsAt: "neutral-2 light / neutral-5 dark", + use: "A secondary action fill.", + status: "core", + }, + { + token: "bg-affordance-subtle-hover", + variable: "--affordance-subtle-hover", + pointsAt: "neutral-3 light / neutral-6 dark", + use: "A secondary action under a pointer.", + status: "core", + }, + { + token: "bg-affordance-subtle-pressed", + variable: "--affordance-subtle-pressed", + pointsAt: "neutral-4 light / neutral-7 dark", + use: "A secondary action while pressed.", + status: "core", + }, + { + token: "bg-affordance-selected", + variable: "--affordance-selected", + pointsAt: "neutral-3 light / neutral-5 dark", + use: "A persistently selected item.", + status: "core", + }, + { + token: "bg-affordance-disabled", + variable: "--affordance-disabled", + pointsAt: "neutral-3 light / neutral-3 dark", + use: "An unavailable control fill.", + status: "core", + }, + { + token: "bg-affordance-danger", + variable: "--affordance-danger", + pointsAt: "red-3 light / red-3 dark", + use: "A destructive action or error fill.", + status: "core", + }, + { + token: "bg-affordance-danger-hover", + variable: "--affordance-danger-hover", + pointsAt: "red-4 light / red-4 dark", + use: "A destructive action under a pointer.", + status: "core", + }, + { + token: "bg-affordance-danger-pressed", + variable: "--affordance-danger-pressed", + pointsAt: "red-5 light / red-5 dark", + use: "A destructive action while pressed.", + status: "core", + }, + { + token: "bg-affordance-warning", + variable: "--affordance-warning", + pointsAt: "amber-3 light / amber-3 dark", + use: "A warning fill.", + status: "core", + }, + { + token: "bg-affordance-success", + variable: "--affordance-success", + pointsAt: "green-3 light / green-3 dark", + use: "A success fill.", + status: "core", + }, + { + token: "bg-affordance-accent", + variable: "--affordance-accent", + pointsAt: "purple-3 light / purple-3 dark", + use: "A quiet identity or selection fill.", + status: "core", + }, + { + token: "bg-affordance-accent-hover", + variable: "--affordance-accent-hover", + pointsAt: "purple-4 light / purple-4 dark", + use: "A quiet identity fill under a pointer.", + status: "core", + }, + { + token: "bg-affordance-accent-prominent", + variable: "--affordance-accent-prominent", + pointsAt: "purple-9 light / purple-9 dark", + use: "An identity accent fill paired with text-on-accent.", + status: "core", + }, + { + token: "bg-affordance-accent-prominent-hover", + variable: "--affordance-accent-prominent-hover", + pointsAt: "purple-10 light / purple-10 dark", + use: "An identity accent fill under a pointer.", + status: "core", + }, + ], + }, + { id: "surfaces", name: "Structural surfaces", description: - "The roles that exist because a ramp step cannot say them: each takes a different step in light and dark, so no single class like `bg-neutral-1` is correct in both. That is the whole test for whether a colour earns a name. Ask one question: is it behind, on, above, or in? (`bg-hover` was here and is now written as `bg-neutral-4` \u2014 it was the same step in both modes. Hover is a *relationship*, one step more contrast than whatever is underneath, which no single token could express anyway.)", + "Legacy aliases maintained during migration. The original rationale was: each takes a different step in light and dark, so no single class like `bg-neutral-1` is correct in both. That is the whole test for whether a colour earns a name. Ask one question: is it behind, on, above, or in? (`bg-hover` was here and is now written as `bg-neutral-4` \u2014 it was the same step in both modes. Hover is a *relationship*, one step more contrast than whatever is underneath, which no single token could express anyway.)", roles: [ { token: "bg-app", @@ -414,7 +695,10 @@ export const VOCABULARY: Array<{ group: string; words: string[] }> = [ { group: "property", words: ["bg", "text", "border", "ring"] }, { group: "region", words: ["app", "panel", "float", "chrome", "inset"] }, { group: "emphasis", words: ["primary", "secondary", "tertiary", "default"] }, - { group: "state", words: ["hover", "selected", "disabled"] }, + { + group: "state", + words: ["hover", "pressed", "selected", "disabled", "loading"], + }, { group: "material", words: ["glass"] }, { group: "modifier", words: ["tint"] }, { group: "identity", words: ["accent", "inverse"] }, @@ -425,11 +709,12 @@ export const VOCABULARY: Array<{ group: string; words: string[] }> = [ { group: "paired", words: ["on-accent", "on-inverse"] }, ]; -export const GRAMMAR = "-[-][-][-]"; +export const GRAMMAR = + "color / / [/ state]"; /** Fixed order, so there is only one correct spelling. */ export const GRAMMAR_EXAMPLES = { - legal: ["bg-accent-tint-hover", "text-primary", "bg-chrome-selected"], + legal: ["bg-surface-panel", "text-standard", "bg-affordance-subtle-hover"], illegal: ["bg-chrome-hover-glass", "bg-hover-chrome"], }; @@ -611,7 +896,7 @@ export const TYPE_FAMILIES = [ /** Source for the primitive ladder and resolved roles displayed below. */ export const TYPE_SOURCE = - "https://github.com/squareup/design-blockinterface/blob/eff766161ba8aaee3258ca107f0d904dd542c708/blockUI/docs/type.resolution.draft.json"; + "https://github.com/block/buzz-app/blob/main/src/shared/design-system/styles/typography.css"; /** Active size primitives, with xsmall sharing the 12px step. */ export const TYPE_RAMPS = [