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:
| Category | Examples | CSS Properties |
|---|---|---|
| Colors | Primary, neutral, semantic | --color-primary-500, --color-danger |
| Spacing | xs through 3xl | --spacing-md, --spacing-lg |
| Typography | Font families, sizes, weights | --font-sans, --text-lg, --font-bold |
| Borders | Radius values | --radius-md, --radius-lg |
| Shadows | Elevation 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:
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:
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
: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:
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 themeconst 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:
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:
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:
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
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:
/* 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:
- Color contrast — All text/background combinations meet WCAG AA (4.5:1 for normal text, 3:1 for large text)
- Dark mode — Every custom color has a dark mode counterpart
- Semantic consistency — Success is green-toned, danger is red-toned, warning is yellow/orange-toned
- Component rendering — Test all component variants with the custom theme
- Interactive states — Hover, focus, active, and disabled states are all visually distinct
What’s Next?
- Customization - Override individual component styles
- Dark Mode - Implementing dark mode
- Colors - Default color palette
- Typography - Font configuration