Skip to main content

Foundations

The raw material: color, type, spacing, radii, and shadows. Components live in the index, where each opens to its full documentation.

Colors

Layer 1 primitives mirror Figma variables 1:1. Layer 2 semantic roles alias from Layer 1 and are what components consume.

Brand

The brand accent — CTAs, focus rings, active nav, hover states. This is the single value a brand overrides on import; the swatch reads it live, so it's always the truth, not a typed copy. The sandbox's own identity is a sibling-neutral green; the deeper ink shade carries small green text on white so links stay legible.

brand-primary
var(--brand-primary)brand / CTA · the sandbox's face
brand-primary-ink
var(--brand-primary-ink)links / small green text on white

Brand Ramp & Gradients

Seven tints of the primary, interpolated in OKLab, plus the primary's own ramp gradient. The signature brand sweep (primary → accent) and the accent's own ramp live in the Accent collection below — they need a second color to mean anything. Decorative: header / hero accents, highlights, sequential charts — they sit behind dark text or none, so no contrast obligation.

brand-primary
var(--brand-primary)step 1 · brand
brand-primary-2
var(--brand-primary-2)
brand-primary-3
var(--brand-primary-3)
brand-primary-4
var(--brand-primary-4)
brand-primary-5
var(--brand-primary-5)
brand-primary-6
var(--brand-primary-6)
brand-primary-7
var(--brand-primary-7)step 7 · ramp end
--gradient-primary-ramp · 135°, primary tints

Accent (second brand color)

The opt-in second color — a real accent with its own button variant, brand tag/chip, ramp, and two gradients: its own accent ramp plus the signature brand sweep (primary → accent), which only exists once there's a second color. Generated by the setup tool's rules. The whole collection is gated behind --brand-accent-enabled, so one-color brands never see it; a two-color brand turns it on in Brand setup.

This brand runs a single color, so the accent family is off (--brand-accent-enabled: 0). Add a usable second color in Brand setup to turn it on — the accent button, brand tag, ramp, and gradient populate automatically.

Mesh Gradient Images (pre-rendered)

Pre-rendered mesh-gradient IMAGE assets (raster PNGs, inlined with the package) — used for avatar backgrounds and reference-page header bands. These are fixed images, not applicable CSS gradients. Grouped by color family.

Blue & Cyan
azure
sky
cloud
breeze
ocean
lagoon
Teal & Mint
teal
mint
Purple & Violet
violet
lavender
twilight
periwinkle
Pink & Magenta
rose
fuchsia
orchid
candy
berry
Coral, Peach & Salmon
coral
salmon
flamingo
peach
tangerine
Yellow & Gold
amber
gold
honey

Surface

Application backgrounds and container fills.

white
#fbfbfaoff-white surfaces
sidebar-grey
#efefefapp background
pure-white
#ffffffuse sparingly

Neutral Scale

Primary text + UI grayscale. Seven tiers from lightest to darkest, read live from the tokens.

neutral-light
var(--neutral-light)
neutral
var(--neutral)
neutral-mutedNEW
var(--neutral-muted)
neutral-medium
var(--neutral-medium)body text · WCAG-tuned
neutral-dark
var(--neutral-dark)
neutral-darkerNEW
var(--neutral-darker)
neutral-black
var(--neutral-black)

Grey Scale

Alternative mid-greys distinct from the neutral family.

grey-light
#ebebeb
grey
#b3b3b3
grey-dark
#626262

Status · Default

Inactive / neutral state for status tags and chips.

status-default-light
#fbfbfa
status-default
#dadada
status-default-dark
#767676

Status · Hue Families

Seven semantic hues. Dark = saturated/bold variant, base = primary shade, light = background tint.

Red (was Salmon)
status-red-light
#ffd8d8
status-red
#ff7b7d
status-red-dark
#e61619
Orange
status-orange-light
#ffe1d8
status-orange
#ff8956
status-orange-dark
#dd5c00
Yellow
status-yellow-light
#fff9c4
status-yellow
#ffca3d
status-yellow-dark
#bf8b00
Green
status-green-light
#d3f8bf
status-green
#8cba5e
status-green-dark
#4d9c00
Blue
status-blue-light
#dae4ff
status-blue
#96b1ff
status-blue-dark
#205aff
Pink
status-pink-light
#ffd1f0
status-pink
#ff9ee1
status-pink-dark
#fb00ac
Purple
status-purple-light
#ecdfff
status-purple
#b68cf7
status-purple-dark
#6d28d9

Bulb Palette

Product colors for Christmas light bulb rendering. Domain-specific.

bulb-red
#ff3b3b
bulb-orange
#fa9200
bulb-yellow
#ffcc00
bulb-green
#57ce33
bulb-teal
#5bcbc6
bulb-blue
#546ff2
bulb-purple
#c35efe
bulb-pink
#f867b7
bulb-cool
#d7eef9
bulb-warm
#f4e7cb
bulb-pure
#ffffff

Wire Palette

Product colors for wire color selection.

wire-green
#4b7460
wire-brown
#7f5e3e
wire-black
#484848
wire-white
#e8e8e8

Semantic Role Tokens (Layer 2)

shadcn-compatible tokens consumed by components. Each aliases to a Layer 1 primitive — read live, so primary and ring track the brand accent automatically.

background
var(--background)→ sidebar-grey
foreground
var(--foreground)→ neutral-dark
card
var(--card)→ white
popover
var(--popover)→ white
primary
var(--primary)→ brand-primary
secondary
var(--secondary)→ neutral-light
muted
var(--muted)→ neutral-light
accent
var(--accent)→ neutral-light
muted-foreground
var(--muted-foreground)→ neutral-medium
destructive
var(--destructive)→ status-red-dark
border
var(--border)→ neutral
input
var(--input)→ neutral
ring
var(--ring)→ brand-primary

Typography

App-specific scale. Max 20px, min 10px. Default body 13–14px. Tight tracking and dense line-heights keep layouts compact and utility-focused. Landing page typography is defined separately and does not share this scale.

Font stacks

Loaded via next/font in src/app/(frontend)/layout.tsx.

--font-sansGeist — default body & UI text
--font-monoGeist Mono — code, tokens, measurements
--font-eyebrowMagdaClean — display / eyebrow accents

Headings (14–20px)

Page title at 20px (max). Drop to 18 / 16 / 14 for nested hierarchy. Tight negative tracking at larger sizes.

Heading / xl (page title)20px · Semi Bold · -0.4px
Clients
Heading / lg (detail title)18px · Semi Bold · -0.36px
Acme Holiday Decor
Heading / md (section title)16px · Semi Bold · -0.32px
Contact Information
Heading / sm (card title)14px · Semi Bold
Active Quotes

Body (12–14px)

Default body sits between 13 and 14. 12 is reserved for secondary / metadata text.

Body / md (default)14px · Regular
Multi-team scheduling. One calendar shows every crew, every job, every day.
Body / sm (compact)13px · Regular
Draft · Created 2h ago · 3 line items · $4,200 estimated
Body / xs (secondary)12px · Regular
Last synced 2 minutes ago · Field created via mobile

Labels & captions (10–11px)

Uppercase eyebrow for section headers. Small field labels. Metadata/captions at the floor of the scale.

Eyebrow / uppercase10px · Semi Bold · 0.8px
SECTION LABEL
Label / field11px · Medium
Customer Email
Caption / metadata10px · Regular · -0.1px
Auto-synced from Figma variables · 2026-04-17

Monospace (10–11px)

Geist Mono for token names, hex values, code snippets, and measurements.

Mono / token name11px · Regular
--brand-primary
Mono / value10px · Regular
#ff4d00

Radii

Scale from 4px to 26px. Use Tailwind classes: rounded-xs, rounded-sm, rounded-md, rounded-lg, rounded-xl, rounded-2xl, rounded-3xl, rounded-4xl, rounded-full.

Scale

Visual comparison of all radius tokens.

radius-xs4px
radius-sm6px
radius-md8px
radius-lg10px
radius-xl14px
radius-2xl18px
radius-3xl22px
radius-4xl26px
radius-full9999px

Common applications

Where each radius is typically applied.

  • radius-xs · tiny elements, inline tags
  • radius-sm · chips, badges, small tags, menu rows
  • radius-md · inputs, form controls
  • radius-lg · default (buttons, cards, popovers)
  • radius-xl · container cards, modal content
  • radius-2xl · list page wrappers, elevated panels
  • radius-3xl/4xl · hero sections, marketing surfaces
  • radius-full · pill shapes (switches, avatars, fully-rounded buttons)

Spacing & Rhythm

Two scales, kept separate. An 8px grid for the space BETWEEN things, and a touch-target scale (24 / 32 / 40) for how tall the things you TAP are. This supersedes the old Brite Design System dashboard scale (4-multiples capped at 24), which is folded in here, conformed.

The core idea — two scales

The mistake to avoid is sizing a control off the spacing scale, or spacing things off the target scale. They do different jobs.

  • Layout spacing — the empty room between things (gaps, padding, margins). Uses the 8px grid.
  • Touch-target size — how tall a control is (button, field, row). Uses the 24 / 32 / 40 tiers (also on the 8px grid).
  • What the grid governs: spacing and element heights. Not widths of fluid elements (cards, containers float with the layout), not type sizes, not radii — those run on their own scales.

Layout spacing — the 8px grid

Every gap, padding, and margin is a multiple of 8 from this scale (the --ws-space-* tokens). No 5, 10, 28, 36. A 4px half-step is allowed only for tiny optical nudges, with a comment.

8px--ws-space-xs
tight — inside a group, icon+text, button gap
16px--ws-space-sm
between sibling groups, card padding, mobile gutter
24px--ws-space-md
between groups, grid gaps
32px--ws-space-lg
between sections, generous group separation
48px--ws-space-xl
major section breaks, section vertical padding (mobile)
64px--ws-space-2xl
section vertical padding (tablet)
96px--ws-space-3xl
section vertical padding (desktop)
128px--ws-space-4xl
outsized hero / landing breaks

Touch targets — 24 / 32 / 40

How tall anything you click, tap, or type into is. Sized by CONSEQUENCE, not frequency: reversible / in-a-list → secondary; navigates / submits / hard-to-undo → primary. The tinted box is the tap area; the glyph inside can stay small (hit-area ≠ glyph).

24px — floornever below this
tap area×example
Absolute minimum (WCAG-AA). Incidental controls only — a close × (a 24×24 square, glyph stays small), a fine-print / legal-row link, an inline icon. We rarely design TO it; it's the guardrail.
32px — secondarythe workhorse
tap areaView detailsexample
Navigational / dense-list controls — footer links, rail rows, breadcrumb, tabs, menu rows, accordion/disclosure rows, filter chips, checkbox/radio (the clickable row, not the box). The dashed outline is the tappable row.
40px — primarymain actions + inputs
tap areaSave changesexample
The screen's main action (submit, save, continue, confirm) AND all form fields (text, search, select). A button and the field beside it end up the same height. Width is whatever the content needs — only the height is the tier.
  • Hit-area ≠ glyph: a 16px icon lives in a 32px padded button. Size the tappable box to the tier; the glyph stays its natural size.
  • Spacing between adjacent targets: ≥ 8px.
  • Uniform across breakpoints: a control is the same size on mobile, tablet, and desktop. The one exception is dense data tables (below).

Vertical rhythm — proximity

Distance encodes hierarchy. Each step OUT in the structure jumps one notch up the 8px scale.

Within a group
Flush — items sit at their tap height with no extra gap (e.g. a footer column's links, fields in one form section).
Between sibling groups
1624 — one footer column to the next, a label group to its input group.
Between major sections
3248 — masthead → links → utility bar; hero → content.
Type-driven density
Row height scales with the text in it — big type earns generous rows, small type gets compact rows. A uniform tall row on small text reads bloated.

Page gutter

The horizontal safe margin so nothing kisses the screen edge. Ramps on the --ws-section-px scale.

16px--ws-section-px-sm
Mobile edge margin — the floor. Non-negotiable.
32px--ws-section-px-md
Tablet.
48px--ws-section-px
Desktop. (Reference pages step further at xl: 64 / 96.)

Special cases

The handful of places that don't take the plain tier rule.

Inline text links
Links embedded inside a running sentence are EXEMPT from the target floor — they ride line-height + text spacing, not 24/32/40. (The one place small is correct.)
Fine-print / legal rows
A divider-separated legal row (Privacy · Terms · …) stays visually small, but each link gets the 24px floor via padding. Wraps cleanly at narrow widths (no dividers on wrap).
Dense data tables
A desktop/tablet density surface — rows follow density (mouse precision, compact is good). Below 768px the table reflows to STACKED CARDS, so a dense tappable row never lives on a touch screen. This is the only place a control gets tighter on desktop.
Checkbox / radio
The box glyph stays small (~16px); the clickable target is the whole label-row at 32px.

Breakpoints

Where layout reflows.

  • Mobile < 768 · Tablet 768–1023 · Desktop 1024–1279 · Large ≥ 1280
  • At each step: nav reflows (rail ↔ drawer below 768), column counts drop (4→2 at 1024, 2→1 at 768), dense tables → cards below 768, type scales per the ramp.

Rules of thumb

Quick checks for anything new you build.

  • Spacing not a multiple of 8? It needs a reason you could say out loud (optical nudge, fixed asset, hairline) — otherwise snap it.
  • Anything tappable gets a tier: 40 if it's the main/consequential action or a field, 32 if it's navigational or in a dense list, 24 only for incidental.
  • Element heights snap to the grid; widths float, type and radii have their own scales — don't force those onto 8s.
  • Same control = same size at every breakpoint. Only dense data tables relax on desktop (and reflow to cards on mobile).
  • Make the tap area the tier, not the glyph — pad small icons up rather than growing them.

Layout Grid

A responsive column system for arranging a page, a dashboard, or a form. It reflows by its container, and it comes in two modes for the two jobs it actually does. It is a pattern to build around when it helps, not a container everything must use. 12 columns because 12 splits cleanly into halves, thirds, quarters, and sixths.

Two modes, one grid

Pick the mode by the job. The rule is short: cards use Dashboard, pages use Content.

Dashboard (default)

Dense cards. Stacks 1-up on a phone, 2-up on a tablet, opens to the full 12 on desktop. Use it for KPI tiles, lists, charts, and the asymmetric 9/3 card layouts.

Content

Full pages. Holds 12 columns at every size; only the columns resize, the count never changes. Use it for marketing and editorial layouts. On a phone the 12 columns get narrow, so reach for this when that is what you want.

Both mirror grids already shipping across Brite, so the system reflects how layouts are really built. Flip between them live in the Responsive Preview tool, at any device width.

Dashboard mode: the canonical card rows

On desktop the 12 columns carry a small set of canonical, asymmetric rows (they each sum to 12). 6+6 is deliberately avoided: symmetric splits read as two things fighting for attention. Prefer the 9/3 with a primary lane and a sidebar lane. On smaller screens these restack to 2-up, then 1-up.

9 / 3 · primary + sidebar
span 9
span 3
6 / 3 / 3 · primary + secondary + sidebar
span 6
span 3
span 3
5 / 4 / 3 · asymmetric pair + sidebar
span 5
span 4
span 3
3 / 3 / 3 / 3 · four-up KPI row
span 3
span 3
span 3
span 3
2 x6 · dense tile row
span 2
span 2
span 2
span 2
span 2
span 2

Content mode: 12 columns that only resize

The same split holds its shape at every size; only the column width changes. Here is an 8 / 4 content + rail split shown wide, then narrow, to show the count never drops.

8 / 4 · wide (desktop)
span 8
span 4
8 / 4 · narrow (phone): still 8 / 4, just tighter
span 8
span 4

Spec & tokens

A few shared values define the grid; change a token in one place and every instance follows.

ModePhoneTabletDesktopGap
Dashboard1 col2 col12 col12px
Content12 col12 col12 col16 / 24 / 32px
--layout-grid-gutter-sm / -md / -lg16 / 24 / 32px content gutter, stepped by width
--layout-grid-gutter-dense12px dashboard card gap
--layout-grid-max1200px; the grid centers within this width

The gutters are grounded in the shared spacing scale (--ws-space-sm / -md / -lg). The breakpoints are the Tailwind v4 container sizes the grid reads from its own width: tablet at 672px, desktop at 1024px. Outer page margins are deliberately not the grid’s job: a section wrapper sets the edge padding with the shared --ws-section-px tokens (16 / 32 / 48px), while the grid stays focused on columns and centers within --layout-grid-max.

Reference, not a requirement. The grid is here to align to when it helps a page hold together. A layout is free to step outside it when the content calls for something bespoke. Build around it; don’t let it box you in.

Shadows & Effects

Composite tokens that combine color, blur, spread, and offset, applied through the component state machines (buttons, switches).

Button shadow tokens

Applied via .btn-default, .btn-primary, and .btn-fx-secondary state machines.

--btn-shadow-restResting elevation for default buttons
--btn-shadow-hover-glowWhite inner glow on hover (primary button)
--btn-shadow-pressedBlack inner shadow — 'push-in' pressed state
--btn-shadow-secondary-pressedSofter dark-neutral inner shadow for secondary pressed

Gradient overlay

A faint warm-white highlight layered over the brand primary via color-dodge — the soft gloss on the primary button's interactive states. The fill is the brand primary (currently the sandbox green); the highlight is a near-cream white.

--btn-gradient-bglinear-gradient(135deg, rgba(255,233,200,0.16), transparent)+ background-color: var(--brand-primary)+ background-blend-mode: color-dodge

Switch glow

The toggle's own on-state effect — a brand-tinted glow built just for switches, separate from the button tokens above. Derived from --brand-primary so it stays on-palette and recolors with the brand.

--switch-on-glowinset white sheen + brand-tinted ring + soft brand outer haloon background-color: var(--brand-primary)

Light-switch glow

The BriteLightSwitch on-state 'rim glow' — an outer halo, a bright inset rim, and a broad inner bloom over a brand-tinted near-black track. Built from the switch hue tokens so it recolors with the brand. Geometry is the lab reference (a 168px track); the component scales it per size.

--light-switch-glowouter halo + inset rim (--switch-on-rim) + inner bloom (--switch-on-inner)on background-color: var(--switch-on-track)

Disabled pattern

45° diagonal stripes at ~3px pitch. Applied as repeating-linear-gradient.

--btn-disabled-patternrepeating-linear-gradient(45deg, transparent 0 3px, var(--btn-disabled-stripe) 3px 4px)

Accessibility

The floor beneath every other foundation. Rebrand the spacing, color, type, and radii freely — but never cross these lines. They are the minimums that keep the interface usable for people on touch screens, with low vision, with color blindness, or who feel motion strongly. Every rule here is a ratified standard, not a preference.

Read this first

How to treat this section when you customize the system.

  • The other foundation pages describe defaults you may change. This page describes limits you may not.
  • If a brand decision and an accessibility rule collide, the accessibility rule wins. Find another way to express the brand.
  • These are floors, not targets — clearing them comfortably is encouraged. Sitting exactly on the line is the minimum, not the goal.

Touch targets — 24 / 32 / 40, by consequence

Interactive controls come in three sizes, chosen by how consequential the action is — floor, secondary, primary. Critically: the size is chosen by consequence, NOT by device. The same tiers apply on desktop and on a phone; the system never resizes a control just because it detects a small screen.

24px — floor
Low-stakes, dense, repeated: compact chips, ghost icon buttons, dots-menu triggers, breadcrumb links
32px — secondaryButton default
The everyday default: standard buttons, dense-list actions, segmented controls
40px — primary
Consequential & form-critical: primary CTAs, full-width buttons, and ALL form fields (height is the tier; width floats)

The one exception: true data tables may run at desktop density and reflow to stacked cards below 768px. Nothing else resizes by device.

The 24px hit-area floor — “touch padding”

No tappable control may have a hit area smaller than 24×24px — ever. When a control needs to LOOK small (a 16px checkbox, a slider thumb, a chip's remove-✕), you do not shrink the tap zone. You keep the small look and pad the tappable area out to 24px. The padding is invisible; the safe target is real. This is what makes a tiny control reliably tappable with a thumb.

The principle
16px visual control · 24px tappable zone (the dashed area is padding, not a border)

Controls that look small but must still clear 24px:

  • Checkbox & radio — small ~16px glyph; the clickable label-row is padded up to its 32px tier
  • Slider thumb & switch — small visual, 24px grab zone
  • Filter-chip / tag remove-✕, tab action triggers
  • Number-input steppers, collapse carets, resize handles

Rule of thumb: if you can tap it, measure the tappable box — not the ink. If that box is under 24px, add padding until it isn't.

Color contrast — 4.5:1 and 3:1

When a brand recolors the system, these ratios are the line. Text or a control that fails them disappears for low-vision users — and our automated accessibility gates reject it. Check every new color pairing against its background.

≥ 4.5:1
Body text and any text below 24px — the core readability floor (WCAG 1.4.3 AA).
≥ 3:1text + UI
Large text (24px+, or 19px+ bold), plus the boundary of every interactive control, icon, and meaningful graphic (WCAG 1.4.11).
Don't encode meaning in hue alone
A status, a required field, an error — none may be signalled by color only. Pair color with an icon, a label, or text, so it survives color blindness and grayscale.
Exempt
Purely decorative graphics and disabled controls have no contrast minimum.

Visible focus — always, and unstyleable-away

Anyone navigating by keyboard needs to see where they are. Every interactive element keeps a visible focus ring; it may be restyled to match the brand, but it may never be removed.

  • Standardize on the full-opacity focus token (--ring / --focus-ring). Never outline: none without a replacement.
  • Provide a forced-colors fallback: under Windows High-Contrast mode the brand ring may vanish, so a system outline must take over.
  • The focus indicator must itself meet 3:1 contrast against what's behind it.

Respect “reduce motion”

Some people get nauseated or disoriented by movement. When the operating system reports a reduced-motion preference, honor it.

  • A global @media (prefers-reduced-motion: reduce) block strips non-essential transitions and animation to near-instant.
  • Essential motion that conveys meaning (a loading state) may remain, but gently — no large parallax, bounce, or auto-playing movement.
  • Never gate a motion preference behind an in-app toggle only; read the OS setting.

Operability — tracked separately (not yet documented here)

This page covers the VISUAL accessibility floor — what a control looks and feels like. A second, equally important question is whether you can OPERATE it without a mouse.

  • Full keyboard navigation, screen-reader announcements, and accessible names for icon-only controls live in the operability track, which has not been completed yet.
  • When that work lands, its standards will join this page. Until then, treat keyboard + screen-reader support as required-but-undocumented here, and don't assume the visual floors above are the whole of accessibility.

All swatches derive from CSS variables · Source of truth: the design system’s tokens.css.