Colour
One indigo accent, a six-step ink ladder, and four status hues that carry no planning content.
The accent
Code
<GdButton variant="primary">Primary action</GdButton>
<GdButton variant="ghost">Ghost / link</GdButton>
<GdChip tone="accent">Selected</GdChip>
<GdBadge :value="5" />--gd-accent#4740DB#5F57E3filled primary buttons, the selected tab rule, the focus ring AA-nontext on --gd-surface
--gd-accent-hover#362FBF#6D66E8hover on a filled accent surface
--gd-accent-pressed#2F2AA3#4740DBpressed
--gd-accent-ink#362FBF#A9A4F2accent TEXT on a light ground — links, ghost buttons, the active contents item, and the label of a latched control on `accent-wash`. The accent proper is 1.9:1 on charcoal and cannot be a link colour in the dark, which is why this forks from `accent` AA on --gd-surfaceAA on --gd-accent-wash
--gd-accent-wash#EEF0FF#3B3866the accent surface — selected rows, count badges, accent chips
--gd-accent-wash-soft#F7F7FD#1C1B2Ethe softest accent ground, for editorial callouts where the full wash shouts
--gd-accent-tintrgba(71, 64, 219, .25)rgba(140, 134, 240, .3)the border of an accent-washed thing
--gd-accent-ringrgba(71, 64, 219, .14)rgba(140, 134, 240, .22)a soft accent halo — the selected parcel's glow on the map, and emphasis behind a chosen thing. NOT a focus indicator: focus is `border-focus-ring` everywhere, because a 14%-alpha wash cannot carry WCAG 1.4.11's 3:1
Ink ladder
--gd-ink#292A2E#F2F2F4headlines and primary text AA on --gd-surface
--gd-ink-body#3D4047#C9CBD2body copy — the readable default for prose AA on --gd-surface
--gd-ink-secondary#5C6068#9A9EA8supporting sentences beneath a title AA on --gd-surface
--gd-ink-muted#6B6E75#8A8E96eyebrows, captions, hints, units — the smallest type that still carries meaning, so it must still clear AA AA on --gd-surface
--gd-ink-faint#8A8E96#74777FLARGE type and non-text marks only (≥18.66px, or ≥14px bold). Never prose — see the header note AA-large on --gd-surface
--gd-ink-placeholder#9A9EA8#5C606Ba disabled control's label, or a placeholder that only repeats its visible label. Fails AA by design and may carry nothing a user needs
--gd-ink-inverse#FFFFFF#F2F2F4text on `surface-inverse` AA on --gd-surface-inverse
--gd-ink-on-solid#FFFFFF#101116text on `surface-solid` — the neutral CTA's label AA on --gd-surface-solid
--gd-ink-on-accent#FFFFFF#FFFFFFtext on a filled accent surface — white in both themes AA on --gd-accent
--gd-ink-on-accent-body#E9E8FB#ECEBFCbody copy on a filled accent band (white at 88%, composited) AA on --gd-accent
--gd-ink-on-accent-muted#CBCAF5#DCDAF9LARGE type (≥18.66px, or ≥14px bold) and non-text marks on an accent band — the floor of the ladder (white at 72% / 78%, composited) AA-large on --gd-accent
Surfaces & lines
--gd-surface#FFFFFF#17181Ethe default ground for cards, panels and rows
--gd-surface-page#F4F4F2#101116the page behind everything — warm off-white in the light, the lowest charcoal in the dark
--gd-surface-subtle#FBFBFC#1D1F26table headers and glyph-tile grounds: a surface that is not quite the card it sits on
--gd-surface-raised#FFFFFF#1D1F26a surface lifted off its ground — a floating pill, a popover, a menu. In light the shadow carries it and this equals `surface`; in dark the surface itself has to
--gd-surface-sunken#F4F4F7#101116recessed groups — segmented tracks, icon tiles, layer lists. Sunken is the one people get wrong: a GROUP of things is recessed, not raised
--gd-surface-hover#FAFAFB#24262Epointer over a row. Hover is a SURFACE change, never a shadow change — a list that lifts under the pointer shimmers
--gd-surface-pressed#F4F4F7#2B2D36the moment of the click
--gd-surface-selected#EEF0FF#3B3866the chosen row or option
--gd-surface-disabled#F0F1F4#212329an inert control's fill
--gd-surface-inverse#14151A#24262Etoasts, code blocks, the ink CTA band — a surface that is dark in BOTH themes
--gd-surface-solid#14151A#F2F2F4the neutral high-emphasis CTA fill — near-black on a light page, near-white on a dark one. NOT `surface-inverse`, which stays dark in both
--gd-surface-map#F0EEE9#22242Bthe basemap ground. Deliberately not black in the dark, so parcel strokes and overlay fills keep the relationship to their ground that they have in daylight
--gd-surface-scrimrgba(20, 21, 26, 0.42)rgba(0, 0, 0, 0.55)the dimmer behind modal surfaces — dialog, drawer, palette. Deeper in the dark, because 42% charcoal over a charcoal page barely reads as a veil
--gd-surface-on-accentrgba(255, 255, 255, .10)rgba(255, 255, 255, .10)a card or row sitting ON a filled accent band
--gd-surface-on-accent-strongrgba(255, 255, 255, .16)rgba(255, 255, 255, .16)a chip on a filled accent band — one step up from the card it sits in
--gd-line#E8E9ED#2B2D36THE border — cards, inputs, panels
--gd-line-hairline#F0F1F4#212329a divider INSIDE a container that already has a border
--gd-line-soft#ECEEF1#212329the second line weight — nested rules, tracks
--gd-line-strong#D7D8DD#3A3D48a firmer boundary, and every dashed edge (upload zones, clear-space diagrams)
--gd-line-hover#C9CCD2#454955border under the pointer
--gd-line-on-accentrgba(255, 255, 255, .18)rgba(255, 255, 255, .18)the border of a card sitting on a filled accent band
--gd-line-on-accent-hairlinergba(255, 255, 255, .05)rgba(255, 255, 255, .05)the survey grid drawn over a filled accent band — the faintest mark the system has
--gd-line-control#8A8E96#6E7280a boundary that IS the control — an unchecked box, an off switch track. WCAG 1.4.11 applies and the design's #E0E1E6 is 1.2:1 here: an off switch drawn in it is invisible until it is already on, so you cannot tell the control exists. Honest caveat: iOS and Material ship light tracks too and the knob's shadow is SOME affordance — 3:1 is the defensible reading, not the only one AA-nontext on --gd-surface
Rules
- Use
--gd-accentfor fills — filled buttons, the selected tab rule, the focus ring. Never for type. - Use
--gd-accent-inkfor accent type — links, ghost buttons, the active contents item, the label of a latched control on--gd-accent-wash. It is the only accent value that may be set in text. - Do not add a second accent. That is a design change, not a styling choice.
- Change the SURFACE for hover and pressed, never the shadow. A list row darkens; it does not lift.
- Reach for
--gd-surface-raisedwhen a thing genuinely floats — a pill, a popover, a menu. Reaching for--gd-surfaceinstead lands at 1.00 against its own ground in the dark. - Use
--gd-surface-sunkenfor a GROUP — a segmented track, a layers list, an icon-tile grid. A group of things is recessed, not raised. - Use
--gd-surface-solid, not--gd-surface-inverse, for a neutral CTA.solidflips with the theme;inverseis deliberately dark in both. - Never set type in a status solid. Each hue's
inkis the only member of the quartet that may carry a sentence. - Take the whole quartet or none of it — solid, ink, wash, tint travel together.
- Never use
--gd-ink-faintfor prose. Large type (≥18.66px, or ≥14px bold) and non-text marks only. - If a label needs to recede, reduce its size or weight — never its contrast.
Behavior & Anatomy
One accent
Exactly one. Indigo was chosen because it collides with no status hue and no zone hue on the map. A second accent is a design change, not a styling choice.
The chrome around it is achromatic on purpose: the map already carries a council's zone cartography, so every hue spent on chrome is a hue taken away from something that means something.
Why the accent forks into a fill and an ink
#4740DB on charcoal is 1.9:1 and cannot be a link colour, so the dark theme needs a lifted step for text while the filled button keeps a bolder indigo underneath white. Two tokens rather than one is what that fork costs.
Why hover and pressed are surface changes
Hover and pressed are SURFACE changes, not shadow changes. A list row darkens; it does not lift. That keeps rows quiet in a long list and stops a panel shimmering as the pointer crosses it.
Why --gd-surface-raised exists
Elevation is carried by different things in the two themes, and only one of them is a shadow. On a light ground a black shadow does the whole job, so the raised surface stays white and this token is a no-op. On a dark ground it does not: --gd-shadow-md is rgba(0,0,0,.45), which over near-black is very nearly nothing — elevation in the dark is carried by the SURFACE getting lighter as it rises. This token is what says that. Without it a floating component has only --gd-surface to reach for, which is 1.00 against its own ground.
The step is chosen against the light theme's own precedent: a white pill on the page tint is 1.101, and #1D1F26 on #17181E is 1.076 — the same relationship, not a new one.
Why solid is not inverse
--gd-surface-inverse does not flip and must not: a toast, a code block and the footer band are dark in both themes on purpose, and a footer that turned white at night would be the brightest thing on a dark page. GdButton variant="solid" borrowed that token anyway, and in the dark theme the result was a #24262E pill on a #17181E bar — 1.3:1, which fails WCAG 1.4.11 for a control's boundary and, more plainly, meant the landing page's one neutral call to action had no visible edge. "Inverse of the page" is what --gd-surface-solid actually means, so it is near-black on a light page and near-white on a dark one.
Where contrast decides the value
Three tokens are pinned by measurement rather than by eye, and each one is a place where the pleasing value and the passing value are not the same. The measurement wins; the rule is that a label recedes by size or weight, never by contrast.
Caution: Eyebrows and captions do not go paler than 5:1
--gd-ink-muted is #6B6E75, 5.11:1 on white. The obvious lighter grey #8A8E96 is 3.29:1, which is under AA for text that is not large — and eyebrows are 10–11px. That value exists as --gd-ink-faint, restricted to large text and non-text marks. Caution: The off switch track is 3.29:1, not a hairline
--gd-line-control carries the OFF state of every switch. A pale #E0E1E6 track is 1.2:1 against the surface: the control is invisible until it is already on, so you cannot tell it is there — which WCAG 1.4.11 does not allow for something that is the control. Honest caveat: iOS and Material ship light tracks too, and the knob's shadow is some affordance — 3:1 is the defensible reading, not the only one. The third is caution. #C9A21A is 2.41:1 on white, so the caution solid is the darker #A8850F; #C9A21A appears only as a callout's left rule, where the heading ink beside it carries the meaning.