Development notes
Design tokens need roles, not just a list of color codes
How this site assigns color roles to backgrounds, cards, text, and actions, maps component-library tokens, and handles states without blindly replacing color codes.
- Design tokens
- Tailwind CSS
- Color
- Component design
Collecting color codes into a few variables does not decide where those colors belong. If the page background, cards, hover states, and primary buttons all share one name, changing its value still leaves you unsure what will change with it.
I care more about roles: does this color distinguish a content surface, make text readable, or help someone find the next action? Tokens can help with maintenance once those responsibilities are clear; otherwise, they just move color codes somewhere else.
This note draws on the site’s shared colors and component usage recorded on 2026-10-24. These are design choices for this site at that point, not a standard palette for every product.
Names should describe purpose, not just appearance
This site separates the page background from ordinary content surfaces: siteBg is the page background, while siteSurface is for content cards. Text, dark surfaces used for emphasis, and the foreground on those surfaces also have separate roles.
Here is part of that division:
| Role | Current color code | Main responsibility |
|---|---|---|
siteBg |
#F1F1F0 |
Page background |
siteSurface |
#FCFBF9 |
Ordinary content surfaces |
siteText |
#25272C |
Primary text on light surfaces |
siteInk |
#25272C |
Dark surfaces or controls for emphasis |
siteOnInk |
#FBFAF7 |
Light foreground on dark surfaces |
siteAction |
#C13F1B |
Fill for primary action controls |
Although siteText and siteInk currently share a color code, their responsibilities differ. One is text; the other is a surface. If they need separate adjustments later, callers will not first have to guess which use of black serves which purpose.
That does not mean every CSS property needs a token. Separate names are useful only when the purpose, pairing, or scope of future changes differs. Giving one responsibility many similar names can also make it harder to choose.
Text emphasis and button fills do not have to share a color
The site’s primary is a darker #A93618, while primary action controls use siteAction, #C13F1B. Button’s default variant explicitly pairs that fill with a siteOnInk foreground and uses siteActionHover on hover.
The point is not to keep an extra shade of orange. It is to let two jobs change independently: one color needs to be readable as text on a light background; the other is a control surface with foreground text. A single token called “brand orange” tends to tie those two questions together.
Colors also need to be considered in pairs. W3C’s guidance on text contrast examines the relationship between text and its background, not whether a color code on its own can be called “readable”.[4] The same orange on off-white, charcoal, or a translucent image cannot automatically share the same assessment.
Semantic token names therefore do not automatically make contrast compliant. The actual foreground, background, text size, and state still need checking. A single static screenshot cannot cover disabled, hover, and focus states.
Map component-library semantics to the site’s existing roles
A site does not have to use the same naming scheme as its component library. This site keeps roles such as siteSurface and siteText, then maps card, popover, foreground, and other names that components need back to them.
A small excerpt from the current mapping looks like this:
@theme inline {
--color-background: var(--color-siteBg);
--color-foreground: var(--color-siteText);
--color-card: var(--color-siteSurface);
--color-card-foreground: var(--color-siteText);
--color-popover: var(--color-siteSurface);
}
Other semantic colors are omitted here; this is not a complete configuration that can replace all the styles. The point is that the same card role still comes from a single site definition. Adding a component library should not mean deciding again, somewhere else, which white a card uses.
Tailwind CSS uses @theme variables to define the available utilities. Its documentation also explains how inline works when a theme variable references another variable.[3] In this example, it connects existing roles to utilities; it does not introduce a second site theme.
Mapping names does not remove the need to inspect a component’s own variants. This site’s default Button fill references siteAction directly. Seeing a component-library token named primary is not enough to assume that every primary button uses the same color code.
Hover can signal interaction without recoloring the whole card
The site’s ordinary note cards keep their off-white surface. On hover and focus-within, a slight lift, shadow, and a local accent on the title or arrow signal the state. They do not become dark emphasis cards whenever the pointer moves over them.
Dark cards have their own siteOnInk, siteOnInkMuted, and siteOnInkAccent roles. This separates content that is already given visual emphasis from a card signaling that it is interactive, without using the same large color change for both.
This is a choice about this site’s visual hierarchy, not a rule that buttons, selected items, or every control should have a transparent background. Controls need to make it clear when they can be used, are selected, or are disabled. Decide by function rather than applying a card’s hover rule to the entire interface.
Keyboard focus also needs a clear indicator of its own; it cannot rely only on mouse hover. Even if they share some colors, the conditions that trigger them and the need to recognize each state remain different.
When changing colors, follow roles instead of making blind global replacements
If I wanted to make the primary action color more noticeable, I would first find the uses of siteAction, then check button foregrounds, hover states, text selection, and other references. I would not replace every matching hexadecimal color code across the project.
To change the reading surface of ordinary cards, by contrast, I would go back to siteSurface and the corresponding text roles. A few similar whites on screen do not mean they should all change together.
The site also keeps separately named colors for illustrative product interfaces. Those belong to a different presentation context. They do not represent the products’ complete production palettes, and should not all be replaced with the site’s action color just to make its token set look smaller.
During an actual check, first identify the affected components and states, then inspect the output CSS and the real color combinations in the browser. Integrating and maintaining local UI components involves other responsibilities as well: centralizing tokens does not automatically preserve props, HTML semantics, or open and close behavior.
This note does not include contrast measurements for every state across the site, visual acceptance testing, or maintenance-cost data. It offers a division of responsibilities to trace when changing colors: identify the role to change, then check the affected combinations, rather than letting one new color code decide the entire design.
Sources
[3] https://tailwindcss.com/docs/theme — Tailwind CSS — Theme variables [4] https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html — W3C — Understanding SC 1.4.3 Contrast (Minimum)