AI CSS Animations

Guides · · Maxwell Prehoda

13 Dark Mode Toggle Examples, From One-Click Icons to Full-Page Reveals

A good dark mode toggle does three things: it starts from the visitor's system setting, it lets them override it and remembers the choice, and it never flashes the wrong theme on load. The animation is the fun part, but it is the last of the four decisions, not the first.

This list covers all four. Some entries are the controls themselves (an icon, a switch, a three-way picker), some are the plumbing that makes them behave, and one is the View Transitions trick for a full-page circular reveal. Every entry says what it is best for and what goes wrong, and our pure CSS versions play live where they fit.

1. GitHub's Appearance settings: Best for products whose users need more than light and dark

Platform: Settings page in GitHub

Live from our directory: Theme Picker Radio Cards. Copy the HTML and CSS →

GitHub does not use a header toggle at all. Under Settings, Appearance, you pick a theme mode: single theme, or follow system settings, where you choose one day theme and one night theme and GitHub switches between them with your OS. The list goes past light and dark to a high contrast theme and light and dark colorblind themes, and you can also change it from the command palette.

That is the model to copy when theme is an accessibility setting, not a style preference. A day theme and a night theme, chosen separately, is also more useful than a binary switch for anyone who works across the evening.

Our Theme Picker Radio Cards below is a pure CSS take on a settings-page picker: light, dark and system drawn as tiny app windows, built on real radio inputs.

Watch out for: A settings page is hard to find for a quick switch. If people change theme often, add a shortcut in the menu or command palette as GitHub does.

2. Vercel's Geist Theme Switcher: Best for a compact Light, System, Dark control in a footer

Platform: React component (Geist), built on next-themes

Live from our directory: Segmented Control with Sliding Pill. Copy the HTML and CSS →

Vercel's design system ships a Theme Switcher with three options, Light, System and Dark, each with its own icon and aria-label. It has a default size and a small variant meant for dense places like footers and dropdowns, and the docs say to use one instance per app, typically in the footer or settings.

The important design choice is that System is a first-class option, not a hidden default. Once someone picks Light or Dark, they can get back to following their OS without clearing storage. The component reads and writes next-themes and disables itself when a page forces a theme.

Our Segmented Control with Sliding Pill below shows the same three-segment shape in pure CSS: radios with a pill that glides behind the chosen label, positioned with :has().

Watch out for: Icon-only segments need labels. Keep the aria-label on each option and a visible tooltip, since a monitor icon for System is not obvious to everyone.

3. Josh W. Comeau's sun and moon toggle: Best for static or SSR sites that must never flash the wrong theme

Platform: Blog post, React + inline script + CSS variables

Live from our directory: Sun and Moon Icon Toggle. Copy the HTML and CSS →

Josh Comeau's blog has an animated toggle that morphs between a sun and a moon, and his post The Quest for the Perfect Dark Mode (2020, updated February 2026) explains the hard part behind it. On a statically generated site the HTML is built before anyone visits, so the first paint cannot know the visitor's theme. His fix is a small blocking script before the body that checks localStorage, then prefers-color-scheme, and sets CSS variables on the root before anything renders.

He also renders the toggle as nothing until the mode is known, so it never shows the wrong icon for a frame, and measured the synchronous localStorage read at about 12 microseconds on a throttled CPU.

The post builds a plain checkbox and leaves the morph out of scope. Our Sun and Moon Icon Toggle below is a pure CSS sun-to-crescent icon you can drop on top of his plumbing.

Watch out for: The fix only works if the script is inline and blocking in the head. Loading it async or from a bundle brings the flash right back.

4. Akash Hamirwasia's View Transitions reveal: Best for a full-page circular reveal from the toggle

Platform: View Transitions API + Web Animations, React

Live from our directory: Dark Mode Toggle. Copy the HTML and CSS →

This November 2023 write-up is the clearest recipe for the effect where the new theme spreads out in a circle from the switch. Wrap the theme change in document.startViewTransition() (with flushSync in React so the DOM updates in time), wait for transition.ready, then animate a clip-path from circle(0px) to a radius that reaches the farthest corner of the viewport, on the ::view-transition-new(root) pseudo-element. His values: 500 ms, ease-in-out.

Two details make it work: turning off the default cross-fade on the old and new root snapshots, and computing the radius with Math.hypot so the circle always covers the screen. He also skips the animation entirely under prefers-reduced-motion and when the API is missing.

Same-document view transitions reached Baseline in October 2025. Our Dark Mode Toggle below fakes the same reveal inside a mini page with only CSS and :has(), no JavaScript.

Watch out for: While the transition runs, the page is shown as snapshots and is not interactive. Keep it around 500 ms, and never run it on initial load.

5. Toggles.dev by Alfie Jones: Best for a ready-made animated icon toggle in React, Svelte or Vue

Platform: npm, shadcn/ui or copy the source

Live from our directory: Sun to Moon Icon Hover. Copy the HTML and CSS →

Toggles.dev is a small library of 14 animated theme toggles with names like Classic, Dark Side, Eclipse, Half Sun, Horizon, Inner Moon, Lightbulb and Within. Each is a sun or moon icon that transforms into the other, and you can install them for React, Svelte or Vue, add them through shadcn/ui, or copy the source.

It is the quickest route when you want a polished icon and already have the theme plumbing from next-themes or your framework. Seeing 14 variants side by side is also a fast way to decide how playful your toggle should be.

Our Sun to Moon Icon Hover below is a pure CSS version of the icon swap, with two icons sharing one grid cell, if you would rather not add a dependency.

Watch out for: The toggle only animates the icon. Persisting the choice, following the OS and avoiding the load flash are still your job.

GitHub ↗

6. Google Chrome Labs' dark-mode-toggle element: Best for framework-free sites that want one drop-in element

Platform: Web component, Apache 2.0

A custom element, dark-mode-toggle, that starts from the visitor's prefers-color-scheme, lets them override it for your site, and with the permanent attribute remembers that choice. The appearance attribute switches between a toggle (dark mode on or off) and a switch (light versus dark theme), and you can set your own legend and labels.

Its neat trick is the stylesheet approach: put each theme in its own stylesheet with a media attribute like (prefers-color-scheme: dark), and the element flips those media attributes. Non-matching sheets still download but do not block rendering. It can also toggle a class on the body if your CSS is already organized that way.

Style it with ::part() selectors and custom properties. It suits blogs, docs and plain HTML sites where pulling in a framework for a theme switch would be silly.

Watch out for: It is not an official Google product and its look is plain by default. Plan to restyle it with ::part() so it matches the rest of your header.

web.dev: prefers-color-scheme ↗

7. next-themes by Paco Coursey: Best for Next.js and React apps, two lines of setup

Platform: React library, MIT

next-themes sits under a large share of Next.js theme toggles, including Vercel's own Theme Switcher. It injects a script so the page does not flash, follows prefers-color-scheme by default, keeps the theme in sync across tabs and windows, and sets either a data-theme attribute or a class on the html element.

Two options matter for animation. disableTransitionOnChange turns off all CSS transitions for the moment of the switch, so buttons and cards with different transition durations do not fade at different speeds and leave the page looking half-changed. forcedTheme pins a page to one theme without overwriting the user's saved choice.

Pair it with any icon from this list. It handles state and storage; you supply the control.

Watch out for: Your toggle cannot know the theme during server rendering. Render a placeholder until the component mounts, or the icon will mismatch and React will warn about hydration.

README ↗

8. Tailwind CSS dark variant: Best for Tailwind projects with a light, dark and system toggle

Platform: Tailwind CSS v4

By default Tailwind's dark: utilities follow prefers-color-scheme with no setup. To drive them from a toggle, v4 uses one line of CSS: **@custom-variant dark (&:where(.dark, .dark *));**, or the same with a [data-theme=dark] selector if you prefer an attribute.

The docs include the three-way script most Tailwind sites copy: on load, add the dark class if localStorage.theme is "dark", or if nothing is stored and the OS prefers dark. Set localStorage.theme to light or dark when the user picks, and remove it when they choose System. The docs say to put it inline in the head to avoid the flash.

Any switch in our Toggle Switches directory sits on top of this, since each one is a real checkbox with role="switch": read its checked state and toggle the class.

Watch out for: Every color needs its dark: twin in the markup, which gets noisy. For large apps, define theme colors as CSS variables once and let utilities read them.

Dark mode docs ↗

9. CSS light-dark() with color-scheme: Best for theming without writing every color twice

Platform: Native CSS, Baseline 2024

The light-dark() function takes two colors and returns one based on the active color scheme: color: light-dark(#333b3c, #efefec). It only works once color-scheme: light dark is set, usually on :root, and MDN lists it as Baseline 2024.

That makes the toggle simpler. Instead of swapping a class that rewrites dozens of variables, your toggle sets color-scheme to light or dark on the root element and every light-dark() value follows, along with native form controls and scrollbars.

Combine it with :has() and a checkbox and you can build a theme switch with no JavaScript at all, though you will still want a few lines to remember the choice.

Watch out for: Newly available means older browsers ignore it. Declare a plain fallback color before the light-dark() line, or older browsers drop the declaration and fall back to defaults.

10. Håvard Brynjulfsen's Light, Dark or Black Theme: Best for offering a true black mode for OLED screens

Platform: CodePen, HTML + CSS

This pen, featured in Speckyboy's 2026 roundup of dark and light mode snippets, puts three color modes in a small settings panel: light, dark and black. The radio buttons are styled to look like a toggle switch and the change between modes is a smooth transition.

A third black option is worth considering for reading apps and anything used at night on phones, where a pure black background looks different from the usual dark grey. Treating it as a radio group, not three buttons, keeps it keyboard friendly by default.

If you build one, a segmented control is a natural shape for three modes.

Watch out for: Pure black with pure white text causes halation for some readers. Use off-white text on the black theme and test long paragraphs.

11. Airen's swinging light bulb toggle: Best for personal sites that want a playful, physical switch

Platform: CodePen, HTML + CSS

Live from our directory: Pull Cord Lamp Switch. Copy the HTML and CSS →

Another pick from Speckyboy's roundup: a hanging bulb that swings when you switch modes. The bulb sits inside a real button element for accessibility, and CSS handles the swing.

Physical metaphors like a lamp or a pull cord read instantly as "lights off", which is exactly what dark mode means to most people. They are a good fit for portfolios and personal blogs where a bit of personality is the point.

Our Pull Cord Lamp Switch below is a pure CSS take on the same idea: tug the cord and the bulb flickers on under a cone of light.

Watch out for: A big decorative toggle competes with your navigation. Keep it small in the header, or put the full lamp only on the about page.

Speckyboy roundup ↗

12. Our Day Night Toggle: Best for a landing page toggle people click twice for fun

Platform: Motions directory, free, HTML + CSS Ours

Live from our directory: Day Night Toggle. Copy the HTML and CSS →

The famous day and night switch, rebuilt in pure CSS: a sun with halo rings rolls across a blue sky, a moon slides over it, the clouds sink away and twinkling stars drop in. It is built from spans and box-shadows on a real checkbox, so it is focusable and works with the keyboard.

Like every snippet in our directory it comes with custom properties for colour and size and a prefers-reduced-motion guard that swaps the motion for an instant change. Wire its checked state to your theme script and it is done.

Watch out for: It is wide and detailed. In a dense app header, use the Sun and Moon Icon Toggle instead and save this one for marketing pages.

13. Our Sunset Toggle Switch: Best for travel, weather and calm lifestyle brands

Platform: Motions directory, free, HTML + CSS Ours

Live from our directory: Sunset Toggle Switch. Copy the HTML and CSS →

A tiny landscape inside the switch: the sun arcs over the hills and sets while the moon rises on the other side and the sky fades to night. Both ride one rotating arm pinned below the horizon, so a single transform does the whole arc.

It is slower and softer than the usual knob slide, which suits brands where the switch should feel like dusk rather than a light switch. Want a different scene? Describe it to the creator and get the keyframes back.

Watch out for: The arc takes longer than a standard switch. Keep the theme change itself instant and let only the illustration play, so the page never feels slow.

How to choose a dark mode toggle

Decide in this order: the options, the plumbing, then the look.

Two states or three

A binary toggle is fine for a blog. Anything people use daily should offer Light, Dark and System, like Vercel's switcher, so users can get back to following their OS. If theme is an accessibility need, go further and copy GitHub's separate day and night themes and high contrast options.

Match the plumbing to your stack

Next.js: next-themes. Tailwind: the dark variant and the docs' three-way script. Plain HTML: the Chrome Labs element or light-dark() with color-scheme. Whatever you pick, the theme must be set by an inline script in the head before first paint, or you get the flash Josh Comeau describes.

Match the motion to the moment

In an app header, a small icon morph under 300 ms is plenty. On a landing page or portfolio, a scene like our Day Night Toggle or a View Transitions reveal is part of the brand. In both cases, switch the colors instantly or with transitions disabled, animate only the control, and respect prefers-reduced-motion.

Ship your theme switch tonight

Do the plumbing first: an inline head script that reads localStorage and prefers-color-scheme, a class or color-scheme on the root, and a System option. That takes ten minutes and fixes most complaints about dark mode.

Then pick a control from our toggle switches, copy the HTML and CSS, and connect its checked state. If none of them fit your brand, describe the one you want and refine it in chat.

A prompt to start from
Make a dark mode toggle button for a site header, 36 px square.
Light state shows a sun; dark state shows a crescent moon. Morph between them in 250 ms with cubic-bezier(0.22, 1, 0.36, 1): the rays shrink and spin away, a bite slides across the disc.
Use a real button with aria-pressed and a visible focus ring.
Animate only the icon, not the page colors.
Under prefers-reduced-motion: reduce, swap the icons instantly.

Make your own with AI →

Common questions

How do I stop the flash of the wrong theme on page load?

Set the theme before the first paint with a small inline script in the head that reads the saved choice from localStorage, falls back to prefers-color-scheme, and adds a class or color-scheme to the html element. Scripts loaded later or from a bundle run too late.

Should a dark mode toggle have a System option?

For anything used regularly, yes. Without it, a user who picks Light or Dark once can never get back to following their OS. Vercel's Geist switcher and the Tailwind docs both treat System as a first-class choice.

Can you make a dark mode toggle with only CSS?

You can switch the theme with a checkbox and :has(), and light-dark() with color-scheme makes the colors follow. CSS alone cannot remember the choice between visits, so you need a few lines of JavaScript and localStorage for that.

How do you do the circular reveal when switching themes?

Wrap the theme change in document.startViewTransition(), then animate clip-path on ::view-transition-new(root) from a 0 px circle at the toggle to a radius that covers the farthest corner. Turn off the default cross-fade and skip it under prefers-reduced-motion.

Should the colors animate when the theme changes?

Usually not. Transitions on many elements finish at different times and the page looks half-switched. Libraries like next-themes offer disableTransitionOnChange for this reason. Animate the toggle itself, or use one full-page reveal.

Sources