Checkbox
A box that is on, off, or partly on.
import { Div, Label } from "@implementjs/core";
import { Checkbox } from "@/lib/components/ui/checkbox";
export default function CheckboxDemo() {
return Div(
{ class: "flex flex-col gap-4" },
Div(
{ class: "flex items-center gap-2" },
Checkbox({ id: "terms" }),
Label(
{ for: "terms", class: "text-sm leading-none font-medium" },
"Accept terms and conditions",
),
),
Div(
{ class: "flex items-center gap-2" },
Checkbox({ id: "emails", checked: true }),
Label(
{ for: "emails", class: "text-sm leading-none font-medium" },
"Send me product updates",
),
),
Div(
{ class: "flex items-center gap-2" },
Checkbox({ id: "partial", indeterminate: true }),
Label(
{ for: "partial", class: "text-sm leading-none font-medium" },
"Select all notifications",
),
),
Div(
{ class: "flex items-center gap-2" },
Checkbox({ id: "disabled", checked: true, disabled: true }),
Label(
{
for: "disabled",
class:
"text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
},
"Disabled",
),
),
);
}Installation
npx jsrepo add @implementjs/ui/checkbox
It installs @implementjs/lucide at the same time.
Copy the file below to src/lib/components/ui/checkbox.ts. It imports cn, so copy utils.ts to src/lib/utils.ts too. Then, on top of @implementjs/core and @implementjs/primitives:
npm install @implementjs/lucide
import { Span, type Child, type ComponentProps } from "@implementjs/core";
import { CheckIcon, MinusIcon } from "@implementjs/lucide";
import { Checkbox as CheckboxPrimitive } from "@implementjs/primitives";
import { createComponent } from "@implementjs/primitives";
import { cn } from "@/lib/utils";
export type CheckboxProps = ComponentProps<typeof CheckboxPrimitive>;
export const Checkbox = createComponent(function Checkbox(
{ class: className, ...props }: CheckboxProps,
...children: Child[]
) {
return CheckboxPrimitive(
{
...props,
"data-slot": "checkbox",
class: cn(
"peer group/checkbox size-4 shrink-0 rounded-[4px] border border-input shadow-xs outline-none transition-shadow",
"dark:bg-input/30",
"data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
"data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground",
"dark:data-[state=checked]:bg-primary dark:data-[state=indeterminate]:bg-primary",
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
"disabled:cursor-not-allowed disabled:opacity-50",
className,
),
},
...(children.length > 0
? children
: [
Span(
{
"data-slot": "checkbox-indicator",
class: "grid place-content-center text-current",
},
CheckIcon({
class: "size-3.5 hidden group-data-[state=checked]/checkbox:block",
"aria-hidden": true,
}),
MinusIcon({
class: "size-3.5 hidden group-data-[state=indeterminate]/checkbox:block",
"aria-hidden": true,
}),
),
]),
);
});Usage
Pass nothing and the checkbox renders its own indicator: a check while checked, a dash while indeterminate. Pass children and they replace it.
checked takes a signal for a controlled box, and "indeterminate" for the third state — the parent of a partly-selected list.
import { signal } from "@implementjs/core";
import { Checkbox } from "@/lib/components/ui/checkbox";
const accepted = signal(false);
Checkbox({ id: "terms", checked: accepted });
With a label
The checkbox is a Button, not an input, so a Label points at it by for and id like any other control:
Div(
{ class: "flex items-center gap-2" },
Checkbox({ id: "terms" }),
Label({ for: "terms", class: "text-sm leading-none font-medium" }, "Accept terms"),
);
Invalid state
aria-invalid is styled as well as announced — the border and focus ring turn destructive:
Checkbox({ id: "terms", "aria-invalid": true });
API Reference
Every prop the styling does not consume is forwarded to the Checkbox primitive, so the tables below are the whole surface — the behavior props and the styling ones together.
Checkbox
A toggle that is checked, unchecked, or indeterminate. Sets role="checkbox" and aria-checked. Give it a look and an indicator; it handles the state. Renders a check — or a dash while indeterminate — unless you pass children of your own. Renders a Button; extra props are forwarded onto it.
| Prop | Type | Default | Description |
|---|---|---|---|
checked | Signal<boolean> | boolean | false | The checked state. Pass a signal to control it from outside; a boolean seeds uncontrolled state. |
indeterminate | Signal<boolean> | boolean | false | Partial selection. While true, data-state is "indeterminate" and aria-checked is "mixed". A click clears it and checks the box. |
name | string | — | If set, a hidden checkbox is rendered so the value submits with a form. |
value | string | "on" | The value submitted while checked. Only used when name is set. |
required | boolean | false | Marks the hidden input as required. Sets aria-required on the button. |
| Data attribute | Value |
|---|---|
[data-checkbox-root] | Present |
[data-state] | "checked" | "unchecked" | "indeterminate" |