Customize
Styling
import '@devix-labs/theme-toggle/styles.css'; // plain
import '@devix-labs/theme-toggle/styles.layer.css'; // the same, inside @layer devix
Variables
.dxm {
--dxm-radius: 9px;
--dxm-size: 28px;
--dxm-gap: 2px;
--dxm-padding: 2px;
--dxm-font-size: 12.5px;
--dxm-bg: #f4f4f5;
--dxm-fg: #71717a;
--dxm-border: #e4e4e7;
--dxm-active-bg: #fff;
--dxm-active-fg: #18181b;
--dxm-accent: #ea4b71;
--dxm-shadow: 0 1px 2px rgb(17 17 28 / 0.08);
}
The control styles itself for dark under .dark or [data-theme="dark"] — the
same attribute it sets — so it follows the theme it controls without being told
which one that is.
Your classes
createThemeToggle(el, {
classNames: { root: 'rounded-full shadow-sm', option: 'px-3', icon: 'opacity-80' },
});
Parts: root, option, button, icon, label.
Your icons
createThemeToggle(el, {
icons: {
light: '<svg viewBox="0 0 24 24">…</svg>',
dark: '<i class="fa-solid fa-moon"></i>',
system: '<i class="fa-solid fa-desktop"></i>',
},
});
Anything you leave out keeps ours. The markup is yours, so an icon font is as welcome as an SVG.
State
| Selector | When |
|---|---|
.dxm[data-theme='dark'] |
The choice is dark — system stays system. |
.dxm-option:has(.dxm-input:checked) |
The selected option. |
.dxm-option:has(.dxm-input:focus-visible) |
Keyboard focus. |
The choice is kept on <html data-theme-choice> as well, so a stylesheet can
style "system was picked" differently from "it resolved to dark" — which a
two-state switch has no way to express.
A round button instead
createThemeToggle(el, { variant: 'button' });
.dxm-button { border-radius: 999px; }
It cycles light → dark → system, and its aria-label names what it will do
next rather than what it currently is — because a button's label should
describe its action.
Updated 15 Sep 2026