Checkbox
A control allowing the user to toggle between checked and not checked. Built with Base UI and Tailwind CSS. Copy-paste ready.
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
export default function Particle() {
return (
<Label>
<Checkbox />
Accept terms and conditions
</Label>
);
}
Installation
pnpm dlx shadcn@latest add @cnippet/checkbox
Usage
import { Checkbox } from "@/components/ui/checkbox"<Checkbox />Examples
For accessible labelling and validation, prefer using the Field component to wrap checkboxes. See the related example: Checkbox field.
Disabled
Use the disabled prop to prevent interaction while keeping the checkbox visible in the form layout.
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
export default function Particle() {
return (
<Label>
<Checkbox defaultChecked disabled />
Accept terms and conditions
</Label>
);
}
With Description
Adds a short description below the label to clarify what the user is agreeing to or enabling.
By clicking this checkbox, you agree to the terms and conditions.
import { useId } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
export default function Particle() {
const id = useId();
return (
<div className="flex items-start gap-2">
<Checkbox defaultChecked id={id} />
<div className="flex flex-col gap-1">
<Label htmlFor={id}>Accept terms and conditions</Label>
<p className="text-muted-foreground text-xs">
By clicking this checkbox, you agree to the terms and conditions.
</p>
</div>
</div>
);
}
Card Style
Wraps the checkbox and its label in a bordered card that highlights on selection — a common terms-acceptance or plan-selection pattern.
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
export default function Particle() {
return (
<Label className="flex items-start gap-2 rounded-lg border p-3 hover:bg-accent/50 has-data-checked:border-primary/48 has-data-checked:bg-accent/50">
<Checkbox defaultChecked />
<div className="flex flex-col gap-1">
<p>Enable notifications</p>
<p className="text-muted-foreground text-xs">
You can enable or disable notifications at any time.
</p>
</div>
</Label>
);
}
Form Integration
Field provides accessible labelling and validation primitives for form controls. Use it with Form to submit values.
"use client";
import type { FormEvent } from "react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Field, FieldLabel } from "@/components/ui/field";
import { Form } from "@/components/ui/form";
export default function Particle() {
const [loading, setLoading] = useState(false);
const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
setLoading(true);
await new Promise((r) => setTimeout(r, 800));
setLoading(false);
const accepted = formData.get("terms");
alert(`Terms: ${accepted}`);
};
return (
<Form className="flex w-auto flex-col gap-4" onSubmit={onSubmit}>
<Field name="terms">
<FieldLabel>
<Checkbox defaultChecked value="yes" />
Accept terms and conditions
</FieldLabel>
</Field>
<Button loading={loading} type="submit">
Submit
</Button>
</Form>
);
}
Payment Card
A selectable card containing payment method details — clicking anywhere on the card checks the checkbox and highlights the selection.
"use client";
import type { SVGProps } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { Frame, FramePanel } from "@/components/ui/frame";
const MastercardIcon = (props: SVGProps<SVGSVGElement>) => (
<svg
fill="none"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<circle cx="7" cy="12" fill="#EB001B" r="7" />
<circle cx="17" cy="12" fill="#F79E1B" r="7" />
<path
d="M12 17.5C13.5 16.2 14.5 14.2 14.5 12C14.5 9.8 13.5 7.8 12 6.5C10.5 7.8 9.5 9.8 9.5 12C9.5 14.2 10.5 16.2 12 17.5Z"
fill="#FF5F00"
/>
</svg>
);
export function Pattern() {
return (
<div className="mx-auto w-full max-w-xs">
<Frame className="w-full">
<FramePanel>
<div className="flex size-10 items-center justify-center rounded-lg border border-border bg-background p-1.5 shadow-black/5 shadow-xs">
<MastercardIcon className="size-full" />
</div>
<div className="flex flex-col items-start gap-0.5">
<span className="font-medium text-sm">
Mastercard ending in 8888
</span>
<span className="text-muted-foreground text-xs">Expires 09/25</span>
</div>
</FramePanel>
<Checkbox
className="absolute top-5 right-5 size-5 rounded-full"
defaultChecked
id="mastercard"
/>
</Frame>
</div>
);
}
Select All with Indeterminate State
A parent checkbox that controls a group of children. When only some children are checked the parent displays the indeterminate state; checking it toggles all children on or off.
View all resources
Create and edit resources
Remove resources permanently
Manage members and billing
"use client";
import { useId, useState } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
const permissions = [
{ description: "View all resources", id: "read", label: "Read" },
{ description: "Create and edit resources", id: "write", label: "Write" },
{
description: "Remove resources permanently",
id: "delete",
label: "Delete",
},
{ description: "Manage members and billing", id: "admin", label: "Admin" },
];
export function Pattern() {
const parentId = useId();
const [checked, setChecked] = useState<Record<string, boolean>>({
admin: false,
delete: false,
read: true,
write: true,
});
const values = Object.values(checked);
const allChecked = values.every(Boolean);
const someChecked = values.some(Boolean) && !allChecked;
const toggleAll = () => {
const next = !allChecked;
setChecked(Object.fromEntries(permissions.map((p) => [p.id, next])));
};
return (
<div className="w-full max-w-xs space-y-3">
<div className="flex items-center gap-2">
<Checkbox
checked={allChecked}
id={parentId}
indeterminate={someChecked}
onCheckedChange={toggleAll}
/>
<Label className="font-semibold" htmlFor={parentId}>
{allChecked ? "Deselect all" : "Select all permissions"}
</Label>
</div>
<Separator />
<div className="space-y-2.5 pl-1">
{permissions.map((perm) => (
<div className="flex items-start gap-2.5" key={perm.id}>
<Checkbox
checked={checked[perm.id]}
id={perm.id}
onCheckedChange={(val) =>
setChecked((prev) => ({ ...prev, [perm.id]: !!val }))
}
/>
<div className="flex flex-col gap-0.5">
<Label htmlFor={perm.id}>{perm.label}</Label>
<p className="text-muted-foreground text-xs">
{perm.description}
</p>
</div>
</div>
))}
</div>
</div>
);
}
Issue Label Picker
Eight colored labels each rendered as a checkbox with a color dot. The header updates the selected count live as labels are toggled.
Labels
2 selected"use client";
import { useState } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { Separator } from "@/components/ui/separator";
const labels = [
{ color: "bg-red-500", id: "bug", name: "bug" },
{ color: "bg-blue-500", id: "feature", name: "feature" },
{ color: "bg-yellow-500", id: "improvement", name: "improvement" },
{ color: "bg-green-500", id: "documentation", name: "documentation" },
{ color: "bg-purple-500", id: "design", name: "design" },
{ color: "bg-orange-500", id: "performance", name: "performance" },
{ color: "bg-pink-500", id: "security", name: "security" },
{ color: "bg-cyan-500", id: "testing", name: "testing" },
];
export function Pattern() {
const [selected, setSelected] = useState<Set<string>>(
new Set(["bug", "feature"]),
);
const toggle = (id: string) =>
setSelected((prev) => {
const next = new Set(prev);
next.has(id) ? next.delete(id) : next.add(id);
return next;
});
return (
<div className="w-full max-w-xs space-y-3">
<div className="flex items-center justify-between">
<p className="font-semibold text-sm">Labels</p>
<span className="text-muted-foreground text-xs">
{selected.size} selected
</span>
</div>
<Separator />
<div className="space-y-0.5">
{labels.map((label) => (
<label
className="flex cursor-pointer items-center gap-2.5 rounded-md px-2 py-1.5 hover:bg-accent"
htmlFor={label.id}
key={label.id}
>
<Checkbox
checked={selected.has(label.id)}
id={label.id}
onCheckedChange={() => toggle(label.id)}
/>
<span className={`size-2.5 shrink-0 rounded-full ${label.color}`} />
<span className="text-sm">{label.name}</span>
</label>
))}
</div>
</div>
);
}
Notification Channel Selector
Four notification channels (email, SMS, push, Slack) as checkboxes. The icon box changes accent color when its channel is enabled, giving immediate visual feedback.
Notification channels
"use client";
import {
BellIcon,
MailIcon,
MessageSquareIcon,
SmartphoneIcon,
} from "lucide-react";
import { useState } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { Separator } from "@/components/ui/separator";
const channels = [
{
description: "Receive updates in your inbox",
icon: MailIcon,
id: "ch-email",
label: "Email",
},
{
description: "Alerts sent to your phone",
icon: SmartphoneIcon,
id: "ch-sms",
label: "SMS",
},
{
description: "Banners while using the app",
icon: BellIcon,
id: "ch-inapp",
label: "In-app",
},
{
description: "Messages to your Slack workspace",
icon: MessageSquareIcon,
id: "ch-slack",
label: "Slack",
},
];
export function Pattern() {
const [checked, setChecked] = useState<Record<string, boolean>>({
"ch-email": true,
"ch-inapp": true,
"ch-slack": false,
"ch-sms": false,
});
const toggle = (id: string, val: boolean) =>
setChecked((prev) => ({ ...prev, [id]: val }));
return (
<div className="w-full max-w-xs">
<p className="mb-3 font-semibold text-sm">Notification channels</p>
<Separator className="mb-1" />
{channels.map((ch) => {
const Icon = ch.icon;
return (
<label
className="flex cursor-pointer items-start gap-3 border-b py-3 last:border-b-0"
htmlFor={ch.id}
key={ch.id}
>
<Checkbox
checked={checked[ch.id]}
id={ch.id}
onCheckedChange={(val) => toggle(ch.id, !!val)}
/>
<div
className={`flex size-8 shrink-0 items-center justify-center rounded-md border transition-colors ${
checked[ch.id]
? "border-primary/20 bg-primary/10 text-primary"
: "border-border bg-muted/50 text-muted-foreground"
}`}
>
<Icon className="size-3.5" />
</div>
<div className="flex flex-col gap-0.5">
<span className="pt-0.5 font-medium text-sm leading-none">
{ch.label}
</span>
<span className="text-muted-foreground text-xs">
{ch.description}
</span>
</div>
</label>
);
})}
</div>
);
}
Multi-Consent Gate
Three required consent items and one optional item. The submit button is disabled until all required items are checked, and its label counts how many remain.
Before you continue
Please review and accept the required agreements.
You accept our terms of use, including policies on prohibited content and account termination.
You understand how we collect, store, and process your personal data.
Our service is only available to users who are at least 18 years old.
Occasional emails about new features and promotions. Unsubscribe any time.
"use client";
import { useId, useState } from "react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
const consents = [
{
description:
"You accept our terms of use, including policies on prohibited content and account termination.",
id: "terms",
label: "I agree to the Terms of Service",
},
{
description:
"You understand how we collect, store, and process your personal data.",
id: "privacy",
label: "I have read the Privacy Policy",
},
{
description:
"Our service is only available to users who are at least 18 years old.",
id: "age",
label: "I confirm I am 18 years of age or older",
},
{
description:
"Occasional emails about new features and promotions. Unsubscribe any time.",
id: "marketing",
label: "Send me product updates and offers (optional)",
optional: true,
},
];
export function Pattern() {
const fieldId = useId();
const [accepted, setAccepted] = useState<Record<string, boolean>>(
Object.fromEntries(consents.map((c) => [c.id, false])),
);
const toggle = (id: string, val: boolean) =>
setAccepted((prev) => ({ ...prev, [id]: val }));
const required = consents.filter((c) => !c.optional);
const allRequired = required.every((c) => accepted[c.id]);
const remaining = required.filter((c) => !accepted[c.id]).length;
return (
<div className="w-full max-w-sm space-y-5">
<div>
<p className="font-semibold text-sm">Before you continue</p>
<p className="mt-0.5 text-muted-foreground text-xs">
Please review and accept the required agreements.
</p>
</div>
<div className="space-y-4">
{consents.map((consent, _i) => (
<div className="flex items-start gap-3" key={consent.id}>
<Checkbox
checked={accepted[consent.id]}
id={`${fieldId}-${consent.id}`}
onCheckedChange={(val) => toggle(consent.id, !!val)}
/>
<div className="flex flex-col gap-0.5">
<label
className="cursor-pointer font-medium text-sm leading-snug"
htmlFor={`${fieldId}-${consent.id}`}
>
{consent.label}
{consent.optional && (
<span className="ml-1.5 font-normal text-muted-foreground text-xs">
(optional)
</span>
)}
</label>
<p className="text-muted-foreground text-xs leading-relaxed">
{consent.description}
</p>
</div>
</div>
))}
</div>
<Button className="w-full" disabled={!allRequired}>
{allRequired
? "Create account"
: `${remaining} required ${remaining === 1 ? "item" : "items"} remaining`}
</Button>
</div>
);
}
Weekly Availability Grid
A checkbox grid with time slots (Morning, Afternoon, Evening) as rows and days of the week as columns, with a live count of selected slots.
Weekly Availability
0 slots selected| Mon | Tue | Wed | Thu | Fri | Sat | Sun | |
|---|---|---|---|---|---|---|---|
| Morning | |||||||
| Afternoon | |||||||
| Evening |
"use client";
import { useId, useState } from "react";
import { Checkbox } from "@/components/ui/checkbox";
const COLUMNS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
const ROWS = ["Morning", "Afternoon", "Evening"];
type Grid = Record<string, boolean>;
function key(row: string, col: string) {
return `${row}-${col}`;
}
export function Pattern() {
const id = useId();
const [grid, setGrid] = useState<Grid>(() =>
Object.fromEntries(
ROWS.flatMap((r) => COLUMNS.map((c) => [key(r, c), false])),
),
);
const toggle = (k: string) => setGrid((prev) => ({ ...prev, [k]: !prev[k] }));
const count = Object.values(grid).filter(Boolean).length;
return (
<div className="w-full max-w-md space-y-3">
<div className="flex items-center justify-between">
<p className="font-semibold text-sm">Weekly Availability</p>
<span className="text-muted-foreground text-xs">
{count} slots selected
</span>
</div>
<div className="overflow-x-auto">
<table className="w-full border-collapse text-xs">
<thead>
<tr>
<th className="w-24 pb-2 text-left font-medium text-muted-foreground" />
{COLUMNS.map((col) => (
<th
className="pb-2 text-center font-medium text-muted-foreground"
key={col}
>
{col}
</th>
))}
</tr>
</thead>
<tbody>
{ROWS.map((row) => (
<tr key={row}>
<td className="py-2 pr-3 text-muted-foreground">{row}</td>
{COLUMNS.map((col) => {
const k = key(row, col);
return (
<td className="py-2 text-center" key={col}>
<Checkbox
checked={grid[k]}
id={`${id}-${k}`}
onCheckedChange={() => toggle(k)}
/>
</td>
);
})}
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}
Role Permissions
Four permission levels (Read, Write, Delete, Admin) each rendered as a bordered card row with a colored badge and a description line.
Role Permissions
"use client";
import { useId, useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Checkbox } from "@/components/ui/checkbox";
const PERMISSIONS = [
{
description: "View dashboards, reports, and analytics.",
id: "read",
label: "Read",
variant: "secondary" as const,
},
{
description: "Create and modify records, posts, and settings.",
id: "write",
label: "Write",
variant: "info" as const,
},
{
description: "Remove records and bulk-delete resources.",
id: "delete",
label: "Delete",
variant: "warning" as const,
},
{
description: "Invite members, change roles, and manage billing.",
id: "admin",
label: "Admin",
variant: "destructive" as const,
},
];
export function Pattern() {
const id = useId();
const [selected, setSelected] = useState<Set<string>>(new Set(["read"]));
const toggle = (pid: string) =>
setSelected((prev) => {
const next = new Set(prev);
next.has(pid) ? next.delete(pid) : next.add(pid);
return next;
});
return (
<div className="w-full max-w-sm space-y-2">
<p className="font-semibold text-sm">Role Permissions</p>
<div className="space-y-1">
{PERMISSIONS.map((perm) => (
<label
className="flex cursor-pointer items-start gap-3 rounded-lg border px-3 py-2.5 transition-colors hover:bg-muted/50"
htmlFor={`${id}-${perm.id}`}
key={perm.id}
>
<Checkbox
checked={selected.has(perm.id)}
className="mt-0.5"
id={`${id}-${perm.id}`}
onCheckedChange={() => toggle(perm.id)}
/>
<div className="flex flex-1 flex-col gap-0.5">
<div className="flex items-center gap-2">
<span className="font-medium text-sm">{perm.label}</span>
<Badge size="sm" variant={perm.variant}>
{perm.id}
</Badge>
</div>
<p className="text-muted-foreground text-xs">
{perm.description}
</p>
</div>
</label>
))}
</div>
</div>
);
}
Notification Preferences Matrix
A table with event rows and channel columns (Email, Push, SMS) where each cell is an individually togglable checkbox, with a Save button below.
Notification Preferences
| Event | Push | SMS | |
|---|---|---|---|
| New message | |||
| Mention | |||
| Task assigned | |||
| Comment | |||
| Status change |
"use client";
import { useId, useState } from "react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
const COLUMNS = [
{ id: "email", label: "Email" },
{ id: "push", label: "Push" },
{ id: "sms", label: "SMS" },
];
const EVENTS = [
{ id: "new-message", label: "New message" },
{ id: "mention", label: "Mention" },
{ id: "task-assigned", label: "Task assigned" },
{ id: "comment", label: "Comment" },
{ id: "status-change", label: "Status change" },
];
type Prefs = Record<string, boolean>;
function cellKey(event: string, channel: string) {
return `${event}__${channel}`;
}
export function Pattern() {
const id = useId();
const [prefs, setPrefs] = useState<Prefs>(() =>
Object.fromEntries(
EVENTS.flatMap((e) =>
COLUMNS.map((c) => [cellKey(e.id, c.id), c.id === "email"]),
),
),
);
const toggle = (k: string) => setPrefs((p) => ({ ...p, [k]: !p[k] }));
return (
<div className="w-full max-w-sm space-y-3">
<p className="font-semibold text-sm">Notification Preferences</p>
<div className="overflow-x-auto rounded-lg border">
<table className="w-full text-xs">
<thead>
<tr className="border-b bg-muted/40">
<th className="py-2 pr-4 pl-3 text-left font-medium text-muted-foreground">
Event
</th>
{COLUMNS.map((c) => (
<th
className="px-3 py-2 text-center font-medium text-muted-foreground"
key={c.id}
>
{c.label}
</th>
))}
</tr>
</thead>
<tbody>
{EVENTS.map((evt, i) => (
<tr
className={i < EVENTS.length - 1 ? "border-b" : ""}
key={evt.id}
>
<td className="py-2.5 pr-4 pl-3 text-foreground">
{evt.label}
</td>
{COLUMNS.map((ch) => {
const k = cellKey(evt.id, ch.id);
return (
<td className="px-3 py-2.5 text-center" key={ch.id}>
<Checkbox
checked={prefs[k]}
id={`${id}-${k}`}
onCheckedChange={() => toggle(k)}
/>
</td>
);
})}
</tr>
))}
</tbody>
</table>
</div>
<Button className="w-full" size="sm">
Save preferences
</Button>
</div>
);
}
Plan Add-on Selector
Selectable feature add-ons where the estimated monthly price updates in real time as the user checks or unchecks items.
Customize your plan
Select the add-ons you need.
"use client";
import { useId, useState } from "react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
const FEATURES = [
{
desc: "Dedicated infrastructure with 99.99% SLA.",
id: "uptime",
label: "High Availability",
},
{
desc: "End-to-end encryption for all data at rest and in transit.",
id: "encryption",
label: "Data Encryption",
},
{
desc: "Access real-time metrics, logs, and alerting dashboards.",
id: "monitoring",
label: "Advanced Monitoring",
},
{
desc: "Enterprise SSO via SAML 2.0 and OIDC.",
id: "sso",
label: "SSO & SAML",
},
{
desc: "Guaranteed 4-hour response for Severity 1 issues.",
id: "support",
label: "Priority Support",
},
];
export function Pattern() {
const id = useId();
const [selected, setSelected] = useState<Set<string>>(
new Set(["uptime", "encryption"]),
);
const toggle = (fid: string) =>
setSelected((prev) => {
const next = new Set(prev);
next.has(fid) ? next.delete(fid) : next.add(fid);
return next;
});
const basePrice = 49;
const addOnPrice = (selected.size - 2) * 12;
const total = basePrice + Math.max(0, addOnPrice);
return (
<div className="w-full max-w-sm space-y-4">
<div>
<p className="font-semibold text-sm">Customize your plan</p>
<p className="mt-0.5 text-muted-foreground text-xs">
Select the add-ons you need.
</p>
</div>
<div className="space-y-1.5">
{FEATURES.map((f) => (
<label
className="flex cursor-pointer items-start gap-3 rounded-lg border px-3 py-2.5 transition-colors hover:bg-muted/50"
htmlFor={`${id}-${f.id}`}
key={f.id}
>
<Checkbox
checked={selected.has(f.id)}
className="mt-0.5"
id={`${id}-${f.id}`}
onCheckedChange={() => toggle(f.id)}
/>
<div className="flex-1">
<span className="font-medium text-sm">{f.label}</span>
<p className="mt-0.5 text-muted-foreground text-xs">{f.desc}</p>
</div>
</label>
))}
</div>
<div className="flex items-center justify-between rounded-lg bg-muted px-4 py-2.5">
<span className="font-medium text-sm">Estimated total</span>
<span className="font-bold text-sm">${total}/mo</span>
</div>
<Button className="w-full">Confirm selection</Button>
</div>
);
}
Sprint Task Checklist
A task list with priority badges (High, Medium, Low). Completed tasks receive a strikethrough, and a celebration banner appears when all tasks are checked off.
Sprint Tasks
6 remaining"use client";
import { useId, useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
const TASKS = [
{ id: "t1", label: "Review pull request #142", priority: "high" },
{ id: "t2", label: "Update API documentation", priority: "medium" },
{ id: "t3", label: "Fix login redirect bug", priority: "high" },
{ id: "t4", label: "Write unit tests for AuthService", priority: "medium" },
{ id: "t5", label: "Migrate legacy endpoints to v2", priority: "low" },
{ id: "t6", label: "Deploy staging environment", priority: "low" },
];
const priorityVariant: Record<string, "destructive" | "warning" | "secondary"> =
{
high: "destructive",
low: "secondary",
medium: "warning",
};
export function Pattern() {
const id = useId();
const [done, setDone] = useState<Set<string>>(new Set());
const toggle = (tid: string) =>
setDone((prev) => {
const next = new Set(prev);
next.has(tid) ? next.delete(tid) : next.add(tid);
return next;
});
const remaining = TASKS.length - done.size;
return (
<div className="w-full max-w-sm space-y-3">
<div className="flex items-center justify-between">
<p className="font-semibold text-sm">Sprint Tasks</p>
<span className="text-muted-foreground text-xs">
{remaining} remaining
</span>
</div>
<div className="space-y-0.5">
{TASKS.map((task) => (
<label
className={`flex cursor-pointer items-center gap-3 rounded-md px-2 py-2 transition-colors hover:bg-muted/50 ${done.has(task.id) ? "opacity-50" : ""}`}
htmlFor={`${id}-${task.id}`}
key={task.id}
>
<Checkbox
checked={done.has(task.id)}
id={`${id}-${task.id}`}
onCheckedChange={() => toggle(task.id)}
/>
<span
className={`flex-1 text-sm ${done.has(task.id) ? "text-muted-foreground line-through" : ""}`}
>
{task.label}
</span>
<Badge size="sm" variant={priorityVariant[task.priority]}>
{task.priority}
</Badge>
</label>
))}
</div>
{remaining === 0 && (
<div className="rounded-lg bg-emerald-50 px-4 py-2.5 text-center font-medium text-emerald-700 text-sm dark:bg-emerald-950/40 dark:text-emerald-400">
All tasks complete! 🎉
</div>
)}
<Button
className="w-full"
onClick={() => setDone(new Set())}
size="sm"
variant="outline"
>
Reset
</Button>
</div>
);
}
On This Page

