Most dark mode toggles instantly swap colors. It works. But it feels dead. One frame: light. Next frame: dark. No continuity. No acknowledgment that something changed. The interface just snaps.
Interfaces communicate through behavior, not just appearance.
What you remove matters more than what you add.
Core claim
- Instant transitions break continuity.
- Interfaces should acknowledge state changes.
- Motion is not decoration - it is communication.
The problem with instant
An instant color swap is technically correct. The theme changes. The function works.
But interfaces are not just function. They are experience.
A hard cut exposes the mechanism instead of hiding it.
It breaks immersion. It draws attention to the seam between states instead of smoothing over it. The user becomes aware of the mechanism rather than just enjoying the result.
Compare it to any well-designed transition in a native app. Settings panels slide. Modals fade. Selection states ripple.
These are not decorations. They are the product communicating that something happened - and that it is under control.
Interfaces should guide, not negotiate.
A theme toggle should do the same.
What I wanted
I wanted the theme change to feel like part of the product. Not an interruption. Not a flash.
Something that originates from the button you just pressed and expands outward - like a decision taking effect.
I had seen the View Transition API mentioned in browser release notes but had not found a real use case for it. A theme toggle turned out to be the perfect fit.
The API lets you snapshot the current and next states of the page, then animate between them.
With a clip-path circle anchored to the toggle button, the new theme ripples outward from exactly where you clicked - expanding until it covers the viewport.
Good defaults are invisible decisions.
Duration: 620ms. Easing: ease-in-out. It feels intentional because it is.
The details that matter
The wave is the main event, but there are four other things that make it work properly.
Image switching. If your light and dark themes use different images, a raw color swap leaves the wrong image visible.
Nightwave checks for data-theme-dark and data-theme-light attributes and swaps src, poster, or background-image automatically. The wave covers the transition.
Reduced motion. Not everyone wants animation.
Nightwave checks prefers-reduced-motion before every wave. If enabled, the animation is skipped. The theme still changes instantly using CSS transitions. Nothing breaks.
System sync. If there is no saved preference, Nightwave reads prefers-color-scheme and applies the correct theme.
It also listens for changes. If the OS theme changes mid-session, the interface follows - without reload.
Flash prevention. Without a small script in <head>, users see a flash of light on every load.
Nightwave reads localStorage before first paint and sets data-theme. No flash. No transition glitch.
Small details shape perception more than large features.
Zero dependency, by choice
Nightwave is a single JavaScript file. No npm. No bundler. No build step.
Drop it in, link the stylesheet, call init(). Done in under a minute.
I wanted something that works everywhere - from full frameworks to static HTML.
Constraints create clarity.
The result is 2 KB of code that does one thing well.
Get it
Nightwave is available as a one-time purchase. Full source, no lock-in.
Try it yourself: the toggle in the top-right corner of this page runs on Nightwave. Full documentation at angusu.de/docs/nightwave.
Die meisten Dark-Mode-Toggles tauschen Farben einfach instant aus. Es funktioniert. Aber es fühlt sich tot an. Ein Frame: hell. Nächster Frame: dunkel. Keine Kontinuität. Kein Signal, dass etwas passiert ist. Das Interface springt einfach.
Interfaces kommunizieren über Verhalten, nicht nur über Aussehen.
Was du entfernst, ist oft wichtiger als das, was du hinzufügst.
Kernaussage
- Instant-Wechsel zerstören Kontinuität.
- Interfaces sollten Zustandsänderungen sichtbar machen.
- Bewegung ist keine Dekoration - sie ist Kommunikation.
Das Problem mit Instant
Ein sofortiger Farbwechsel ist technisch korrekt. Das Theme ändert sich. Die Funktion funktioniert.
Aber Interfaces sind nicht nur Funktion. Sie sind Erfahrung.
Ein harter Schnitt macht den Mechanismus sichtbar, statt ihn zu verstecken.
Er bricht die Immersion. Er lenkt Aufmerksamkeit auf den Übergang statt ihn zu glätten. Der Nutzer nimmt das System wahr, statt einfach das Ergebnis zu erleben.
Vergleiche das mit gut gestalteten Transitions in nativen Apps. Panels gleiten. Modals blenden ein. Selektionen reagieren.
Das ist keine Dekoration. Das ist Kommunikation.
Interfaces sollten führen, nicht verhandeln.
Ein Theme-Toggle sollte das auch tun.
Was ich wollte
Ich wollte, dass sich der Theme-Wechsel wie ein Teil des Produkts anfühlt. Nicht wie ein Bruch. Nicht wie ein Flash.
Sondern wie etwas, das dort entsteht, wo man klickt - und sich dann ausbreitet.
Wie eine Entscheidung, die Wirkung zeigt.
Die View Transition API war dafür perfekt.
Sie erlaubt es, den aktuellen und den nächsten Zustand zu „snapshotten“ und dazwischen zu animieren.
Mit einem Clip-Path-Kreis, der am Toggle startet, breitet sich das neue Theme genau von dort aus - bis es den gesamten Screen füllt.
Gute Defaults sind unsichtbare Entscheidungen.
620ms. ease-in-out. Es fühlt sich bewusst an, weil es bewusst ist.
Die Details, die zählen
Die Welle ist das sichtbare Feature. Aber vier andere Dinge machen es erst vollständig.
Image-Switching. Unterschiedliche Themes brauchen unterschiedliche Bilder.
Nightwave erkennt data-theme-dark und data-theme-light und tauscht automatisch Bilder aus. Die Welle verdeckt den Wechsel.
Reduced Motion. Nicht jeder möchte Animation.
Wenn prefers-reduced-motion aktiv ist, wird die Animation übersprungen. Das Theme wechselt trotzdem. Ohne Bruch.
System-Sync. Ohne gespeicherte Präferenz folgt Nightwave dem OS-Theme.
Ändert sich dieses, folgt das Interface automatisch. Ohne Reload.
Flash-Vermeidung. Ohne Script im <head> gibt es bei jedem Load einen Light-Flash.
Nightwave setzt das Theme vor dem ersten Paint. Kein Flash.
Kleine Details beeinflussen Wahrnehmung stärker als große Features.
Zero Dependency, bewusst
Nightwave ist eine einzige JS-Datei. Kein npm. Kein Build.
Einbinden, init(), fertig.
Constraints schaffen Klarheit.
2 KB Code. Eine Aufgabe. Sauber gelöst.
Get it
Nightwave ist als einmaliger Kauf verfügbar. Voller Source Code, keine Bindung.
Teste es selbst: Der Toggle oben rechts nutzt Nightwave.
If this is the kind of thinking you want in your product, say hello.
Start the conversation.