Skip to content
Design Tokens

Build dark mode properly with Tailwind CSS

How to build dark mode from roles instead of color values, switch it without a flash and know the places that break in the dark: images, shadows and contrast.

9 min readUpdated: 24 August 2026
Design tokensTailwindCSSAccessibility
nordfeld.studio10 values4 roles
  • primary#1f5eff
  • surface#f8fafc
  • text#0f172a
  • border#e2e8f0

Dark mode is not a second color palette, it is the same page in different light. Swapping black and white alone gives you grey slabs, invisible edges and images that glow out of the page. This guide shows how to build the dark mode from roles, how to switch it without a flash and which places almost always break.

The short version
  • Do not switch colors, switch roles. Background, surface, text and border each get one value per mode.
  • Pure black is rarely right. A very dark grey feels calmer and lets surfaces become visible in the first place.
  • Shadows do not work in the dark. Depth comes from lighter surfaces there, not from darker edges.
  • The system setting is the default, the user's choice overrides it. Both together need one line in the head, otherwise the page flashes on load.
01

Roles instead of color values

The most common mistake is already in the markup: every element carries two colors, one for light and one for dark. With twenty components that is forty decisions nobody keeps in sync.

A layer in between is cleaner. You name what a color is for, not which one it is: base for the page, surface for everything sitting on it, text for the type, border for separation. These roles get one value per mode, and the components only know the role.

The payoff shows up with the next request. If the dark mode should get a touch warmer, you change four values in one place instead of forty spread through the code.

Read out16 colours, many nearly identical.
#1f5eff#1f5dfe#2060ff#0f172a#0f172b#111827#ffffff#fefefe#f8fafc#f7f9fb#e2e8f0#e3e8f1#64748b#65748a#94a3b8#1f5eff
KeptFour roles, each with one job.
  • primaryPrimary#1f5eff
  • surfaceSurface#f8fafc
  • textText#0f172a
  • borderBorder#e2e8f0

On the left many individual color values, on the right four roles that carry the mode.

Light mode first, then darkAs long as the roles are not settled in light mode, dark mode only doubles the mess. Once light is clean, dark is a list of four to eight values.
02

The setup in Tailwind

In Tailwind the roles live in CSS variables, and dark mode overrides exactly those variables. The classes in the markup stay untouched, because bg-surface means the same in both modes, only the value behind it changes.

There are two ways to switch. The system setting is read through the prefers-color-scheme query, an explicit choice hangs on a class on the html element. If you offer both, you define the values twice: once in the query, once on the class.

One detail gets forgotten often: color-scheme. It tells the browser how to render scrollbars, select fields and inputs. Without it a few brightly lit controls stay behind in the middle of your dark page.

:root {  color-scheme: light;  --color-base: #ffffff;  --color-surface: #f5f5f4;  --color-text: #1c1917;  --color-border: #e7e5e4;} @media (prefers-color-scheme: dark) {  :root:not(.light) {  color-scheme: dark;    --color-base: #0c0a09;    --color-surface: #1c1917;    --color-text: #e7e5e4;    --color-border: #292524;  }}
The roles once light, once dark. The class wins over the system setting.
03

Switching without a flash

If the saved choice only runs after React has loaded, the browser paints a light page first. The switch after that is visible as a short flash, and that is exactly how you spot a dark mode bolted on later.

The fix is unspectacular: a short script in the head that reads the saved choice and sets the class on the html element before the first pixel appears. It runs without a framework, without imports, and needs fewer than ten lines.

The toggle itself then knows three states: light, dark and follow the system. The third one belongs there, because many devices switch by themselves in the evening, and a one-off choice should not turn that off.

No transition on the base colorAnimating the background and text color makes the switch look dirty, because every surface passes through its own intermediate color. Let the change be instant, it only takes a frame anyway.

Few roles beat many values.

A list of hex numbers is an inventory. It becomes a system only once every value has a job.

ColourFour roles instead of sixteen values.
TypographyOne scale, no one-offs.
SpacingA four pixel step is enough.
04

What breaks in the dark

Colors are the easy part. A dark mode gets noticed in the places nobody thought of as color: images with a white background, shadows, logos saved as black files and charts with a light grid.

Images need either a transparent background or a frame that ties them into the page. A photo may stay bright, a logo may not: for logos and icons a second version that follows the text color instead of a fixed one is worth the effort.

Commonly seen
  • Pure black as the base. Every surface on it looks like a hole and borders disappear.
  • The same shadow as in light mode. You cannot see it, the card sticks to the background.
  • White type in a thin weight. It blooms on a dark ground and gets hard to read.
Works
  • Very dark grey as the base, the surfaces on top one step lighter.
  • Depth through brightness: whatever sits in front is lighter, not outlined darker.
  • Off-white for text and a weight that is not too thin.
05

Check before you ship

Dark mode rarely fails while you build it. It fails in the states you do not look at while building.

  1. Forms in every state. Empty, filled, with an error, disabled and focused. The focus ring in particular gets lost in the dark, because it has the same brightness as the border next to it.
  2. Everything that sits above the page. Menus, dialogs and toasts often carry their own surface in the code. If one of them was forgotten, it lights up brightly on the dark page.
  3. The first visit in a private window. Without a saved choice the system setting applies. That is the only way to see what new visitors actually get.
  4. The print view. A dark background becomes a black page on paper. A small print rule saves toner and trouble.

A good dark mode does not stand out. It just feels like the page was always meant that way.

Share
Written by
Amelie RoesmannCreative directionLeonie RoesmannDesign engineering

We build websites and web apps at Systra Studios in Münster and put the building blocks from that work here. What you read comes from real projects, not from a brochure.

Systra Studios: web design from Münster

You know what matters.
The reading is the tool's job.

Enter a URL, get colours, typography and spacing. As a DESIGN.md for people or a tokens.json for your build.

  • Colours are checked against the rendered page.
  • Values close to each other get merged.
  • Output as markdown or JSON, both ready to copy.
Open Style Extractor
Result
  • primary#1f5eff
  • surface#f8fafc
  • text#0f172a
  • border#e2e8f0
TypographyAaInter · 16 / 20 / 28 / 40
FAQ

Frequently asked questions

Because the depth is missing. In light mode a shadow separates the card from the background, in the dark you cannot see it. Invert the logic: the further forward a surface sits, the lighter it is. Two or three steps are enough for a whole interface.

Didn't find your question? We're happy to help.

Get in touch