<script setup lang="ts">
import { ref } from 'vue'
import { Switch } from '@hina-ui/vue'
const autoplay = ref(true)
</script>
<template>
<Switch v-model="autoplay">Autoplay the next episode</Switch>
</template>
Usage
import { Switch } from '@hina-ui/vue'
A switch stands for a setting that takes effect immediately, unlike a checkbox that waits for a submit. v-model binds a boolean, the default slot is the label, and clicking either the label or the track toggles. Attributes it does not declare land on the inner track element.
Value: false
<script setup lang="ts">
import { ref } from 'vue'
import { Switch, Stack, Text } from '@hina-ui/vue'
const nsfw = ref(false)
</script>
<template>
<Stack gap="sm">
<Switch v-model="nsfw">Show mature content</Switch>
<Text size="sm" tone="muted">Value: {{ nsfw }}</Text>
</Stack>
</template>
Examples
Description
description adds a line of supporting text under the label, one size smaller.
<script setup lang="ts">
import { ref } from 'vue'
import { Switch } from '@hina-ui/vue'
const sync = ref(true)
</script>
<template>
<Switch
v-model="sync"
description="Pick up where you left off when you open the same work on another device."
>
Sync reading progress
</Switch>
</template>
Settings rows
Use control-placement="end" to place the control after the text and block to fill the available width. The description stays below the label. start and end follow the text direction.
<script setup lang="ts">
import { ref } from 'vue'
import { Switch } from '@hina-ui/vue'
const sync = ref(true)
</script>
<template>
<Switch
v-model="sync"
control-placement="end"
block
description="Pick up where you left off when you open the same work on another device."
>
Sync reading progress
</Switch>
</template>
Sizes
size is sm, md or lg; the track is 20, 24 or 28 pixels tall and the text follows.
<script setup lang="ts">
import { Switch, Stack } from '@hina-ui/vue'
</script>
<template>
<Stack gap="sm">
<Switch size="sm" model-value>Small</Switch>
<Switch size="md" model-value>Medium</Switch>
<Switch size="lg" model-value>Large</Switch>
</Stack>
</template>
States
invalid gives the track a danger-coloured border; disabled disables the whole control.
<script setup lang="ts">
import { Switch, Stack } from '@hina-ui/vue'
</script>
<template>
<Stack gap="sm">
<Switch invalid>Must be on to continue</Switch>
<Switch disabled>Disabled</Switch>
<Switch disabled model-value>Disabled and on</Switch>
</Stack>
</template>
Track only
Without a label only the track renders, and it must then be named with aria-label.
<script setup lang="ts">
import { ref } from 'vue'
import { Switch, Inline } from '@hina-ui/vue'
const dark = ref(false)
</script>
<template>
<Inline gap="sm">
<Switch v-model="dark" aria-label="Dark mode" />
</Inline>
</template>
In a form
Inside a FormField the error message is rendered by the field and linked to the switch; validation rules and submission belong to the Form. The switch carries its own text, so the field needs no label, and its value often decides whether another field is required; such a rule goes on the object and names the field the error lands on.
<script setup lang="ts">
import { reactive, ref } from 'vue'
import * as v from 'valibot'
import { Button, Form, FormField, Input, Switch, Text } from '@hina-ui/vue'
const schema = v.pipe(
v.object({
isPublic: v.boolean(),
summary: v.string(),
}),
v.forward(
v.check(
input => !input.isPublic || input.summary.trim().length > 0,
'A public work needs a summary',
),
['summary'],
),
)
const values = reactive({ isPublic: true, summary: '' })
const saved = ref('')
async function save(data: unknown) {
await new Promise(resolve => setTimeout(resolve, 600))
saved.value = JSON.stringify(data)
}
</script>
<template>
<Form v-slot="{ submitting }" :values="values" :rules="schema" class="w-80" @submit="save">
<FormField name="isPublic">
<Switch v-model="values.isPublic">Public</Switch>
</FormField>
<FormField name="summary" label="Summary">
<Input v-model="values.summary" />
</FormField>
<Button type="submit" :loading="submitting" class="self-start">Save</Button>
<Text v-if="saved" tone="muted" size="sm">Saved: {{ saved }}</Text>
</Form>
</template>
Behaviour
- Clicking the label or the track toggles: the thumb slides across and the track changes colour. Tab lands on the track and Space toggles.
- Hovering anywhere on the control inks the track, pressing deepens it.
Accessibility
- The track is a button with
role="switch"andaria-checked. The root is alabel, so the text is the accessible name. - Without a label, name the track with
aria-labeloraria-labelledby. invalidalso setsaria-invalid.
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | boolean | false | Whether the switch is on |
size | 'sm' | 'md' | 'lg' | 'md' | Size |
description | string | — | Supporting text under the label |
controlPlacement | 'start' | 'end' | 'start' | Position of the control relative to its text |
block | boolean | false | Fill the available row width |
disabled | boolean | false | Whether the control is disabled |
invalid | boolean | false | Whether the control failed validation |
class | string | — | Classes appended to the root element |
Slots
| Slot | Payload | Description |
|---|---|---|
default | — | The label |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue | value: boolean | The value changed |