Tailwind & shadcn theme generator
Turn one brand color into a complete, accessible design system — light & dark — and export it to Tailwind v4, shadcn/ui or CSS variables.
Live preview
Every pixel below is drawn from your tokens · D to switchKnow your revenue, the moment it happens.
Lumen turns billing events into live metrics, forecasts and alerts — so your team can act on numbers, not hunches.
Create your workspace
Free for 14 days. No credit card.
By continuing you agree to the Terms of Service.
Monthly revenue
$48,290
Your trial ends in 3 days
Add a payment method to keep your dashboards live.
Payment failed
We couldn't charge the card ending 4242.
Pro
Most popular$29/ seat / month
Everything growing teams need to move fast.
- Unlimited dashboards
- Realtime revenue alerts
- Forecasts & cohorts
- SSO and audit log
Recent invoices
4 of 128 this month
| Invoice | Customer | Status | Amount |
|---|---|---|---|
| INV-2041 | Northwind billing@northwind.io | Paid | $2,450.00 |
| INV-2040 | Globex ap@globex.com | Pending | $1,280.00 |
| INV-2039 | Initech finance@initech.co | Overdue | $920.00 |
| INV-2038 | Umbrella pay@umbrella.dev | Draft | $3,100.00 |
Delete project
Permanently remove “Acme Analytics” and all of its dashboards. This can't be undone.
Changes saved
Workspace settings were updated.
Usage at 82% of your monthly events.
Color scales
11 steps, 50–950, evenly spaced in OKLCH lightness and gamut-mapped to sRGB.
Export
Paste into your main CSS file. Gives you bg-primary-500 … and bg-background, text-muted-foreground …
/* Generated by Kulr Theme Builder — https://kulr.app/theme?c=7c3aed */@import "tailwindcss";@custom-variant dark (&:where(.dark, .dark *));/* Color scales → bg-primary-500, text-neutral-600, border-accent-200 … */@theme { /* primary */ --color-primary-50: oklch(0.969 0.016 293.756); --color-primary-100: oklch(0.938 0.032 293.415); --color-primary-200: oklch(0.882 0.063 293.029); --color-primary-300: oklch(0.797 0.113 292.833); --color-primary-400: oklch(0.692 0.168 293.322); --color-primary-500: oklch(0.604 0.217 293.025); --color-primary-600: oklch(0.541 0.247 293.009); --color-primary-700: oklch(0.486 0.237 292.949); --color-primary-800: oklch(0.43 0.202 292.991); --color-primary-900: oklch(0.381 0.163 292.973); --color-primary-950: oklch(0.276 0.123 292.852); /* secondary */ --color-secondary-50: oklch(0.974 0.016 322.694); --color-secondary-100: oklch(0.95 0.031 322.589); --color-secondary-200: oklch(0.906 0.056 323.255); --color-secondary-300: oklch(0.84 0.1 322.491); --color-secondary-400: oklch(0.761 0.149 322.853); --color-secondary-500: oklch(0.686 0.192 322.665); --color-secondary-600: oklch(0.611 0.218 323.052); --color-secondary-700: oklch(0.541 0.21 323.162); --color-secondary-800: oklch(0.466 0.18 323.818); --color-secondary-900: oklch(0.408 0.144 324.632); --color-secondary-950: oklch(0.277 0.109 325.668); /* accent */ --color-accent-50: oklch(0.968 0.015 257.196); --color-accent-100: oklch(0.936 0.031 258.944); --color-accent-200: oklch(0.882 0.058 258.069); --color-accent-300: oklch(0.797 0.103 259.906); --color-accent-400: oklch(0.691 0.15 260.796); --color-accent-500: oklch(0.603 0.196 262.157); --color-accent-600: oklch(0.541 0.222 262.987); --color-accent-700: oklch(0.487 0.213 263.856); --color-accent-800: oklch(0.429 0.182 264.685); --color-accent-900: oklch(0.383 0.145 265.212); --color-accent-950: oklch(0.276 0.112 267.222); /* neutral (overrides Tailwind's default neutral) */ --color-neutral-50: oklch(0.986 0.004 286.327); --color-neutral-100: oklch(0.971 0.004 286.325); --color-neutral-200: oklch(0.923 0.004 286.321); --color-neutral-300: oklch(0.871 0.004 286.315); --color-neutral-400: oklch(0.708 0.007 295.39); --color-neutral-500: oklch(0.555 0.009 293.755); --color-neutral-600: oklch(0.438 0.01 293.646); --color-neutral-700: oklch(0.371 0.009 295.153); --color-neutral-800: oklch(0.27 0.005 286.025); --color-neutral-900: oklch(0.206 0.006 285.871); --color-neutral-950: oklch(0.146 0.004 285.857);}/* Semantic tokens → bg-background, text-muted-foreground, ring-ring … */:root { --radius: 0.625rem; --background: oklch(0.995 0.003 286.352); --foreground: oklch(0.146 0.004 285.857); --card: oklch(1 0 0); --card-foreground: oklch(0.146 0.004 285.857); --popover: oklch(1 0 0); --popover-foreground: oklch(0.146 0.004 285.857); --primary: oklch(0.541 0.247 293.009); --primary-foreground: oklch(1 0 0); --secondary: oklch(0.956 0.03 324.139); --secondary-foreground: oklch(0.32 0.12 323.145); --muted: oklch(0.971 0.004 286.325); --muted-foreground: oklch(0.541 0.008 295.314); --accent: oklch(0.954 0.022 265.764); --accent-foreground: oklch(0.321 0.12 262.993); --destructive: oklch(0.578 0.236 27.267); --destructive-foreground: oklch(1 0 0); --success: oklch(0.539 0.148 150.166); --success-foreground: oklch(1 0 0); --warning: oklch(0.801 0.16 75.173); --warning-foreground: oklch(0.301 0.07 55.249); --border: oklch(0.923 0.004 286.321); --input: oklch(0.899 0.005 297.716); --ring: oklch(0.604 0.217 293.025);}.dark { --background: oklch(0.146 0.004 285.857); --foreground: oklch(0.986 0.004 286.327); --card: oklch(0.194 0.006 300.982); --card-foreground: oklch(0.986 0.004 286.327); --popover: oklch(0.206 0.006 285.871); --popover-foreground: oklch(0.986 0.004 286.327); --primary: oklch(0.59 0.217 293.099); --primary-foreground: oklch(1 0 0); --secondary: oklch(0.281 0.046 323.394); --secondary-foreground: oklch(0.941 0.04 323.326); --muted: oklch(0.27 0.005 286.025); --muted-foreground: oklch(0.708 0.007 295.39); --accent: oklch(0.299 0.051 263.372); --accent-foreground: oklch(0.941 0.028 261.478); --destructive: oklch(0.58 0.22 25.191); --destructive-foreground: oklch(1 0 0); --success: oklch(0.719 0.17 150.046); --success-foreground: oklch(0.221 0.051 149.344); --warning: oklch(0.821 0.16 80.061); --warning-foreground: oklch(0.25 0.061 54.46); --border: oklch(0.279 0.007 297.355); --input: oklch(0.32 0.007 297.428); --ring: oklch(0.604 0.217 293.025);}@theme inline { --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); --color-card-foreground: var(--card-foreground); --color-popover: var(--popover); --color-popover-foreground: var(--popover-foreground); --color-primary: var(--primary); --color-primary-foreground: var(--primary-foreground); --color-secondary: var(--secondary); --color-secondary-foreground: var(--secondary-foreground); --color-muted: var(--muted); --color-muted-foreground: var(--muted-foreground); --color-accent: var(--accent); --color-accent-foreground: var(--accent-foreground); --color-destructive: var(--destructive); --color-destructive-foreground: var(--destructive-foreground); --color-success: var(--success); --color-success-foreground: var(--success-foreground); --color-warning: var(--warning); --color-warning-foreground: var(--warning-foreground); --color-border: var(--border); --color-input: var(--input); --color-ring: var(--ring); --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px);}Contrast report
| Pair | Light | Dark |
|---|---|---|
| Foreground on backgroundforeground / background · 4.5:1 | 19.47AAALc 105 | 18.99AAALc 105 |
| Card textcard-foreground / card · 4.5:1 | 19.78AAALc 106 | 17.62AAALc 104 |
| Popover textpopover-foreground / popover · 4.5:1 | 19.78AAALc 106 | 17.17AAALc 104 |
| Primary buttonprimary-foreground / primary · 4.5:1 | 5.70AALc 83 | 4.53AALc 76 |
| Secondary buttonsecondary-foreground / secondary · 4.5:1 | 11.70AAALc 90 | 12.29AAALc 91 |
| Muted text on mutedmuted-foreground / muted · 4.5:1 | 4.65AALc 69 | 5.81AALc 48 |
| Muted text on backgroundmuted-foreground / background · 4.5:1 | 4.98AALc 74 | 7.62AAALc 51 |
| Accent surfaceaccent-foreground / accent · 4.5:1 | 11.30AAALc 90 | 11.55AAALc 90 |
| Destructive buttondestructive-foreground / destructive · 4.5:1 | 4.87AALc 76 | 4.79AALc 77 |
| Success badgesuccess-foreground / success · 4.5:1 | 4.75AALc 78 | 7.34AAALc 56 |
| Warning badgewarning-foreground / warning · 4.5:1 | 7.27AAALc 62 | 9.18AAALc 69 |
| Focus ringring / background · 3:1 | 4.19UI 3:1Lc 68 | 4.65AALc 33 |
| Primary as link textprimary / background · 3:1 | 5.61AALc 76 | 4.37UI 3:1Lc 31 |
Text pairs need 4.5:1 (WCAG 2.2 AA, 1.4.3); focus rings need 3:1 (1.4.11). “Primary as link text” is advisory — light brand colors like yellow make great fills but poor text.
How the theme builder works
Start with a single brand color. Kulr converts it to OKLCH, finds the step of an 11-step scale (50–950) it naturally belongs to, and keeps your exact hex on that step. The remaining steps are spread evenly in perceived lightness, with chroma tapering toward the lightest tints and the darkest shades, and every color is gamut-mapped into sRGB by reducing chroma — never by clipping channels, which is what makes cheap generators turn yellow into mud and blue into purple.
From those scales the builder derives a complete set of semantic tokens for both light and dark mode — background, foreground, card, popover, primary, secondary, muted, accent, destructive, success, warning, border, input and ring. Secondary and accent colors come from a harmony rule (analogous, complementary, split or triadic) unless you lock your own, and neutrals are gently tinted toward your brand hue so grays never feel disconnected from the palette.
Every text/background pair is then checked against WCAG 2 AA. When a pair fails, auto-fix nudges lightness to the nearest value that passes and tells you exactly what changed. The live preview shows the result on a realistic app UI, so you judge the theme in context instead of as a row of squares.
Why OKLCH makes better color scales
OKLCH describes a color by lightness, chroma (colorfulness) and hue, and — unlike HSL — its lightness is perceptual. Two colors with the same OKLCH lightness really do look equally bright, so a 500 step in a blue scale and a 500 step in a green scale carry the same visual weight. That's why Tailwind CSS v4 and shadcn/ui moved their palettes to OKLCH.
- Even steps. Lightness is interpolated in a perceptual space, so no two neighbouring shades look identical and none jump.
- Hue-aware curves. Yellows peak in chroma near white and blues near the middle, so the builder adapts the lightness curve to each hue — the same idea behind Tailwind's hand-tuned palettes.
- Natural hue drift. Optional hue shifting warms dark yellows toward amber and deepens cyans toward blue, so shades look rich instead of muddy.
Using your theme with Tailwind CSS v4
Choose the Tailwind v4 export and paste it into your main CSS file. The scales are declared inside @theme as --color-primary-50 … --color-primary-950, so utilities like bg-primary-600, text-accent-700 and border-neutral-200 just work. Semantic tokens live in :root and .dark and are mapped with @theme inline, giving you bg-background, text-muted-foreground and ring-ring that switch automatically with dark mode.
Still on Tailwind v3? Use the Tailwind v3 config export together with the CSS variables export. Prefer a browsable reference of the default palette? See the Tailwind colors page.
Using your theme with shadcn/ui
The shadcn/ui export is a complete globals.css in the current format: oklch() values in :root and .dark, chart and sidebar tokens, the @theme inline mapping and radius scale. Replace the generated file in your project (usually app/globals.css) and every shadcn component — buttons, inputs, dialogs, charts, the sidebar — picks up your brand instantly.
Need to fine-tune a specific pair afterwards? Check it in the Contrast Checker, or explore complementary palettes in the Palette Generator.
Frequently asked questions
How do I generate a Tailwind color palette from one color?
Paste your brand hex (or pick one) and Kulr builds an 11-step scale from 50 to 950 in OKLCH. Your exact color is kept on the step it fits best, the other steps are spaced evenly in perceived lightness, and the result is exported as Tailwind v4 @theme variables like --color-primary-500 — so classes such as bg-primary-500 and text-primary-700 work immediately.
Does it work with shadcn/ui?
Yes. The shadcn/ui export is a drop-in globals.css in the current Tailwind v4 format: :root and .dark blocks with oklch() values for background, foreground, card, popover, primary, secondary, muted, accent, destructive, border, input, ring, chart-1 to chart-5 and the sidebar tokens, plus the @theme inline mapping and radius variables.
Why does the builder change my brand color slightly?
Only when auto-fix is on and a text pair would fail WCAG AA (4.5:1). The builder first tries the text color; if white or black text still can't reach 4.5:1, it nudges the fill's lightness to the nearest passing value while keeping its hue and chroma. Every change is listed in the contrast report, and you can turn auto-fix off to keep your exact hex.
Why OKLCH instead of HSL?
HSL lightness isn't perceptual: a yellow and a blue with the same HSL lightness look completely different. OKLCH lightness matches how bright colors actually look, so the steps in a scale feel evenly spaced, hues don't drift unexpectedly, and dark mode tokens can be derived predictably. Tailwind v4 and shadcn/ui both use OKLCH for their default palettes.
What is the difference between WCAG contrast and APCA?
WCAG 2 contrast ratio (1:1 to 21:1) is the current legal and industry standard; AA requires 4.5:1 for body text and 3:1 for large text and UI components. APCA (Lc values) is the candidate method for WCAG 3 and models perceived contrast more accurately, especially for dark mode. Kulr validates against WCAG 2 and shows APCA Lc as extra information.
Can I use the generated theme commercially?
Yes. Everything Kulr generates is free to use in personal and commercial projects with no attribution. The builder runs entirely in your browser — nothing is uploaded, and your last theme is saved only in your own browser's local storage.