Skip to content

Theming

Nim UI is built on a design token system that maps to Tailwind CSS utility classes. By modifying these tokens, you can create a fully branded theme that applies consistently across all components.

Design Tokens Overview

Design tokens are the foundational values of the design system. They define colors, spacing, typography, borders, and shadows. Nim UI organizes tokens into the following categories:

CategoryExamplesCSS Properties
ColorsPrimary, neutral, semantic--color-primary-500, --color-danger
Spacingxs through 3xl--spacing-md, --spacing-lg
TypographyFont families, sizes, weights--font-sans, --text-lg, --font-bold
BordersRadius values--radius-md, --radius-lg
ShadowsElevation levels--shadow-sm, --shadow-lg

Tailwind CSS v4 Theme Configuration

Nim UI uses Tailwind CSS v4, which supports theme configuration through both the tailwind.config.js file and CSS custom properties.

Extending the Theme

Add custom values to the theme without overriding the defaults:

tailwind.config.js
export default {
darkMode: 'class',
content: [
'./src/**/*.{js,ts,jsx,tsx}',
'./node_modules/@nim-ui/components/dist/**/*.js',
],
theme: {
extend: {
colors: {
brand: {
50: '#fdf4ff',
100: '#fae8ff',
200: '#f5d0fe',
300: '#f0abfc',
400: '#e879f9',
500: '#d946ef',
600: '#c026d3',
700: '#a21caf',
800: '#86198f',
900: '#701a75',
},
},
fontFamily: {
sans: ['Outfit', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'],
},
borderRadius: {
'4xl': '2rem',
},
spacing: {
'18': '4.5rem',
'88': '22rem',
},
},
},
};

Overriding Default Colors

Replace the default primary color scale with your brand colors:

tailwind.config.js
export default {
theme: {
extend: {
colors: {
primary: {
50: '#fff1f2',
100: '#ffe4e6',
200: '#fecdd3',
300: '#fda4af',
400: '#fb7185',
500: '#f43f5e', // Your brand primary
600: '#e11d48',
700: '#be123c',
800: '#9f1239',
900: '#881337',
},
},
},
},
};

All Nim UI components that reference primary-* colors will automatically use your custom palette.

CSS Custom Properties

For runtime theming or more granular control, define design tokens as CSS custom properties.

Base Tokens

src/styles/tokens.css
:root {
/* Colors */
--color-primary: 14 165 233; /* RGB for #0ea5e9 */
--color-success: 34 197 94; /* RGB for #22c55e */
--color-warning: 245 158 11; /* RGB for #f59e0b */
--color-danger: 239 68 68; /* RGB for #ef4444 */
--color-info: 59 130 246; /* RGB for #3b82f6 */
/* Typography */
--font-family-sans: 'Inter', system-ui, -apple-system, sans-serif;
--font-family-mono: 'Fira Code', 'Courier New', monospace;
/* Spacing */
--spacing-unit: 0.25rem;
/* Borders */
--radius-default: 0.375rem;
/* Shadows */
--shadow-color: 0 0% 0%;
}
.dark {
--color-primary: 56 189 248; /* Lighter for dark mode */
--shadow-color: 0 0% 100%;
}

Using CSS Variables with Tailwind

Reference CSS custom properties in Tailwind using arbitrary value syntax:

// Use RGB variables with opacity support
<div className="bg-[rgb(var(--color-primary))]">
Primary background
</div>
<div className="bg-[rgb(var(--color-primary)/0.1)]">
10% primary background
</div>

Connecting Variables to Tailwind Config

Map CSS variables to Tailwind theme values for cleaner usage:

tailwind.config.js
export default {
theme: {
extend: {
colors: {
primary: {
DEFAULT: 'rgb(var(--color-primary) / <alpha-value>)',
},
success: 'rgb(var(--color-success) / <alpha-value>)',
warning: 'rgb(var(--color-warning) / <alpha-value>)',
danger: 'rgb(var(--color-danger) / <alpha-value>)',
info: 'rgb(var(--color-info) / <alpha-value>)',
},
},
},
};

Now you can use the standard Tailwind utilities with full opacity support:

<div className="bg-primary text-white">Solid primary</div>
<div className="bg-primary/10">10% primary</div>
<div className="border-primary/50">50% primary border</div>

Creating a Custom Theme

Step 1: Define Your Color Palette

Generate a full color scale from your brand color. Tools like Tailwind CSS Color Generator can help.

// Example: Rose-based brand theme
const brandColors = {
50: '#fff1f2',
100: '#ffe4e6',
200: '#fecdd3',
300: '#fda4af',
400: '#fb7185',
500: '#f43f5e',
600: '#e11d48',
700: '#be123c',
800: '#9f1239',
900: '#881337',
};

Step 2: Configure Typography

Choose fonts that match your brand identity:

tailwind.config.js
export default {
theme: {
extend: {
fontFamily: {
sans: ['Outfit', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'],
heading: ['Cal Sans', 'sans-serif'],
},
},
},
};

Step 3: Adjust Border Radius

Control the roundness of all components:

tailwind.config.js
export default {
theme: {
extend: {
borderRadius: {
sm: '0.125rem', // Sharper
DEFAULT: '0.25rem',
md: '0.375rem',
lg: '0.5rem',
xl: '1rem', // Rounder
},
},
},
};

Step 4: Define Shadows

Create custom elevation levels:

tailwind.config.js
export default {
theme: {
extend: {
boxShadow: {
'soft-sm': '0 2px 4px -1px rgba(0, 0, 0, 0.04)',
'soft-md': '0 4px 8px -2px rgba(0, 0, 0, 0.06)',
'soft-lg': '0 8px 16px -4px rgba(0, 0, 0, 0.08)',
'soft-xl': '0 16px 32px -8px rgba(0, 0, 0, 0.1)',
},
},
},
};

Step 5: Put It All Together

tailwind.config.js
export default {
darkMode: 'class',
content: [
'./src/**/*.{js,ts,jsx,tsx}',
'./node_modules/@nim-ui/components/dist/**/*.js',
],
theme: {
extend: {
colors: {
primary: {
50: '#fff1f2',
100: '#ffe4e6',
200: '#fecdd3',
300: '#fda4af',
400: '#fb7185',
500: '#f43f5e',
600: '#e11d48',
700: '#be123c',
800: '#9f1239',
900: '#881337',
},
},
fontFamily: {
sans: ['Outfit', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'],
},
borderRadius: {
sm: '0.125rem',
DEFAULT: '0.25rem',
md: '0.375rem',
lg: '0.5rem',
xl: '1rem',
},
boxShadow: {
'soft-sm': '0 2px 4px -1px rgba(0, 0, 0, 0.04)',
'soft-md': '0 4px 8px -2px rgba(0, 0, 0, 0.06)',
'soft-lg': '0 8px 16px -4px rgba(0, 0, 0, 0.08)',
},
},
},
};

Multi-theme Support

For applications that support multiple themes (e.g., per-workspace or per-tenant themes), use CSS custom properties with data attributes:

src/styles/themes.css
/* Default theme */
:root {
--primary-500: #0ea5e9;
--primary-600: #0284c7;
--radius: 0.375rem;
}
/* Theme: Rose */
[data-theme='rose'] {
--primary-500: #f43f5e;
--primary-600: #e11d48;
--radius: 0.5rem;
}
/* Theme: Violet */
[data-theme='violet'] {
--primary-500: #8b5cf6;
--primary-600: #7c3aed;
--radius: 0.75rem;
}
/* Dark variants */
.dark {
--primary-500: #38bdf8;
--primary-600: #0ea5e9;
}
.dark[data-theme='rose'] {
--primary-500: #fb7185;
--primary-600: #f43f5e;
}
.dark[data-theme='violet'] {
--primary-500: #a78bfa;
--primary-600: #8b5cf6;
}

Apply themes by setting the data-theme attribute:

function ThemeSwitcher() {
const applyTheme = (theme: string) => {
document.documentElement.setAttribute('data-theme', theme);
};
return (
<div className="flex gap-2">
<button onClick={() => applyTheme('default')}>Default</button>
<button onClick={() => applyTheme('rose')}>Rose</button>
<button onClick={() => applyTheme('violet')}>Violet</button>
</div>
);
}

Theme Validation

When creating custom themes, verify these aspects:

  1. Color contrast — All text/background combinations meet WCAG AA (4.5:1 for normal text, 3:1 for large text)
  2. Dark mode — Every custom color has a dark mode counterpart
  3. Semantic consistency — Success is green-toned, danger is red-toned, warning is yellow/orange-toned
  4. Component rendering — Test all component variants with the custom theme
  5. Interactive states — Hover, focus, active, and disabled states are all visually distinct

What’s Next?