All posts

How-to

Tailwind dark mode color palette: which steps to use

Which Tailwind color steps to use for backgrounds, text, borders and accents in dark mode, how to apply them with dark: or CSS variables, and how to mirror them in Figma.

By HuekitPublished 7 min read

For a Tailwind dark mode color palette, you don't need new colors. You reuse the same families from the other end of the scale: backgrounds from 950, 900 and 800, primary text from white or 50, secondary text from 400, borders from 800 or 700 (or a low-opacity white), and accents a step or two lighter than in light mode, usually 400 or 500 instead of 600. Then check each text and background pair against WCAG contrast, because the right step depends on the family.

Below is a starting map, what Tailwind's own docs do, and how to turn the choices into tokens so you aren't writing dark: on every element.

How Tailwind's palette is built

Tailwind's colors page lists 26 color families in the default palette as of October 2026, from red to the newer taupe, mauve, mist and olive. Every family has 11 steps: 50 is the lightest and 950 the darkest. In Tailwind v4 the values are defined in OKLCH.

That 950 step matters for dark mode. It gives you a near-black with a hint of the family's hue, which is often a better page background than pure black.

A starting map for dark mode

Use one neutral family (gray, zinc, slate, neutral or stone) for surfaces and text, and one brand family for actions.

RoleLight modeDark modeNotes
Page backgroundwhite or neutral-50neutral-950900 if 950 feels too heavy
Raised surface (cards)whiteneutral-900 or 800Lighter than the page, not darker
Subtle backgroundneutral-100neutral-800Hover rows, wells
Primary textneutral-950 or 900white or neutral-50
Secondary textneutral-500 or 600neutral-400Check contrast on raised surfaces
Borderneutral-200neutral-800 or white/10Lower contrast than in light mode looks fine; too low disappears
Action (button)brand-600brand-500 or 400Text on it may need to flip to dark
Tinted backgroundbrand-50 or 100brand-400/10Low-opacity tint instead of a pale step
Danger textred-600red-400Same idea for green, amber and others

These are starting points, not answers. Yellow, amber and lime behave very differently from blue and violet at the same step, so test every pair you actually ship.

Tailwind's own examples follow the same pattern. The colors docs show a card going from bg-white to dark:bg-gray-800, names going from text-gray-950 to dark:text-white, body text from text-gray-700 to dark:text-gray-400, and a tinted badge using dark:bg-pink-400/10 with a dark:border-pink-300/10 border. The dark mode docs use text-gray-500 with dark:text-gray-400 for secondary text.

Why you can't just flip the scale

It's tempting to map 50 to 950, 100 to 900 and so on. It rarely works, for three reasons:

  • Elevation goes the other way. In light mode, raised cards are usually the same white as the page with a shadow. In dark mode shadows barely show, so a raised surface needs a lighter step than the page to read as "above" it.
  • Saturated colors glow on dark. A brand-600 button that looks solid on white can feel harsh on neutral-950. One step lighter, with slightly lower saturation, usually sits better.
  • Contrast isn't symmetric. Gray-500 text on white and gray-500 text on gray-950 give different ratios. Each mode needs its own check.

Checking the pairs

WCAG 2.2 sets a minimum ratio of 4.5:1 for body-size text, dropping to 3:1 once text is large (W3C), and 3:1 for the parts of a control people rely on to spot it, such as the outline of a text field (W3C).

The pairs worth checking in dark mode:

  1. Primary text on the page background and on raised surfaces.
  2. Secondary text on raised surfaces (this is the one that usually fails first).
  3. Button text on the action color.
  4. Input borders on the surface they sit on.
  5. Status text (danger, success, warning) on the page background.

In Figma you don't need a plugin for single pairs: the color picker includes a contrast view that rates the selected text color against what's behind it, with AA and AAA markers (Figma).

Stop writing dark: on every element

Hand-picking dark: classes is fine for a landing page. For an app, it means every component carries two sets of colors, and someone eventually forgets one. The alternative is semantic tokens: name the role once and let the theme decide the step.

css
@import "tailwindcss";

@custom-variant dark (&:where(.dark, .dark *));

:root {
  --surface: var(--color-white);
  --surface-raised: var(--color-white);
  --fg: var(--color-zinc-950);
  --fg-muted: var(--color-zinc-600);
  --line: var(--color-zinc-200);
}

.dark {
  --surface: var(--color-zinc-950);
  --surface-raised: var(--color-zinc-900);
  --fg: var(--color-zinc-50);
  --fg-muted: var(--color-zinc-400);
  --line: var(--color-zinc-800);
}

@theme inline {
  --color-surface: var(--surface);
  --color-surface-raised: var(--surface-raised);
  --color-fg: var(--fg);
  --color-fg-muted: var(--fg-muted);
  --color-line: var(--line);
}

Now a card is bg-surface-raised text-fg border-line in both themes. The step choices from the table live in one place. Tailwind's theme docs recommend inline when theme variables reference other variables, and the @custom-variant line switches dark mode on a .dark class instead of the system setting.

For naming those roles, we keep a separate naming reference. If your developers export these values from Figma, see our guide to exporting Figma variables to Tailwind.

Mirroring the palette in Figma

The same structure works in Figma variables: the Tailwind palette as a single-mode Primitives collection, and a Semantic collection with Light and Dark modes whose values alias the steps from the table. Switching a frame to Dark then shows you the real pairs, so you can check contrast on actual screens. Our variable modes setup guide walks through it.

Two plugins can get the palette into Figma quickly:

  • Tailwind Color Palettes by Varun (65,810 users) adds Tailwind's colors to your file as color styles, with presets for Tailwind v4, v3, v2 and v1, and can build a custom set from a config file.
  • Tailwind CSS Color Generator (112,423 users) creates a custom 11-step scale from a hex or HSL color and saves it as color variables, with an option to keep APCA contrast consistent across generated shades. It's based on uicolors.app.

Huekit, our plugin, has a Tailwind preset (on paid plans) that builds the scales as Figma variables, with semantic tokens and Light and Dark modes on top, so the dark-mode mapping exists as variables you can inspect and change. See huekit.io for details.

FAQ

What Tailwind color should I use for a dark mode background?

Start with your neutral family's 950 for the page and 900 or 800 for cards. Tailwind's own docs use dark:bg-gray-800 for a card.

What text color works on Tailwind dark backgrounds?

White or the 50 step for primary text, and 400 for secondary text. Check secondary text against your raised surface, since that pair has the least contrast.

Should I use pure black for dark mode in Tailwind?

You can, but a 950 step usually feels softer and keeps a hint of your neutral's hue. Tailwind's palette includes 950 for every family.

How do I add dark mode colors in Tailwind v4?

Use the dark: variant on utilities, or define semantic CSS variables for each theme and expose them with @theme inline. Add @custom-variant dark if you want a class toggle instead of the system setting.

Do I need a separate dark color palette?

No. Reuse the same families from different steps, and keep the mapping in semantic tokens.

Try it on one card

Take one card component, list its five colors (surface, title, body, border, button), and choose the dark step for each from the table. Check the five pairs, then turn them into semantic variables. Every other component will follow the same pattern.

About the author

Written by the Huekit team.

Want the fast path?

Huekit builds the color scales, semantic tokens, and Light/Dark modes inside your Figma file, then exports JSON tokens or a Tailwind config.

Get Huekit on Figma

Keep reading