Dark Mode
Learn how Neura handles dark mode and how to customize it
Overview
By default, Neura will handle the appearance of your application by adding a
dark
class to the html
element depending
on the user's system preference or selected appearance.
Neura includes a built-in theme switcher component that allows users to toggle between light, dark, and system preferences. The theme preference is automatically saved to localStorage and persists across page reloads. Theme changes animate with the View Transition API when the browser supports it.
Using the Theme Switcher
Neura provides a theme switcher component that you can add to your application:
<neura::theme-switcher variant="dropdown" />
The theme switcher component supports multiple variants:
dropdown
, inline
, stacked
, and pullcord
.
Animation Types
Theme changes use the browser
View Transition API
.
Pick an effect with the animation
prop on
neura::theme-switcher
, or set a kit-wide default via
$theme.configure()
. Unsupported browsers and
prefers-reduced-motion
fall back to an instant switch.
circle
blur-circle
qr-scan
polygon
polygon-gradient
none
<neura::theme-switcher variant="inline" animation="circle" />
<neura::theme-switcher variant="inline" animation="blur-circle" />
<neura::theme-switcher variant="inline" animation="qr-scan" />
<neura::theme-switcher variant="inline" animation="polygon" />
<neura::theme-switcher variant="inline" animation="polygon-gradient" />
<neura::theme-switcher variant="inline" animation="none" />
| Type | Description |
|---|---|
| circle | Expanding circle from the clicked control (default) |
| blur-circle | Circle reveal with a soft blurred edge |
| qr-scan | Horizontal scan line sweeping left to right |
| polygon | Diagonal wipe (toward dark from top-left, toward light from bottom-right) |
| polygon-gradient | Diagonal wipe with a soft gradient edge from the top-left |
| gif | Reveal through a custom GIF mask — requires gifUrl
via $theme.configure()
|
| none | Instant theme change with no animation |
Configure defaults globally (animation type, duration, blur, GIF mask):
<div x-data x-init="$theme.configure({
animationType: 'blur-circle',
duration: 900,
blurAmount: 4,
})">
<neura::theme-switcher variant="inline" />
</div>
<div x-data x-init="$theme.configure({
animationType: 'gif',
gifUrl: 'https://example.com/mask.gif',
duration: 2000,
})">
<button type="button" @click="$theme.toggle($event)">Toggle</button>
</div>
PullCord
A ceiling pull-cord you can grab — the rope is a real Verlet simulation that hangs, swings, and settles.
Pull past the detent to toggle the theme (fires mid-pull, like a real chain). Click and Enter work too.
Look up
The cord mounts with
position: fixed
at the top of the viewport.
Try the live demo in the top-right of this page after enabling the example below — or drop the
component into your layout.
App chrome
Grab the knob hanging from the ceiling and pull — or click / press Enter on it.
<neura::theme-switcher variant="pullcord" />
<neura::theme-switcher
variant="pullcord"
:gravity="1250"
:damping="0.94"
:iterations="20"
:stretch-max="26"
/>
<style>
:root {
--nk-pullcord-top: 0px;
--nk-pullcord-right: 7rem;
--nk-pullcord-z: 50;
--nk-pullcord-ink: rgba(127, 127, 127, 0.45);
}
</style>
| Prop / CSS var | Default | Description |
|---|---|---|
| variant | 'pullcord' | Enables the hanging cord switcher |
| gravity | 1250 | Hang tension / fall speed |
| damping | 0.94 | Higher = snappier retract |
| iterations | 20 | Rope stiffness (constraint solver passes) |
| stretchMax | 26 | How deep you can pull past rest |
| noEntrance | false | Skip the drop-in entrance animation |
| --nk-pullcord-top | 0px | Anchor offset from the top |
| --nk-pullcord-right | 7rem | Distance from the right edge |
| --nk-pullcord-ink | rgba(127,127,127,.45) | Rope stroke colour |
Alpine.js Theme Magic
Neura provides an Alpine.js magic property
$theme
that you can use in your components:
<button @click="$theme.setLight($event)">
Light Mode
</button>
<button @click="$theme.setDark($event)">
Dark Mode
</button>
<button @click="$theme.setSystem($event)">
System Preference
</button>
<div x-show="$theme.isResolvedToDark">
This content is only visible in dark mode
</div>
Pass
$event
so the View Transition expands from the control.
Available methods and properties:
| Method/Property | Description |
|---|---|
| $theme.setLight($event?) | Set theme to light mode |
| $theme.setDark($event?) | Set theme to dark mode |
| $theme.setSystem($event?) | Use system preference |
| $theme.toggle($event?) | Toggle between light and dark |
| $theme.configure(options) | Set kit-wide animation defaults (animationType
, duration
, blurAmount
, gifUrl
, …) |
| $theme.isResolvedToLight | Check if currently in light mode |
| $theme.isResolvedToDark | Check if currently in dark mode |
Manual Theme Management
If you don't want Neura to handle theme management for you, you can remove the theme management from the JavaScript
globals and handle the appearance of your application manually.
To disable automatic theme management, you can comment out or remove the theme initialization in your JavaScript files.
Custom Dark Mode Styles
Neura uses Tailwind's dark mode variant. You can customize dark mode styles using the
dark:
prefix:
<div class="bg-white dark:bg-neutral-900 text-neutral-900 dark:text-white">
Content that adapts to theme
</div>