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="border-muted bg-foreground data-[state=unchecked]:border-border-input data-[state=unchecked]:bg-background data-[state=unchecked]:hover:border-dark-40 peer inline-flex size-[25px] items-center justify-center rounded-md border transition-all duration-150 ease-in-out active:scale-[0.98]"
name="hello"
indeterminate
>
{#snippet children({ checked, indeterminate })}
<div class="text-background inline-flex items-center justify-center">
{#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>
@import url("https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap");
@import "tailwindcss";
@plugin "tailwindcss-animate";
@custom-variant dark (&:is(.dark *));
@font-face {
font-family: "Cal Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("/CalSans-SemiBold.woff2") format("woff2");
}
:root {
/* Colors */
--background: hsl(0 0% 100%);
--background-alt: hsl(0 0% 100%);
--foreground: hsl(0 0% 9%);
--foreground-alt: hsl(0 0% 32%);
--muted: hsl(240 5% 96%);
--muted-foreground: hsla(0 0% 9% / 0.4);
--border: hsl(240 6% 10%);
--border-input: hsla(240 6% 10% / 0.17);
--border-input-hover: hsla(240 6% 10% / 0.4);
--border-card: hsla(240 6% 10% / 0.1);
--dark: hsl(240 6% 10%);
--dark-10: hsla(240 6% 10% / 0.1);
--dark-40: hsla(240 6% 10% / 0.4);
--dark-04: hsla(240 6% 10% / 0.04);
--accent: hsl(204 94% 94%);
--accent-foreground: hsl(204 80% 16%);
--destructive: hsl(347 77% 50%);
--tertiary: hsl(37.7 92.1% 50.2%);
--line: hsl(0 0% 100%);
/* black */
--contrast: hsl(0 0% 0%);
/* Shadows */
--shadow-mini: 0px 1px 0px 1px rgba(0, 0, 0, 0.04);
--shadow-mini-inset: 0px 1px 0px 0px rgba(0, 0, 0, 0.04) inset;
--shadow-popover: 0px 7px 12px 3px hsla(var(--dark-10));
--shadow-kbd: 0px 2px 0px 0px rgba(0, 0, 0, 0.07);
--shadow-btn: 0px 1px 0px 1px rgba(0, 0, 0, 0.03);
--shadow-card: 0px 2px 0px 1px rgba(0, 0, 0, 0.04);
--shadow-date-field-focus: 0px 0px 0px 3px rgba(24, 24, 27, 0.17);
}
.dark {
/* Colors */
--background: hsl(0 0% 5%);
--background-alt: hsl(0 0% 8%);
--foreground: hsl(0 0% 95%);
--foreground-alt: hsl(0 0% 70%);
--muted: hsl(240 4% 16%);
--muted-foreground: hsla(0 0% 100% / 0.4);
--border: hsl(0 0% 96%);
--border-input: hsla(0 0% 96% / 0.17);
--border-input-hover: hsla(0 0% 96% / 0.4);
--border-card: hsla(0 0% 96% / 0.1);
--dark: hsl(0 0% 96%);
--dark-40: hsl(0 0% 96% / 0.4);
--dark-10: hsl(0 0% 96% / 0.1);
--dark-04: hsl(0 0% 96% / 0.04);
--accent: hsl(204 90% 90%);
--accent-foreground: hsl(204 94% 94%);
--destructive: hsl(350 89% 60%);
--line: hsl(0 0% 9.02%);
--tertiary: hsl(61.3 100% 82.2%);
/* white */
--contrast: hsl(0 0% 100%);
/* Shadows */
--shadow-mini: 0px 1px 0px 1px rgba(0, 0, 0, 0.3);
--shadow-mini-inset: 0px 1px 0px 0px rgba(0, 0, 0, 0.5) inset;
--shadow-popover: 0px 7px 12px 3px hsla(0deg 0% 0% / 30%);
--shadow-kbd: 0px 2px 0px 0px rgba(255, 255, 255, 0.07);
--shadow-btn: 0px 1px 0px 1px rgba(0, 0, 0, 0.2);
--shadow-card: 0px 2px 0px 1px rgba(0, 0, 0, 0.4);
--shadow-date-field-focus: 0px 0px 0px 3px rgba(244, 244, 245, 0.1);
}
@theme inline {
--color-background: var(--background);
--color-background-alt: var(--background-alt);
--color-foreground: var(--foreground);
--color-foreground-alt: var(--foreground-alt);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-border: var(--border-card);
--color-border-input: var(--border-input);
--color-border-input-hover: var(--border-input-hover);
--color-border-card: var(--border-card);
--color-dark: var(--dark);
--color-dark-10: var(--dark-10);
--color-dark-40: var(--dark-40);
--color-dark-04: var(--dark-04);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-tertiary: var(--tertiary);
--color-line: var(--line);
--color-contrast: var(--contrast);
--shadow-mini: var(--shadow-mini);
--shadow-mini-inset: var(--shadow-mini-inset);
--shadow-popover: var(--shadow-popover);
--shadow-kbd: var(--shadow-kbd);
--shadow-btn: var(--shadow-btn);
--shadow-card: var(--shadow-card);
--shadow-date-field-focus: var(--shadow-date-field-focus);
--text-xxs: 10px;
--radius-card: 16px;
--radius-card-lg: 20px;
--radius-card-sm: 10px;
--radius-input: 9px;
--radius-button: 5px;
--radius-5px: 5px;
--radius-9px: 9px;
--radius-10px: 10px;
--radius-15px: 15px;
--spacing-input: 3rem;
--spacing-input-sm: 2.5rem;
--breakpoint-desktop: 1440px;
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
--animate-caret-blink: caret-blink 1s ease-out infinite;
--animate-scale-in: scale-in 0.2s ease;
--animate-scale-out: scale-out 0.15s ease;
--animate-fade-in: fade-in 0.2s ease;
--animate-fade-out: fade-out 0.15s ease;
--animate-enter-from-left: enter-from-left 0.2s ease;
--animate-enter-from-right: enter-from-right 0.2s ease;
--animate-exit-to-left: exit-to-left 0.2s ease;
--animate-exit-to-right: exit-to-right 0.2s ease;
--font-sans: "Inter", "sans-serif";
--font-mono: "Source Code Pro", "monospace";
--font-alt: "Courier", "sans-serif";
--font-display: "Cal Sans", "sans-serif";
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--bits-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--bits-accordion-content-height);
}
to {
height: 0;
}
}
@keyframes caret-blink {
0%,
70%,
100% {
opacity: 1;
}
20%,
50% {
opacity: 0;
}
}
@keyframes enter-from-right {
from {
opacity: 0;
transform: translateX(200px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes enter-from-left {
from {
opacity: 0;
transform: translateX(-200px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes exit-to-right {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(200px);
}
}
@keyframes exit-to-left {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(-200px);
}
}
@keyframes scale-in {
from {
opacity: 0;
transform: rotateX(-10deg) scale(0.9);
}
to {
opacity: 1;
transform: rotateX(0deg) scale(1);
}
}
@keyframes scale-out {
from {
opacity: 1;
transform: rotateX(0deg) scale(1);
}
to {
opacity: 0;
transform: rotateX(-10deg) scale(0.95);
}
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
}
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-border-card, currentColor);
}
* {
@apply border-border;
}
html {
-webkit-text-size-adjust: 100%;
font-variation-settings: normal;
scrollbar-color: var(--bg-muted);
}
body {
@apply bg-background text-foreground;
font-feature-settings:
"rlig" 1,
"calt" 1;
}
::selection {
background: #fdffa4;
color: black;
}
}
@layer components {
*:not(body):not(.focus-override) {
outline: none !important;
&:focus-visible {
@apply focus-visible:ring-foreground focus-visible:ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-1;
}
}
.link {
@apply hover:text-foreground/80 focus-visible:ring-foreground focus-visible:ring-offset-background rounded-xs focus-visible:outline-hidden inline-flex items-center gap-1 font-medium underline underline-offset-4 focus-visible:ring-2 focus-visible:ring-offset-2;
}
}
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.
<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:
<script lang="ts">
import MyCheckbox from "$lib/components/MyCheckbox.svelte";
</script>
<MyCheckbox labelText="Enable notifications" />
Managing Checked State
This section covers how to manage the checked
state of the Checkbox.
Two-Way Binding
Use bind:checked
for simple, automatic state synchronization:
<script lang="ts">
import { Checkbox } from "bits-ui";
let myChecked = $state(false);
</script>
<button onclick={() => (myChecked = false)}> uncheck </button>
<Checkbox.Root bind:checked={myChecked} />
Fully Controlled
Use a Function Binding for complete control over the state's reads and writes.
<script lang="ts">
import { Checkbox } from "bits-ui";
let myChecked = $state(false);
function getChecked() {
return myChecked;
}
function setChecked(newChecked: boolean) {
myChecked = newChecked;
}
</script>
<Checkbox.Root bind:checked={getChecked, setChecked}>
<!-- ... -->
</Checkbox.Root>
Managing Indeterminate State
This section covers how to manage the indeterminate
state of the Checkbox.
Two-Way Binding
Use bind:indeterminate
for simple, automatic state synchronization:
<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} />
Fully Controlled
Use a Function Binding for complete control over the state's reads and writes.
<script lang="ts">
import { Checkbox } from "bits-ui";
let myIndeterminate = $state(true);
function getIndeterminate() {
return myIndeterminate;
}
function setIndeterminate(newIndeterminate: boolean) {
myIndeterminate = newIndeterminate;
}
</script>
<Checkbox.Root bind:indeterminate={getIndeterminate, setIndeterminate}>
<!-- ... -->
</Checkbox.Root>
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>
<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-foreground-alt text-sm font-medium">
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="border-muted bg-foreground data-[state=unchecked]:border-border-input data-[state=unchecked]:bg-background data-[state=unchecked]:hover:border-dark-40 peer inline-flex size-[25px] items-center justify-center rounded-md border transition-all duration-150 ease-in-out active:scale-[0.98]"
name="hello"
{value}
>
{#snippet children({ checked, indeterminate })}
<div class="text-background inline-flex items-center justify-center">
{#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}
@import url("https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap");
@import "tailwindcss";
@plugin "tailwindcss-animate";
@custom-variant dark (&:is(.dark *));
@font-face {
font-family: "Cal Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("/CalSans-SemiBold.woff2") format("woff2");
}
:root {
/* Colors */
--background: hsl(0 0% 100%);
--background-alt: hsl(0 0% 100%);
--foreground: hsl(0 0% 9%);
--foreground-alt: hsl(0 0% 32%);
--muted: hsl(240 5% 96%);
--muted-foreground: hsla(0 0% 9% / 0.4);
--border: hsl(240 6% 10%);
--border-input: hsla(240 6% 10% / 0.17);
--border-input-hover: hsla(240 6% 10% / 0.4);
--border-card: hsla(240 6% 10% / 0.1);
--dark: hsl(240 6% 10%);
--dark-10: hsla(240 6% 10% / 0.1);
--dark-40: hsla(240 6% 10% / 0.4);
--dark-04: hsla(240 6% 10% / 0.04);
--accent: hsl(204 94% 94%);
--accent-foreground: hsl(204 80% 16%);
--destructive: hsl(347 77% 50%);
--tertiary: hsl(37.7 92.1% 50.2%);
--line: hsl(0 0% 100%);
/* black */
--contrast: hsl(0 0% 0%);
/* Shadows */
--shadow-mini: 0px 1px 0px 1px rgba(0, 0, 0, 0.04);
--shadow-mini-inset: 0px 1px 0px 0px rgba(0, 0, 0, 0.04) inset;
--shadow-popover: 0px 7px 12px 3px hsla(var(--dark-10));
--shadow-kbd: 0px 2px 0px 0px rgba(0, 0, 0, 0.07);
--shadow-btn: 0px 1px 0px 1px rgba(0, 0, 0, 0.03);
--shadow-card: 0px 2px 0px 1px rgba(0, 0, 0, 0.04);
--shadow-date-field-focus: 0px 0px 0px 3px rgba(24, 24, 27, 0.17);
}
.dark {
/* Colors */
--background: hsl(0 0% 5%);
--background-alt: hsl(0 0% 8%);
--foreground: hsl(0 0% 95%);
--foreground-alt: hsl(0 0% 70%);
--muted: hsl(240 4% 16%);
--muted-foreground: hsla(0 0% 100% / 0.4);
--border: hsl(0 0% 96%);
--border-input: hsla(0 0% 96% / 0.17);
--border-input-hover: hsla(0 0% 96% / 0.4);
--border-card: hsla(0 0% 96% / 0.1);
--dark: hsl(0 0% 96%);
--dark-40: hsl(0 0% 96% / 0.4);
--dark-10: hsl(0 0% 96% / 0.1);
--dark-04: hsl(0 0% 96% / 0.04);
--accent: hsl(204 90% 90%);
--accent-foreground: hsl(204 94% 94%);
--destructive: hsl(350 89% 60%);
--line: hsl(0 0% 9.02%);
--tertiary: hsl(61.3 100% 82.2%);
/* white */
--contrast: hsl(0 0% 100%);
/* Shadows */
--shadow-mini: 0px 1px 0px 1px rgba(0, 0, 0, 0.3);
--shadow-mini-inset: 0px 1px 0px 0px rgba(0, 0, 0, 0.5) inset;
--shadow-popover: 0px 7px 12px 3px hsla(0deg 0% 0% / 30%);
--shadow-kbd: 0px 2px 0px 0px rgba(255, 255, 255, 0.07);
--shadow-btn: 0px 1px 0px 1px rgba(0, 0, 0, 0.2);
--shadow-card: 0px 2px 0px 1px rgba(0, 0, 0, 0.4);
--shadow-date-field-focus: 0px 0px 0px 3px rgba(244, 244, 245, 0.1);
}
@theme inline {
--color-background: var(--background);
--color-background-alt: var(--background-alt);
--color-foreground: var(--foreground);
--color-foreground-alt: var(--foreground-alt);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-border: var(--border-card);
--color-border-input: var(--border-input);
--color-border-input-hover: var(--border-input-hover);
--color-border-card: var(--border-card);
--color-dark: var(--dark);
--color-dark-10: var(--dark-10);
--color-dark-40: var(--dark-40);
--color-dark-04: var(--dark-04);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-tertiary: var(--tertiary);
--color-line: var(--line);
--color-contrast: var(--contrast);
--shadow-mini: var(--shadow-mini);
--shadow-mini-inset: var(--shadow-mini-inset);
--shadow-popover: var(--shadow-popover);
--shadow-kbd: var(--shadow-kbd);
--shadow-btn: var(--shadow-btn);
--shadow-card: var(--shadow-card);
--shadow-date-field-focus: var(--shadow-date-field-focus);
--text-xxs: 10px;
--radius-card: 16px;
--radius-card-lg: 20px;
--radius-card-sm: 10px;
--radius-input: 9px;
--radius-button: 5px;
--radius-5px: 5px;
--radius-9px: 9px;
--radius-10px: 10px;
--radius-15px: 15px;
--spacing-input: 3rem;
--spacing-input-sm: 2.5rem;
--breakpoint-desktop: 1440px;
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
--animate-caret-blink: caret-blink 1s ease-out infinite;
--animate-scale-in: scale-in 0.2s ease;
--animate-scale-out: scale-out 0.15s ease;
--animate-fade-in: fade-in 0.2s ease;
--animate-fade-out: fade-out 0.15s ease;
--animate-enter-from-left: enter-from-left 0.2s ease;
--animate-enter-from-right: enter-from-right 0.2s ease;
--animate-exit-to-left: exit-to-left 0.2s ease;
--animate-exit-to-right: exit-to-right 0.2s ease;
--font-sans: "Inter", "sans-serif";
--font-mono: "Source Code Pro", "monospace";
--font-alt: "Courier", "sans-serif";
--font-display: "Cal Sans", "sans-serif";
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--bits-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--bits-accordion-content-height);
}
to {
height: 0;
}
}
@keyframes caret-blink {
0%,
70%,
100% {
opacity: 1;
}
20%,
50% {
opacity: 0;
}
}
@keyframes enter-from-right {
from {
opacity: 0;
transform: translateX(200px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes enter-from-left {
from {
opacity: 0;
transform: translateX(-200px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes exit-to-right {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(200px);
}
}
@keyframes exit-to-left {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(-200px);
}
}
@keyframes scale-in {
from {
opacity: 0;
transform: rotateX(-10deg) scale(0.9);
}
to {
opacity: 1;
transform: rotateX(0deg) scale(1);
}
}
@keyframes scale-out {
from {
opacity: 1;
transform: rotateX(0deg) scale(1);
}
to {
opacity: 0;
transform: rotateX(-10deg) scale(0.95);
}
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
}
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-border-card, currentColor);
}
* {
@apply border-border;
}
html {
-webkit-text-size-adjust: 100%;
font-variation-settings: normal;
scrollbar-color: var(--bg-muted);
}
body {
@apply bg-background text-foreground;
font-feature-settings:
"rlig" 1,
"calt" 1;
}
::selection {
background: #fdffa4;
color: black;
}
}
@layer components {
*:not(body):not(.focus-override) {
outline: none !important;
&:focus-visible {
@apply focus-visible:ring-foreground focus-visible:ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-1;
}
}
.link {
@apply hover:text-foreground/80 focus-visible:ring-foreground focus-visible:ring-offset-background rounded-xs focus-visible:outline-hidden inline-flex items-center gap-1 font-medium underline underline-offset-4 focus-visible:ring-2 focus-visible:ring-offset-2;
}
}
Managing Value State
This section covers how to manage the value
state of a Checkbox Group.
Two-Way Binding
Use bind:value
for simple, automatic state synchronization:
<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>
Fully Controlled
Use a Function Binding for complete control over the state's reads and writes.
<script lang="ts">
import { Checkbox } from "bits-ui";
let myValue = $state<string[]>([]);
function getValue() {
return myValue;
}
function setValue(newValue: string[]) {
myValue = newValue;
}
</script>
<Checkbox.Group bind:value={getValue, setValue}>
<!-- ... -->
</Checkbox.Group>
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
The button component used to toggle the state of the checkbox.
Property | Type | Description |
---|---|---|
checked $bindable | boolean | The checkbox button's checked state. Default: false |
onCheckedChange | function | A callback that is fired when the checkbox button's checked state changes. Default: undefined |
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 |
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. |
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 |
disabled | boolean | Whether or not the checkbox group is disabled. If 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. |
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. |