HextaUI

Checkbox

Una casilla de verificación cuya marca se dibuja, con padres indeterminados, grupos y etiquetas que comparten su hover.

import { Checkbox } from "@/components/ui/checkbox"

export function CheckboxDemo() {
  return (
    <div className="flex flex-col gap-3">
      <label className="flex items-center gap-3 text-sm">
        <Checkbox />
        Email me about product updates
      </label>
      <label className="flex items-center gap-3 text-sm">
        <Checkbox defaultChecked />
        Remember this device
      </label>
      <label className="flex max-w-sm items-start gap-3 text-sm">
        <span className="flex h-5 items-center">
          <Checkbox />
        </span>
        <span className="flex flex-col gap-0.5">
          <span className="leading-5 font-medium">Mentions and replies</span>
          <span className="text-muted-foreground">
            We’ll only notify you about activity on your own posts.
          </span>
        </span>
      </label>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/checkbox.json

Añade el componente, los tokens del tema de HextaUI y los componentes de HextaUI de los que depende.

import { Checkbox, CheckboxGroup } from "@/components/ui/checkbox"
<label className="flex items-center gap-3">
  <Checkbox defaultChecked />
  Remember this device
</label>
CheckboxGroup
└── Checkbox

Estados

disabled, readOnly, indeterminate y aria-invalid. Una casilla de solo lectura conserva su valor y puede recibir foco, pero ignora los clics y las teclas.

import { Checkbox } from "@/components/ui/checkbox"

export function CheckboxStates() {
  return (
    <div className="flex flex-col gap-3 text-sm">
      <label className="flex items-center gap-3">
        <Checkbox disabled />
        Disabled
      </label>
      <label className="flex items-center gap-3">
        <Checkbox disabled defaultChecked />
        Disabled and checked
      </label>
      <label className="flex items-center gap-3">
        <Checkbox readOnly defaultChecked />
        Read-only
      </label>
      <label className="flex items-center gap-3">
        <Checkbox indeterminate />
        Indeterminate
      </label>
      <label className="flex items-center gap-3">
        <Checkbox aria-invalid />
        Invalid
      </label>
      <label className="flex items-center gap-3">
        <Checkbox aria-invalid defaultChecked />
        Invalid and checked
      </label>
    </div>
  )
}

Seleccionar todo

Dentro de un <CheckboxGroup />, un checkbox con parent marca todos los valores de allValues. Pasa a indeterminado cuando solo algunos están marcados.

"use client"

import * as React from "react"

import { Checkbox, CheckboxGroup } from "@/components/ui/checkbox"

const fruits = ["Apple", "Banana", "Cherry", "Mango", "Peach"]

export function CheckboxSelectAll() {
  const [value, setValue] = React.useState<string[]>(["Banana"])

  return (
    <div className="text-sm">
      <CheckboxGroup
        aria-label="Fruits"
        value={value}
        onValueChange={setValue}
        allValues={fruits}
      >
        <label className="flex items-center gap-3">
          <Checkbox parent />
          <span>
            Select all{" "}
            <span className="text-muted-foreground">
              ({value.length}/{fruits.length})
            </span>
          </span>
        </label>
        <div className="flex flex-col gap-3 ps-7">
          {fruits.map((fruit) => (
            <label key={fruit} className="flex items-center gap-3">
              <Checkbox value={fruit} />
              {fruit}
            </label>
          ))}
        </div>
      </CheckboxGroup>
    </div>
  )
}

Grupos anidados

Los grupos se anidan. Cada padre refleja el estado del grupo inmediatamente inferior, y el padre superior abarca todo.

"use client"

import * as React from "react"

import { Checkbox, CheckboxGroup } from "@/components/ui/checkbox"

const userPermissions = ["users.read", "users.write", "users.delete"]
const billingPermissions = ["billing.read", "billing.write"]

export function CheckboxNestedGroups() {
  const [users, setUsers] = React.useState<string[]>(["users.read"])
  const [billing, setBilling] = React.useState<string[]>([])

  return (
    <div className="text-sm">
      <CheckboxGroup
        aria-label="Permissions"
        value={[...users, ...billing]}
        onValueChange={(next) => {
          setUsers(next.filter((value) => value.startsWith("users.")))
          setBilling(next.filter((value) => value.startsWith("billing.")))
        }}
        allValues={[...userPermissions, ...billingPermissions]}
      >
        <label className="flex items-center gap-3">
          <Checkbox parent />
          All permissions
        </label>
        <div className="flex flex-col gap-3 ps-7">
          <CheckboxGroup
            aria-label="Users"
            value={users}
            onValueChange={setUsers}
            allValues={userPermissions}
          >
            <label className="flex items-center gap-3">
              <Checkbox parent />
              Users
            </label>
            <div className="flex flex-col gap-3 ps-7">
              {userPermissions.map((permission) => (
                <label key={permission} className="flex items-center gap-3">
                  <Checkbox value={permission} />
                  {permission}
                </label>
              ))}
            </div>
          </CheckboxGroup>
          <CheckboxGroup
            aria-label="Billing"
            value={billing}
            onValueChange={setBilling}
            allValues={billingPermissions}
          >
            <label className="flex items-center gap-3">
              <Checkbox parent />
              Billing
            </label>
            <div className="flex flex-col gap-3 ps-7">
              {billingPermissions.map((permission) => (
                <label key={permission} className="flex items-center gap-3">
                  <Checkbox value={permission} />
                  {permission}
                </label>
              ))}
            </div>
          </CheckboxGroup>
        </div>
      </CheckboxGroup>
    </div>
  )
}

Controlado

Pasa checked y onCheckedChange para mantener el estado en tu propio código.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"

export function CheckboxControlled() {
  const [checked, setChecked] = React.useState(false)

  return (
    <div className="flex flex-col items-start gap-3">
      <label className="flex items-center gap-3 text-sm">
        <Checkbox checked={checked} onCheckedChange={setChecked} />
        Controlled ({checked ? "on" : "off"})
      </label>
      <Button variant="outline" size="sm" onClick={() => setChecked(!checked)}>
        Toggle from outside
      </Button>
    </div>
  )
}

Form

Un input oculto envía name y value como un checkbox nativo, y required bloquea el envío hasta que se marque.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"

export function CheckboxForm() {
  const [submitted, setSubmitted] = React.useState<string>()

  return (
    <form
      className="flex flex-col items-start gap-3 text-sm"
      onSubmit={(event) => {
        event.preventDefault()
        const data = new FormData(event.currentTarget)
        setSubmitted(JSON.stringify(Object.fromEntries(data.entries())))
      }}
    >
      <label className="flex items-center gap-3">
        <Checkbox name="terms" required />I agree to the terms
      </label>
      <label className="flex items-center gap-3">
        <Checkbox name="newsletter" value="weekly" defaultChecked />
        Weekly newsletter
      </label>
      <Button type="submit" size="sm">
        Submit
      </Button>
      <output className="text-muted-foreground">
        {submitted ?? "Nothing submitted yet."}
      </output>
    </form>
  )
}

Etiqueta hermana

Cuando la etiqueta no puede envolver la casilla, renderiza el checkbox como un <button> con nativeButton y apunta la etiqueta a él con htmlFor.

import { Checkbox } from "@/components/ui/checkbox"

export function CheckboxSiblingLabel() {
  return (
    <div className="flex items-center gap-2 text-sm">
      <Checkbox id="terms" nativeButton render={<button />} />
      <label htmlFor="terms">Accept terms and conditions</label>
    </div>
  )
}

Tarjetas

Envuelve una tarjeta completa en la etiqueta para que la tarjeta sea el área de clic, y dale estilo con has-data-checked.

import { Checkbox, CheckboxGroup } from "@/components/ui/checkbox"

const addOns = [
  { value: "analytics", title: "Analytics", text: "Dashboards and exports." },
  { value: "backups", title: "Backups", text: "Daily snapshots, 30 days." },
]

export function CheckboxCards() {
  return (
    <CheckboxGroup
      aria-label="Add-ons"
      defaultValue={["analytics"]}
      className="w-full max-w-md"
    >
      <div className="grid gap-3 sm:grid-cols-2">
        {addOns.map((addOn) => (
          <label
            key={addOn.value}
            className="flex items-start gap-3 rounded-xl border p-4 text-sm transition-colors has-data-checked:border-primary"
          >
            <span className="flex h-5 items-center">
              <Checkbox value={addOn.value} />
            </span>
            <span className="flex flex-col gap-0.5">
              <span className="leading-5 font-medium">{addOn.title}</span>
              <span className="text-muted-foreground">{addOn.text}</span>
            </span>
          </label>
        ))}
      </div>
    </CheckboxGroup>
  )
}

Contenido largo

La casilla permanece en la primera línea mientras una etiqueta larga y el texto sin espacios se ajustan a su lado.

import { Checkbox } from "@/components/ui/checkbox"

export function CheckboxLongContent() {
  return (
    <label className="flex w-64 max-w-full items-start gap-3 text-sm">
      <span className="flex h-5 items-center">
        <Checkbox />
      </span>
      <span className="flex min-w-0 flex-col gap-0.5 wrap-anywhere">
        <span className="leading-5 font-medium">
          A label long enough to wrap onto a second and even a third line in a
          narrow container
        </span>
        <span className="text-muted-foreground">
          averyveryverylongunbrokenstringthatwouldotherwiseescapeitscontainer
        </span>
      </span>
    </label>
  )
}

De derecha a izquierda

La casilla va en el lado inicial en línea y la etiqueta sigue la dirección de lectura.

import { Checkbox } from "@/components/ui/checkbox"

export function CheckboxRtl() {
  return (
    <div dir="rtl" className="flex flex-col gap-3 text-sm">
      <label className="flex items-center gap-3">
        <Checkbox defaultChecked />
        تذكر هذا الجهاز
      </label>
      <label className="flex max-w-sm items-start gap-3">
        <span className="flex h-5 items-center">
          <Checkbox />
        </span>
        <span className="flex flex-col gap-0.5">
          <span className="leading-5 font-medium">الإشارات والردود</span>
          <span className="text-muted-foreground">
            سنرسل لك إشعارات فقط حول نشاط منشوراتك.
          </span>
        </span>
      </label>
    </div>
  )
}
KeyAcción
SpaceMarca o desmarca el checkbox.
EnterEnvía el formulario al que pertenece el checkbox, como un checkbox nativo. Nunca alterna la casilla.
TabMueve el foco al siguiente checkbox.
  • Envuelve el checkbox y su texto en un <label>. La etiqueta nombra el checkbox, y pasar el cursor o pulsarla le da a la casilla el mismo feedback que pasar el cursor sobre la propia casilla.
  • Dale a cada <CheckboxGroup /> un aria-label o aria-labelledby para que los lectores de pantalla anuncien para qué sirve el grupo.
  • El área de clic se extiende más allá de la casilla de 16px, y crece en pantallas táctiles.
  • La marca se dibuja al marcarse. Con movimiento reducido aparece sin la animación del trazo.

Construido sobre el checkbox y el checkbox group de Base UI. Ambos aceptan las props de la primitiva que envuelven.

PropTipoPredeterminado
checked
boolean–
defaultChecked
booleanfalse
onCheckedChange
(checked: boolean, details) => void–
indeterminateMuestra un guion: ni marcado ni desmarcado.
booleanfalse
disabled
booleanfalse
readOnlyPuede recibir foco, pero el valor no puede cambiar.
booleanfalse
required
booleanfalse
nameSe envía con el formulario cuando está marcado.
string–
valueIdentifica la casilla dentro de un grupo y es lo que envía el formulario. Recurre a name y luego a “on”.
string–
uncheckedValueSe envía cuando está desmarcado. Nada por defecto.
string–
parentControla todos los valores de allValues del grupo. Solo dentro de un CheckboxGroup.
booleanfalse
inputRef
Ref<HTMLInputElement>–
nativeButtonPonlo en true cuando render sea un <button>.
booleanfalse
render
ReactElement | (props, state) => ReactElement<span>
AtributoDescripción
data-slot="checkbox"Selecciona la caja en CSS.
data-checkedPresente cuando está marcado.
data-uncheckedPresente cuando está desmarcado.
data-indeterminatePresente cuando es indeterminado.
data-disabledPresente cuando está deshabilitado.
data-readonlyPresente cuando es de solo lectura.
data-requiredPresente cuando es obligatorio.
data-invalidPresente cuando no es válido dentro de un Field de Base UI.
PropTipoPredeterminado
valueValores de los checkboxes marcados.
string[]–
defaultValue
string[]–
onValueChange
(value: string[], details) => void–
allValuesTodos los valores del grupo. Obligatorio para un checkbox padre.
string[]–
disabled
booleanfalse
render
ReactElement | (props, state) => ReactElement<div>
AtributoDescripción
data-slot="checkbox-group"Apunta al grupo en CSS.
data-disabledPresente cuando el grupo está deshabilitado.

Usado en bloques

Bloques que se construyen sobre Checkbox.