OOratiq

Colors

Primitive ramps, semantic roles, and how to re-theme.

Three layers

Components only ever reference layer 2. That's what makes re-theming a token edit instead of a refactor.

1

Primitive ramps

Raw values: --brand-500, --neutral-800. Never referenced by a component.

2

Semantic roles

What a colour means: --primary, --muted, --destructive. Components use only these.

3

Theme mapping

@theme inline exposes roles as utilities: bg-primary, text-muted-foreground.

Semantic roles

Each role pairs a surface with a foreground guaranteed to meet contrast on it. Toggle the theme in the header — every pair holds.

--background

--foreground

--card

--popover

--primary

Brand action

--secondary

--muted

Recessed surface

--accent

Hover surface

--destructive

--success

--warning

--info

--border

--input

Control borders

--ring

Focus indicator

Pairings

Never mix a foreground from one role onto another role's surface.

Primary

bg-primary text-primary-foreground

Secondary

bg-secondary text-secondary-foreground

Muted

bg-muted text-muted-foreground

Destructive

bg-destructive text-destructive-foreground

Success

bg-success text-success-foreground

Warning

bg-warning text-warning-foreground

Brand ramp

Kept addressable for marketing surfaces and charts, where a specific step is the point.

50

100

200

300

400

500

600

700

800

900

950

Re-theming

Override layer 2, nothing else

To ship your own brand, redefine the semantic roles in your own CSS — --primary, --primary-foreground, --ring, and the dark-mode block. Every component picks it up. You should never need to edit a component file to change a colour; if you do, that's a bug in the system, not in your theme.

Accessibility

  • Foreground/background pairs meet WCAG AA (4.5:1) for body text in both themes.
  • Borders and focus rings meet the 3:1 non-text contrast minimum.
  • Status is never carried by colour alone — pair it with an icon or text.
  • The brand green is used as a surface with dark text, never as light text on dark: #CBFE00 on #1D1D1D passes, but the reverse fails at small sizes.