Per-component colors derived from the semantic colors.
Draft
The values and key names shown here are conceptual and may change.
Components is the last layer of the theme. It gives each component its own named colors: the background of a solid button, the border of a focused input, the track of a checked switch.
Components read this layer, never colors or the palette directly.
01 Tokens
The palette step.
blue.50002 ThemeColors
The semantic color pointing to it.
border.focus03 Components
The component token reading the color.
input.outline.focused.border04 Your components
Button, Input, Switch… read this layer only.
Each row below traces a real component token back to its semantic color and palette step, in both schemes.
palette (light / dark)
ThemeColors
Components
→
feedback.success
→
switch.default.checked.track
→
border.focus
→
input.outline.focused.border
→
background.inverse
→
button.solid.default.background
Say a button reads a semantic color directly:
backgroundColor : colors.background.inverse;
Now you want blue buttons, and nothing else should change. You have two bad options:
change background.inverse: toasts, snackbars and tooltips turn blue too;
hardcode a color in the button file: dark mode stops following.
With component tokens, the button reads button.solid.default.background. You point that one token to another semantic color, and only the button changes. Dark mode still works because the value still comes from ThemeColors.
Switch between the default and the custom button below. The link, the checkbox and the focused input are blue in both cases; only the solid button follows the change.
Light Dark
Default Custom button
Sign in
jane@axiom.dev
Password
Password is required Use Face ID
Sign in
Create account
button.solid.default.background: colors.background.inverse colors.content.link
The layer also:
puts every component color in one file, instead of spread across dozens of components;
keeps the stylesheet, unistyles and tailwind variants identical, since all three read the same value.
Component tokens hold colors only : anything that changes between light and dark, or with your brand.
In Components Elsewhere Background, foreground, border, icon, placeholder, track, thumb Heights, padding, radius, font sizes: read from tokens One value per variant and state Minimum tap target (44pt): metrics State logic, gestures, animation: core and hooks
Every token follows the same path:
<component>.<variant>.<state>.<property>
Segment Rule Examples componentcamelCase, same name as in the registry button, iconButton, segmentedControlvariantThe values of the variant prop. Components without one use default solid, outline, ghoststateA closed list, always starting with default default, pressed, focused, checked, invalid, disabledpropertyA shared vocabulary background, foreground, border, placeholder, icon, indicator, track, thumb
States other than default only list what changes. A missing property falls back to default: a pressed outline button keeps its border and text color and only changes its background.
components/ui/button-tokens.ts type ButtonColors = {
background ?: string ;
foreground : string ;
border ?: string ;
};
type ButtonStates = States < ButtonColors , "pressed" | "disabled" >;
export type ButtonTokens = {
solid : ButtonStates ;
outline : ButtonStates ;
ghost : ButtonStates ;
};
States comes from the theme: default is complete, every other state is a Partial of it.
Each component brings its tokens in its own file, copied next to it: button-tokens.ts next to button.tsx. The file exports a pure function of the semantic colors:
components/ui/button-tokens.ts export const buttonTokens = ( colors : ThemeColors ) : ButtonTokens => ({
solid: {
default: {
background: colors.background.inverse,
foreground: colors.content.inverse,
},
pressed: { background: colors.content.muted },
disabled: {
background: colors.border.default,
foreground: colors.content.disabled,
},
},
outline: {
default: {
background: colors.background.default,
foreground: colors.content.default,
border: colors.border.default,
},
pressed: { background: colors.background.subtle },
disabled: {
foreground: colors.content.disabled,
border: colors.border.subtle,
},
},
ghost: {
default: { foreground: colors.content.default },
pressed: { background: colors.background.subtle },
disabled: { foreground: colors.content.disabled },
},
});
Rules:
The function is pure and only reads colors. Never write raw color values or palette steps in it.
Never branch on the scheme (if (dark)). If a component needs a different value in dark mode, that difference belongs in ThemeColors.
The theme’s components.ts gathers every tokens file of the project. You don’t write it by hand: npx axiom add <component> copies the tokens file and registers it between two pairs of markers.
// axiom:imports:start
import { bottomSheetTokens } from "@/components/ui/bottom-sheet-tokens" ;
import { buttonTokens } from "@/components/ui/button-tokens" ;
// axiom:imports:end
export const components = ( colors : ThemeColors ) => ({
// axiom:components:start
bottomSheet: bottomSheetTokens (colors),
button: buttonTokens (colors),
// axiom:components:end
});
export type Components = ReturnType < typeof components>;
The key is the component name in camelCase, the function is <key>Tokens.
The CLI only adds missing lines, and only when the tokens file is in the project. It never removes or rewrites a line.
Keep the markers. Without them, add stops and asks you to copy the theme again.
Removing a component: delete its two lines, then its files.
components is called once per color scheme:
export const light = {
tokens,
colors: lightColors,
components: components (lightColors),
} satisfies Theme ;
export const dark = {
tokens,
colors: darkColors,
components: components (darkColors),
} satisfies Theme ;
Each table lists a component’s tokens, the semantic color they read and the result in both schemes. Grayed rows are states that override default.
button.solid.default.background
background.inverse
button.solid.default.foreground
content.inverse
button.solid.pressed.background
content.muted
button.solid.disabled.background
border.default
button.solid.disabled.foreground
content.disabled
button.outline.default.background
background.default
button.outline.default.foreground
content.default
button.outline.default.border
border.default
button.outline.pressed.background
background.subtle
button.outline.disabled.foreground
content.disabled
button.outline.disabled.border
border.subtle
button.ghost.default.foreground
content.default
button.ghost.pressed.background
background.subtle
button.ghost.disabled.foreground
content.disabled
button.destructive.default.background
feedback.error
button.destructive.default.foreground
content.inverse
button.destructive.pressed.foreground
feedback.errorSubtle
button.destructive.disabled.background
border.default
button.destructive.disabled.foreground
content.disabled
floatingButton.solid.default.background
background.inverse
floatingButton.solid.default.foreground
content.inverse
floatingButton.solid.pressed.background
content.muted
floatingButton.solid.disabled.background
border.default
floatingButton.solid.disabled.foreground
content.disabled
floatingButton.tinted.default.background
background.elevated
floatingButton.tinted.default.foreground
content.default
floatingButton.tinted.default.border
border.default
floatingButton.tinted.pressed.background
background.subtle
floatingButton.tinted.disabled.foreground
content.disabled
iconButton.ghost.default.foreground
content.default
iconButton.ghost.pressed.background
background.subtle
iconButton.ghost.selected.background
background.subtle
iconButton.ghost.disabled.foreground
content.disabled
iconButton.tinted.default.background
background.subtle
iconButton.tinted.default.foreground
content.default
iconButton.tinted.pressed.background
border.default
iconButton.tinted.selected.background
border.default
iconButton.tinted.disabled.foreground
content.disabled
iconButton.outline.default.background
background.default
iconButton.outline.default.foreground
content.default
iconButton.outline.default.border
border.default
iconButton.outline.pressed.background
background.subtle
iconButton.outline.selected.border
border.focus
iconButton.outline.disabled.foreground
content.disabled
iconButton.outline.disabled.border
border.subtle
iconButton.solid.default.background
background.inverse
iconButton.solid.default.foreground
content.inverse
iconButton.solid.pressed.background
content.muted
iconButton.solid.selected.background
content.muted
iconButton.solid.disabled.background
border.default
iconButton.solid.disabled.foreground
content.disabled
Also used by TextArea, and by InputGroup for its border, text and placeholder.
input.outline.default.background
background.default
input.outline.default.border
border.default
input.outline.default.text
content.default
input.outline.default.placeholder
content.subtle
input.outline.default.affix
content.muted
input.outline.default.caret
content.link
input.outline.focused.border
border.focus
input.outline.invalid.border
feedback.error
input.outline.invalid.caret
feedback.error
input.outline.disabled.background
background.subtle
input.outline.disabled.border
border.subtle
input.outline.disabled.text
content.disabled
input.outline.disabled.affix
content.disabled
input.filled.default.background
background.subtle
input.filled.default.text
content.default
input.filled.default.placeholder
content.subtle
input.filled.default.affix
content.muted
input.filled.default.caret
content.link
input.filled.focused.border
border.focus
input.filled.invalid.border
feedback.error
input.filled.invalid.caret
feedback.error
input.filled.disabled.text
content.disabled
input.filled.disabled.affix
content.disabled
switch.default.default.track
border.default
switch.default.default.thumb
background.elevated
switch.default.checked.track
feedback.success
switch.default.disabled.track
border.subtle
checkbox.default.default.border
border.strong
checkbox.default.default.indicator
content.inverse
checkbox.default.checked.background
content.link
checkbox.default.checked.border
content.link
checkbox.default.invalid.border
feedback.error
checkbox.default.disabled.border
border.subtle
checkbox.default.disabled.indicator
content.disabled
radio.default.default.border
border.strong
radio.default.default.indicator
content.link
radio.default.checked.border
content.link
radio.default.disabled.border
border.subtle
radio.default.disabled.indicator
content.disabled
segmentedControl.default.default.track
background.subtle
segmentedControl.default.default.indicator
background.elevated
segmentedControl.default.default.border
border.default
segmentedControl.default.default.foreground
content.muted
segmentedControl.default.selected.foreground
content.default
segmentedControl.default.disabled.foreground
content.disabled
slider.default.default.track
border.default
slider.default.default.fill
content.link
slider.default.default.thumb
content.link
slider.default.disabled.fill
content.disabled
slider.default.disabled.thumb
content.disabled
inputGroup.default.default.addon
background.subtle
inputGroup.default.default.divider
border.default
inputGroup.default.disabled.divider
border.subtle
inputOtp.default.default.background
background.default
inputOtp.default.default.border
border.default
inputOtp.default.default.text
content.default
inputOtp.default.default.caret
content.link
inputOtp.default.active.border
border.focus
inputOtp.default.invalid.background
feedback.errorSubtle
inputOtp.default.invalid.border
feedback.error
inputOtp.default.invalid.text
feedback.error
inputOtp.default.success.background
feedback.successSubtle
inputOtp.default.success.border
feedback.success
inputOtp.default.success.text
feedback.success
inputOtp.default.disabled.background
background.subtle
inputOtp.default.disabled.border
border.subtle
inputOtp.default.disabled.text
content.disabled
chip.outline.default.background
background.default
chip.outline.default.foreground
content.default
chip.outline.default.border
border.default
chip.outline.pressed.background
background.subtle
chip.outline.selected.background
background.inverse
chip.outline.selected.foreground
content.inverse
chip.outline.selected.border
background.inverse
chip.outline.disabled.foreground
content.disabled
chip.outline.disabled.border
border.subtle
chip.filled.default.background
background.subtle
chip.filled.default.foreground
content.default
chip.filled.pressed.background
border.default
chip.filled.selected.background
background.inverse
chip.filled.selected.foreground
content.inverse
chip.filled.disabled.foreground
content.disabled
badge.neutral.default.background
background.subtle
badge.neutral.default.foreground
content.muted
badge.info.default.background
feedback.infoSubtle
badge.info.default.foreground
feedback.info
badge.success.default.background
feedback.successSubtle
badge.success.default.foreground
feedback.success
badge.warning.default.background
feedback.warningSubtle
badge.warning.default.foreground
feedback.warning
badge.error.default.background
feedback.errorSubtle
badge.error.default.foreground
feedback.error
badge.outline.default.foreground
content.default
badge.outline.default.border
border.default
badge.inverse.default.background
background.inverse
badge.inverse.default.foreground
content.inverse
badge.count.default.background
feedback.error
badge.count.default.foreground
content.inverse
badge.count.default.border
background.default
avatar.default.default.background
border.default
avatar.default.default.foreground
content.muted
avatar.default.default.ring
background.default
avatar.status.default.online
feedback.success
avatar.status.default.away
feedback.warning
avatar.status.default.busy
feedback.error
avatar.status.default.offline
content.subtle
card.elevated.default.background
background.elevated
card.elevated.pressed.background
background.subtle
card.outlined.default.background
background.default
card.outlined.default.border
border.default
card.outlined.pressed.background
background.subtle
card.filled.default.background
background.subtle
card.filled.pressed.background
border.subtle
item.default.default.divider
border.default
item.default.pressed.background
background.subtle
item.default.selected.background
feedback.infoSubtle
skeleton.default.default.background
border.subtle
skeleton.default.default.highlight
border.default
alert.info.default.background
feedback.infoSubtle
alert.info.default.icon
feedback.info
alert.success.default.background
feedback.successSubtle
alert.success.default.icon
feedback.success
alert.warning.default.background
feedback.warningSubtle
alert.warning.default.icon
feedback.warning
alert.error.default.background
feedback.errorSubtle
alert.error.default.icon
feedback.error
alert.neutral.default.background
background.subtle
alert.neutral.default.icon
content.muted
alert.neutral.default.border
border.subtle
empty.neutral.default.media
background.subtle
empty.neutral.default.icon
content.muted
empty.error.default.media
feedback.errorSubtle
empty.error.default.icon
feedback.error
toast.default.default.background
background.elevated
toast.default.default.border
border.subtle
toast.default.default.icon
content.default
toast.default.success.icon
feedback.success
toast.default.error.icon
feedback.error
toast.default.info.icon
feedback.info
snackbar.default.default.background
background.inverse
snackbar.default.default.foreground
content.inverse
snackbar.default.default.action
feedback.info
calendar.day.default.foreground
content.default
calendar.day.default.range
feedback.infoSubtle
calendar.day.default.dot
content.link
calendar.day.pressed.background
background.subtle
calendar.day.today.foreground
content.link
calendar.day.selected.background
content.link
calendar.day.selected.foreground
content.inverse
calendar.day.selected.dot
content.inverse
calendar.day.inRange.foreground
content.default
calendar.day.outside.foreground
content.subtle
calendar.day.disabled.foreground
content.disabled
bottomSheet.default.default.background
background.elevated
bottomSheet.default.default.handle
border.strong
dialog.default.default.background
background.elevated
menu.default.default.background
background.elevated
menu.default.default.separator
background.subtle
menu.default.default.foreground
content.default
menu.default.pressed.item
background.subtle
menu.default.destructive.foreground
feedback.error
menu.default.disabled.foreground
content.disabled
Each table is the default tokens file of the component. A project only has the tokens of the components it added.
The values are the same; only the way components read them changes.
const theme = useTheme ();
< Pressable
style = {{ backgroundColor: theme.components.button.solid.default.background }}
/>;
const styles = StyleSheet. create (( theme ) => ({
solid: { backgroundColor: theme.components.button.solid.default.background },
}));
/* Each token becomes a CSS variable, one set per scheme */
--button-solid-default-background: var(--background-inverse);
< Pressable className = "bg-button-solid-default-background active:bg-button-solid-pressed-background" />
Edit the tokens file of the component. It belongs to your project, like the component itself:
components/ui/button-tokens.ts — blue solid buttons solid : {
default : {
background: colors.content.link,
foreground: colors.content.inverse,
},
pressed : { background : colors.content.link },
},
Pick the layer that matches the scope of the change:
You want to change Edit One component Its tokens file (button-tokens.ts) A role across the app (every border, every link) ThemeColorsThe brand hue or a step everywhere The palette
npx axiom add button asks before overwriting a tokens file you changed.