Neumorphic Toggle
A soft, pressed-in segmented toggle — a gradient pill with two opposing inset shadows and a raised thumb that slides between sun and moon. Pure CSS.
click either side — the thumb slides
Usage
import { NeuToggle } from "~/components/toggle/neu-toggle";// Sun / moon by default<NeuToggle value={theme} onChange={setTheme} aria-label="Theme" />// Or your own two options<NeuToggleoptions={[{ value: "list", icon: <List />, label: "List" },{ value: "grid", icon: <LayoutGrid />, label: "Grid" },]}defaultValue="grid"/>
How the softness works
It started as a Sketch recipe: a pill with a left-to-right gradient, then two inset shadows pointing opposite ways. One dark (#171717) pushed in from one side, one light (#494949) from the other. Those opposing shadows are the whole neumorphic trick. The surface looks like it was pressed out of a single soft material instead of drawn with a border.
I added the part the static recipe leaves out: a raised thumb that actually moves. It's a rounded knob with its own drop shadow and a top highlight, and it slides between the two halves with a translateX on a springy ease. The icons sit above it, so whichever one the thumb is under reads as bright and the other dims back. Keyboard and screen readers get a real radiogroup.
/* The track: gradient + two opposing inset shadows = the soft pressed look. */.neu-toggle {border-radius: 100px;background: linear-gradient(90deg, #4b4b4b, #111111);box-shadow:inset 20px 0 30px -10px #171717, /* dark side */inset -20px 0 30px -10px #494949, /* light side */0 1px 2px rgba(0, 0, 0, 0.5);}/* The raised knob, sliding between halves. */.neu-toggle__thumb {position: absolute; top: 6px; left: 6px;width: calc(50% - 6px); height: calc(100% - 12px);border-radius: 100px;background: linear-gradient(180deg, #3c3c3c, #1b1b1b);box-shadow: 0 2px 6px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.1);transition: transform .28s cubic-bezier(.2,.7,.2,1);}.neu-toggle[data-index="1"] .neu-toggle__thumb { transform: translateX(100%); }
Props
| prop | type | default | description |
|---|---|---|---|
| options | [Option, Option] | sun / moon | Exactly two options: { value, icon, label }. |
| value | string | — | Controlled selected value. |
| defaultValue | string | options[0] | Uncontrolled initial value. |
| onChange | (value) => void | — | Fires on select. |
| aria-label | string | "Toggle" | Label for the radiogroup. |