nili/ui
All components

Checkbox

Overview
8 exports5 variant axes26 props4 files
import { Checkbox } from '@nili/ui';

This is the library’s own overview page, rendered live. Switch the language or the theme in the header and it follows.

States

A real checkbox input, so checked, defaultChecked, disabled and required all behave as the browser intends.

import { Checkbox } from "@nili/ui"

export function Example() {
  return (
    <>
      <Checkbox label={t('states.unchecked')} />
      <Checkbox label={t('states.checked')} defaultChecked />
      <Checkbox label={t('states.indeterminate')} indeterminate />
      <Checkbox label={t('states.invalid')} invalid />
      <Checkbox label={t('states.disabled')} disabled />
      <Checkbox
            label={t('states.disabledChecked')}
            disabled
            defaultChecked
          />
    </>
  )
}

The label row

sublabel is muted text inline after the label; badge is the trailing slot; description is the second line; linkButton is the action under it. All four are Figma toggles, as props.

(optional)
Free
(recommended)We will only email you about things you asked for.Manage preferences
import { Checkbox, Badge, LinkButton } from "@nili/ui"

export function Example() {
  return (
    <Checkbox
      defaultChecked
      label="Email notifications"
      sublabel="(recommended)"
      badge={<Badge size="small" color="green" appearance="lighter">Free</Badge>}
      description="We will only email you about things you asked for."
      linkButton={<LinkButton href="#" size="sm">Manage preferences</LinkButton>}
    />
  )
}

Label position

Figma calls this “Flip”. It is logical, so start is left in English and right in Persian.

import { Checkbox } from "@nili/ui"

export function Example() {
  return (
    <>
      <Checkbox label={t('position.end')} labelPosition="end" />
      <Checkbox label={t('position.start')} labelPosition="start" />
    </>
  )
}

Indeterminate — select all

indeterminate is not a DOM attribute; it has to be assigned to the element, which is why this component always attaches a ref internally. The usual use is a parent checkbox over a list.

import { Checkbox } from "@nili/ui"

export function Example() {
  const [items, setItems] = useState([true, false, false])
  const all = items.every(Boolean)
  const some = items.some(Boolean) && !all

  return (
    <>
      <Checkbox
        label="Select all"
        checked={all}
        indeterminate={some}
        onChange={(e) => setItems(items.map(() => e.target.checked))}
      />

      {items.map((checked, index) => (
        <Checkbox
          key={index}
          label={`Item ${index + 1}`}
          checked={checked}
          onChange={(e) =>
            setItems((current) =>
              current.map((v, i) => (i === index ? e.target.checked : v)),
            )
          }
        />
      ))}
    </>
  )
}

Without a label

Omit label for a bare control — a table's row-select column, for instance. Give it an aria-label, since there is nothing else to name it.

import { Checkbox } from "@nili/ui"

export function Example() {
  return <Checkbox aria-label={t('bare.selectRow')} />
}