Checkbox
A drawn box that ticks with a pull up and out past the corner, and dashes when indeterminate.
Built on Base UI CheckboxOnce a month, written by hand. Well, typed.
"use client";
import { useState } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { Field, FieldContent, FieldDescription, FieldGroup, FieldLabel } from "@/components/ui/field";
import { Label } from "@/components/ui/label";
const items = ["Paper", "Ink", "A steady hand"];
export default function CheckboxDemo() {
const [picked, setPicked] = useState(["Paper"]);
const all = picked.length === items.length;
return (
<FieldGroup className="max-w-md">
<Field orientation="horizontal">
<Checkbox id="cb-terms" defaultChecked seed="cb-terms" />
<FieldContent>
<FieldLabel htmlFor="cb-terms">Send me the newsletter</FieldLabel>
<FieldDescription>Once a month, written by hand. Well, typed.</FieldDescription>
</FieldContent>
</Field>
<div className="flex flex-col gap-3">
<Label>
<Checkbox
seed="cb-all"
checked={all}
indeterminate={picked.length > 0 && !all}
onCheckedChange={(checked) => setPicked(checked ? items : [])}
/>
Everything you need
</Label>
{items.map((item) => (
<Label key={item} className="pl-7">
<Checkbox
seed={`cb-${item}`}
checked={picked.includes(item)}
onCheckedChange={(checked) => setPicked((p) => (checked ? [...p, item] : p.filter((x) => x !== item)))}
/>
{item}
</Label>
))}
</div>
<Label>
<Checkbox disabled seed="cb-disabled" />
Disabled
</Label>
</FieldGroup>
);
}Installation
pnpm dlx shadcn@latest add @ballpoint/ checkboxOr copy the source
"use client";
import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
import { cn } from "@/lib/utils";
import { usePen, type Pen } from "@/hooks/use-ink-box";
import { Stroke } from "@/lib/ink";
import { InkOutline } from "@/lib/ink-outline";
import { dashStroke, tickStroke } from "@/lib/ink-sketch";
/**
* A drawn box. Checking it ticks it, the pen running up and out past the
* corner; unchecking pulls the tick back out. Indeterminate is a dash.
*/
function Checkbox({
className,
seed,
roughness,
passes,
radius,
corners,
draw,
weight,
speed,
...props
}: CheckboxPrimitive.Root.Props & Omit<Pen, "fill" | "shadow"> & { seed?: string | number }) {
const pen = usePen({ roughness, passes, radius, corners, draw, weight, speed });
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
"peer group/checkbox relative inline-flex size-5 shrink-0 cursor-pointer items-center justify-center outline-none",
// A finger-sized hit area around the small box.
"after:absolute after:-inset-x-3 after:-inset-y-2",
"text-ink-3 transition-colors duration-(--dur-hover) not-data-checked:not-data-indeterminate:hover:text-ink data-checked:text-ink data-indeterminate:text-ink",
"aria-invalid:text-destructive data-invalid:text-destructive",
"focus-visible:outline-solid focus-visible:outline-[1.5px] focus-visible:outline-offset-4 focus-visible:outline-ring focus-visible:[border-radius:var(--hand-radius)]",
"data-disabled:cursor-not-allowed data-disabled:opacity-50",
className,
)}
{...props}
>
<InkOutline pen={pen} seed={seed} estimate={[20, 20]} pad={6} maxRadius={6}>
{({ w, h, s }) => (
<>
<Stroke d={tickStroke(s + 40, w)} draw="checked" duration={280} width={1.9} />
<Stroke d={dashStroke(s + 41, w, h / 2)} draw="indeterminate" duration={200} width={1.9} />
</>
)}
</InkOutline>
</CheckboxPrimitive.Root>
);
}
export { Checkbox };Usage
import { Checkbox } from "@/components/ui/checkbox"
import { Label } from "@/components/ui/label"
<Label>
<Checkbox defaultChecked />
Send me the newsletter
</Label>API reference
- indeterminate
boolean - Draws a dash instead of a tick.
- seed
string | number - Pins the drawing.
Every other prop goes to Base UI's Checkbox.
Pen settings
The drawing also takes draw, roughness, passes, radius, corners, weight and speed, as props or from the nearest InkProvider. What each one does.