Brik Design System
Foundation

Color

Semantic color tokens, interaction states, status colors, and the primitive palettes they reference.

Color tokens define the visual identity across all BDS themes. The system separates primitive color scales (raw hex values) from semantic color roles (purpose-bound aliases). Components reference the semantic roles only.

Never use a token outside its semantic category. --text-* is for text. --background-* is for backgrounds. --border-* is for borders. No --text-primary on a background-color. The cascade is a contract — bypassing it leaks brand drift everywhere it isn't caught.

Click any swatch to copy var(--token-name) to your clipboard.

Page and surface

page-primary
...
page-secondary
...
page-brand-primary
...
surface-primary
...
surface-secondary
...
surface-muted
...
surface-brand-primary
...

Background

background-brand-primary
...
background-brand-secondary
...
background-primary
...
background-secondary
...
background-inverse
...
background-input
...
background-muted
...

Text

Aa
text-primary
...
Aa
text-secondary
...
Aa
text-muted
...
Aa
text-brand-primary
...
Aa
text-inverse
...
Aa
text-on-color-dark
...
Aa
text-on-color-light
...

Border

border-primary
...
border-secondary
...
border-muted
...
border-brand-primary
...
border-input
...
border-inverse
...

Interaction states

Hover, press, and disabled. These replace filter: brightness() and opacity hacks — the proper tokens compose with the rest of the cascade. These are the swatches; for when to reach for a solid state-pair token versus a composable overlay, see Interaction States.

background-brand-primary-hover
...
background-brand-primary-pressed
...
background-primary-hover
...
background-primary-pressed
...
background-secondary-hover
...
background-secondary-pressed
...
background-disabled
...
Aa
text-disabled
...
border-disabled
...

Status

Canonical Figma tokens for positive, negative, and warning states. Background variants are saturated; surface variants are subtle tints.

background-positive
...
background-negative
...
background-warning
...
surface-positive
...
surface-negative
...
surface-warning
...
Aa
text-positive
...
Aa
text-negative
...
Aa
text-warning
...
border-positive
...
border-negative
...
border-warning
...

The info signal — blue, and one of the five valence values. It came from Figma under the status- segment until #1959 freed the name: the gray family that used to hold --*-info is now --*-neutral, so info means blue and only blue. The --*-status-info* spellings still resolve as deprecated aliases for one minor (#1958 deletes them). There is no --border-info — the gray one retired with the rename and blue has no border counterpart.

background-info
...
surface-info
...
Aa
text-info
...

The neutral intent family — gray, Figma-sourced across all four purposes since #1959. Note the purpose split the rename made honest: --background-neutral is the saturated charcoal, --surface-neutral the subtle tint. A gray Badge or Tag chip wants --surface-neutral.

background-neutral
...
surface-neutral
...
Aa
text-neutral
...
border-neutral
...

Hues with no valence — purple and orange are hue names, not valence values, so ADR-033 § 1's five words have no slot for them. They belong to the hue-source scope documented under Accent below and are Figma-sourced there, so #1958 retired the --background-status-{purple,orange} spellings into it rather than amending the ADR.

background-accent-purple
...
background-accent-orange
...

Accent

The scope-stable accent — one brand highlight that stays fixed regardless of [data-audience] scope (brik fills it from tan; each Brand Kit fills its own). Use it for audience-independent affordances such as an active carousel dot or an eyebrow rule.

It is distinct from two neighbours it is easy to confuse:

  • --*-brand-primary re-tints per audience scope (Vale: land → gold, healthcare → olive). The accent must not shift, so it is its own role — not a brand-tier token.
  • The per-color status accents (--background-accent-blue, --text-accent-green, …) encode state/category, not the brand highlight.
page-accent
...
surface-accent
...
background-accent
...
Aa
text-accent
...
border-accent
...

Brand Kits fill --*-accent per client (brik → tan, Vale Partners → gold). Because the audience scopes re-bind only the brand-primary tier, the accent stays constant across every [data-audience] — that scope-stability is the whole point of the role.

The per-hue accent set

Separate from the scope-stable accent above, accent-{hue} names a category color — six hues for tagging things that have no valence. A calendar's event categories, a chart series, a tag palette.

The set is mirrored 1:1 from the ❖ Brik Brand Kit Figma library, which is the source of truth for it. Five hues fill from the system palette — that is what keeps accent-purple the same purple whichever client theme is loaded — and the purpose split is the one the valence families use:

PurposeFills fromUse for
--text-accent-{hue}--color-system-{hue} — the vivid stepColored label or icon on a neutral surface
--background-accent-{hue}--color-system-{hue} — the vivid stepA saturated fill: a dot, a solid chip, a chart mark
--border-accent-{hue}--color-system-{hue} — the vivid stepA hairline or left-rule keyed to the category
--surface-accent-{hue}--color-system-{hue}-light — the pale tintA wash behind content: a subtle chip, a callout, a row highlight
--page-accent-{hue}--color-system-{hue}-light — the pale tintA full-page or section-wide wash
surface-accent-yellow
...
surface-accent-blue
...
surface-accent-green
...
surface-accent-purple
...
surface-accent-orange
...

The grid is deliberately not square, because canon isn't. Two gaps are mirrored from Figma rather than filled in:

  • --page-accent-{hue} exists for yellow, blue, orange and red only — there is no page-level green or purple wash.
  • red is the exception to the system-palette rule. All five of its purposes alias the brand poppy (#e35335), not --color-system-red. So --surface-accent-red is a saturated fill, not a pale tint like the other five — it is gated AA rather than AAA in the matrix, and small body copy does not belong on it. Avatar's red initials fill is the shipped consumer.

Two consequences worth knowing before you reach for one:

  • Put dark ink on a surface-accent-{hue}, not --text-primary. The tints hold the same pale value in both themes while --text-primary flips to near-white in dark, which lands at 1.2:1. --text-on-color-light is the theme-stable pairing and clears AAA on all five tints (AA on the red fill) — the six accent rows in the matrix gate it.
  • --text-accent-{hue} is not a foreground for --surface-accent-{hue}. Same-hue vivid-on-pale only clears AA for blue; the other four fall below the 3:1 floor (#2080).

--surface-accent-{hue} shipped in #2065 pointing at the Brand Kit mid-tone ramp (--color-{hue}-light, itself a deprecated alias for the -500 step). #2066 re-pointed it onto the system tint, then reconciled the whole family against the Figma Brand Kit — adding --border-accent-{hue}, --page-accent-{hue} and --surface-accent-red, and moving accent-red onto the brand poppy. Dark ink on the purple tint went from 5.68:1 to 12.87:1 in the process.

Presence

Online/offline indicator tokens used by Avatar and Dot components.

presence-online
...
presence-away
...
presence-busy
...
presence-offline
...

Color modifiers

Within the color role/modifier vocabulary, four concepts share light/dark wording but answer different questions. Pick the wrong one and it compiles — silently wrong.

on-color

The legible foreground for content resting on a colored, branded, or dark surface. Grayscale, brand-agnostic, consumed by component variants (Button on-color, the Tab-bar onColor mode).

--bds-text-on-color-light   /* dark FG  — for a light colored fill */
--bds-text-on-color-dark    /* white FG — for a dark colored fill  */

on-color is the whole role; light/dark names the fill tone it targets. It never carries a hue.

-on-light and -on-dark

For a token whose hue is already named (a service line, a brand scope), pins the value for a known, fixed backdrop tone.

--bds-background-service-brand-on-light   /* brand fill, on a light surface */
--bds-text-service-brand-on-dark          /* brand text, on a dark surface  */

The -color- infix is dropped because the scope (service-brand) already implies color. The leading on- marks context — distinct from tone.

Two on forms, on purpose. on-color-* is a complete neutral role; -on-light/-on-dark is a context modifier on a colored scope. Same word, different slot in the formula — not an inconsistency, and not a rename candidate.

-inverse

The hard reverse across light/dark mode (lightest ↔ darkest), mirroring the reverse of a -primary token. A theme-level concept — --background-inverse, --text-inverse. A service line normally adapts to a known backdrop through its -on-light/-on-dark context tokens, not a per-line inverse — with one carve-out: --surface-service-{line}-inverse (surface purpose only) flips white in light → {hue}-darkest in dark, for service-identified cards that must read as neutral chrome in light mode but carry the line's deep tint in dark. See ADR-020. No per-line inverse exists on background/text/border.

-light and -dark

The pastel or deep shade of a surface (--surface-service-brand-light = pastel). Tone shifts the shade; context targets the backdrop. Tone is why scoped context had to be spelled -on-light/-on-dark — -light/-dark was already taken.

You want…Token
Legible text/icon on any colored or dark fill--{purpose}-on-color-{light|dark}
A service hue tuned for a known backdrop--{purpose}-service-{line}-on-{light|dark}
An element that hard-flips with the theme--{purpose}-inverse
A pastel/deep variant of a service surface--surface-service-{line}-{light|dark}
A service card surface: neutral white in light, deep tint in dark--surface-service-{line}-inverse

The 11-step scale

Every family is an 11-step numeric ramp — 50, 100, 200, … 900, 950, lightest to darkest. 50 and 950 bracket the ends; the middle nine carry the working values. The scale is generated, not hand-tuned per family: scripts/generate-color-ramps.mjs pins the six historical swatches as anchors and interpolates the other five stops as OKLCh midpoints, writing design-tokens/color-ramps.generated.json (#1737). Shipped in v0.152.0.

Reach for a stop by its number. --color-{family}-{stop} is the Primitive-tier name; components still consume Semantic tokens only.

Deprecated 6-step aliases

The old ladder (lightest → lighter → light → dark → darker → darkest) still resolves — #1739 aliased each name onto its stop so nothing broke on upgrade. The aliases are deprecated and land at these positions in every family:

Deprecated aliasResolves toAnchor?
--color-{family}-lightest100anchor
--color-{family}-lighter300anchor
--color-{family}-light500anchor
--color-{family}-dark700anchor
--color-{family}-darker800anchor
--color-{family}-darkest950anchor

Note the ladder was never evenly spaced: dark and darker sit one stop apart (700/800) while darker to darkest skips 900. Prose that said "one step deeper" under the 6-step names does not translate to one numeric stop — count stops, don't translate names.

Don't write a deprecated alias into new code. npm run lint-mdx-tokens fails the build on one in a docs code block or table, and lint-tokens Rule 12 covers component CSS. Consumer migration is tracked in #1740; the aliases are removed only once it closes.

The two new stops that matter

Five stops per family are new. Two of them close specific, filed gaps:

StopValueWhy it exists
--bds-color-poppy-600#c944284.84:1 on white — clears AA (4.5:1). The near-Poppy step ADR-015/ADR-016 said did not exist. Not the button fill: ADR-015 (2026-10-22) keeps buttons on vibrant Poppy
--bds-color-grayscale-400#b0b0b0Fills the #828282 → #e0e0e0 gap from #1726. It is not #bdbdbd — no even OKLCh ramp lands there, so consumers that hand-pinned #bdbdbd are migrating to this value, not matching it

Colour families by purpose

Four colour groups sit beside the numeric ramps. Each has one purpose, declared in tokens/naming-grammar.json ($purposes). Pick by purpose, not by how the hue looks.

FamilyPurposeRule for agents
systemMessaging. Error, success, warning, neutral and info colours for Alert, Badge and Toast, plus link blue and accent hues.Never read --bds-color-system-* in a component. Read the Semantic that aliases it, such as --bds-text-error. lint-tokens rule 18 fails the build otherwise.
socialThird-party brand marks. YouTube, X/Twitter, Facebook, Instagram, LinkedIn, Yelp, Google, Apple, Bing and TikTok, for SocialIcon.These are the platforms' colours, not Brik's. Never use one as a UI accent.
annotationAnnotation tooling. Mark-up colours for design and doc annotation.Never in product UI.
grayscale-black, grayscale-whiteBrand-guide-invariant constants. Foundation black and white that every brand guide carries.A brand kit never rebinds them.

transparent and lightbox are utility constants that keep their system names as grammar namedExceptions. They are not messaging colours. Full rules: Token Anatomy.

Primitives

Raw color scales from the Figma Brand Kit, rendered from color-ramps.generated.json. Components reference these via semantic tokens — never directly. The primitives change rarely; the semantic mappings change per theme.

Grayscale

--color-grayscale-50
#f8f8f8
--color-grayscale-100
#f2f2f2
--color-grayscale-200
#e9e9e9
--color-grayscale-300
#e0e0e0
--color-grayscale-400
#b0b0b0
--color-grayscale-500
#828282
--color-grayscale-600
#6e6e6e
--color-grayscale-700
#5a5a5a
--color-grayscale-800
#333333
--color-grayscale-900
#272727
--color-grayscale-950
#1b1b1b

Poppy (brand primary)

--color-poppy-50
#fff7f5
--color-poppy-100
#ffefeb
--color-poppy-200
#ffccc0
--color-poppy-300
#ffa693
--color-poppy-400
#f27e66
--color-poppy-500
#e35335
--color-poppy-600
#c94428
--color-poppy-700
#b0351b
--color-poppy-800
#7d1d09
--color-poppy-900
#631503
--color-poppy-950
#4a0d00

Tan

--color-tan-50
#f8f7f5
--color-tan-100
#f1f0ec
--color-tan-200
#e0ded8
--color-tan-300
#cfcdc5
--color-tan-400
#bebbb2
--color-tan-500
#adaaa0
--color-tan-600
#9c998e
--color-tan-700
#8b887d
--color-tan-800
#69665c
--color-tan-900
#58554c
--color-tan-950
#47453c

Orange

--color-orange-50
#fff4ee
--color-orange-100
#ffe8dc
--color-orange-200
#ffccb6
--color-orange-300
#ffad92
--color-orange-400
#f48865
--color-orange-500
#e76134
--color-orange-600
#cd5127
--color-orange-700
#b4411a
--color-orange-800
#812608
--color-orange-900
#5f1b02
--color-orange-950
#3f1000

Yellow

--color-yellow-50
#fffff0
--color-yellow-100
#fffee1
--color-yellow-200
#fef6c7
--color-yellow-300
#ffecac
--color-yellow-400
#f9e08a
--color-yellow-500
#f4d364
--color-yellow-600
#dabb53
--color-yellow-700
#c1a443
--color-yellow-800
#8e7729
--color-yellow-900
#795e1f
--color-yellow-950
#634716

Green

--color-green-50
#fcfff9
--color-green-100
#f8fff3
--color-green-200
#eaffda
--color-green-300
#daffc0
--color-green-400
#ccffa8
--color-green-500
#bcff8c
--color-green-600
#abec7c
--color-green-700
#9ada6c
--color-green-800
#71a74a
--color-green-900
#437f4e
--color-green-950
#2a5542

Blue

--color-blue-50
#fcfeff
--color-blue-100
#f8fdff
--color-blue-200
#d6f0fa
--color-blue-300
#b2e3f5
--color-blue-400
#a0cfe0
--color-blue-500
#8ebbcc
--color-blue-600
#7da8b7
--color-blue-700
#6c95a3
--color-blue-800
#4d6e7b
--color-blue-900
#3f5b66
--color-blue-950
#314952

Purple

--color-purple-50
#f8f7fb
--color-purple-100
#f2f0f7
--color-purple-200
#dad0f2
--color-purple-300
#c4b0eb
--color-purple-400
#b19dd6
--color-purple-500
#9e8bc2
--color-purple-600
#8b7aad
--color-purple-700
#796999
--color-purple-800
#574a71
--color-purple-900
#403758
--color-purple-950
#2b2440

Pink

--color-pink-50
#fff4fa
--color-pink-100
#ffe9f6
--color-pink-200
#ffcaea
--color-pink-300
#ffa8dd
--color-pink-400
#ff8ad1
--color-pink-500
#ff67c3
--color-pink-600
#ff4db7
--color-pink-700
#ff25aa
--color-pink-800
#d20080
--color-pink-900
#af006b
--color-pink-950
#8e0057

On this page

💬