CheckboxGroup

A set of checkboxes sharing one array value.

<script setup lang="ts">
  import { ref } from 'vue'
  import { CheckboxGroup } from '@hina-ui/vue'

  const types = ref(['gal', 'ln'])
  const options = [
    { value: 'gal', label: 'Galgame' },
    { value: 'ln', label: 'Light novel' },
    { value: 'manga', label: 'Manga' },
  ]
</script>

<template>
  <CheckboxGroup v-model="types" :options="options" aria-label="Work types" />
</template>
vue

Usage

import { CheckboxGroup } from '@hina-ui/vue'
ts

The group renders one Checkbox per entry in options, and v-model binds the array of chosen values; the option type is the same as Select’s. Attributes it does not declare land on the root, so name the group with aria-label or aria-labelledby.

Value: [ "mail" ]

<script setup lang="ts">
  import { ref } from 'vue'
  import { CheckboxGroup, Stack, Text } from '@hina-ui/vue'

  const channels = ref(['mail'])
  const options = [
    { value: 'mail', label: 'Email' },
    { value: 'push', label: 'Push notification' },
    { value: 'sms', label: 'SMS' },
  ]
</script>

<template>
  <Stack gap="sm">
    <CheckboxGroup v-model="channels" :options="options" aria-label="Notification channels" />
    <Text size="sm" tone="muted">Value: {{ channels }}</Text>
  </Stack>
</template>
vue

Examples

Horizontal

With orientation="horizontal" the options run in a row and wrap when they run out of room.

<script setup lang="ts">
  import { ref } from 'vue'
  import { CheckboxGroup } from '@hina-ui/vue'

  const days = ref(['sat', 'sun'])
  const options = [
    { value: 'mon', label: 'Mon' },
    { value: 'wed', label: 'Wed' },
    { value: 'fri', label: 'Fri' },
    { value: 'sat', label: 'Sat' },
    { value: 'sun', label: 'Sun' },
  ]
</script>

<template>
  <CheckboxGroup
    v-model="days"
    :options="options"
    orientation="horizontal"
    aria-label="Release days"
  />
</template>
vue

Description

An option’s description shows under its label.

<script setup lang="ts">
  import { ref } from 'vue'
  import { CheckboxGroup } from '@hina-ui/vue'

  const scopes = ref(['read'])
  const options = [
    { value: 'read', label: 'Read', description: 'View your library and reading progress' },
    { value: 'write', label: 'Write', description: 'Change your library and ratings' },
    { value: 'admin', label: 'Manage', description: 'Manage tokens and granted scopes' },
  ]
</script>

<template>
  <CheckboxGroup v-model="scopes" :options="options" aria-label="Scopes" />
</template>
vue

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. With block, vertical items fill each row; horizontal items share the row equally. orientation still controls the arrangement of options.

<script setup lang="ts">
  import { ref } from 'vue'
  import { CheckboxGroup } from '@hina-ui/vue'

  const scopes = ref(['read'])
  const options = [
    { value: 'read', label: 'Read', description: 'View your library and reading progress' },
    { value: 'write', label: 'Write', description: 'Change your library and ratings' },
    { value: 'admin', label: 'Manage', description: 'Manage tokens and granted scopes' },
  ]
</script>

<template>
  <CheckboxGroup
    v-model="scopes"
    control-placement="end"
    block
    :options="options"
    aria-label="Scopes"
  />
</template>
vue

Sizes

size is passed to every checkbox.

<script setup lang="ts">
  import { CheckboxGroup, Inline } from '@hina-ui/vue'

  const options = [
    { value: 'gal', label: 'Galgame' },
    { value: 'ln', label: 'Light novel' },
  ]
</script>

<template>
  <Inline gap="lg" align="start">
    <CheckboxGroup size="sm" :options="options" :model-value="['gal']" aria-label="Small" />
    <CheckboxGroup size="md" :options="options" :model-value="['gal']" aria-label="Medium" />
    <CheckboxGroup size="lg" :options="options" :model-value="['gal']" aria-label="Large" />
  </Inline>
</template>
vue

States

disabled on an option disables that option only; on the group it disables them all. invalid reaches every box.

<script setup lang="ts">
  import { CheckboxGroup, Inline } from '@hina-ui/vue'

  const options = [
    { value: 'gal', label: 'Galgame' },
    { value: 'ln', label: 'Light novel' },
    { value: 'manga', label: 'Manga', disabled: true },
  ]
</script>

<template>
  <Inline gap="lg" align="start">
    <CheckboxGroup :options="options" :model-value="['gal']" aria-label="With a disabled option" />
    <CheckboxGroup disabled :options="options" :model-value="['gal']" aria-label="Disabled group" />
    <CheckboxGroup invalid :options="options" :model-value="[]" aria-label="Invalid" />
  </Inline>
</template>
vue

Custom content

The option slot customises each label.

The complete option type is inferred from options. Slot parameters preserve additional fields and their types; v-model still binds to an array of option values. See Select for the type definitions.

<script setup lang="ts">
  import { ref } from 'vue'
  import { BookOpen, Gamepad2, Images } from '@lucide/vue'
  import { CheckboxGroup, Inline } from '@hina-ui/vue'

  const icons = { gal: Gamepad2, ln: BookOpen, manga: Images }
  const types = ref(['gal'])
  const options = [
    { value: 'gal', label: 'Galgame' },
    { value: 'ln', label: 'Light novel' },
    { value: 'manga', label: 'Manga' },
  ]
</script>

<template>
  <CheckboxGroup v-model="types" :options="options" aria-label="Work types">
    <template #option="{ option }">
      <Inline as="span" gap="xs" align="center">
        <component :is="icons[option.value as keyof typeof icons]" class="text-muted size-4" />
        {{ option.label }}
      </Inline>
    </template>
  </CheckboxGroup>
</template>
vue

In a form

Inside a FormField the label is linked to the whole group through aria-labelledby, and the description and error message are rendered by the field; validation rules and submission belong to the Form.

One or two of them

<script setup lang="ts">
  import { reactive, ref } from 'vue'
  import * as v from 'valibot'
  import { Button, CheckboxGroup, Form, FormField, Text } from '@hina-ui/vue'

  const options = [
    { label: 'Games', value: 'game' },
    { label: 'Novels', value: 'novel' },
    { label: 'Manga', value: 'manga' },
    { label: 'Anime', value: 'anime' },
  ]

  const schema = v.object({
    interests: v.pipe(
      v.array(v.string('Pick your interests')),
      v.minLength(1, 'Pick at least one'),
      v.maxLength(2, 'Pick at most two'),
    ),
  })

  const values = reactive({ interests: [] as Array<string | number> })
  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="interests" label="Interests" description="One or two of them" required>
      <CheckboxGroup v-model="values.interests" :options="options" />
    </FormField>
    <Button type="submit" :loading="submitting" class="self-start">Save</Button>
    <Text v-if="saved" tone="muted" size="sm">Saved: {{ saved }}</Text>
  </Form>
</template>
vue

Behaviour

  • Ticking adds the value to the array, ticking again removes it; the array keeps the order values were ticked in.
  • Every checkbox is its own Tab stop, Space toggles the focused one and the arrow keys do not move focus, as with a native group of checkboxes.

Accessibility

  • The root is role="group", named with aria-label or aria-labelledby; each item keeps the Checkbox semantics.

API

Props

T extends SelectOption is inferred from options and defaults to SelectOption.

Prop
Type
Default
Description
modelValue
Array<string | number>
[]
The chosen values
options
T[]
—
The items, see Select
orientation
'vertical' | 'horizontal'
'vertical'
Layout direction
size
'sm' | 'md' | 'lg'
'md'
Size of every checkbox
controlPlacement
'start' | 'end'
'start'
Position of the control relative to its text
block
boolean
false
Fill the group; horizontal items share the width
disabled
boolean
false
Whether the whole group is disabled
invalid
boolean
false
Whether the group failed validation
class
string
—
Classes appended to the root element

Slots

Slot
Payload
Description
option
{ option: T }
Label of each item

Events

Event
Payload
Description
update:modelValue
value: Array<string | number>
The chosen values changed