# Checkbox

> A tri-look checkbox — unchecked, checked, indeterminate and disabled — built on a native checkbox input.

- Package: `fancy-ui-svelte` (Svelte 5 + Tailwind CSS v4)
- Category: Forms
- Tags: checkbox, form, indeterminate, validation
- Docs: https://fancy-ui.rama.app/docs/components/checkbox
- Source: https://github.com/RamaHerbin/fancy-ui/tree/main/src/lib/fancy-ui/checkbox

## Import

```svelte
<script lang="ts">
  import { Checkbox } from 'fancy-ui-svelte';
</script>
```

Setup (once per project): `npm install fancy-ui-svelte`, then add `@import "fancy-ui-svelte/tailwind.css";` after `@import "tailwindcss";` in your main CSS file. Full guide: https://fancy-ui.rama.app/llms.txt

React: `import { Checkbox } from 'fancy-ui-react';` (same prop names; `class` becomes `className`, snippets become `ReactNode`).

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `checked` | `boolean` | `false` | Whether the box is checked; bindable |
| `indeterminate` | `boolean` | `false` | Mixed/dash visual state; bindable |
| `onCheckedChange` | `(checked: boolean) => void` | — | Called with the new checked value whenever the box is activated |
| `disabled` | `boolean` | `false` | Blocks interaction; excluded from form submission |
| `required` | `boolean` | `false` | Native required |
| `invalid` | `boolean` | `false` | Drives the error border and aria-invalid |
| `id` | `string` | — | Element id |
| `name` | `string` | — | Native name, read on form submission |
| `value` | `string` | — | Form value submitted while checked |
| `label` | `string` | — | Accessible name, rendered as aria-label; skip it when children already supplies the visible label text |
| `class` | `string` | — | Additional CSS classes, merged onto the wrapping label |
| `ref` | `HTMLInputElement \| null` | `null` | Bindable element reference to the native input |
| `sound` | `boolean` | `false` | Plays toggle-on or toggle-off on each change, once the user has enabled sound |

## Snippets

| Snippet | Description |
| --- | --- |
| `children` | Visible label text, rendered beside the box |

## Examples

### Basic Usage

A single checkbox with a bound checked state.

```svelte
<script lang="ts">
	import { Checkbox } from "fancy-ui-svelte";

	let agreed = $state(false);
</script>

<Checkbox sound bind:checked={agreed}>I agree to the terms and conditions</Checkbox>
```

### States

Unchecked, checked, indeterminate and disabled, side by side.

```svelte
<script lang="ts">
	import { Checkbox } from "fancy-ui-svelte";
</script>

<div class="flex flex-col gap-3">
	<Checkbox sound checked={false}>Unchecked</Checkbox>
	<Checkbox sound checked={true}>Checked</Checkbox>
	<Checkbox sound checked={false} indeterminate={true}>Indeterminate</Checkbox>
	<Checkbox checked={false} disabled>Disabled</Checkbox>
</div>
```

### Indeterminate

A parent checkbox reflecting a mixed selection of children.

```svelte
<script lang="ts">
	import { Checkbox } from "fancy-ui-svelte";

	let items = $state([
		{ label: "Design", done: true },
		{ label: "Build", done: false },
		{ label: "Ship", done: false },
	]);

	const allDone = $derived(items.every((item) => item.done));
	const noneDone = $derived(items.every((item) => !item.done));

	function setAll(checked: boolean) {
		items = items.map((item) => ({ ...item, done: checked }));
	}
</script>

<div class="flex flex-col gap-3">
	<Checkbox sound checked={allDone} indeterminate={!allDone && !noneDone} onCheckedChange={setAll}>
		Select all
	</Checkbox>
	<div class="border-border ml-[10px] flex flex-col gap-2 border-l pl-4">
		{#each items as item, index (item.label)}
			<Checkbox
				sound
				checked={item.done}
				onCheckedChange={(checked) => (items[index] = { ...item, done: checked })}
			>
				{item.label}
			</Checkbox>
		{/each}
	</div>
</div>
```

### With FormField

Wrapped in FormField — no id or invalid prop needed on Checkbox itself.

```svelte
<script lang="ts">
	// FormField renders its own internal Label from the `label` prop below and
	// derives invalid state from whether `error` is set — it supplies
	// controlId, aria-describedby and aria-invalid to Checkbox through
	// context, so Checkbox itself needs no id or invalid prop here. The
	// FormField's `label` ("Terms") is a group heading; Checkbox's own
	// children ("I agree...") is still the control's own accessible label —
	// the two serve different purposes.
	import { FormField } from "fancy-ui-svelte";
	import { Checkbox } from "fancy-ui-svelte";

	let agreed = $state(false);
</script>

<FormField
	label="Terms"
	required
	error={agreed ? undefined : "You must agree before continuing."}
	class="w-full max-w-sm"
>
	<Checkbox sound bind:checked={agreed}>I agree to the terms and conditions</Checkbox>
</FormField>
```
