Persistent Light/Dark Mode Toggle in Webflow (GSAP)

Advanced 30:53 webflowgsapdark-modevariablesjavascript

Build a persistent light/dark toggle in Webflow with variable modes, a GSAP class toggle, smooth color transitions, and localStorage that survives reloads.

Key takeaways

  • A whole theme swap can come down to toggling one class — if you invest up front: define your colors as variables and add a manual "dark mode" variable mode that overrides them.
  • GSAP's set action (not animate) flips that class instantly — set assigns a value with no duration or easing, exactly right for switching a theme.
  • Don't put CSS transitions on every colored element — they'd fight your other GSAP animations. Gate them behind a temporary theme-transition class added at the start of the toggle and removed at the end.
  • Persistence needs a little JavaScript: save the choice to localStorage on click, and read it back in the <head> before paint (also honoring prefers-color-scheme) so there's no flash.
  • Apply the theme class to <html>, not <body> — html exists before the body renders, so the class lands before first paint and the flicker never happens.

Video chapters

  1. 00:00 Intro
  2. 01:28 Defining light and dark mode with variables and variable modes
  3. 07:03 Implementing the color mode toggle button
  4. 09:32 Implementing the light/dark switch interaction
  5. 18:58 Leveraging CSS for a smooth color-mode transition
  6. 21:21 Integrating the smooth transition within the GSAP interaction
  7. 24:41 Leveraging JavaScript for a persistent color-mode switcher
  8. 29:34 Outro

A good light/dark toggle in Webflow isn’t one feature — it’s a small system: a color palette built on variables, a GSAP interaction that flips a single class, a smooth color transition that doesn’t sabotage your other animations, and a touch of JavaScript so the choice survives reloads and page changes. Build it once, the right way, and you can drop it into any project.

The payoff of doing the groundwork is almost magical: an entire theme change comes down to toggling one class. This lesson is also a great look at GSAP’s set action — the instant, no-duration cousin of animate — and how it plays with Webflow’s variable modes.

How it works

Variables and variable modes are the foundation. Put every color the theme should affect into a variable collection (grouped, e.g. backgrounds and fonts). The default base mode is your light theme; add a second manual variable mode called dark mode and override the values there. Then connect those variables to everything that uses color — start at the Body (All Pages) tag for font and background (most elements inherit from it), then specific classes and states as needed. With that done, applying a dark-mode class that carries the dark variable mode flips the whole palette instantly.

GSAP’s set action flips the class. The toggle uses two interactions, both with control toggle play/reverse (so each click reverses): one animates the little switch knob across the track, the other uses a set action on the class. Set is different from animate — instead of an “animated properties” section it has set properties, and under it a class option with add / remove / toggle. Toggling the dark-mode class is the entire theme switch. (Set runs on the page you built it — remember to set runs on: all pages so a copied toggle works everywhere, and keep the dark-mode class attached to something in your style guide so Webflow doesn’t prune it as unused.)

Smoothing the change without breaking everything. By default the theme flips instantly. You could add CSS transitions to every colored element, but that’s impractical and worse — those transitions would collide with any GSAP scroll/hover animation touching the same properties. The smarter move is a theme-transition class defined in custom head CSS that applies color transitions to all elements only while it’s present:

<!-- Site Settings → Custom Code → Head -->
<style>
  .theme-transition,
  .theme-transition *,
  .theme-transition *::before,
  .theme-transition *::after {
    transition:
      color 0.8s cubic-bezier(0.16, 1, 0.3, 1),
      background-color 0.8s cubic-bezier(0.16, 1, 0.3, 1),
      border-color 0.8s cubic-bezier(0.16, 1, 0.3, 1),
      outline-color 0.8s cubic-bezier(0.16, 1, 0.3, 1);
  }
</style>

In the switch interaction, set the theme-transition class on at 0s and set it off at 0.8s. Now colors ease over 800ms during the switch, and the transition is gone the rest of the time so it never interferes with anything else.

Persistence needs JavaScript — and this is where the flicker fix from lesson 2 returns in spirit. Two small scripts in Site Settings: a head script that runs before paint, reads the saved theme from localStorage (falling back to the system prefers-color-scheme), and applies the dark-mode class; and a footer script that saves the current choice to localStorage on each toggle. Critically, the persistence class goes on the <html> element, not <body> — html exists before the body renders, so applying the class before first paint means no flash of the wrong theme. (For that reason, switch the toggle’s set-class target from body to html too.)

How to use it

  1. Set up variables. Create a color variable collection, group your colors, and add a manual variable mode named dark mode with overridden values.
  2. Wire colors to the system. Assign the variables at the Body (All Pages) level first, then to specific classes/states so everything the theme touches is variable-driven.
  3. Build the toggle interactions. One interaction moves the switch knob (animate X, toggle play/reverse). Another uses a set → class → toggle action on dark-mode (target html), toggle play/reverse, runs on all pages.
  4. Add the smooth transition. Paste the theme-transition CSS into the head. In the switch interaction, set theme-transition on at 0s and off at 0.8s. Keep a theme-transition element in your style guide so Webflow keeps the class.
  5. Add persistence. Add the head + footer scripts (from the cloneable) for localStorage + prefers-color-scheme, then make the toggle a component and reuse it across pages. Publish and test on the live link (custom code doesn’t always run in preview).

The exact JavaScript (head persistence + footer save) lives in the cloneable — grab it there and match your class/variable names rather than retyping it.

Resources

Frequently asked questions

How do I build a dark mode in Webflow with variables?
Create a color variable collection and put every color the theme touches in it, then add a second variable mode (set it to manual) called dark mode and override the values there. A single 'dark-mode' class that applies the dark variable mode can then flip the whole palette at once.
What's the difference between GSAP's set and animate actions?
Animate transitions a property from one value to another over a duration with easing. Set assigns a value instantly — no duration, no easing — and can also add, remove, or toggle a class. For flipping a theme class, set is the right tool.
How do I make the dark-mode color change animate smoothly without breaking my other animations?
Don't add transitions to every element permanently — they'd interfere with your GSAP timelines. Define a theme-transition class (in custom head CSS) that applies color transitions to everything, then use the interaction to add it right before toggling the theme and remove it at the end, so the transition only happens during the switch.
How do I keep a Webflow dark mode from resetting on reload or when changing pages?
Add a little JavaScript: a footer script saves the current choice to localStorage on each toggle, and a head script reads that value before the page paints and applies the theme class (falling back to the system's prefers-color-scheme). Because it runs before paint and targets the html element, the theme persists with no flash of the wrong colors.

← Back to the course