Switch

A toggle switch component for boolean states

Usage

<script setup lang="ts">
import { Switch, SwitchGroup } from '@hareui/vue'
</script>
<script setup lang="ts">import { Switch } from '@hareui/vue'</script><template>  <Switch label="Enable notifications" /></template>

Anatomy

Switch is a single component — no compound parts. Label, description, and the thumb icon are composed through props or named slots.

<Switch label="…" description="…" error-message="…">
  <template #label="{ checked }" />       <!-- Label content -->
  <template #description />               <!-- Description content -->
  <template #icon="{ checked }" />        <!-- Icon rendered inside the thumb -->
  <template #error="{ validationErrors }" /> <!-- FieldError content, rendered while invalid -->
</Switch>
ui keydata-slotElement
baseswitch<div> root
contentswitch-contentThe Reka SwitchRoot button
controlswitch-controlThe track
thumbswitch-thumbThe moving thumb
iconswitch-iconIcon container inside the thumb (iff #icon)
—descriptionDescription (iff description / #description)
—field-errorFieldError (while invalid, iff error-message / #error / validation errors)

Group multiple switches with SwitchGroup, a layout-only wrapper (orientation: 'vertical' | 'horizontal', default vertical):

<SwitchGroup>
  <Switch name="a" label="Option 1" />
  <Switch name="b" label="Option 2" />
</SwitchGroup>
ui keydata-slotElement
baseswitch-group<div> root
itemsswitch-group-itemsContainer for the switch items

Examples

Sizes

<script setup lang="ts">import { Switch } from '@hareui/vue'</script><template>  <div class="flex gap-6">    <Switch size="sm" label="Small" />    <Switch size="md" label="Medium" />    <Switch size="lg" label="Large" />  </div></template>

With Icons

<script setup lang="ts">import { Icon } from '@iconify/vue'import { Switch } from '@hareui/vue'import { reactive } from 'vue'const icons = [  { key: 'check', off: 'gravity-ui:power', on: 'gravity-ui:check', selectedControlClass: 'bg-green-500/80' },  { key: 'darkMode', off: 'gravity-ui:moon', on: 'gravity-ui:sun', selectedControlClass: '' },  { key: 'microphone', off: 'gravity-ui:microphone', on: 'gravity-ui:microphone-slash', selectedControlClass: 'bg-red-500/80' },  { key: 'notification', off: 'gravity-ui:bell-slash', on: 'gravity-ui:bell-fill', selectedControlClass: 'bg-purple-500/80' },  { key: 'volume', off: 'gravity-ui:volume-fill', on: 'gravity-ui:volume-slash-fill', selectedControlClass: 'bg-blue-500/80' },]const selected = reactive<Record<string, boolean>>(  Object.fromEntries(icons.map(icon => [icon.key, true])),)</script><template>  <div class="flex gap-3">    <Switch      v-for="icon in icons"      :key="icon.key"      v-model="selected[icon.key]"      :aria-label="icon.key"      size="lg"      :ui="{ control: selected[icon.key] ? icon.selectedControlClass : '' }"    >      <template #icon="{ checked }">        <Icon          :icon="checked ? icon.on : icon.off"          class="size-3 text-inherit"          :class="checked ? 'opacity-100' : 'opacity-70'"        />      </template>    </Switch>  </div></template>

Disabled

<script setup lang="ts">import { Switch } from '@hareui/vue'</script><template>  <Switch disabled label="Enable notifications" /></template>

Without Label

<script setup lang="ts">import { Switch } from '@hareui/vue'</script><template>  <Switch aria-label="Enable notifications" /></template>

With Description

Allow others to see your profile information
<script setup lang="ts">import { Switch } from '@hareui/vue'</script><template>  <div class="max-w-sm">    <Switch label="Public profile" description="Allow others to see your profile information" />  </div></template>

Default Selected

<script setup lang="ts">import { Switch } from '@hareui/vue'</script><template>  <Switch default-value label="Enable notifications" /></template>

Controlled

Switch is off

<script setup lang="ts">import { Switch } from '@hareui/vue'import { ref } from 'vue'const isSelected = ref(false)</script><template>  <div class="flex flex-col gap-4">    <Switch v-model="isSelected" label="Enable notifications" />    <p class="text-sm text-muted">      Switch is {{ isSelected ? 'on' : 'off' }}    </p>  </div></template>

Label Position

HareUI has no dedicated label-position prop; reverse the order with a content class override:

<script setup lang="ts">import { Switch } from '@hareui/vue'</script><template>  <div class="flex flex-col gap-4">    <Switch label="Label after" />    <Switch label="Label before" :ui="{ content: 'flex-row-reverse justify-end' }" />  </div></template>

Group

<script setup lang="ts">import { Switch, SwitchGroup } from '@hareui/vue'</script><template>  <SwitchGroup>    <Switch name="notifications" label="Allow Notifications" />    <Switch name="marketing" label="Marketing emails" />    <Switch name="social" label="Social media updates" />  </SwitchGroup></template>

Group Horizontal

<script setup lang="ts">import { Switch, SwitchGroup } from '@hareui/vue'</script><template>  <SwitchGroup class="overflow-x-auto" orientation="horizontal">    <Switch name="notifications" label="Notifications" />    <Switch name="marketing" label="Marketing" />    <Switch name="social" label="Social" />  </SwitchGroup></template>

Form Integration

<script setup lang="ts">import { Button, Switch, SwitchGroup } from '@hareui/vue'function handleSubmit(e: Event) {  e.preventDefault()  const formData = new FormData(e.target as HTMLFormElement)  alert(    `Form submitted with:\n${Array.from(formData.entries())      .map(([key, value]) => `${key}: ${value}`)      .join('\n')}`,  )}</script><template>  <form class="flex flex-col gap-4" @submit="handleSubmit">    <SwitchGroup>      <Switch name="notifications" value="on" label="Enable notifications" />      <Switch default-value name="newsletter" value="on" label="Subscribe to newsletter" />      <Switch name="marketing" value="on" label="Receive marketing updates" />    </SwitchGroup>    <Button class="mt-4" size="sm" type="submit" variant="primary">      Submit    </Button>  </form></template>

Render Props

The #label, #description and #icon slots receive { checked }:

<script setup lang="ts">import { Switch } from '@hareui/vue'</script><template>  <Switch>    <template #label="{ checked }">      {{ checked ? 'Enabled' : 'Disabled' }}    </template>  </Switch></template>

Customization

Tailwind CSS

Override the control's checked colors with CSS variables through :ui, and stack a Description under the label inside #label:

<script setup lang="ts">import { Description, Switch } from '@hareui/vue'</script><template>  <Switch    id="autosave"    :ui="{ control: '[--switch-control-bg-checked-hover:var(--success)] [--switch-control-bg-checked:var(--success)]' }"  >    <template #label>      <span class="flex flex-col gap-0.5">        <span>Auto-save drafts</span>        <Description>Changes are saved as you type.</Description>      </span>    </template>  </Switch></template>

Global Configuration

app.use(createHareUI({
  ui: { switch: { slots: { control: 'rounded-md' } } },
}))

Styling Reference

Slots

  • base → [data-slot="switch"] – root container, carries data-selected / data-disabled / data-invalid / data-readonly / data-required
  • content → [data-slot="switch-content"] – the interactive SwitchRoot element
  • control → [data-slot="switch-control"] – the track
  • thumb → [data-slot="switch-thumb"] – the moving thumb
  • icon → [data-slot="switch-icon"] – optional icon inside the thumb

Interactive States

  • Selected: [data-selected="true"] on the root (thumb position and background color change)
  • Hover: :hover on the root
  • Focus: :focus-visible on the content element (shows focus ring)
  • Disabled: [data-disabled="true"] on the root (reduced opacity, no pointer events)
  • Pressed: :active on the root
  • Invalid: [data-invalid="true"] on the root, aria-invalid="true" on the content element
  • Read only: [data-readonly="true"] on the root, aria-readonly="true" on the content element
  • Required: [data-required="true"] on the root

API Reference

Props

PropTypeDefaultDescription
modelValuebooleanundefinedThe checked state. Bind with v-model.
defaultValuebooleanfalseThe initial checked state when uncontrolled.
labelstring-The label rendered next to the control.
descriptionstring-Help text rendered below the label.
errorMessagestring-Error message rendered below the label while invalid. Defaults to the validation errors.
size"md" | "sm" | "lg"'md'Size of the switch.
disabledbooleanfalseWhether the switch is disabled.
invalidbooleanundefinedWhether the switch is invalid. Sets aria-invalid on the control and data-invalid on the root. Overrides validation when set.
readonlybooleanfalseWhether the switch is read only: focusable and announced, but its state can't be changed.
requiredbooleanfalseWhether user input is required on the switch before form submission.
validateValidateFn<boolean>-Validates the selection (true when on). Return an error message (or several) when invalid.
validationBehaviorValidationBehavior'native'native blocks form submission and shows errors on change or submit; aria shows errors in realtime. Defaults to the surrounding Form.
namestring-The name of the switch, used when submitting an HTML form.
valuestring'on'The value of the switch, used when submitting an HTML form.
idstring-The id of the switch control. Generated when omitted; use it to point an external Label at the switch.
uiComponentSlots<{ slots: { base: string[]; content: string[]; control: string[]; thumb: string[]; icon: string; }; variants: { size: { sm: { base: string; control: string; thumb: string; }; md: {}; lg: { base: string; control: string; thumb: string; }; }; }; defaultVariants: { size: string; }; }>-Per-slot class overrides.

Slots

SlotPropsDescription
label{ checked: boolean; }The label content. Replaces the label prop.
description{ checked: boolean; }The description content. Replaces the description prop.
errorValidationResultThe error content, rendered while invalid. Replaces the errorMessage prop.
icon{ checked: boolean; }Icon rendered inside the thumb.

Emits

EventPayloadDescription
update:modelValue[value: boolean]-

SwitchGroup

SwitchGroup is a layout-only wrapper: it renders no state of its own and shares no context with its Switch children — each switch keeps its own name/value/v-model. Use it purely to apply the grouped spacing/orientation styling HeroUI ships for stacks of switches.

Props

PropTypeDefaultDescription
orientation"horizontal" | "vertical"'vertical'The axis the switches are laid out along.
uiComponentSlots<{ slots: { base: string; items: string; }; variants: { orientation: { horizontal: { items: string; }; vertical: { items: string; }; }; }; defaultVariants: { orientation: string; }; }>-Per-slot class overrides.

Slots

SlotPropsDescription
defaultanyThe Switch items to render.

Accessibility

  • Clicking anywhere in the content area (including the label) toggles the switch
  • Use aria-label when no visible label / #label is provided
  • description is linked via aria-describedby
  • required and disabled set the native attributes on the underlying control

On this page

No Headings