Skip to content
Kulr Theme Builder

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

Lumen
JD
New — realtime dashboards

Know 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.

2FA

By continuing you agree to the Terms of Service.

Monthly revenue

$48,290

▲ 12.5%
Subscriptions One-timeLast 7 days

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

DefaultSecondaryOutlineMuted
InvoiceCustomerStatusAmount
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

your color · click any swatch to copy

11 steps, 50–950, evenly spaced in OKLCH lightness and gamut-mapped to sRGB.

Primary
#7C3AED
Secondary
#A532B3
Accent
#2160ED
Neutral
tinted gray

Export

app.css

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

WCAG 2 ratio · APCA Lc
24/24 pairs pass AA 1 auto-fix applied
Darkprimarychanged from #8b5cf4 to#8757EF
PairLightDark
Foreground on backgroundforeground / background · 4.5:1
19.47AAA
18.99AAA
Card textcard-foreground / card · 4.5:1
19.78AAA
17.62AAA
Popover textpopover-foreground / popover · 4.5:1
19.78AAA
17.17AAA
Primary buttonprimary-foreground / primary · 4.5:1
5.70AA
4.53AA
Secondary buttonsecondary-foreground / secondary · 4.5:1
11.70AAA
12.29AAA
Muted text on mutedmuted-foreground / muted · 4.5:1
4.65AA
5.81AA
Muted text on backgroundmuted-foreground / background · 4.5:1
4.98AA
7.62AAA
Accent surfaceaccent-foreground / accent · 4.5:1
11.30AAA
11.55AAA
Destructive buttondestructive-foreground / destructive · 4.5:1
4.87AA
4.79AA
Success badgesuccess-foreground / success · 4.5:1
4.75AA
7.34AAA
Warning badgewarning-foreground / warning · 4.5:1
7.27AAA
9.18AAA
Focus ringring / background · 3:1
4.19UI 3:1
4.65AA
Primary as link textprimary / background · 3:1
5.61AA
4.37UI 3:1

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.