Checkbox

Allow users to switch between checked, unchecked, and indeterminate states.

	<script lang="ts">
  import { Checkbox, Label } from "bits-ui";
  import Check from "phosphor-svelte/lib/Check";
  import Minus from "phosphor-svelte/lib/Minus";
</script>
 
<div class="flex items-center space-x-3">
  <Checkbox.Root
    id="terms"
    aria-labelledby="terms-label"
    class="peer inline-flex size-[25px] items-center justify-center rounded-md border border-muted bg-foreground transition-all duration-150 ease-in-out active:scale-98 data-[state=unchecked]:border-border-input data-[state=unchecked]:bg-background data-[state=unchecked]:hover:border-dark-40"
    name="hello"
    indeterminate
  >
    {#snippet children({ checked, indeterminate })}
      <div class="inline-flex items-center justify-center text-background">
        {#if indeterminate}
          <Minus class="size-[15px]" weight="bold" />
        {:else if checked}
          <Check class="size-[15px]" weight="bold" />
        {/if}
      </div>
    {/snippet}
  </Checkbox.Root>
  <Label.Root
    id="terms-label"
    for="terms"
    class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
  >
    Accept terms and conditions
  </Label.Root>
</div>

Overview

The Checkbox component provides a flexible and accessible way to create checkbox inputs in your Svelte applications. It supports three states: checked, unchecked, and indeterminate, allowing for complex form interactions and data representations.

Key Features

  • Tri-State Support: Handles checked, unchecked, and indeterminate states, providing versatility in form design.
  • Accessibility: Built with WAI-ARIA guidelines in mind, ensuring keyboard navigation and screen reader support.
  • Flexible State Management: Supports both controlled and uncontrolled state, allowing for full control over the checkbox's checked state.

Architecture

The Checkbox component is composed of the following parts:

  • Root: The main component that manages the state and behavior of the checkbox.

Structure

Here's an overview of how the Checkbox component is structured in code:

	<script lang="ts">
	import { Checkbox } from "bits-ui";
</script>
 
<Checkbox.Root>
	{#snippet children({ checked, indeterminate })}
		{#if indeterminate}
			-
		{:else if checked}

		{:else}

		{/if}
	{/snippet}
</Checkbox.Root>

Reusable Components

It's recommended to use the Checkbox primitive to create your own custom checkbox component that can be used throughout your application. In the example below, we're using the Checkbox and Label components to create a custom checkbox component.

MyCheckbox.svelte
	<script lang="ts">
	import { Checkbox, Label, useId, type WithoutChildrenOrChild } from "bits-ui";
 
	let {
		id = useId(),
		checked = $bindable(false),
		ref = $bindable(null),
		labelRef = $bindable(null),
		...restProps
	}: WithoutChildrenOrChild<Checkbox.RootProps> & {
		labelText: string;
		labelRef?: HTMLLabelElement | null;
	} = $props();
</script>
 
<Checkbox.Root bind:checked bind:ref {...restProps}>
	{#snippet children({ checked, indeterminate })}
		{#if indeterminate}
			-
		{:else if checked}

		{:else}

		{/if}
	{/snippet}
</Checkbox.Root>
<Label.Root for={id} bind:ref={labelRef}>
	{labelText}
</Label.Root>

You can then use the MyCheckbox component in your application like so:

+page.svelte
	<script lang="ts">
	import MyCheckbox from "$lib/components/MyCheckbox.svelte";
</script>
 
<MyCheckbox labelText="Enable notifications" />

Managing Checked State

Bits UI offers several approaches to manage and synchronize the Checkbox's checked state, catering to different levels of control and integration needs.

1. Two-Way Binding

For seamless state synchronization, use Svelte's bind:checked directive. This method automatically keeps your local state in sync with the checkbox's internal state.

	<script lang="ts">
	import MyCheckbox from "$lib/components/MyCheckbox.svelte";
	let myChecked = $state(false);
</script>
 
<button onclick={() => (myChecked = false)}> uncheck </button>
 
<MyCheckbox bind:checked={myChecked} />

Key Benefits

  • Simplifies state management
  • Automatically updates myChecked when the checkbox changes (e.g., via clicking on the checkbox)
  • Allows external control (e.g., checking via a separate button/programmatically)

2. Change Handler

For more granular control or to perform additional logic on state changes, use the onCheckedChange prop. This approach is useful when you need to execute custom logic alongside state updates.

	<script lang="ts">
	import MyCheckbox from "$lib/components/MyCheckbox.svelte";
	let myChecked = $state(false);
</script>
 
<MyCheckbox
	checked={myChecked}
	onCheckedChange={(checked) => {
		myChecked = checked;
		if (checked === "indeterminate") {
			// do something different
		}
		// additional logic here.
	}}
/>

Use Cases

  • Implementing custom behaviors on checked/unchecked
  • Integrating with external state management solutions
  • Triggering side effects (e.g., logging, data fetching)

3. Fully Controlled

For complete control over the checkbox's checked state, use the controlledChecked prop. This approach requires you to manually manage the checked state, giving you full control over when and how the checkbox responds to change events.

To implement controlled state:

  1. Set the controlledChecked prop to true on the Checkbox.Root component.
  2. Provide a checked prop to Checkbox.Root, which should be a variable holding the current state.
  3. Implement an onCheckedChange handler to update the state when the internal state changes.
	<script lang="ts">
	import { Checkbox } from "bits-ui";
	let myChecked = $state(false);
</script>
 
<Checkbox.Root controlledChecked checked={myChecked} onCheckedChange={(c) => (myChecked = c)}>
	<!-- ... -->
</Checkbox.Root>

When to Use

  • Implementing complex checked/unchecked logic
  • Coordinating multiple UI elements
  • Debugging state-related issues

Managing Indeterminate State

Bits UI offers several approaches to manage and synchronize the Checkbox's indeterminate state, catering to different levels of control and integration needs.

1. Two-Way Binding

For seamless state synchronization, use Svelte's bind:indeterminate directive. This method automatically keeps your local state in sync with the checkbox's internal state.

	<script lang="ts">
	import MyCheckbox from "$lib/components/MyCheckbox.svelte";
	let myIndeterminate = $state(true);
</script>
 
<button onclick={() => (myIndeterminate = false)}> clear indeterminate </button>
 
<MyCheckbox bind:indeterminate={myIndeterminate} />

Key Benefits

  • Simplifies state management
  • Automatically updates myIndeterminate when the checkbox changes (e.g., via clicking on the checkbox)
  • Allows external control (e.g., checking via a separate button/programmatically)

2. Change Handler

For more granular control or to perform additional logic on state changes, use the onIndeterminateChange prop. This approach is useful when you need to execute custom logic alongside state updates.

	<script lang="ts">
	import MyCheckbox from "$lib/components/MyCheckbox.svelte";
	let myIndeterminate = $state(true);
</script>
 
<MyCheckbox
	indeterminate={myIndeterminate}
	onIndeterminateChange={(indeterminate) => {
		myIndeterminate = indeterminate;
		// additional logic here.
	}}
/>

Use Cases

  • Implementing custom behaviors
  • Integrating with external state management solutions
  • Triggering side effects (e.g., logging, data fetching)

3. Fully Controlled

For complete control over the checkbox's checked state, use the controlledIndeterminate prop. This approach requires you to manually manage the indeterminate state, giving you full control over when and how the checkbox responds to change events.

To implement controlled state:

  1. Set the controlledIndeterminate prop to true on the Checkbox.Root component.
  2. Provide a indeterminate prop to Checkbox.Root, which should be a variable holding the current state.
  3. Implement an onIndeterminateChange handler to update the state when the internal state changes.
	<script lang="ts">
	import { Checkbox } from "bits-ui";
	let myIndeterminate = $state(true);
</script>
 
<Checkbox.Root
	controlledIndeterminate
	indeterminate={myIndeterminate}
	onIndeterminateChange={(i) => (myIndeterminate = i)}
>
	<!-- ... -->
</Checkbox.Root>

When to Use

  • Implementing complex indeterminate logic
  • Coordinating multiple UI elements
  • Debugging state-related issues

Disabled State

You can disable the checkbox by setting the disabled prop to true.

	<MyCheckbox disabled labelText="Enable notifications" />

HTML Forms

If you set the name prop, a hidden checkbox input will be rendered to submit the value of the checkbox with a form.

By default, the checkbox will be submitted with default checkbox value of 'on' if the checked prop is true.

	<MyCheckbox name="notifications" labelText="Enable notifications" />

Custom Input Value

If you'd prefer to submit a different value, you can use the value prop to set the value of the hidden input.

For example, if you wanted to submit a string value, you could do the following:

	<MyCheckbox value="hello" name="notifications" labelText="Enable notifications" />

Required

If you want to make the checkbox required, you can use the required prop.

	<Checkbox.Root required>
	<!-- ... -->
</Checkbox.Root>

This will apply the required attribute to the hidden input element, ensuring that proper form submission is enforced.

Checkbox Groups

You can use the Checkbox.Group component to create a checkbox group.

	<script lang="ts">
	import { Checkbox } from "bits-ui";
</script>
 
<Checkbox.Group name="notifications">
	<Checkbox.GroupLabel>Notifications</Checkbox.GroupLabel>
	<Checkbox.Root value="marketing" />
	<Checkbox.Root value="promotions" />
	<Checkbox.Root value="news" />
</Checkbox.Group>
Notifications
	<script lang="ts">
  import { Checkbox, Label, useId } from "bits-ui";
  import Check from "phosphor-svelte/lib/Check";
  import Minus from "phosphor-svelte/lib/Minus";
 
  let myValue = $state<string[]>(["marketing", "news"]);
</script>
 
<Checkbox.Group
  class="flex flex-col gap-3"
  bind:value={myValue}
  name="notifications"
>
  <Checkbox.GroupLabel class="text-sm font-medium text-foreground-alt">
    Notifications
  </Checkbox.GroupLabel>
  <div class="flex flex-col gap-4">
    {@render MyCheckbox({ label: "Marketing", value: "marketing" })}
    {@render MyCheckbox({ label: "Promotions", value: "promotions" })}
    {@render MyCheckbox({ label: "News", value: "news" })}
    {@render MyCheckbox({ label: "Updates", value: "updates" })}
  </div>
</Checkbox.Group>
 
{#snippet MyCheckbox({ value, label }: { value: string; label: string })}
  {@const id = useId()}
  <div class="flex items-center">
    <Checkbox.Root
      {id}
      aria-labelledby="{id}-label"
      class="peer inline-flex size-[25px] items-center justify-center rounded-md border border-muted bg-foreground transition-all duration-150 ease-in-out active:scale-98 data-[state=unchecked]:border-border-input data-[state=unchecked]:bg-background data-[state=unchecked]:hover:border-dark-40"
      name="hello"
      {value}
    >
      {#snippet children({ checked, indeterminate })}
        <div class="inline-flex items-center justify-center text-background">
          {#if indeterminate}
            <Minus class="size-[15px]" weight="bold" />
          {:else if checked}
            <Check class="size-[15px]" weight="bold" />
          {/if}
        </div>
      {/snippet}
    </Checkbox.Root>
    <Label.Root
      id="{id}-label"
      for={id}
      class="pl-3 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
    >
      {label}
    </Label.Root>
  </div>
{/snippet}

Managing Value State

Bits UI offers several approaches to manage and synchronize a Checkbox Group's value state, catering to different levels of control and integration needs.

1. Two-Way Binding

For seamless state synchronization, use Svelte's bind:value directive. This method automatically keeps your local state in sync with the group's internal state.

	<script lang="ts">
	import { Checkbox } from "bits-ui";
	let myValue = $state<string[]>([]);
</script>
 
<button
	onclick={() => {
		myValue = ["item-1", "item-2"];
	}}
>
	Open Items 1 and 2
</button>
 
<Checkbox.Group name="myItems" bind:value={myValue}>
	<Checkbox.GroupLabel>Items</Checkbox.GroupLabel>
	<Checkbox.Root value="item-1" />
	<Checkbox.Root value="item-2" />
	<Checkbox.Root value="item-3" />
</Checkbox.Group>
Key Benefits
  • Simplifies state management
  • Automatically updates myValue when the accordion changes (e.g., via clicking on an item's trigger)
  • Allows external control (e.g., opening an item via a separate button)

2. Change Handler

For more granular control or to perform additional logic on state changes, use the onValueChange prop. This approach is useful when you need to execute custom logic alongside state updates.

	<script lang="ts">
	import { Checkbox } from "bits-ui";
	let myValue = $state<string[]>([]);
</script>
 
<Checkbox.Group
	value={myValue}
	onValueChange={(value) => {
		myValue = value;
		// additional logic here.
	}}
>
	<Checkbox.GroupLabel>Items</Checkbox.GroupLabel>
	<Checkbox.Root value="item-1" />
	<Checkbox.Root value="item-2" />
	<Checkbox.Root value="item-3" />
</Accordion.Root>

Use Cases

  • Implementing custom behaviors on value change
  • Integrating with external state management solutions
  • Triggering side effects (e.g., logging, data fetching)

3. Fully Controlled

For complete control over the Checkbox Group's value state, use the controlledValue prop. This approach requires you to manually manage the value state, giving you full control over when and how the group responds to value change events.

To implement controlled state:

  1. Set the controlledValue prop to true on the Checkbox.Group component.
  2. Provide a value prop to Checkbox.Group, which should be a variable holding the current state.
  3. Implement an onValueChange handler to update the state when the internal state changes.
	<script lang="ts">
	import { Checkbox } from "bits-ui";
	let myValue = $state("");
</script>
 
<Checkbox.Group controlledValue value={myValue} onValueChange={(v) => (myValue = v)}>
	<!-- ... -->
</Checkbox.Group>
When to Use
  • Implementing complex logic
  • Coordinating multiple UI elements
  • Debugging state-related issues

HTML Forms

To render hidden <input /> elements for the various checkboxes within a group, pass a name to Checkbox.Group. All descendent checkboxes will then render hidden inputs with the same name.

	<Checkbox.Group name="notifications">
	<!-- ... -->
</Checkbox.Group>

When a Checkbox.Group component is used, its descendent Checkbox.Root components will use certain properties from the group, such as the name, required, and disabled.

API Reference

Checkbox.Root

The button component used to toggle the state of the checkbox.

Property Type Description
checked $bindable
boolean

The checkbox button's checked state. This can be a boolean or the string 'indeterminate', which would typically display a dash in the checkbox.

Default: false
onCheckedChange
function

A callback that is fired when the checkbox button's checked state changes.

Default: undefined
controlledChecked
boolean

Whether or not the checked state is controlled or not. If true, the component will not update the checked state internally, instead it will call onCheckedChange when it would have otherwise, and it is up to you to update the checked prop that is passed to the component.

Default: false
indeterminate $bindable
boolean

Whether the checkbox is an indeterminate state or not.

Default: false
onIndeterminateChange
function

A callback that is fired when the indeterminate state changes.

Default: undefined
controlledIndeterminate
boolean

Whether or not the indeterminate state is controlled or not. If true, the component will not update the indeterminate state internally, instead it will call onIndeterminateChange when it would have otherwise, and it is up to you to update the indeterminate prop that is passed to the component.

Default: false
disabled
boolean

Whether or not the checkbox button is disabled. This prevents the user from interacting with it.

Default: false
required
boolean

Whether or not the checkbox is required.

Default: false
name
string

The name of the checkbox. If provided a hidden input will be render to use for form submission. If not provided, the hidden input will not be rendered.

Default: undefined
value
string

The value of the checkbox. This is what is submitted with the form when the checkbox is checked.

Default: undefined
ref $bindable
HTMLButtonElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See Child Snippet docs for more information.

Default: undefined
Data Attribute Value Description
data-state
enum

The checkbox's state of checked, unchecked, or indeterminate.

data-disabled
''

Present when the checkbox is disabled.

data-checkbox-root
''

Present on the root element.

Checkbox.Group

A group that synchronizes its value state with its descendant checkboxes.

Property Type Description
value $bindable
string[]

The value of the group. This is an array of the values of the checked checkboxes within the group.

Default: []
onValueChange
function

A callback that is fired when the checkbox group's value state changes.

Default: undefined
controlledValue
boolean

Whether or not the value is controlled or not. If true, the component will not update the value state internally, instead it will call onValueChange when it would have otherwise, and it is up to you to update the value prop that is passed to the component.

Default: false
disabled
boolean

Whether or not the checkbox group is disabled. If true, all checkboxes within the group will be disabled. To disable a specific checkbox in the group, pass the disabled prop to the checkbox.

Default: false
required
boolean

Whether or not the checkbox group is required for form submission.

Default: false
name
string

The name of the checkbox group. If provided a hidden input will be rendered to use for form submission.

Default: undefined
ref $bindable
HTMLDivElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See Child Snippet docs for more information.

Default: undefined
Data Attribute Value Description
data-disabled
''

Present when the checkbox group is disabled.

data-checkbox-group
''

Present on the group element.

Checkbox.GroupLabel

An accessible label for the checkbox group.

Property Type Description
ref $bindable
HTMLLabelElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See Child Snippet docs for more information.

Default: undefined
Data Attribute Value Description
data-disabled
''

Present when the checkbox group is disabled.

data-checkbox-group-label
''

Present on the label element.