Checkbox
Toggle one option on or off; checked state is a solid primary fill, with an indeterminate state for partial selections.
import { Checkbox } from "@/components/ui/checkbox";import { Label } from "@/components/ui/label";import { useState } from "react";
export default function Demo() { const [some, setSome] = useState({ checked: false, indeterminate: true }); return ( <div className="flex flex-col gap-4"> <Label className="flex items-start gap-2.5 text-sm"> <Checkbox id="mandate" defaultChecked /> <span className="grid gap-0.5"> <span className="font-medium">Accept the mandate</span> <span className="text-[12.5px] text-muted-foreground"> Weights and fees lock after launch. </span> </span> </Label> <Label className="flex items-center gap-2.5 text-sm"> <Checkbox checked={some.checked} indeterminate={some.indeterminate} onCheckedChange={(checked) => setSome({ checked, indeterminate: false }) } />{" "} Some selected </Label> <Label className="flex items-center gap-2.5 text-sm"> <Checkbox disabled /> Disabled </Label> </div> );}<script setup lang="ts">import { Checkbox } from "@/components/ui/checkbox";import { ref } from "vue";
const some = ref<boolean | "indeterminate">("indeterminate");</script>
<template> <div class="flex flex-col gap-4"> <label class="flex items-start gap-2.5 text-sm"> <Checkbox id="mandate" default-value /> <span class="grid gap-0.5"> <span class="font-medium">Accept the mandate</span> <span class="text-[12.5px] text-muted-foreground">Weights and fees lock after launch.</span> </span> </label> <label class="flex items-center gap-2.5 text-sm"> <Checkbox v-model="some" /> Some selected </label> <label class="flex items-center gap-2.5 text-sm"> <Checkbox disabled /> Disabled </label> </div></template><script lang="ts"> import { Checkbox } from "$lib/components/ui/checkbox";</script>
<div class="flex flex-col gap-4"> <label class="flex items-start gap-2.5 text-sm"> <Checkbox id="mandate" checked /> <span class="grid gap-0.5"> <span class="font-medium">Accept the mandate</span> <span class="text-[12.5px] text-muted-foreground">Weights and fees lock after launch.</span> </span> </label> <label class="flex items-center gap-2.5 text-sm"> <Checkbox indeterminate /> Some selected </label> <label class="flex items-center gap-2.5 text-sm"> <Checkbox disabled /> Disabled </label></div>Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/checkboxpnpm dlx shadcn@latest add @edmi-ui/checkboxyarn dlx shadcn@latest add @edmi-ui/checkboxbunx shadcn@latest add @edmi-ui/checkboxPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install @base-ui/reactTerminal window pnpm add @base-ui/reactTerminal window yarn add @base-ui/reactTerminal window bun add @base-ui/reactAdd the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/checkboxpnpm dlx shadcn-vue@latest add @edmi-ui/checkboxyarn dlx shadcn-vue@latest add @edmi-ui/checkboxbunx shadcn-vue@latest add @edmi-ui/checkboxPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install reka-ui @vueuse/coreTerminal window pnpm add reka-ui @vueuse/coreTerminal window yarn add reka-ui @vueuse/coreTerminal window bun add reka-ui @vueuse/coreAdd the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/checkbox.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/checkbox.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/checkbox.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/checkbox.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
import { Checkbox } from "@/components/ui/checkbox";import { Label } from "@/components/ui/label";
<div className="flex flex-col gap-4"> <Label className="flex items-start gap-2.5 text-sm"> <Checkbox id="mandate" defaultChecked /> <span className="grid gap-0.5"> <span className="font-medium">Accept the mandate</span> <span className="text-[12.5px] text-muted-foreground"> Weights and fees lock after launch. </span> </span> </Label> <Label className="flex items-center gap-2.5 text-sm"> <Checkbox />{" "} Some selected </Label> <Label className="flex items-center gap-2.5 text-sm"> <Checkbox disabled /> Disabled </Label></div><script setup lang="ts">import { Checkbox } from "@/components/ui/checkbox";</script>
<template> <div class="flex flex-col gap-4"> <label class="flex items-start gap-2.5 text-sm"> <Checkbox id="mandate" default-value /> <span class="grid gap-0.5"> <span class="font-medium">Accept the mandate</span> <span class="text-[12.5px] text-muted-foreground">Weights and fees lock after launch.</span> </span> </label> <label class="flex items-center gap-2.5 text-sm"> <Checkbox /> Some selected </label> <label class="flex items-center gap-2.5 text-sm"> <Checkbox disabled /> Disabled </label> </div></template><script lang="ts"> import { Checkbox } from "$lib/components/ui/checkbox";</script>
<div class="flex flex-col gap-4"> <label class="flex items-start gap-2.5 text-sm"> <Checkbox id="mandate" checked /> <span class="grid gap-0.5"> <span class="font-medium">Accept the mandate</span> <span class="text-[12.5px] text-muted-foreground">Weights and fees lock after launch.</span> </span> </label> <label class="flex items-center gap-2.5 text-sm"> <Checkbox indeterminate /> Some selected </label> <label class="flex items-center gap-2.5 text-sm"> <Checkbox disabled /> Disabled </label></div>Component source
Section titled “Component source”"use client";
import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";import { cn } from "cn";import { MinusIcon, CheckIcon } from "@phosphor-icons/react";
import { type Elevation, useElevation } from "@/registry/edmi/ui/elevation";
// Flat by default: checked = solid --primary fill. ✦ `elevation` raised/floating: only the checked box rises (bevel).// Indeterminate uses the same fill.function Checkbox({ className, elevation, ...props}: CheckboxPrimitive.Root.Props & { /** ✦ depth: raised +1 / floating +2 make the checked box rise. */ elevation?: Elevation;}) { const level = useElevation(elevation, "control"); const raised = level === "raised" || level === "floating"; return ( <CheckboxPrimitive.Root data-slot="checkbox" className={cn( "peer relative flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border border-input bg-card text-primary-foreground transition-[box-shadow] outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:shadow-ring disabled:cursor-not-allowed disabled:opacity-50 data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 aria-invalid:border-destructive aria-invalid:shadow-ring-error data-[invalid]:border-destructive data-[invalid]:shadow-ring-error group-has-disabled/field:opacity-50", "data-[checked]:border-primary data-[checked]:bg-primary data-[indeterminate]:border-primary data-[indeterminate]:bg-primary", raised && "data-[checked]:border-transparent data-[checked]:[background-image:var(--r1-p-face)] data-[checked]:shadow-btn-raised-primary data-[indeterminate]:border-transparent data-[indeterminate]:[background-image:var(--r1-p-face)] data-[indeterminate]:shadow-btn-raised-primary", className, )} {...props} > <CheckboxPrimitive.Indicator data-slot="checkbox-indicator" className="grid place-content-center text-current transition-none [&>svg]:size-3.5" render={(indicatorProps, state) => ( <span {...indicatorProps}> {state.indeterminate ? ( <MinusIcon strokeWidth={3} /> ) : ( <CheckIcon strokeWidth={3} /> )} </span> )} /> </CheckboxPrimitive.Root> );}
export { Checkbox };<script setup lang="ts">import type { CheckboxRootEmits, CheckboxRootProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { computed } from "vue"import { Check, Minus } from "@lucide/vue"import { reactiveOmit } from "@vueuse/core"import { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from "reka-ui"import { cn } from "@/registry/edmi/lib/utils"import { type Elevation, useElevation } from "@/registry/edmi/ui/elevation"
// Flat by default: checked = solid primary. `elevation` ✦ raised/floating: only the checked box rises (bevel).// Indeterminate uses the same fill.const props = withDefaults(defineProps<CheckboxRootProps & { class?: HTMLAttributes["class"], elevation?: Elevation }>(), { elevation: undefined })const emits = defineEmits<CheckboxRootEmits>()
const delegatedProps = reactiveOmit(props, "class", "elevation")
const level = useElevation(() => props.elevation, "control")const raised = computed(() => level.value === "raised" || level.value === "floating")
const forwarded = useForwardPropsEmits(delegatedProps, emits)
// A bare `default-value` attribute arrives as "" (Reka types it loosely, so Vue does not boolean-cast it);// read it as true so `<Checkbox default-value />` renders on, like `:default-value="true"`.const defaultValue = computed(() => ((props.defaultValue as unknown) === "" ? true : props.defaultValue))</script>
<template> <CheckboxRoot v-slot="slotProps" data-slot="checkbox" v-bind="forwarded" :default-value="defaultValue" :class=" cn( 'peer relative flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border border-input bg-card text-primary-foreground transition-[box-shadow] outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:shadow-ring disabled:cursor-not-allowed disabled:opacity-50 data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 aria-invalid:border-destructive aria-invalid:shadow-ring-error group-has-disabled/field:opacity-50', 'data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary', raised && 'data-[state=checked]:border-transparent data-[state=checked]:[background-image:var(--r1-p-face)] data-[state=checked]:shadow-btn-raised-primary data-[state=indeterminate]:border-transparent data-[state=indeterminate]:[background-image:var(--r1-p-face)] data-[state=indeterminate]:shadow-btn-raised-primary', props.class, ) " > <CheckboxIndicator data-slot="checkbox-indicator" class="grid place-content-center text-current transition-none" > <slot v-bind="slotProps"> <Minus v-if="slotProps.state === 'indeterminate'" class="size-3.5" :stroke-width="3" /> <Check v-else class="size-3.5" :stroke-width="3" /> </slot> </CheckboxIndicator> </CheckboxRoot></template>export { default as Checkbox } from "./Checkbox.vue";<script lang="ts"> import { Checkbox as CheckboxPrimitive } from "bits-ui"; import CheckIcon from "phosphor-svelte/lib/Check"; import MinusIcon from "phosphor-svelte/lib/Minus"; import { cn, type WithoutChildrenOrChild } from "$lib/utils.js"; import { type Elevation, useElevation } from "$lib/registry/ui/elevation/index.js";
// Flat by default: checked = solid primary. elevation ✦ raised/floating: only the checked box rises (bevel). let { ref = $bindable(null), checked = $bindable(false), indeterminate = $bindable(false), class: className, elevation = "auto", ...restProps }: WithoutChildrenOrChild<CheckboxPrimitive.RootProps> & { /** ✦ depth: raised +1 / floating +2 make the checked box rise. */ elevation?: Elevation; } = $props();
const level = useElevation(() => elevation, "control"); const raised = $derived(level.current === "raised" || level.current === "floating");</script>
<CheckboxPrimitive.Root bind:ref data-slot="checkbox" class={cn( "peer relative flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border border-input bg-card text-primary-foreground transition-[box-shadow] outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:shadow-ring disabled:cursor-not-allowed disabled:opacity-50 data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 aria-invalid:border-destructive aria-invalid:shadow-ring-error group-has-disabled/field:opacity-50", "data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary", raised && "data-[state=checked]:border-transparent data-[state=checked]:[background-image:var(--r1-p-face)] data-[state=checked]:shadow-btn-raised-primary data-[state=indeterminate]:border-transparent data-[state=indeterminate]:[background-image:var(--r1-p-face)] data-[state=indeterminate]:shadow-btn-raised-primary", className )} bind:checked bind:indeterminate {...restProps}> {#snippet children({ checked, indeterminate })} <div data-slot="checkbox-indicator" class="grid place-content-center text-current transition-none [&>svg]:size-3.5" > {#if checked} <CheckIcon stroke-width={3} /> {:else if indeterminate} <MinusIcon stroke-width={3} /> {/if} </div> {/snippet}</CheckboxPrimitive.Root>import Root from "./checkbox.svelte";
export { Root, // Root as Checkbox,};Elevation ✦
Section titled “Elevation ✦”See the Elevation guide for the levels, roles and layered mode.
Only the checked (and indeterminate) box rises: raised (+1) or floating (+2) gives it a bevel. The unchecked box stays flat.
Flat (0)
Raised (+1)
Flat (0)
Raised (+1)
Flat (0)
Raised (+1)
import { Checkbox } from "@/components/ui/checkbox";import { Label } from "@/components/ui/label";
const levels = [ { value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" },] as const;
export default function Demo() { return ( <div className="flex flex-col gap-5"> {levels.map(({ value, label }) => ( <div key={value} className="flex flex-col gap-2"> <p className="text-xs font-medium text-muted-foreground">{label}</p> <div className="flex flex-wrap items-center gap-6"> <Label className="flex items-center gap-2.5 text-sm"> <Checkbox elevation={value} defaultChecked /> Checked </Label> <Label className="flex items-center gap-2.5 text-sm"> <Checkbox elevation={value} indeterminate /> Indeterminate </Label> <Label className="flex items-center gap-2.5 text-sm"> <Checkbox elevation={value} /> Unchecked </Label> </div> </div> ))} </div> );}<script setup lang="ts">import { Checkbox } from "@/components/ui/checkbox";
const levels = [{ value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }];</script>
<template> <div class="flex flex-col gap-5"> <div v-for="level in levels" :key="level.value" class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{{ level.label }}</p> <div class="flex flex-wrap items-center gap-6 text-sm"> <label class="flex items-center gap-2.5"><Checkbox :elevation="level.value" :default-value="true" /> Checked</label> <label class="flex items-center gap-2.5"><Checkbox :elevation="level.value" model-value="indeterminate" /> Indeterminate</label> <label class="flex items-center gap-2.5"><Checkbox :elevation="level.value" /> Unchecked</label> </div> </div> </div></template><script lang="ts"> import { Checkbox } from "$lib/components/ui/checkbox";
const levels = [{ value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }] as const;</script>
<div class="flex flex-col gap-5"> {#each levels as level (level.value)} <div class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{level.label}</p> <div class="flex flex-wrap items-center gap-6 text-sm"> <label class="flex items-center gap-2.5"><Checkbox elevation={level.value} checked /> Checked</label> <label class="flex items-center gap-2.5"><Checkbox elevation={level.value} indeterminate /> Indeterminate</label> <label class="flex items-center gap-2.5"><Checkbox elevation={level.value} /> Unchecked</label> </div> </div> {/each}</div>Built on Base UI Checkbox. Same props as shadcn.
| Prop | Type | Default |
|---|---|---|
checked / defaultChecked |
boolean |
false |
indeterminate |
boolean |
false |
onCheckedChange |
(checked: boolean, event) => void |
- |
disabled |
boolean |
false |
aria-invalid |
boolean |
- |
elevation |
"auto" | "sunken" | "flat" | "raised" | "floating" |
"auto" |
✦ Edmi additions
Section titled “✦ Edmi additions”- Checked and indeterminate use a solid
--primaryfill (elevationraises the checked box) with a check/minus icon.