Skip to content

Colors

raft-ui colors come in three layers. Components never name a raw color: they ask for a purpose, such as “the fill of a primary button” or “error text”, and each theme decides which color that is.

LayerNameHolds
Palette--r-palette-{hue}-{1..12}The Raft brand hues as 12-step scales
Alias--rw-alias-{property}-{state}-{role}What a color is for; points at one palette step
Component--rl-{component}-*A component’s own slots; read only aliases

Palette

Each hue has 12 steps, and every step has the same job in every hue. Step 9 is the brand color itself. The light scales are shared by Brutal and Elegant light; Elegant dark swaps in dark scales with the same step meanings, so every alias follows the mode by itself. Switch the theme at the top of the page to see them.

123456789101112contrastgrayyellowpinkcyanlimeorangered

contrast is the text color for a step-9 fill: a dark shade of the same hue, never plain black.

What each step is for

This follows Radix Colors.

StepUseAlias
1–2Page and subtle backgroundsbg-page, bg-panel (neutral)
3 / 4 / 5Component background: rest, hover, pressedsurface, surface-hover, surface-pressed
6 / 7 / 8Border: subtle, default, hoverborder-subtle, border, border-hover
9 / 10Solid fill: rest, hoverfill, fill-hover
11Colored text on white or on steps 1–5text
12High-contrast text in the same huetext-strong
contrastText on a solid filltext-on-fill

Two rules keep text readable:

  • Text on a color is a dark shade of that color. A yellow fill takes dark olive-brown text, a cyan fill takes dark teal. Black or grey text on a colored background looks dirty.
  • Colored text on a light background uses step 11. It is the most colorful shade that still passes WCAG AA (4.5:1) on steps 1–5.

Aliases

Names follow how-to-style: property first, then state, then the role last, so a component maps every type by changing only the last segment.

css
.r-button { &&--error-type { --rl-button-family-fill: var(--rw-alias-fill-error); --rl-button-family-fill-hover: var(--rw-alias-fill-hover-error); --rl-button-family-text: var(--rw-alias-text-error); } }

The roles and the hue each one uses:

surface3surface-hover4surface-pressed5border-subtle6border7border-hover8fill9fill-hover10text11text-strong12text-on-fillcontrast
neutralgray
primaryyellow
accentpink
infocyan
successlime
warningorange
errorred

Neutral text and backgrounds have their own names: --rw-alias-bg-page, --rw-alias-bg-panel, --rw-alias-bg-raised (something lifted above a recessed surface, like the selected segment: white in light mode, lighter than the surface in dark mode), --rw-alias-label-primary / -secondary / -tertiary / -disabled, --rw-alias-border-strong (Brutal’s ink) and --rw-alias-border-focus. --rw-static-white and --rw-static-black never change.

Feedback

A status (info, success, warning, error) has to read the same way on every component, so components don’t pick its colors: they read --rw-feedback-{property}-{role}, and each theme decides once how a status looks.

Status colors are their own, not the brand hues the roles above borrow. Error is a raspberry at 15°, warning an amber at 72° and success a true green at 153°, so the three are easy to tell apart and stay out of the warm brand band. Only error is loud; warning and success are quieter. Fields take the mid-tone; text takes a darker shade of the same hue so it passes AA (4.5:1). Dark mode has its own lighter set, where one color does both.

TokenElegant (light and dark)Brutal
--rw-feedback-text-{role}the status color, darkened to pass AA in light mode; for messages and iconssame
--rw-feedback-border-{role}the status colorthe status color, replacing the ink
--rw-feedback-shadow-{role}nonethe hard shadow, in the status color

A field in a status changes color as one piece: Elegant’s hairline takes the status color, and Brutal’s outline and hard shadow both do. Switch the theme at the top of the page to compare.

info
Synced 2 minutes ago
success
Looks good
warning
Double-check this
error
Not a valid email

Input and Select’s status read these tokens; so will every field and message after them.

Contrast

Measured live for the current theme. Every pair a component uses should pass 4.5:1.

pairneutralprimaryaccentinfosuccesswarningerror
11 on panelAa—Aa—Aa—Aa—Aa—Aa—Aa—
11 on 3Aa—Aa—Aa—Aa—Aa—Aa—Aa—
11 on 5Aa—Aa—Aa—Aa—Aa—Aa—Aa—
contrast on 9Aa—Aa—Aa—Aa—Aa—Aa—Aa—
contrast on 10Aa—Aa—Aa—Aa—Aa—Aa—Aa—

Changing colors

Override a palette step to recolor every alias that points at it, in the whole page or inside one ConfigProvider:

tsx
<ConfigProvider themeOverrides={{ "--r-palette-yellow-9": "oklch(0.86 0.17 85)" }}>…</ConfigProvider>

Override an alias to change what one purpose uses without touching the palette:

tsx
<ConfigProvider themeOverrides={{ "--rw-alias-fill-primary": "var(--r-palette-orange-9)" }}>…</ConfigProvider>