Colors

Roadie uses OKLCH color scales with 14 steps (0-13) per intent. Colors are applied through three utility types: bg-*, text-*, and border-* — each scoped to its own CSS property.

Color scales

Each intent has a 14-step OKLCH scale. Step 0 is the lightest extreme, step 13 the darkest. Dark mode swaps the values — step numbers stay the same.

neutral

0

1

2

3

4

5

6

7

8

9

10

11

12

13

brand

0

1

2

3

4

5

6

7

8

9

10

11

12

13

brand-secondary

0

1

2

3

4

5

6

7

8

9

10

11

12

13

accent

0

1

2

3

4

5

6

7

8

9

10

11

12

13

danger

0

1

2

3

4

5

6

7

8

9

10

11

12

13

success

0

1

2

3

4

5

6

7

8

9

10

11

12

13

warning

0

1

2

3

4

5

6

7

8

9

10

11

12

13

info

0

1

2

3

4

5

6

7

8

9

10

11

12

13

Color utilities

Three utility namespaces for applying colors. Each maps semantic levels to specific scale steps via the current intent.

Levelbg-*text-*border-*
subtlerstep 9 @ 6% alphastep 10step 5
subtlestep 9 @ 11% alphastep 11step 6
normalstep 1step 12step 7
strongstep 9 (neutral: 12)step 13step 9
invertedstep 12step 0step 12
raisedstep 1 (dark: 2-3)
sunkenstep 2 (dark: 0)

Usage

bg-normalbg-subtlebg-raisedtext-subtletext-strongborder-subtleborder-b-subtlerdivide-subtler

Surface tokens

Four elevation levels in one picture. Pick the surface that matches the element's depth in the layout — sunken for recessed areas, normal for the page, raised for elevated cards, floating for popovers and modals.

Sunken

bg-sunken

Recessed areas, inset panels, empty-state wells. Pairs with emphasis-sunken for the full inset shadow treatment.

Normal

bg-normal

Default page background. The baseline everything else sits on — use it when nothing should feel elevated or recessed.

Raised

bg-raised

Elevated cards, sticky headers, grouped content that should appear to lift off the page. Pairs with emphasis-raised for rim-light + shadow-md.

Floating

emphasis-floating

Popovers, modals, dropdowns — anything that floats above the document flow. Uses the top elevation shadow and strong rim light.

The four levels map to a depth hierarchy: sunken normalraisedfloating. Reach for the emphasis-* shortcut when you need the full bg + border + shadow treatment; use bg-raised / bg-sunken directly when you just need the background colour.

Intent + emphasis

Set intent-* on a container to choose the color palette. Use emphasis-* shortcuts for combined bg + text + interactive states, or individual utilities for composability.

neutral

emphasis-strong
emphasis-normal
emphasis-subtle
emphasis-subtler

brand

emphasis-strong
emphasis-normal
emphasis-subtle
emphasis-subtler

brand-secondary

emphasis-strong
emphasis-normal
emphasis-subtle
emphasis-subtler

accent

emphasis-strong
emphasis-normal
emphasis-subtle
emphasis-subtler

danger

emphasis-strong
emphasis-normal
emphasis-subtle
emphasis-subtler

success

emphasis-strong
emphasis-normal
emphasis-subtle
emphasis-subtler

warning

emphasis-strong
emphasis-normal
emphasis-subtle
emphasis-subtler

info

emphasis-strong
emphasis-normal
emphasis-subtle
emphasis-subtler

Dark mode

Automatic. The .dark class on <html> swaps all OKLCH values. No dark: Tailwind variants needed — bg-normal, text-subtle, border-subtle all adapt automatically.

The CSS also sets color-scheme: light on :root and color-scheme: dark on .dark, so native browser UI (scrollbars, form controls) matches your theme.

Setup

Add a blocking script in <head> to prevent flash of wrong theme. The getThemeScript helper generates this for you — import from @oztix/roadie-core/theme (no React dependency).

import { getThemeScript } from '@oztix/roadie-core/theme'

// In your <head>:
<meta name="color-scheme" content="light" />
<script dangerouslySetInnerHTML={{ __html: getThemeScript() }} />

Following system preference

By default, the theme is light. Pass followSystem: true to respect the user's OS preference when they haven't explicitly toggled.

getThemeScript({ followSystem: true })

React: ThemeProvider

For React apps, wrap your app in ThemeProvider to get the useTheme() hook with isDark and setDark.

import { ThemeProvider, useTheme } from '@oztix/roadie-components'

// In your layout:
<ThemeProvider followSystem>
  <App />
</ThemeProvider>

// In a toggle component:
function ThemeToggle() {
  const { isDark, setDark } = useTheme()
  return <button onClick={() => setDark(!isDark)}>{isDark ? 'Light' : 'Dark'}</button>
}

Once a user explicitly toggles, their choice persists in localStorage and overrides the system preference.

Vue / vanilla JS

For non-React apps, use getThemeScript() for initial load and toggle with plain DOM calls.

function setDark(dark) {
  document.documentElement.classList.toggle('dark', dark)
  document.documentElement.style.colorScheme = dark ? 'dark' : 'light'
  localStorage.setItem('theme', dark ? 'dark' : 'light')
}

Dynamic accent

The accent scale is CSS-native. The ThemeProvider sets --accent-hue and --accent-chroma — CSS oklch() computes all accent and neutral colors in the browser. No JavaScript color generation needed for modern browsers.