Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions scripts/design-system/check-color.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,10 @@ function auditLayers() {
// 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."],
Expand Down
5 changes: 5 additions & 0 deletions scripts/design-system/check-contrast.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ const TEXT_ROLES = [
"--text-disabled",
"--purple-12", // accent text: links, active nav, chip labels
"--red-12", // error text: failed session start, rejected form
"--amber-12", // warning text in delivery notices and dialogs
"--green-12", // completion text in the foundation alignment proposal
];

/**
Expand All @@ -98,6 +100,7 @@ const PAIRS = [
// now that the roles are gone. Still measured as a pair, because the text
// follows the fill: move the fill and this has to be re-measured.
["--neutral-1", "--neutral-11"],
["--neutral-1", "--neutral-12"],
];

/**
Expand All @@ -108,6 +111,8 @@ const PAIRS = [
* cursor — and the hover is the harder one, which is where the gap was.
*/
const TINT_PAIRS = [
["--amber-12", "--amber-3"],
["--green-12", "--green-3"],
["--purple-12", "--purple-3"],
["--purple-12", "--purple-4"],
];
Expand Down
9 changes: 5 additions & 4 deletions scripts/design-system/check-type.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ const VIEWER = fileURLToPath(

/** Size roles a component may use. Kept in sync with typography.css. */
const SIZE_ROLES = [
"label",
"label-sm",
"caption",
"display",
"title",
"heading",
Expand All @@ -58,8 +61,6 @@ const SIZE_ROLES = [
*/
const RETIRED_ROLES = new Map([
["subheading", "text-heading, or text-body-lg if it is prose"],
["label", "text-body, or text-body-sm in dense chrome"],
["caption", "text-body-sm"],
["meta", "text-body-sm"],
["code", "text-mono"],
]);
Expand Down Expand Up @@ -132,9 +133,9 @@ const RULES = [
//
// `font-semibold` and `font-normal` are absent from this list on purpose:
// they are the two legal weights.
pattern: /\bfont-(?:thin|extralight|light|medium|bold|extrabold|black)\b/g,
pattern: /\bfont-(?:thin|extralight|light|bold|extrabold|black)\b/g,
message:
"off-ramp font weight — the system is 400 and 600. Bold is font-semibold. If a one-off genuinely needs another weight, add it to OVERRIDES with a reason.",
"off-ramp font weight — the system is 400 and 500. Emphasis is font-medium; legacy font-semibold resolves to 500. If a one-off genuinely needs another weight, add it to OVERRIDES with a reason.",
},
{
id: "retired-role",
Expand Down
22 changes: 22 additions & 0 deletions src/shared/design-system/DESIGN.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# DESIGN.md

## Current local adoption

The product owner has requested BlockUI foundations in the actual Buzz app.
Shared neutrals now use BlockUI text and surface values; primary actions are
neutral. Inter uses the BlockUI 16/24 body, 14/20 small body, 20/28 lead,
24/24 section, 32/32 title and 56/56 hero settings, with 400/500 weights.
Labels have separate 16/24 and 14/20 roles; caption is 12/16.
Buzz retains its 11/13/15 mono roles for source code.
Existing host color names alias the shared tokens. Panel padding is 24, control
inset 16, group gap 32 and page-section gap 64 (all rem-based).
This supersedes the historical 14px body, 400/600 and neutral-ramp values below.
Dark secondary/tertiary text select the lighter BlockUI ramp steps to preserve
Buzz’s APCA targets on raised panels; the raw BlockUI grays remain in the palette.
Buzz keeps its full-window gradient backdrops and glass navigation in both modes.
The app and shared system use the same backdrop and glass materials; content
panels keep the new neutral surfaces. Status, picker, dialog and host compatibility colors now resolve to the shared
palette. Application typography, insets, gaps and corners use shared roles.
The app-wide foundation guard covers src/ (including the Emoji Mart adapter);
layout dimensions, emoji artwork geometry and terminal ANSI/artwork remain
renderer-owned. The terminal UI itself uses the shared colors and mono type.


How to design well in this client. The token registry says which value to use; this says what tokens cannot express — the judgement a designer makes without thinking and an agent gets wrong without being told. Read it before building a surface.

Run `pnpm design:dev` and open `/tests/fixtures/design-system.html` to see the system rendered from the tokens themselves.
Expand Down
26 changes: 14 additions & 12 deletions src/shared/design-system/styles/components.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
padding: 0 var(--space-control-inset);
background: transparent;
color: var(--text-primary);
font: inherit;
font-family: inherit;
@apply text-label;
cursor: pointer;
transition:
color var(--duration-state) var(--easing-state),
Expand All @@ -21,21 +22,22 @@
}

.buzz-button[data-size="compact"] {
@apply text-label-sm;
height: var(--size-row);
border-radius: var(--radius-row);
padding-inline: var(--space-control-inset);
}

.buzz-button[data-variant="primary"] {
background: var(--purple-9);
color: var(--text-on-accent);
background: var(--neutral-11);
color: var(--neutral-1);
}

/* The accent's own hover step, not a fade. `opacity: 0.9` lightened the label
along with the fill and composited an unnamed colour nothing could measure —
the move check-color.mjs rejects everywhere else. */
.buzz-button[data-variant="primary"]:hover:not([data-disabled]) {
background: var(--purple-10);
background: var(--neutral-12);
}

.buzz-button[data-variant="quiet"] {
Expand Down Expand Up @@ -104,12 +106,12 @@
}

.buzz-button[data-icon-variant="solid"] {
background: var(--purple-9);
color: var(--text-on-accent);
background: var(--neutral-11);
color: var(--neutral-1);
}

.buzz-button[data-icon-variant="solid"]:hover:not([data-disabled]) {
background: var(--purple-10);
background: var(--neutral-12);
}

/* A gentle acknowledgement/submit action on a quiet surface. This cannot be
Expand Down Expand Up @@ -187,7 +189,7 @@
}

html[data-keyboard-navigation] .buzz-preview-card-example:focus-visible {
outline: 2px solid var(--purple-8);
outline: 2px solid var(--text-primary);
outline-offset: 2px;
}

Expand Down Expand Up @@ -557,7 +559,7 @@
}

html[data-keyboard-navigation] .search-field input:focus-visible {
outline: 2px solid var(--purple-8);
outline: 2px solid var(--text-primary);
outline-offset: 2px;
}

Expand Down Expand Up @@ -664,7 +666,7 @@
html[data-keyboard-navigation] .buzz-button:focus-visible,
html[data-keyboard-navigation] .navigation-item:focus-visible,
html[data-keyboard-navigation] .buzz-tabs-tab:focus-visible {
outline: 2px solid var(--purple-8);
outline: 2px solid var(--text-primary);
outline-offset: 2px;
}

Expand Down Expand Up @@ -745,7 +747,7 @@
background: var(--neutral-3);
}
.buzz-accordion-trigger:focus-visible {
outline: 2px solid var(--purple-8);
outline: 2px solid var(--text-primary);
outline-offset: 2px;
}
.buzz-accordion-trigger[aria-expanded="true"] > svg {
Expand Down Expand Up @@ -817,7 +819,7 @@
cursor: not-allowed;
}
html[data-keyboard-navigation] .buzz-switch-control:focus-visible {
outline: 2px solid var(--purple-8);
outline: 2px solid var(--text-primary);
outline-offset: 2px;
}
}
Expand Down
90 changes: 50 additions & 40 deletions src/shared/design-system/styles/tokens.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* BlockUI neutral values (canonical text and ratified surface ladder), adapted
to the existing Buzz ramp. Chromatic ramps and glass remain Buzz-specific. */
/*
* Buzz colour tokens.
*
Expand Down Expand Up @@ -289,17 +291,17 @@
is a deliberately cool near-neutral, not a warm tint; use it directly
rather than preserve an exploratory #f9f9f9 stop or recreate it with
alpha. */
--neutral-2: #f5f5f6; /* quiet compact controls, callouts, and grouping */
--neutral-3: #f0f0f0; /* available for a future, genuinely recessed region; bg-panel in dark */
--neutral-4: #e8e8e8; /* border-primary in light mode */
--neutral-5: #e0e0e0; /* unused in light; float + selected, dark */
--neutral-6: #d9d9d9; /* border-primary in dark mode */
--neutral-7: #cecece; /* no readers */
--neutral-8: #bbbbbb; /* text-disabled */
--neutral-9: #8d8d8d; /* text-tertiary */
--neutral-10: #646464; /* text-secondary */
--neutral-11: #262626; /* inverse fill, paired with neutral-1 */
--neutral-12: #0a0a0a; /* text-primary */
--neutral-2: #f0f0f0; /* quiet compact controls, callouts, and grouping */
--neutral-3: #e8e8e8; /* available for a future, genuinely recessed region; bg-panel in dark */
--neutral-4: #dadada; /* border-primary in light mode */
--neutral-5: #cccccc; /* unused in light; float + selected, dark */
--neutral-6: #b3b3b3; /* border-primary in dark mode */
--neutral-7: #959595; /* no readers */
--neutral-8: #808080; /* text-disabled */
--neutral-9: #666666; /* text-tertiary */
--neutral-10: #525252; /* text-secondary */
--neutral-11: #191919; /* inverse fill, paired with neutral-1 */
--neutral-12: #000000; /* text-primary */

/* --- Glass. A translucency ramp of fills. ---------------- */
--glass-1: #ffffff5c; /* barely there */
Expand Down Expand Up @@ -343,12 +345,16 @@
The scale is intentionally short: 4/8/12/16/20/24 covers the repeated
panel gap, row inset, header inset, and workspace gutter without turning
every measured distance into a token. */
--space-half: 0.125rem;
--space-1h: 0.375rem;
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-5: 1.25rem;
--space-6: 1.5rem;
--space-8: 2rem;
--space-16: 4rem;

/* --- Corner. Four shapes, each with a distinct job. ------- */
--corner-row: 0.625rem;
Expand All @@ -373,24 +379,24 @@
are deliberately tighter at the bottom. A documented divergence, not drift:
if these ever want to be palette steps, the palette's gray ramp is what
should change. */
--neutral-1: #0f0f0f;
--neutral-2: #161616;
--neutral-3: #1c1c1c;
--neutral-1: #000000;
--neutral-2: #101010;
--neutral-3: #1a1a1a;
--neutral-4: #232323;
--neutral-5: #2a2a2a;
--neutral-6: #333333;
--neutral-7: #424242;
--neutral-8: #5a5a5a;
--neutral-5: #333333;
--neutral-6: #404040;
--neutral-7: #595959;
--neutral-8: #737373;
/* Steps 9 and 10 are lighter than a linear ramp would put them, because in
dark mode they carry text and APCA is polarity-asymmetric: light-on-dark
needs more separation than the WCAG ratio implies. At #6e6e6e and #8f8f8f
they measured Lc 23–26 and Lc 38–41 against the Lc 60 body-text target
while passing WCAG AA — legal, and visibly murky. Sized against the
lightest dark surface (bg-float #2a2a2a), which is the binding case. */
--neutral-9: #a6a6a6;
--neutral-10: #c1c1c1;
--neutral-11: #e5e5e5;
--neutral-12: #f5f5f5;
--neutral-9: #808080;
--neutral-10: #a4a4a4;
--neutral-11: #c7c7c7;
--neutral-12: #ffffff;

/* Every coloured family is absent here on purpose. A family points at a
palette step, and `.dark` already redefines the whole palette — so
Expand Down Expand Up @@ -491,6 +497,12 @@
--bg-app: var(--gradient-1);
--bg-panel: var(--neutral-1); /* dark: neutral-3 */
--bg-float: var(--neutral-1); /* dark: neutral-5 */
--bg-scrim: #00000060;
--border-control: var(--neutral-8);
/* 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.
Expand All @@ -515,13 +527,13 @@
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-8);
--text-disabled: var(--neutral-7);

/* 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-4);
--border-primary: var(--neutral-3);

/* --- Identity: deliberately absent. ----------------------
There were six accent roles and every one held the same purple
Expand Down Expand Up @@ -601,10 +613,11 @@
/* --- Geometry. Repeated relationships from Messages. ----- */
--space-workspace-inset: var(--space-4);
--space-panel-gap: var(--space-2);
--space-panel-inset: var(--space-5);
--space-control-inset: var(--space-3);
--space-panel-inset: var(--space-6);
--space-control-inset: var(--space-4);
--space-row-gap: var(--space-2);
--space-section-gap: var(--space-4);
--space-section-gap: var(--space-8);
--space-page-section-gap: var(--space-16);
/* An inline chip sits in a line of text, so its inset and gap are tighter
than a control's and are its own repeated relationship. Both are below the
4px spacing floor because a chip hugs its label — the design draws 3px of
Expand Down Expand Up @@ -678,22 +691,18 @@
.dark {
--bg-panel: var(--neutral-3);
--bg-float: var(--neutral-5);
--bg-scrim: #000000a6;
--border-control: var(--neutral-8);
--bg-chrome-selected: var(--neutral-5);

/* Two steps lighter than light mode's neutral-4, because a hairline needs more
separation on a dark surface than the same step number provides. Measured
against every surface a border sits on: neutral-4 is 1.08:1 on a panel and
1.09:1 on the floating composer — drawn, and effectively invisible.
neutral-6 is 1.35 and 1.14.

Not neutral-5, which is one step and would have been the obvious answer:
`bg-float` and `bg-chrome-selected` are both neutral-5 in dark, so a border
on a floating surface would measure 1.00:1 — the same colour as the thing it
is meant to bound.
/* 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);

**This asymmetry is the reason the role exists.** Light and dark do not have
to reach for the same step, and no `border-neutral-4` class could say this. */
--border-primary: var(--neutral-6);
/* BlockUI subtle border: E8E8E8 in light, 232323 in dark. */
--border-primary: var(--neutral-4);

--rim-lit: #ffffff2e;
--rim-shade: #ffffff0f;
Expand Down Expand Up @@ -841,6 +850,7 @@
--color-orange-12: var(--orange-12);

--color-app: var(--bg-app);
--color-inset: var(--neutral-2);
--color-panel: var(--bg-panel);
--color-float: var(--bg-float);

Expand Down
Loading
Loading