Skip to content

Toggle

A switch for binary on and off settings. It is a native checkbox exposed with the switch role, so it submits a form value like any other field.

Import

import { Toggle } from "@hyzer-labs/ui"

Demo

Toggle vs Checkbox

Reach for Toggle when a setting reads as on or off, like a preference. It renders role="switch", which screen readers announce as on or off. Use Checkbox for selections from a list, multi-select membership, or the indeterminate select-all pattern.
let notifications = $state(true);

<Toggle name="notifications" label="Round notifications" bind:checked={notifications} />

Props

NameTypeDefaultNote
namestringRequired. Form field name.
labelstringRequired. Always in the DOM; sr-only with hideLabel.
checkedbooleanfalseBindable.
valuestringSubmitted value when on.
descriptionstringHelp text on its own row.
errorstringInline error message; sets the error state.
requiredbooleanfalse
disabledbooleanfalse
hideLabelbooleanfalse
classstringMerged after the hz-field hz-field--toggle classes.

Theme hooks

What this component promises your CSS. The reference theme styles exactly these — from @layer hz-theme, so your unlayered rules win. See Styling Components for the how.

Root class: .hz-field hz-field--toggle

Overriding this needs extra care

.hz-field--toggle input.hz-toggle — the track and thumb — is the one rule in the reference theme shipped outside @layer hz-theme, on purpose: headless, Toggle is a bare native checkbox, so this rule needs to out-rank the component's own scoped structural reset unconditionally, and a layer can only lose to an unlayered rule regardless of specificity. It sits at a raw (0,2,1) specificity. A normal override written inside a layer — or even an unlayered rule that ties on specificity and loses on source order — silently does nothing; the toggle keeps its default look with no error. To win reliably, give your override enough specificity to clear (0,2,1) outright, e.g. by scoping it under your own theme's root class: .your-theme .hz-field--toggle input.hz-toggle { … }. See the Terminal example theme for a worked example (theme/examples/terminal/components/toggle.css), which does exactly this. The same applies to the --hz-toggle-width / --hz-toggle-height custom properties below — they're declared directly on that selector, so setting them from a lower-specificity or losing rule never reaches the element.

Data attributes

HookValuesStyles
data-state (on the root)'default' | 'error' | 'disabled'The universal field state hook, as on every field. Precedence is fixed: error beats disabled.
data-state (on input.hz-toggle)'on' | 'off'A second data-state nested inside the root’s, with a disjoint vocabulary — this is the stable hook for the track and thumb. It exists rather than relying on :checked because the toggle rules must win the specificity fight described above, and an attribute selector composes into them cleanly.

Custom properties

HookValuesStyles
--hz-toggle-width<length> — default 2.5remTrack width, and the distance the thumb travels.
--hz-toggle-height<length> — default 1.5remTrack height. The thumb derives from it (height minus a 2px inset all round), so there is no separate thumb knob — and the travel stays correct under any override.

Part classes

HookValuesStyles
.hz-field-labelchild elementThe label.
.hz-field-requiredchild elementThe required marker.
.hz-field-descriptionchild elementThe hint text.
.hz-field-errorchild elementThe error message.
.hz-togglethe checkbox inputThe track. Its ::before is the thumb, so the thumb has no class of its own.

Accessibility

Toggle renders an <input type="checkbox" role="switch"> associated with its label via id/for. Screen readers announce it as a switch, while the native checked state, keyboard behavior (Space toggles, Enter submits the form), and form participation all come from the platform.

description and error chain into aria-describedby, description first. required sets aria-required, and an error sets aria-invalid.

References: APG Switch pattern · MDN: switch role