Klarhimmel UI

Checkbox

Stable

A box that is on, off or mixed, with its label inside a 44 px row. Built on Radix Checkbox.

npx shadcn add @klarhimmel/checkbox

When to use

  • Use for

    Choices that are submitted with a form, and lists where several can be picked.

  • Not for

    A setting that applies right away. Use Switch.

  • Pairs with

    Field rows, fieldset and legend, Button.

Preview

Anyone in your organisation can see it.

You need to accept the terms to continue.

checkbox.tsx
import { Theme } from '@/components/klarhimmel/theme/style';
import { Checkbox } from '@/components/klarhimmel/ui/checkbox';

export default function Example() {
  return (
    <Theme theme="brand">
      <Checkbox label="Send me the weekly summary" defaultChecked />
    </Theme>
  );
}

Install

Import

import { Checkbox } from '@/components/klarhimmel/ui/checkbox';
Lands in
components/klarhimmel/ui/checkbox.tsx, plus cn() and the theme item if they are missing. Import theme.css after Tailwind.
Render in
Render it inside <Theme>, once near the root of your app. Tokens and fonts are scoped to it, so outside it the component is unstyled.

Examples

  • Group

    Rooms to include

    1 of 3 selected

    import { useState } from 'react';
    import { Checkbox } from '@/components/klarhimmel/ui/checkbox';
    
    const rooms = ['Seaside suite', 'Garden room', 'Attic double'];
    
    export default function CheckboxGroup() {
      const [picked, setPicked] = useState<string[]>(['Garden room']);
      const all = picked.length === rooms.length ? true : picked.length ? 'indeterminate' : false;
      return (
        <fieldset className="flex flex-col">
          <legend className="mb-1 text-kh-small font-medium">Rooms to include</legend>
          <Checkbox label="All rooms" checked={all} onCheckedChange={(c) => setPicked(c === true ? rooms : [])} />
          <div className="flex flex-col pl-8">
            {rooms.map((r) => (
              <Checkbox key={r} label={r} checked={picked.includes(r)} onCheckedChange={(c) => setPicked((p) => (c === true ? [...p, r] : p.filter((x) => x !== r)))} />
            ))}
          </div>
          <p className="mt-2 text-kh-small text-kh-muted" aria-live="polite">{picked.length} of {rooms.length} selected</p>
        </fieldset>
      );
    }
  • Invalid

    import { useState, type FormEvent } from 'react';
    import { Button } from '@/components/klarhimmel/ui/button';
    import { Checkbox } from '@/components/klarhimmel/ui/checkbox';
    
    export default function CheckboxInvalid() {
      const [agreed, setAgreed] = useState(false);
      const [tried, setTried] = useState(false);
      const [sent, setSent] = useState(false);
      const invalid = tried && !agreed;
      const submit = (e: FormEvent) => {
        e.preventDefault();
        setTried(true);
        setSent(agreed);
      };
      return (
        <form onSubmit={submit} className="flex max-w-md flex-col items-start gap-3">
          <Checkbox
            label="I accept the booking terms"
            invalid={invalid}
            hint={invalid ? 'You need to accept the terms to continue.' : undefined}
            checked={agreed}
            onCheckedChange={(c) => setAgreed(c === true)}
          />
          <Button type="submit">Book the room</Button>
          <p role="status" className="text-kh-small text-kh-muted">{sent ? 'Booked. A confirmation is on its way.' : ''}</p>
        </form>
      );
    }

API reference

PropTypeDefaultDescription
labelReactNode-The visible label. Without one, pass aria-label.
hintReactNode-Help text under the label, wired as the description.
invalidbooleanfalseMarks the box invalid; the hint turns into the error text.
checked / defaultCheckedboolean | 'indeterminate'-Controlled or uncontrolled state. "indeterminate" shows a dash.
onCheckedChange(checked: boolean | 'indeterminate') => void-Called when the state changes.
disabledbooleanfalsePrevents interaction.
classNamestring-Extra classes, merged after the component's own.

Accessibility

KeyDoes
SpaceChecks or unchecks
TabMoves focus; the ring is always visible

A group needs a fieldset and legend so the question is read with each box.

Accessibility