A centered dot, until it needs to say more.
.iz-theme-toggle is a compact Light/Dark control for navigation, app
headers and product chrome — not a general binary setting. At rest it shows only
a small state dot, no visible track. Hover or keyboard focus reveals a small
vertical track and the dot moves to its current theme's position; leaving
hover/focus collapses it back to center without changing the selected theme. Only
a click or tap actually switches the theme.
.iz-theme-toggle on a real <button type="button">.aria-pressed="true" = dark is currently selected (dot: lower position). "false"/absent = light (upper).<button
class="iz-theme-toggle"
type="button"
aria-label="Switch to dark theme"
aria-pressed="false"
>
<span class="iz-theme-toggle__track" aria-hidden="true">
<span class="iz-theme-toggle__indicator"></span>
</span>
</button>
Try it — hover, Tab to it, click it.
This one is wired up for real: it reads and writes this page's actual
data-theme, the same state the masthead toggle above uses. Hover or
focus it to see the reveal; click it to actually switch the whole page's theme.
Tab to it and watch the track reveal exactly as hover does; press Space or Enter to toggle. Move focus away (or the pointer off) without clicking and the track collapses back to center — the selected theme does not change until an actual click/activation.
Light is up. Dark is down. Always.
Consistent everywhere the control appears — the dot's position within the revealed track always means the same thing, regardless of the page's own current theme.
Ink on light. Lime on dark. Never a filled track.
The dot's color follows the page's actual ambient theme (not the toggle's own
selected-state position) — Ink on a light surface, Lime on a dark one, the same
guarded pattern design-tokens/themes.css uses everywhere else. The
revealed track is always a quiet neutral surface with a restrained border — never
filled Green or Lime.
Lime was compared here against the light theme-text foreground at this same tiny 12px scale before settling on Lime: a small, precise dot is the same category of use as Tabs' selection marker or Switch's on-state track — brand color used functionally at a restrained scale, not a large field — so Lime reads as refined here rather than loud, consistent with that existing precedent.
Two different controls, two different jobs.
One tap is the whole action.
--motion-duration-fast / --motion-ease-standard — the same restrained hover-state timing used everywhere else in the system. No spring, no bounce, no glow.prefers-reduced-motion: reduce — the track appears/collapses and the dot repositions instantly; nothing about the state becomes unclear.--color-focus), never Blue.Presentation only.
.iz-theme-toggle has no persistence and no FOUC-prevention logic — a
consuming app owns reading the stored/OS-preferred theme before first paint,
flipping data-theme, and persisting the choice. The live specimen
above shows the minimal amount of controller code that requires (read current
theme, toggle data-theme, update aria-pressed and the
accessible label) — see this page's own inline script for the exact pattern.
ThemeToggle from @irozon/design-system-react.
Presentational only, by design — it takes isDark and
onToggle and renders the control above; how (or whether) the choice
persists across visits is your app's decision, same as the vanilla-JS pattern this
page documents.
import { ThemeToggle } from "@irozon/design-system-react";
<ThemeToggle
isDark={theme === "dark"}
onToggle={() => setTheme(theme === "dark" ? "light" : "dark")}
/>