NimUI
Design System

Colors

Color palette and design tokens for Nim UI

Nim UI uses a color system built on semantic scales, every one of which ships a dark-mode counterpart. Which pairings of those scales clear a given contrast threshold is a separate question, answered under Contrast Ratios — the scales are raw material, not a guarantee about every combination you can build from them.

Primary Colors

The primary color palette is used for primary actions, links, and brand elements.

Primary

50oklch(0.982 0.003 247.8)
100oklch(0.956 0.006 247.8)
200oklch(0.902 0.011 247.9)
300oklch(0.812 0.018 247.7)
400oklch(0.682 0.025 248.0)
500oklch(0.534 0.030 248.2)
600oklch(0.424 0.028 248.5)
700oklch(0.326 0.024 249.0)
800oklch(0.254 0.020 249.4)
900oklch(0.200 0.018 250.0)

Usage

// In Tailwind classes
<div className="bg-primary-500 text-white">Primary</div>
<button className="hover:bg-primary-600">Hover</button>

// In components
<Button variant="primary">Primary Button</Button>

Neutral Colors

Neutral colors (grays) are used for text, backgrounds, borders, and other UI elements.

Neutral

50oklch(0.985 0.000 0)
100oklch(0.970 0.000 0)
200oklch(0.922 0.000 0)
300oklch(0.869 0.000 0)
400oklch(0.708 0.000 0)
500oklch(0.551 0.000 0)
600oklch(0.432 0.000 0)
700oklch(0.371 0.000 0)
800oklch(0.278 0.000 0)
900oklch(0.214 0.000 0)

Usage

// Text colors
<p className="text-neutral-900 dark:text-neutral-100">Primary text</p>
<p className="text-neutral-600 dark:text-neutral-400">Secondary text</p>

// Backgrounds
<div className="bg-neutral-50 dark:bg-neutral-900">Background</div>

// Borders
<div className="border border-neutral-200 dark:border-neutral-800">Card</div>

Semantic Colors

Semantic colors provide meaning and context to UI elements.

Success

Used for positive actions, confirmations, and success states.

Success

50oklch(0.976 0.010 158.0)
100oklch(0.945 0.026 158.0)
200oklch(0.890 0.050 157.5)
300oklch(0.805 0.075 157.0)
400oklch(0.700 0.090 156.5)
500oklch(0.590 0.090 156.0)
600oklch(0.505 0.080 156.0)
700oklch(0.430 0.066 156.0)
800oklch(0.360 0.052 156.0)
900oklch(0.305 0.040 156.0)
<Alert variant="success">Operation successful!</Alert>
<Badge variant="success">Active</Badge>

Warning

Used for warnings, cautions, and items requiring attention.

Warning

50oklch(0.980 0.012 82.0)
100oklch(0.948 0.028 82.0)
200oklch(0.902 0.052 82.0)
300oklch(0.830 0.075 82.0)
400oklch(0.740 0.092 82.0)
500oklch(0.650 0.096 82.0)
600oklch(0.555 0.086 82.0)
700oklch(0.465 0.072 82.0)
800oklch(0.385 0.056 82.0)
900oklch(0.320 0.044 82.0)
<Alert variant="warning">Please review your changes</Alert>
<Badge variant="warning">Pending</Badge>

Danger

Used for errors, destructive actions, and critical states.

Danger

50oklch(0.974 0.010 25.0)
100oklch(0.940 0.025 25.0)
200oklch(0.880 0.050 25.0)
300oklch(0.790 0.080 25.0)
400oklch(0.680 0.110 25.0)
500oklch(0.560 0.120 25.0)
600oklch(0.500 0.110 25.0)
700oklch(0.435 0.092 25.0)
800oklch(0.365 0.072 25.0)
900oklch(0.305 0.055 25.0)
<Alert variant="destructive">An error occurred</Alert>
<Button variant="destructive">Delete</Button>

Info

Used for informational messages and neutral notifications.

Info

50oklch(0.974 0.006 248.0)
100oklch(0.944 0.014 248.0)
200oklch(0.888 0.025 248.0)
300oklch(0.800 0.038 248.0)
400oklch(0.690 0.050 248.0)
500oklch(0.545 0.058 248.0)
600oklch(0.455 0.055 248.0)
700oklch(0.380 0.048 248.0)
800oklch(0.315 0.040 248.0)
900oklch(0.260 0.032 248.0)
<Alert variant="info">Did you know...</Alert>
<Badge variant="info">New</Badge>

Color Usage Guidelines

Contrast Ratios

WCAG 2.1 AA sets these thresholds (SC 1.4.3, SC 1.4.11):

  • Text on backgrounds: 4.5:1 minimum contrast ratio
  • Large text: 3:1 minimum — the allowance only applies at 24px+ regular weight or 18.66px+ (14pt) bold; anything smaller still needs 4.5:1
  • Interactive elements / non-text UI components: 3:1 minimum contrast ratio

These are targets to check a pairing against, not a guarantee that every combination of the scales above already clears them. The steel (primary-*) scale is the clearest case: no single step reaches 3:1 against both a light and a dark surface — the 400 step measures 2.84:1 on white, 2.72:1 on the 50 surface, and 2.61:1 on the 100 surface, and the 500 step measures 2.86:1 on the 800 surface (focus-ring-contrast.test.ts). That failure is exactly why the kit's own focus indicator ships as a light/dark pair instead of one colour, and it's the reason a dedicated guard exists: it reads the compiled stylesheet, runs each shipped indicator colour through WCAG's own contrast arithmetic, and fails the build if a pair drops under 3:1 in either theme. Every indicator the kit ships today clears it, at 4.09:1–5.16:1 on the light surfaces it checks and 5.19:1–6.96:1 on the dark ones — but that guard's surface list (the page, a card, the tab strip, a dark panel) is a hand-maintained judgement about where these components sit, not derived from the stylesheet, so a shade you place on a background outside that list is unverified. Check your own pairing with the tools below before shipping it.

// ✅ Good contrast
<div className="bg-neutral-950 text-white">High contrast</div>
<div className="bg-neutral-100 text-neutral-900">Good readability</div>

// ❌ Poor contrast
<div className="bg-primary-300 text-white">Low contrast</div>
<div className="bg-neutral-200 text-neutral-400">Hard to read</div>

The first pairing is Button's own primary variant — ink, not steel. The primary-* scale is reserved for focus rings, selection, and links; it is never a background fill, which is also why it isn't a safe stand-in for "high contrast" on its own.

Dark Mode

Colors automatically adjust for dark mode:

// Automatically adapts to dark mode
<div className="bg-white dark:bg-neutral-900">
  <p className="text-neutral-900 dark:text-neutral-100">Text color changes with theme</p>
</div>

Customizing Colors

Override a Scale

Tailwind v4 configures color in CSS, not in a tailwind.config.js — v4 does not read one unless you point at it explicitly with @config, and a config file left lying around is simply ignored (Configuration covers this in full). Add your own @theme block after importing @nim-ui/components/styles, and redeclare the variables you want to change:

src/index.css
@import 'tailwindcss';
@import '@nim-ui/components/styles';

@theme {
  --color-primary-500: oklch(0.58 0.21 300);
  --color-primary-600: oklch(0.50 0.19 300);
}

Every component reads its color through the variable (var(--color-primary-500)), not a fixed value, so this re-tints the kit everywhere that shade is used — focus rings, links, selection, and more — without touching a component file or rebuilding the library. The order matters: your @theme block has to come after the import, since both compile into the same @layer theme and the later declaration wins. See Theming for the full override mechanism, including the contrast obligation that comes with retinting primary.

Note the scale this page surfaces as "Danger" is named --color-error-* in tokens.css, not --color-danger — redeclare that name if you customize it.

Add a Scale of Your Own

Any --color-<name>-<shade> variable declared in a @theme block becomes a utility. Add a scale alongside the defaults instead of replacing one:

src/index.css
@theme {
  --color-brand-50: oklch(0.97 0.02 300);
  --color-brand-500: oklch(0.58 0.21 300);
  --color-brand-900: oklch(0.28 0.11 300);
}

See Configuration → Theme Customization for the rest of the @theme surface — typography, spacing, and radius alongside color.

Color Accessibility

Testing Tools

Use these tools to verify color contrast:

Best Practices

  1. Use semantic colors appropriately

    • Success: green tones
    • Warning: yellow/orange tones
    • Danger: red tones
    • Info: blue tones
  2. Don't rely on color alone

    • Add icons or labels
    • Use patterns or textures
    • Provide text alternatives
  3. Test in different modes

    • Light mode
    • Dark mode
    • High contrast mode
    • Color blind simulation

What's Next?

On this page