Colors & Theme
Canvas uses a semantic token system. Every color is a named token (primary, muted-foreground, border, …) that ships as a plain value per scheme. Components read the active set through useTheme() and build their React Native styles from it, so the same code themes correctly on iOS, Android, and the web, with no per-platform fork. You never touch the values directly: you pick a component's look with semantic boolean props.
Try this.
Toggle the theme. Every sample below reads the active scheme live, so the values change with it.
Brand
Two brand colors with different jobs. primary is the selection color: checked, selected and current states, links and focus. action is the call to action: primary buttons, meters and count badges. Each has a partner for the label on its fill, and primary-text colors links, text actions and focused Android field labels on neutral surfaces.
Anatomy
ring is Dark Factory's violet as drawn, so the focus outline holds 3:1 on the page, the card and the muted surfaces. primary is the same hue solved until its label clears 4.5:1, which puts it a step darker than the ring in light.
primary
#a496ff
oklch(0.729 0.150 288)
primary-foreground
#221f3a
oklch(0.257 0.050 287)
primary-text
#b8b0ff
oklch(0.793 0.111 288)
ring
#a496ff
oklch(0.729 0.150 288)
action
#5fd18a
oklch(0.776 0.146 154)
action-foreground
#0d2a17
oklch(0.257 0.050 153)
primary (dark)
#a496ff
oklch(0.729 0.150 288)
Brand text uses primary-text.
Neutrals
Dark Factory's lavender-tinted surfaces, hairlines and indigo-gray inks that everything else sits on.
Anatomy
The surfaces and the inks share one hue family, so hierarchy comes from lightness rather than from a change of hue. background is the frosted page, card the white pane on it (a deep indigo in dark), and muted the quieter panel inside a card.
background
#272544
oklch(0.283 0.056 285)
card
#252741
oklch(0.284 0.048 280)
muted
#2d2f4c
oklch(0.317 0.052 280)
border
#3b3d54
oklch(0.368 0.040 280)
field-border
#44455c
oklch(0.399 0.039 283)
input
#7c7e93
oklch(0.599 0.032 281)
muted-foreground
#a6a8c9
oklch(0.741 0.047 282)
foreground
#ebebf7
oklch(0.944 0.016 286)
Semantic
The meaning-bearing tones. Fill tokens have paired foregrounds for their labels; destructive-text supplies readable error and destructive text on neutral surfaces.
Anatomy
Use destructive-foreground on a destructive fill, and destructive-text for error text and semantic destructive actions. The default text role also accounts for the kit's tonal capsules and enabled pressed states.
destructive
#ff8b93
oklch(0.765 0.140 17)
destructive-text
#ffadb0
oklch(0.827 0.097 17)
success
#5fd18a
oklch(0.776 0.146 154)
warning
#ffc46b
oklch(0.856 0.126 76)
secondary
#2d2f4c
oklch(0.317 0.052 280)
accent
#323254
oklch(0.334 0.059 283)
Destructive text uses destructive-text.
Washes and overlays
The translucent roles: the soft washes behind a toned badge, an alert band or a tonal selection, the field fill, the scrim that dims the page behind a modal, the shadow tint, and the inverse surface of a tooltip or a snackbar.
Anatomy
Each is optional in a custom token map. scrim is the one every modal reads today; the rest are adopted skin by skin, and a map that omits a role keeps that skin's older recipe.
primary-soft
rgba(164, 150, 255, 0.16)
oklch(0.729 0.150 288 / 0.16)
success-soft
rgba(95, 209, 138, 0.16)
oklch(0.776 0.146 154 / 0.16)
warning-soft
rgba(255, 180, 80, 0.16)
oklch(0.823 0.143 71 / 0.16)
destructive-soft
rgba(255, 139, 147, 0.16)
oklch(0.765 0.140 17 / 0.16)
field-fill
rgba(255, 255, 255, 0.06)
oklch(1.000 0.000 0 / 0.06)
hover
rgba(164, 150, 255, 0.1)
oklch(0.729 0.150 288 / 0.1)
scrim
rgba(5, 5, 20, 0.55)
oklch(0.127 0.035 280 / 0.55)
shade
rgba(0, 0, 0, 0.5)
oklch(0.000 0.000 0 / 0.5)
inverse
#26264a
oklch(0.288 0.065 282)
inverse-foreground
#ffffff
oklch(1.000 0.000 0)
inverse-primary
#b1a7ff
oklch(0.771 0.124 288)
Status surfaces
The tinted surfaces behind alerts and status badges. Each tone is one hue family sampled at four steps: a 50 fill, a 200 border, a 500 dot, and a 700 label.
Anatomy
Alert and Badge read the same tone-to-hue map (statusHues), so a warning banner and a warning pill in one view are guaranteed to be the same amber.
success
green-50 / 200 / 500 / 700
warning
amber-50 / 200 / 500 / 700
error
red-50 / 200 / 500 / 700
info
blue-50 / 200 / 500 / 700
Active
Pending
Failed
Info
Inactive
Data-viz series
Eight colors for charts, assigned in a fixed order and never re-ranked. Distinct enough at 1-2px marks, with no two adjacent hues vibrating.
Anatomy
The series is identical in light and dark on purpose: the set was validated against both card surfaces, so a chart keeps its colour identity when the scheme flips.
chart-1
#7b6cf0
oklch(0.613 0.191 284)
chart-2
#03919d
oklch(0.599 0.102 205)
chart-3
#d36225
oklch(0.626 0.159 45)
chart-4
#d25798
oklch(0.633 0.170 350)
chart-5
#4382e3
oklch(0.614 0.161 259)
chart-6
#009574
oklch(0.596 0.118 170)
chart-7
#b560cf
oklch(0.632 0.180 318)
chart-8
#a37e05
oklch(0.612 0.124 88)
Glass
Glass is a theming-level surface mode, not a per-component prop: pass glass on the ThemeProvider and every surface takes the material together, layered, or solid to force the flat look; neither means the platform default.
Anatomy
Glass publishes its OWN fills instead of rewriting a semantic one, one per layer of the model, and each platform has its own set. Here, in the active scheme: the functional shells paint glass-tint (rgba(255, 255, 255, 0.045)), the content panes the denser glass-tint-content (rgba(37, 39, 65, 0.72)), the controls the glass-tint-control puck (rgba(255, 255, 255, 0.06)), and the read-and-act surfaces (menus, alert dialogs, toasts, tooltips) the densest glass-tint-dense (rgba(37, 39, 65, 0.92)). The web's are Dark Factory's frost tints; iOS and Android keep their own (glassByScheme). popover and card keep their opaque values in BOTH schemes, so solid mode is untouched and the fills stay independent; every surface takes its tint UNDER the real material.
Solid
Workspace
Home
Library
Settings
Project settings
Project name
Glass
Workspace
Home
Library
Settings
Project settings
Project name
Both samples use the same Navbar, Card, Input and Dropdown components. The provider selects their material. Open a menu to compare its dense overlay with the content panel beneath it.
Functional navigation and overlays, content panels, controls and dense menus each use their own tint. Inner fills become ink tints so selected rows and code pills remain part of the surface.
Supported iOS devices use native Liquid Glass for eligible functional surfaces; web surfaces use Dark Factory frost with a 24px blur and inset hairline. Native frost uses supported blur integrations. Missing or unsafe material support restores the complete opaque appearance.
Reduce Transparency and Increase Contrast also select opaque surfaces. The text, controls and layout stay usable in every mode.
Rebranding the accent
The default accent is Dark Factory's violet. These palette steps are starting points for a custom brand. Check your filled controls and text against their actual backgrounds in both schemes.
Anatomy
ThemeProvider carries a primary-only override into primary-text and into action, so links and the call-to-action buttons follow a one-color rebrand. Supply primary-text for a different text shade, and action with action-foreground to keep a distinct call-to-action color. primary-foreground remains the label on the primary fill; it is not recalculated.
Violet (default)
#7262e5
oklch(0.581 0.191 284)
Indigo
#4f46e5
oklch(0.511 0.230 277)
Teal
#0d9488
oklch(0.600 0.104 185)
Rose
#f43f5e
oklch(0.645 0.215 16)
Amber
#f59e0b
oklch(0.769 0.165 70)
Slate
#475569
oklch(0.446 0.037 257)
CSS hand-off rebrands set --primary, --primary-text and --action (with --action-foreground). Setting --primary-text: var(--primary) and --action: var(--primary) at the override scope gives the single-color behavior. CSS does not apply the ThemeProvider override cascade.
The same override rule applies to destructive and destructive-text. ThemeProvider uses a destructive-only override for semantic error text too; supply destructive-text separately for a readable text shade. CSS consumers set --destructive-text explicitly, or use --destructive-text: var(--destructive) for the previous behavior. Menus with a documented fixed red palette retain their independent colors. Check custom colors against their actual resting and pressed surfaces.
Full token reference
Every color token the kit ships, in both schemes and both notations. The hex is what a React Native call site reads; the oklch is what the web token layer publishes.
Anatomy
The two notations describe the same colour. For the saturated accents the CSS carries a wider-gamut chroma than an sRGB hex can express, so on a P3 display the web layer is the more saturated of the two.
How theming works
You change a component's look with semantic boolean props. Each one resolves through the active scheme's tokens, so the same markup renders correctly in every theme and accent, with no re-skinning and no per-call overrides.
1 · Tokens (tokens.ts)
tsx
2 · The theme runtime (useTheme)
tsx
3 · One prop, resolved live
tsx
The primary prop is the whole styling API; the button reads tokens.action from useTheme(). Switch the scheme or point the accent at a new hue and the ThemeProvider swaps the token set, so every component bound to it re-renders with the new colour.
Do's and don'ts
Keep colour theme-routed. These are the patterns that follow the accent and dark mode for free, beside the ones that quietly break them.
Don’t
tsx
A hard-coded fill painted over the component. It bypasses the theme: an accent change won't reach it, and it looks wrong in dark mode.
Do
tsx
The same button, styled by its semantic prop. The prop resolves through the active scheme's tokens, so theme and accent changes are free.
Don’t
tsx
String-valued enum props are rejected on components: variant, size, and tone are not part of the API.
Do
tsx
Flat boolean props, at most one per axis. The prop name is the value.
Don’t
tsx
In a skin's style object, foreground is the body-text color, not the partner of a primary fill: this pairing is not contrast-guaranteed.
Do
tsx
Every fill token has a -foreground partner. Pair them and contrast holds in both schemes.
Don’t
tsx
Frozen to one scheme: this value never updates when the theme flips.
Do
tsx
Reads the active scheme and re-renders when the theme changes.
Don’t
tsx
Glass is not a per-component prop, and you never hand-paint a blur onto one component.
Do
tsx
Glass is a surface mode set once on the provider, so every surface that takes the material turns together. Same boolean grammar as every component axis: glass or solid, and omitting both keeps the platform default.