Checkbox

Ticks one option, or several out of many.

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

  const notify = ref(true)
</script>

<template>
  <Checkbox v-model="notify">Notify me about new chapters</Checkbox>
</template>
vue

Usage

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

The checkbox joins the box and its label into one clickable unit. v-model binds a boolean, and the value 'indeterminate' shows the mixed state. The default slot is the label; clicking either the label or the box toggles. Attributes it does not declare land on the inner box element.

Value: false

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

  const remember = ref(false)
</script>

<template>
  <Stack gap="sm">
    <Checkbox v-model="remember">Keep me signed in</Checkbox>
    <Text size="sm" tone="muted">Value: {{ remember }}</Text>
  </Stack>
</template>
vue

Examples

Description

description adds a line of supporting text under the label, one size smaller.

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

  const weekly = ref(true)
</script>

<template>
  <Checkbox
    v-model="weekly"
    description="A digest of the week's updates every Monday. Unsubscribe any time."
  >
    Weekly newsletter
  </Checkbox>
</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.

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

  const weekly = ref(true)
</script>

<template>
  <Checkbox
    v-model="weekly"
    control-placement="end"
    block
    description="A digest of the week's updates every Monday. Unsubscribe any time."
  >
    Weekly newsletter
  </Checkbox>
</template>
vue

Indeterminate

The value 'indeterminate' shows a dash. The usual case is “select all”: the parent is indeterminate while some children are ticked, and clicking it ticks them all.

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

  const types = ['Galgame', 'Light novel', 'Manga']
  const picked = ref(['Galgame'])

  const all = computed<boolean | 'indeterminate'>({
    get: () =>
      picked.value.length === 0
        ? false
        : picked.value.length === types.length
          ? true
          : 'indeterminate',
    set: value => {
      picked.value = value === true ? [...types] : []
    },
  })

  function toggle(type: string, on: boolean | 'indeterminate') {
    picked.value = on === true ? [...picked.value, type] : picked.value.filter(t => t !== type)
  }
</script>

<template>
  <Stack gap="sm">
    <Checkbox v-model="all">All types</Checkbox>
    <Stack gap="sm" class="ps-6">
      <Checkbox
        v-for="type in types"
        :key="type"
        :model-value="picked.includes(type)"
        @update:model-value="toggle(type, $event)"
      >
        {{ type }}
      </Checkbox>
    </Stack>
  </Stack>
</template>
vue

Sizes

size is sm, md or lg; the box is 14, 16 or 18 pixels and the text follows.

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

<template>
  <Stack gap="sm">
    <Checkbox size="sm" model-value>Small</Checkbox>
    <Checkbox size="md" model-value>Medium</Checkbox>
    <Checkbox size="lg" model-value>Large</Checkbox>
  </Stack>
</template>
vue

States

invalid turns the box border to the danger colour; disabled disables the whole control.

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

<template>
  <Stack gap="sm">
    <Checkbox invalid>I have read and accept the terms</Checkbox>
    <Checkbox disabled>Disabled</Checkbox>
    <Checkbox disabled model-value>Disabled and checked</Checkbox>
  </Stack>
</template>
vue

Box only

Without a label only the box renders, and it must then be named with aria-label. Row selection in a table is the typical case.

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

  const rows = ref([true, false, false])
</script>

<template>
  <Inline gap="sm">
    <Checkbox
      v-for="(_, index) in rows"
      :key="index"
      v-model="rows[index]"
      :aria-label="`Select row ${index + 1}`"
    />
  </Inline>
</template>
vue

In a form

Inside a FormField the error message is rendered by the field and linked to the checkbox; validation rules and submission belong to the Form. The checkbox carries its own text, so the field needs no label.

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

  const schema = v.object({
    agreed: v.literal(true, 'Please read and accept the terms first'),
  })

  const values = reactive({ agreed: false as boolean | 'indeterminate' })
  const saved = ref(false)

  async function save() {
    await new Promise(resolve => setTimeout(resolve, 600))
    saved.value = true
  }
</script>

<template>
  <Form v-slot="{ submitting }" :values="values" :rules="schema" class="w-80" @submit="save">
    <FormField name="agreed">
      <Checkbox v-model="values.agreed">I have read and accept the terms of service</Checkbox>
    </FormField>
    <Button type="submit" :loading="submitting" class="self-start">Sign up</Button>
    <Text v-if="saved" tone="muted" size="sm">Signed up.</Text>
  </Form>
</template>
vue

Behaviour

  • Clicking the label or the box toggles. Tab lands on the box, Space toggles and Enter does not, as with a native checkbox.
  • Clicking an indeterminate checkbox ticks it.
  • Hovering anywhere on the control inks the box, pressing deepens it; the tick scales in and fades out.

Accessibility

  • The box is a button with role="checkbox" and aria-checked, mixed when indeterminate. The root is a label, so the text is the accessible name.
  • Without a label, name the box with aria-label or aria-labelledby.
  • invalid also sets aria-invalid.

API

Props

Prop
Type
Default
Description
modelValue
boolean | 'indeterminate'
false
Whether ticked; 'indeterminate' for mixed
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