website/src/design/tokens.css,
the Agent UI’s CSS custom properties, and
tui/internal/ui/theme/theme.go.
Implements milestone 0 of the UI redesign plan proposed in
amd/gaia#3803.
Roles, not numbers
A surface asks for a role — canvas, primary text, accent — and never for a hex literal. That is what keeps a value tuned against one background from going unreadable on another, and it is why every surface routes colour through tokens rather than inline hexes.
Most of the screen is neutral. Colour marks the prompt, focus, selection, the one
primary action — and, in rendered markdown, headings and inline code. Nothing
else; ordinary prose stays uncoloured.
Headings and code spans are structure, not emphasis. A reader scans for them
rather than reading them, there are few per screen, and the accent is what makes
them findable at a glance. Emphasis inside a sentence is the opposite: accent on
every bold word turns running prose into the speckle the palette reserves for the
prompt and the one primary action. That is why the TUI’s markdown renderer paints
strong in primary text and leaves the accent to the two structural marks
(tui/internal/ui/components/markdown_style.go).
The terminal has no canvas, and its status band is not the raised surface
The first two rows are the Agent UI’s grounds (its light theme substitutes white; see Surface mappings). A terminal’s background belongs to the user’s emulator, so the TUI sets none — it paints foregrounds onto whatever the reader already chose. The status band is the one place the TUI fills a background, and it uses its own pair (#C2B8A8 light, #615D69 dark) rather than the raised surface above. The
raised-surface values would vanish there: pale stone measures 1.25:1 on a
white terminal and warm slate 1.32:1 on a black one, both under the 1.5:1
chrome floor a band has to clear to be seen at all. Those two values are cards on
a canvas GAIA controls; the band sits on a background it does not. What carries
across is the cast — warm stone on light, violet graphite on dark — not the
literal. See SurfaceBG in tui/internal/ui/theme/theme.go.
Why copper is two different values, and one of them is not the obvious one
The dark copper#EBA474 is the identity colour. The light value is not a
simple darkening of it, because the light accent has to survive a constraint the
dark one does not.
macOS Terminal is 256-colour only, so every value is rounded to the nearest of
256 fixed indices before it reaches the screen. Among the colours dark enough to
hold 4.5:1 on every light terminal background the TUI measures — pure white
through Solarized Light #FDF6E3 — that cube offers exactly one saturated warm
entry: #875F00, and #875F00 is already where Warning and Selected land. A
light copper that rounds there stops being a distinct colour and silently becomes
the warning colour.
The qualifier is load-bearing. Against pure white alone the cube offers two:
#AF5F00 reads 4.71:1 there and 4.51:1 on One Half Light, then drops to 4.37:1
on Solarized Light. Judging the palette against white only is how a value that
fails on a real terminal passes review.
#9A4930 is the most saturated light copper that rounds to #875F5F instead: a
warm near-neutral. On a 256-colour light terminal GAIA’s accent therefore reads
as a dark warm grey rather than copper. That is the documented, intended
degradation — the palette’s rule is that a colour may fade toward neutral, but
may never land on a different saturated meaning. Truecolor terminals and the
Agent UI render #9A4930 as copper.
Using the same #9A4930 in the Agent UI as in the terminal is deliberate: the
surfaces previously carried three near-but-unequal golds, and one shared literal
is what makes “the same assistant” true rather than approximate.
The website
amd-gaia.ai is where people decide whether to install GAIA, not where they work with it, so it carries AMD’s brand rather than the assistant’s working palette. It is dark-only: black ground, white type, AMD red as the single accent, and AMD orange for release candidates and nothing else.
AMD red
#ED1C24 measures 4.01:1 on the raised surfaces, below the 4.5:1 text
floor, so it is held to the 3:1 large-text floor and used only at display size.
Text-size red is lifted to #FF4B51 instead. The filled button is
deepened to #D3121A, which carries white at 5.42:1; the brand red itself
carries white at only 4.38:1.
Red is the brand here, not a status. That does not break “a hue that carries a
status meaning is not available for decoration”: the site shows no status
outside a code panel, and inside one, danger keeps its own #F2787C, a
distinctly pinker red than the brand.
What still matches the other two surfaces: the roles and their floors, and the
status hues inside a code panel (cross-surface.test.ts holds green, amber and
danger equal to the Agent UI’s). What does not: the accent, the grounds, and the
typography — Antonio, a tall condensed face, for headings, buttons and figures,
and Titillium Web for body text (it stands in for Klavika, AMD’s licensed face).
The AMD logo is never drawn, imitated or used as a pattern; “AMD” appears as text.
Motion is allowed on the website, within limits. The site has one ambient
layer: a canvas of stars streaking past, which arrives at speed, settles to a
cruise, and speeds up as the visitor scrolls. On top of it go entrance and
scroll reveals, a headline that resolves out of noise once on load, a red
glow on the primary action and on the hero’s red phrase, and a breathing dot on the release-candidate line.
Nothing else moves. Every one of these stops under prefers-reduced-motion,
which gets a single static frame, and the footer carries a control that pauses
the background and the dot for good. Below the hero the field dims, so body text
never sits on a full-strength streak. The “no glow” and
“no simulated typing” rules below still govern the Agent UI and the TUI.
Semantic colours keep their conventional hues
The brand motif moves off green; status does not. Success stays green, warning amber, danger red, information blue. A terminal that paints a failed check in copper to stay on-brand is a worse terminal. The plan’s “no green motif” governs the accent, mascot, and brand marks — not whether a passing check looks like a passing check. Status must never depend on colour alone. Every state carries a word or an icon as well, so the palette degrading to neutral on a limited terminal costs polish, never meaning. The reservation runs the other way too: a hue that carries a status meaning is not available for decoration. Green, amber, red, and blue are spoken for, which leaves no palette to give each tool or file type its own colour — so those stay neutral and are told apart by their icon. The alternative is a green badge beside a red failure icon in the same row. One recognised exception: a link stays blue. That convention is older than this palette and readers arrive already knowing it, so the blue is carrying meaning rather than decorating — which is why the TUI’s markdown links resolve to the information role rather than to neutral text.Contrast floors
Each surface enforces these in its own test suite; the floors are the same everywhere so a role cannot be legible in the browser and marginal in the terminal.
Measured minimums for the roles above, against their own canvas and raised
surface: 6.62:1 dark, 4.97:1 light. Accent-as-fill carries white at 6.23:1.
The TUI additionally re-checks every floor against ten real terminal
backgrounds and against the ANSI-256 value that actually lands, because a
colour that clears its floor in truecolor can drop below it once rounded. See
tui/internal/ui/theme/contrast_test.go.
Surface mappings
The Agent UI’s light theme is neutral, not porcelain. Its light canvas is
white (
#FFFFFF) with faintly cool greys for raised surfaces, rules and hover,
because the warm stone read as brown in a full-window app. The accent, the accent
fill and every status hue are the shared values above, and the dark theme is the
shared graphite. contrast.test.ts holds the Agent UI’s values to the same
floors.
Rules for adding a colour
- Add a role, not a literal. A new hex in a component file is a colour
nothing can police, and every surface has a test that catches it —
website/src/design/literals.test.ts,src/gaia/apps/webui/src/styles/__tests__/contrast.test.ts, andtui/internal/ui/theme/literals_test.go. Each sweeps its own tree and keeps a short allowlist where every entry says why a role cannot carry it. - Declare what it owes the reader. The TUI will not compile past its tests without a contrast floor and a hue family for the new token.
- Check the degraded value, not just the one you typed. Truecolor contrast is not evidence; the TUI test measures what a 256-colour terminal shows.
- Never let two roles share a hue family. That is how “selected” and “warning” become the same thing on a user’s machine but not on yours.
Typography and spacing
Shared starting points, not pixel-matched measurements:- Body text 15–16px, readable proportional type for prose on the website.
- System monospace for composer input, the
>prompt, labels, and activity lines. The terminal follows the user’s own font — never force size, truecolor, or background. - Roughly 760px readable content width; a 240–260px collapsible sidebar.
- Restrained rounding, subtle borders, generous spacing. No glow, no gradient-filled panels.
- No simulated typing. A blinking cursor means output is still streaming — it is a status indicator, never decoration, and never runs when nothing is being written.
- Light, dark, and system appearance in the Agent UI (the website is dark-only);
motion that honours
prefers-reduced-motion; visible keyboard focus at every interactive element.
styles/__tests__/contrast.test.ts), and “no
gradient-filled panels” by a test on both web surfaces (that file, and
design/literals.test.ts on the site); the rest are held by review. Where each
gap is scheduled in the redesign plan:
outline: noneappears in most component stylesheets. Most sit on a:focusrule that draws a border or ring in its place, but the pairing is unverified and nothing fails when it is missing. Milestone 3 covers keyboard behaviour.- “A hue that carries a status meaning is not available for decoration” is enforced for red only. Extending it to amber, green, blue, cyan and purple surfaces 84 rules that wear a status hue for decoration — a switch whose on state is the information blue, memory categories each given their own colour, links tinted green — so closing it is a repaint across 18 stylesheets, not a test change. Milestone 3 for the task surfaces, Milestone 4 for Settings and the document and memory panels.