Skip to content

Controls ​

The three small inputs a user flips: a checkbox, a radio, a switch. Checkbox and Radio are styled native <input>s, Toggle is a headlessui Switch drawn as a pill. None of them render their own label, so pair each with a <span> inside a <label> (or with FormLabel inside a FormItem). One surprise before you start: Checkbox does not take v-model — it takes :checked and @change. See the asymmetry below.

ts
import { Checkbox, Radio, Toggle } from '@scaler-tech/aurora/controls'

Checkbox

Radio

Toggle

States

Use when ​

SituationUseWhy
A setting that takes effect the moment it flipsTogglereads as a switch, not as pending form state
A boolean the user submits later with a formCheckboxbelongs to the form, not to the app state
Several checkboxes, any number selectableCheckboxmulti-select is what a checkbox means
One of a few options, all visible, in a formRadiomutually exclusive, keyboard-groupable
One of a few options as a view/mode switchSegmentedControlthe options are the UI, not a form field
One of many options, or the list is longListbox / Comboboxradios stop scaling past ~5 options

The v-model asymmetry (read this first) ​

Only two of the three take v-model. Checkbox has no defineModel and no update:modelValue emit at all — it is a bare native input, so v-model binds to nothing and the box never updates.

ComponentState bindingNotes
Checkbox:checked + @changeno v-model, no modelValue
Radiov-model + :valuedefineModel<string | number | boolean | null>()
Togglev-modeldefineModel<boolean>({ default: false })

API ​

Checkbox ​

No declared props. Everything falls through to the native <input type="checkbox">, so the API is the DOM's: checked, disabled, indeterminate, name, value, required, aria-*. class is merged with tailwind-merge, so a utility you pass wins over the default. Note that indeterminate is a DOM property with no HTML attribute — it works because Vue sets it as a property, and it stays true until you set it back; ticking the box does not clear it for you.

Radio ​

No declared props either — same fall-through — plus the model.

PropTypeDefaultDescription
v-modelstring | number | boolean | nullundefinedThe selected value, shared by every radio in the group
valueany—This radio's own value; required, or nothing can be selected
namestring—Groups the inputs for arrow-key navigation

Toggle ​

Renders a <button role="switch"> with no accessible name of its own, so wrap it in a <label> with visible text or pass aria-label.

PropTypeDefaultDescription
v-modelbooleanfalseOn/off state
disabledbooleanfalseDeclared prop (not fall-through) — greys the track and blocks the click

Examples ​

vue
<label class="flex cursor-pointer items-center gap-2">
    <Checkbox :checked="agreed" @change="agreed = !agreed" />
    <span class="paragraph-sm text-primary">I agree to the terms and conditions</span>
</label>

<label v-for="option in frequencies" :key="option.value" class="flex cursor-pointer items-center gap-2">
    <Radio v-model="frequency" name="frequency" :value="option.value" />
    <span class="paragraph-sm text-primary">{{ option.label }}</span>
</label>

<label class="flex cursor-pointer items-center gap-2">
    <Toggle v-model="notifications" />
    <span class="paragraph-sm text-primary">Email me product updates</span>
</label>

<!-- A parent checkbox over a group of children takes the native indeterminate property -->
<Checkbox
    :checked="state === 'checked'"
    :indeterminate="state === 'indeterminate'"
    @change="toggleAll()"
/>

Incorrect / Correct ​

Incorrect (v-model on a Checkbox — it renders, it just never changes):

vue
<Checkbox v-model="isInternal" />

Correct (drive the native input with :checked and flip it in @change):

vue
<Checkbox :checked="isInternal" @change="isInternal = !isInternal" />

Gotchas ​

  • A Checkbox with v-model shows a checkmark on click and never writes to your ref — that checkmark is the browser's uncontrolled state, and the next re-render snaps it back silently.
  • @change fires with the DOM Event, not a boolean. Flip your own state (@change="x = !x") or read ($event.target as HTMLInputElement).checked.
  • Radios that all light up together, or a model that comes back as "on", means :value is missing — give each radio its own :value, one shared v-model, and a shared name.
  • A checked Checkbox or Radio fills with the surrounding text colour: @tailwindcss/forms paints currentColor on :checked [external], so change it with text-* — bg-* loses.