Webflow

How to Create a Working Dark Mode Toggle in Webflow

TS Talha Shahzad··10 min read
The short version
  • Webflow Variables enable true theme switching without duplicating classes
  • A toggle flips a data attribute that activates the dark variable mode
  • localStorage remembers the user's theme choice across pages and sessions
  • Respect prefers-color-scheme on first visit for a seamless default
  • Check color contrast in both modes before launching

Webflow Variables made real dark mode possible without the old hack of duplicating every class with dark color overrides. You define color variables with light and dark modes, bind a toggle to swap the active mode, and persist the user's choice with a small localStorage script.

The result is a genuine theme toggle that switches every color on the site in one action, remembers the user's preference across pages and sessions, and respects the operating system's light/dark preference on the first visit.

Here is how to build the whole thing, from the variable setup to the toggle interaction to the persistence layer.

How Webflow Variables Changed Dark Mode

Before Variables, building dark mode in Webflow was painful. You had two options:

  1. Duplicate every class. Create a combo class like "dark" and override every background, text, border, and shadow color manually. A site with 40 classes meant 40 combo class overrides. Miss one, and you have light text on a light background somewhere.
  2. CSS custom properties via custom code. Define CSS variables in the head, switch them with JavaScript, and apply them to Webflow elements. This worked but was fragile because you were managing styles in two places (Webflow and custom code), and other team members could not see the dark mode values in the Designer.

Webflow Variables eliminated both approaches. You now define a color variable (like "Background Primary") with two modes: Light and Dark. In Light mode, it is #FFFFFF. In Dark mode, it is #1A1A2E. Apply that variable to an element's background in the Webflow Designer, and the element automatically uses the correct value based on the active mode.

You can read more about how Variables work in Webflow's official documentation.

The mode switch is handled by a data attribute on the <html> element. When the attribute changes, every variable-bound property updates. One attribute change, entire site color swap.

Set Up Your Color Variables

Before building the toggle, define your variable palette. Open the Variables panel in Webflow (the V icon in the left sidebar).

Create variables for every color role in your design system. Here is the minimum set I use:

  • Background Primary. Main page background. Light: #FFFFFF, Dark: #0F0F14
  • Background Secondary. Cards, sections, inputs. Light: #F5F5F7, Dark: #1A1A24
  • Text Primary. Body text. Light: #1A1A1A, Dark: #E8E8EC
  • Text Secondary. Captions, meta text. Light: #666666, Dark: #9898A4
  • Border. Dividers and borders. Light: #E0E0E4, Dark: #2A2A34
  • Accent. Brand color, CTAs. This might stay the same in both modes, or shift slightly for contrast.
  • Surface. Modals, dropdowns, tooltips. Light: #FFFFFF, Dark: #24242E

Apply these variables to every element in your site instead of hardcoded color values. Background colors, text colors, borders, shadows, everything. This is the upfront investment. It takes time to rebind existing styles to variables, but once it is done, the dark mode switch is automatic.

For new builds, I start with variables from day one. For existing sites, I refactor section by section. It usually takes 2-3 hours for a typical marketing site.

Build the Toggle Switch

The toggle itself is a simple interactive element. I build mine as a Div Block styled to look like a switch:

  1. Create a Div Block (the track). Width: 48px, Height: 28px, border-radius: 14px, background: bind to a "Toggle Track" variable (Light: #E0E0E4, Dark: #4A4A54).
  2. Inside it, create another Div Block (the thumb). Width: 24px, Height: 24px, border-radius: 12px, background: white, position: relative, top: 2px, left: 2px.
  3. Add a cursor: pointer to the track.

You can also use icons instead of a switch. A sun icon and moon icon that swap on click works well and is more immediately recognizable. Use a Div Block with two image or SVG children, where one is visible and the other is hidden. The toggle interaction swaps their visibility.

Place the toggle in your navbar, usually on the right side next to your CTA button. On mobile, put it in the hamburger menu or in a fixed corner position.

Give the outer toggle element a custom attribute: data-theme-toggle="true". The JavaScript will use this to attach the click handler.

The JavaScript for Theme Switching and Persistence

Add this script to your site-wide head code. Yes, the head, not the before-body. This is one of the rare cases where head placement matters. You want the theme to be applied before the page renders so users never see a flash of the wrong theme.

<script>
(function() {
  const saved = localStorage.getItem('theme');
  const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
  const theme = saved || (prefersDark ? 'dark' : 'light');
  document.documentElement.setAttribute('data-theme', theme);
})();
</script>

This inline script runs immediately, before the page renders. It checks localStorage for a saved preference. If none exists, it checks the OS preference. It then sets the data-theme attribute on the <html> element. Because Webflow Variables respond to this attribute, the correct theme colors load on the first paint.

Now add the toggle handler. This goes in the site-wide before-body code because it needs to find the toggle element in the DOM:

<script>
document.addEventListener('DOMContentLoaded', function() {
  const toggle = document.querySelector('[data-theme-toggle]');
  if (!toggle) return;

  // Set toggle visual state on load
  const currentTheme = document.documentElement.getAttribute('data-theme');
  if (currentTheme === 'dark') {
    toggle.classList.add('is-dark');
  }

  toggle.addEventListener('click', function() {
    const html = document.documentElement;
    const isDark = html.getAttribute('data-theme') === 'dark';
    const newTheme = isDark ? 'light' : 'dark';

    html.setAttribute('data-theme', newTheme);
    localStorage.setItem('theme', newTheme);
    toggle.classList.toggle('is-dark');
  });
});
</script>

The is-dark class on the toggle controls its visual state (thumb position, icon swap). Set up a Webflow interaction or CSS transition that moves the thumb from left to right when the is-dark class is present.

Want a website that turns visitors into customers, not just compliments?

Book a 15-min intro

Respect prefers-color-scheme on First Visit

The first script already handles this: when there is no saved preference in localStorage, it falls back to the OS preference via window.matchMedia('(prefers-color-scheme: dark)').

This means a user who has their MacBook or iPhone set to dark mode will see your dark theme on their first visit, without clicking anything. That is the expected behavior and it feels seamless.

When they manually toggle your switch, their explicit choice overrides the OS preference and gets saved to localStorage. From that point on, their chosen theme persists regardless of OS settings.

One subtle UX touch: if you want to let users reset to "follow system," you can add a third state to your toggle (light / dark / auto). But for most marketing sites, a simple light/dark toggle is sufficient.

Prevent the Flash of Wrong Theme

The number one complaint about dark mode implementations is the flash. The user has dark mode selected, they navigate to a new page, and for a split second they see a white flash before the dark theme kicks in. It is jarring, especially in a dim room.

The head script I showed above prevents this because it runs synchronously before the first paint. The browser reads the script, sets the data-theme attribute, and then renders the page with the correct variable values. No flash.

There are two things that can break this:

  1. The script is in before-body instead of head. Then the page renders with the default (light) theme before the script runs. Move it to the head.
  2. The script is loaded externally with defer or async. Then it downloads in parallel and executes later. Keep this script inline in the head. It is small (under 200 characters minified) and does not warrant external hosting.

Handle Images and Media in Both Modes

Colors are only part of dark mode. Images, illustrations, and logos also need attention.

Logos. If your logo is dark text on transparent background, it disappears on a dark background. Use a different logo version for dark mode. You can swap them with a Webflow interaction tied to the theme toggle, or use CSS to hide one and show the other based on the data-theme attribute:

[data-theme="dark"] .logo-light {
  display: none;
}
[data-theme="dark"] .logo-dark {
  display: block;
}
[data-theme="light"] .logo-dark {
  display: none;
}
[data-theme="light"] .logo-light {
  display: block;
}

Screenshots and UI images. If you show product screenshots on your site, light-themed screenshots on a dark background look odd (and vice versa). Options: add a border or shadow to screenshots so they float on any background, use device mockups with their own background, or provide two versions and swap them with the same CSS approach.

Illustrations. If your illustrations use your brand colors and you have bound those colors to variables, SVG illustrations inline in your page can adapt automatically. Raster illustrations (PNG, JPG) cannot, so you might need two versions or a border/shadow treatment.

Check Contrast in Both Modes

This is where I see dark mode implementations fail most often. A site looks great in light mode, the team toggles to dark mode, and suddenly:

  • Secondary text is too faint on the dark background
  • Borders are invisible
  • Button hover states lose contrast
  • Links look the same as body text

Test every page in both modes before launching. Use your browser's DevTools to check contrast ratios. The WCAG minimum is 4.5:1 for normal text and 3:1 for large text.

Pay special attention to:

  • Form inputs. Input backgrounds, placeholder text, and borders all need to be visible in both modes.
  • Code blocks. If your blog has code snippets, the code block background and text need to work in both themes. Sometimes a slightly adjusted code block background for dark mode is better than using the same gray in both.
  • Alerts and badges. Red error messages, green success messages, and colored badges need to maintain contrast on both light and dark backgrounds.

Dark Mode for CMS Content

If your site has a Webflow CMS blog (like this one), dark mode needs to handle CMS rich text content. The rich text element renders headings, paragraphs, links, images, code blocks, and blockquotes, and all of those elements need correct colors in both modes.

Since you are using variables for your base text and background colors, most of this works automatically. But rich text has some elements that Webflow's Designer does not let you style directly:

  • Inline code. The <code> element inside paragraphs often has a hardcoded gray background. Override it:
[data-theme="dark"] .w-richtext code {
  background-color: #2A2A34;
  color: #E8E8EC;
}
  • Blockquote borders. The left border on blockquotes is often a light gray that disappears on dark backgrounds:
[data-theme="dark"] .w-richtext blockquote {
  border-left-color: #4A4A54;
}
  • Table borders. If your CMS content includes tables:
[data-theme="dark"] .w-richtext table td,
[data-theme="dark"] .w-richtext table th {
  border-color: #2A2A34;
}

These overrides go in your site-wide head code inside a <style> tag. They are small and well within the character limit.

Performance Impact

Dark mode adds negligible performance overhead. The head script is under 200 characters. The toggle handler is under 500 characters. The CSS overrides for rich text and images are a few hundred characters more.

Webflow Variables are resolved by the browser's CSS engine, which handles variable resolution extremely efficiently. Switching between modes triggers a repaint (the browser redraws with new colors), but that repaint is fast because color changes do not affect layout.

The only performance consideration is the number of variables. A site with 50+ color variables might see a marginally slower repaint on very old devices. For typical sites with 10-15 color variables, the switch is instant.

Wrapping Up

Dark mode in Webflow is no longer a hack or a luxury feature. Variables make it architecturally clean. The implementation is: define your color variables with light and dark modes, bind them to all elements, add a small head script for persistence, and wire up a toggle.

The upfront cost is rebinding your existing hardcoded colors to variables. Once that is done, you have a production-ready dark mode that respects user preferences, persists across sessions, and loads without a flash.

If you are building a new Webflow site and want dark mode from the start, or if you need to retrofit it onto an existing build, let me know. I also work with agencies through my white-label partnership for builds like this that need clean, maintainable implementation.

Prefer to hire through Upwork?
Top Rated Plus, 100% Job Success, 450+ projects shipped. See the reviews and start a contract.
Hire me on Upwork

FAQ

Can I add dark mode to a Webflow site without duplicating every class?

Yes. Webflow Variables let you define color tokens with light and dark modes. Switching the mode changes all colors at once without touching individual classes.

How do I save the user's dark mode preference in Webflow?

Use a small JavaScript snippet that saves the selected theme to localStorage. On page load, the script reads localStorage and applies the saved theme before the page renders.

What is prefers-color-scheme and should I use it?

It is a CSS media query that detects the user's operating system theme preference (light or dark). You should respect it on first visit when the user has not manually toggled your site's theme.

Does dark mode affect SEO or performance?

Not directly. The CSS and JavaScript for dark mode are minimal. However, if your dark mode has poor contrast or broken images, it can hurt user experience and indirectly affect engagement metrics.

All posts
the next step is small

Want a site that does this for you?

15 minutes, no deck, no pressure. Worst case, you leave with a free plan.

keep reading

More notes