Colors
Semantic colors and the light and dark themes.
Draft
The values and key names shown here are conceptual and may change.
ThemeColors is the semantic layer. It names colors by role instead of by hue, so components ask for “the border color” and not “gray 200”.
Each color points to a step of the palette. The roles stay the same in both themes; only the step they point to changes.
Colors are grouped into four roles:
| Role | Answers the question |
|---|---|
background | What is this surface painted with? |
content | What color is the text or icon on it? |
border | What outlines or separates it? |
feedback | What state is it communicating? |
export type ThemeColors = {
background: {
default: string;
subtle: string;
elevated: string;
inverse: string;
};
content: {
default: string;
muted: string;
subtle: string;
disabled: string;
inverse: string;
link: string;
};
border: { default: string; subtle: string; strong: string; focus: string };
feedback: {
info: string;
infoSubtle: string;
success: string;
successSubtle: string;
warning: string;
warningSubtle: string;
error: string;
errorSubtle: string;
};
};background
Surfaces, from the screen itself to whatever floats above it. The keys describe depth, not components: a card and a bottom sheet both use elevated.
defaultis the base of a screen.subtlesets grouped content apart, like iOS grouped lists. Putelevatedsurfaces on top of it.elevatedis for anything above the screen: cards, sheets, dialogs, menus. In dark mode it is lighter thandefault, since shadows are barely visible on black.inverseflips the scheme for short, high-contrast messages. Pair it withcontent.inverse.
content
Text and icons. The keys go from most to least emphasis, so each piece of text picks a level of importance rather than a gray.
defaultis for titles, body text and icons.mutedis for supporting text: descriptions, secondary lines of an item, timestamps.subtleis for placeholders and captions. Keep it off text the user has to read.disabledis only for disabled controls.inversegoes onbackground.inverseand on filled buttons.linkmarks tappable text.
Contrast
default and muted are meant for readable text on default, subtle and
elevated backgrounds. Check contrast again when you change these steps.
border
Outlines and separators. Most borders use default; the other keys change the weight or signal an interaction.
defaultoutlines inputs and cards and draws separators between list items.subtledivides content inside a surface that already has an outline.strongis for controls that need to stay visible when empty: an unchecked checkbox or radio.focusshows the focused input or the selected item.
feedback
States the user must notice: information, success, warning and error. Each state has two keys:
- the base key (
error) for icons, text, fills and destructive actions; - the
Subtlekey (errorSubtle) for the tinted background behind that message.
Put base text on its own Subtle background, as in an Alert. Don’t rely on color alone: pair a feedback color with an icon or a label.
light and dark
Axiom ships two complete themes, light and dark. Both implement the same ThemeColors contract with different steps:
export const lightColors = {
background: {
default: "hsla(0, 0%, 100%, 1)",
subtle: palette.gray[50],
elevated: "hsla(0, 0%, 100%, 1)",
inverse: palette.gray[950],
},
content: { default: palette.gray[950], muted: palette.gray[600] /* … */ },
border: { default: palette.gray[200] /* … */ },
feedback: { error: palette.red[500], errorSubtle: palette.red[50] /* … */ },
} satisfies ThemeColors;
export const darkColors = {
background: {
default: "hsla(0, 0%, 0%, 1)",
subtle: palette.gray[950],
elevated: palette.gray[900],
inverse: palette.gray[50],
},
content: { default: palette.gray[50], muted: palette.gray[400] /* … */ },
border: { default: palette.gray[800] /* … */ },
feedback: { error: palette.red[400], errorSubtle: palette.red[950] /* … */ },
} satisfies ThemeColors;In dark mode, feedback and link colors use a lighter step (400 instead of 500) so they stay readable on dark surfaces.
The names match the conventions of each styling tool:
| Variant | How the names are used |
|---|---|
stylesheet | light / dark objects picked by a useTheme() hook based on useColorScheme |
unistyles | Registered as adaptive themes |
tailwind | Mapped to the dark: prefix |
The stylesheet variant follows the system color scheme without a global provider. Add a provider only if you want to force a theme manually.
Customizing
Edit the copied theme files. Because components read component tokens derived from these colors, a change here reaches every component.
To change the brand color, point content.link, border.focus and feedback.info to another hue.