Label
Free, copy-and-go Label components built on the SevenUI Label primitive.Read the primitive docs.
"use client";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export default function Label01() {
return (
<form
className="flex w-full max-w-sm flex-col gap-5"
onSubmit={(event) => event.preventDefault()}
>
<div className="flex flex-col gap-2">
<Label htmlFor="label-01-company">
Company name
<span aria-hidden="true" className="-ml-1.5 text-destructive">
*
</span>
<span className="sr-only">(required)</span>
</Label>
<Input
id="label-01-company"
name="company"
autoComplete="organization"
placeholder="Northwind Logistics"
required
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="label-01-vat">
VAT number
<span className="ml-auto text-xs font-normal text-muted-foreground">
Optional
</span>
</Label>
<Input id="label-01-vat" name="vat" placeholder="DE 123 456 789" />
</div>
<p className="text-xs text-muted-foreground">
Fields marked with{" "}
<span aria-hidden="true" className="text-destructive">
*
</span>
<span className="sr-only">an asterisk</span> are required.
</p>
</form>
);
}
npx shadcn@latest add @sevenui/component/label-01pnpm dlx shadcn@latest add @sevenui/component/label-01yarn dlx shadcn@latest add @sevenui/component/label-01bunx --bun shadcn@latest add @sevenui/component/label-01Stored encrypted. We only show the last four characters after saving.
"use client";
import { Info, KeyRound } from "lucide-react";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
export default function Label02() {
return (
<TooltipProvider>
<div className="flex w-full max-w-sm flex-col gap-2">
<div className="flex items-center gap-1.5">
<Label htmlFor="label-02-key">
<KeyRound
aria-hidden="true"
className="size-4 text-muted-foreground"
/>
API secret key
</Label>
<Tooltip>
<TooltipTrigger
aria-label="Where to find your secret key"
className="inline-flex size-5 items-center justify-center rounded-full text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50"
>
<Info aria-hidden="true" className="size-3.5" />
</TooltipTrigger>
<TooltipContent side="top">
Settings → Developers → API keys. Secret keys start with sk_live.
</TooltipContent>
</Tooltip>
</div>
<Input
id="label-02-key"
type="password"
autoComplete="off"
placeholder="sk_live_…"
className="font-mono"
aria-describedby="label-02-key-hint"
/>
<p id="label-02-key-hint" className="text-xs text-muted-foreground">
Stored encrypted. We only show the last four characters after saving.
</p>
</div>
</TooltipProvider>
);
}
npx shadcn@latest add @sevenui/component/label-02pnpm dlx shadcn@latest add @sevenui/component/label-02yarn dlx shadcn@latest add @sevenui/component/label-02bunx --bun shadcn@latest add @sevenui/component/label-02"use client";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
const fields = [
{
id: "label-03-name",
label: "Display name",
placeholder: "Maya Chen",
autoComplete: "name",
},
{
id: "label-03-title",
label: "Job title",
placeholder: "Staff product designer",
autoComplete: "organization-title",
},
{
id: "label-03-location",
label: "Location",
placeholder: "Lisbon, Portugal",
autoComplete: "address-level2",
},
];
export default function Label03() {
return (
<form
className="grid w-full max-w-lg gap-4"
onSubmit={(event) => event.preventDefault()}
>
{fields.map((field) => (
<div
key={field.id}
className="grid gap-2 sm:grid-cols-[8rem_1fr] sm:items-center sm:gap-4"
>
<Label htmlFor={field.id} className="sm:justify-end sm:text-right">
{field.label}
</Label>
<Input
id={field.id}
placeholder={field.placeholder}
autoComplete={field.autoComplete}
/>
</div>
))}
<div className="grid gap-2 sm:grid-cols-[8rem_1fr] sm:items-start sm:gap-4">
<Label
htmlFor="label-03-bio"
className="sm:justify-end sm:pt-2 sm:text-right"
>
Bio
</Label>
<Textarea
id="label-03-bio"
rows={3}
placeholder="Designing billing flows at Northwind."
/>
</div>
</form>
);
}
npx shadcn@latest add @sevenui/component/label-03pnpm dlx shadcn@latest add @sevenui/component/label-03yarn dlx shadcn@latest add @sevenui/component/label-03bunx --bun shadcn@latest add @sevenui/component/label-03Read-only. Contact support to transfer this account.
"use client";
import { Lock } from "lucide-react";
import * as React from "react";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
const addressFields = [
{
id: "label-04-street",
label: "Street address",
placeholder: "221 Harbor Way",
},
{ id: "label-04-city", label: "City", placeholder: "Portland" },
];
export default function Label04() {
const [sameAsShipping, setSameAsShipping] = React.useState(true);
return (
<div className="flex w-full max-w-sm flex-col gap-5">
<div className="flex flex-col gap-2">
<Label htmlFor="label-04-account">
<Lock aria-hidden="true" className="size-3.5 text-muted-foreground" />
Account ID
</Label>
<Input
id="label-04-account"
readOnly
defaultValue="acct_7Q2mX91c"
aria-describedby="label-04-account-hint"
className="bg-muted/50 font-mono text-muted-foreground"
/>
<p id="label-04-account-hint" className="text-xs text-muted-foreground">
Read-only. Contact support to transfer this account.
</p>
</div>
<div className="flex items-center justify-between gap-4 rounded-lg border border-border p-3">
<Label htmlFor="label-04-same" className="cursor-pointer">
Billing address same as shipping
</Label>
<Switch
id="label-04-same"
checked={sameAsShipping}
onCheckedChange={setSameAsShipping}
/>
</div>
<div className="group flex flex-col gap-4" data-disabled={sameAsShipping}>
{addressFields.map((field) => (
<div key={field.id} className="flex flex-col gap-2">
<Label htmlFor={field.id}>{field.label}</Label>
<Input
id={field.id}
placeholder={field.placeholder}
disabled={sameAsShipping}
/>
</div>
))}
</div>
</div>
);
}
npx shadcn@latest add @sevenui/component/label-04pnpm dlx shadcn@latest add @sevenui/component/label-04yarn dlx shadcn@latest add @sevenui/component/label-04bunx --bun shadcn@latest add @sevenui/component/label-04"use client";
import * as React from "react";
import { cn } from "cn";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
type Density = "compact" | "default" | "comfortable";
const densities: { value: Density; label: string }[] = [
{ value: "compact", label: "Compact" },
{ value: "default", label: "Default" },
{ value: "comfortable", label: "Comfortable" },
];
const styles: Record<Density, { stack: string; label: string; input: string }> =
{
compact: {
stack: "gap-1",
label: "text-xs",
input: "h-7 text-xs md:text-xs",
},
default: { stack: "gap-2", label: "text-sm", input: "h-8" },
comfortable: {
stack: "gap-2.5",
label: "text-base",
input: "h-10 px-3 text-base md:text-base",
},
};
const fields = [
{ id: "sku", label: "SKU", placeholder: "TEE-BLK-M" },
{
id: "stock",
label: "Units in stock",
placeholder: "148",
inputMode: "numeric" as const,
},
];
export default function Label05() {
const [density, setDensity] = React.useState<Density>("default");
const style = styles[density];
return (
<div className="flex w-full max-w-sm flex-col gap-5">
<ToggleGroup
variant="outline"
size="sm"
spacing={0}
aria-label="Form density"
value={[density]}
onValueChange={(value) => {
const next = value[0] as Density | undefined;
if (next) setDensity(next);
}}
className="w-full"
>
{densities.map((item) => (
<ToggleGroupItem
key={item.value}
value={item.value}
className="flex-1"
>
{item.label}
</ToggleGroupItem>
))}
</ToggleGroup>
<div
className={cn(
"grid grid-cols-2",
density === "compact" ? "gap-2" : "gap-4",
)}
>
{fields.map((field) => (
<div key={field.id} className={cn("flex flex-col", style.stack)}>
<Label htmlFor={`label-05-${field.id}`} className={style.label}>
{field.label}
</Label>
<Input
id={`label-05-${field.id}`}
placeholder={field.placeholder}
inputMode={"inputMode" in field ? field.inputMode : undefined}
className={style.input}
/>
</div>
))}
</div>
</div>
);
}
npx shadcn@latest add @sevenui/component/label-05pnpm dlx shadcn@latest add @sevenui/component/label-05yarn dlx shadcn@latest add @sevenui/component/label-05bunx --bun shadcn@latest add @sevenui/component/label-05Delivery speed
Overnight orders must be placed before 2 pm local time.
"use client";
import { Package, Plane, Truck } from "lucide-react";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
const options = [
{
value: "standard",
icon: Package,
title: "Standard",
detail: "5–7 days",
price: "Free",
},
{
value: "express",
icon: Truck,
title: "Express",
detail: "2–3 days",
price: "$9",
},
{
value: "overnight",
icon: Plane,
title: "Overnight",
detail: "Next day",
price: "$24",
},
];
export default function Label06() {
return (
<div className="flex w-full max-w-sm flex-col gap-3">
<p id="label-06-heading" className="text-sm font-medium">
Delivery speed
</p>
<RadioGroup
aria-labelledby="label-06-heading"
defaultValue="express"
className="grid grid-cols-3 gap-2"
>
{options.map((option) => (
<Label
key={option.value}
className="relative cursor-pointer flex-col items-start gap-3 rounded-lg border border-border bg-card p-3 leading-normal transition-colors hover:bg-muted/50 has-data-checked:border-primary has-data-checked:bg-primary/5 has-[:focus-visible]:ring-3 has-[:focus-visible]:ring-ring/50"
>
<RadioGroupItem
value={option.value}
className="absolute top-3 right-3"
/>
<option.icon
aria-hidden="true"
className="size-5 text-muted-foreground"
/>
<span className="flex flex-col gap-0.5">
<span>{option.title}</span>
<span className="text-xs font-normal text-muted-foreground">
{option.detail}
</span>
</span>
<span className="text-sm font-semibold tabular-nums">
{option.price}
</span>
</Label>
))}
</RadioGroup>
<p className="text-xs text-muted-foreground">
Overnight orders must be placed before 2 pm local time.
</p>
</div>
);
}
npx shadcn@latest add @sevenui/component/label-06pnpm dlx shadcn@latest add @sevenui/component/label-06yarn dlx shadcn@latest add @sevenui/component/label-06bunx --bun shadcn@latest add @sevenui/component/label-06"use client";
import { Check } from "lucide-react";
import * as React from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
const fields = [
{
id: "label-07-email",
label: "Work email",
type: "email",
autoComplete: "email",
defaultValue: "maya@northwind.io",
},
{
id: "label-07-password",
label: "Password",
type: "password",
autoComplete: "current-password",
defaultValue: "",
},
];
export default function Label07() {
const [signedInAs, setSignedInAs] = React.useState<string | null>(null);
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
const data = new FormData(event.currentTarget);
setSignedInAs(String(data.get("label-07-email") ?? ""));
}
return (
<form
className="flex w-full max-w-sm flex-col gap-3"
onSubmit={handleSubmit}
onChange={() => setSignedInAs(null)}
>
{fields.map((field) => (
<div key={field.id} className="relative">
{/* A blank placeholder lets :placeholder-shown detect the empty state. */}
<Input
id={field.id}
name={field.id}
type={field.type}
autoComplete={field.autoComplete}
defaultValue={field.defaultValue}
required
placeholder=" "
className="peer h-12 px-3 pt-5 pb-1.5"
/>
<Label
htmlFor={field.id}
className="pointer-events-none absolute top-2 left-3 origin-left text-xs font-normal text-muted-foreground transition-[top,font-size,color] duration-200 ease-out peer-placeholder-shown:top-4.5 peer-placeholder-shown:text-sm peer-focus:top-2 peer-focus:text-xs peer-focus:text-foreground motion-reduce:transition-none"
>
{field.label}
</Label>
</div>
))}
<Button type="submit" className="mt-1 h-10">
{signedInAs ? (
<>
<Check aria-hidden="true" />
Signed in
</>
) : (
"Sign in"
)}
</Button>
<p
aria-live="polite"
className={
signedInAs ? "text-center text-xs text-muted-foreground" : "sr-only"
}
>
{signedInAs ? `Welcome back, ${signedInAs}.` : null}
</p>
</form>
);
}
npx shadcn@latest add @sevenui/component/label-07pnpm dlx shadcn@latest add @sevenui/component/label-07yarn dlx shadcn@latest add @sevenui/component/label-07bunx --bun shadcn@latest add @sevenui/component/label-07Notifications
Choose where each kind of update reaches you.
| Event | Push | SMS | |
|---|---|---|---|
| MentionsSomeone @mentions you in a comment | |||
| Assigned to youA ticket lands in your queue | |||
| Status changesTickets you follow move columns | |||
| Weekly digestMonday summary of your team |
"use client";
import * as React from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
const channels = ["Email", "Push", "SMS"] as const;
type Channel = (typeof channels)[number];
const events = [
{
id: "mentions",
name: "Mentions",
hint: "Someone @mentions you in a comment",
defaults: ["Email", "Push"],
},
{
id: "assigned",
name: "Assigned to you",
hint: "A ticket lands in your queue",
defaults: ["Email", "Push", "SMS"],
},
{
id: "status",
name: "Status changes",
hint: "Tickets you follow move columns",
defaults: ["Push"],
},
{
id: "digest",
name: "Weekly digest",
hint: "Monday summary of your team",
defaults: ["Email"],
},
] satisfies { id: string; name: string; hint: string; defaults: Channel[] }[];
export default function Label08() {
const [matrix, setMatrix] = React.useState<Record<string, Channel[]>>(() =>
Object.fromEntries(events.map((event) => [event.id, event.defaults])),
);
function toggle(eventId: string, channel: Channel, checked: boolean) {
setMatrix((current) => {
const selected = current[eventId] ?? [];
return {
...current,
[eventId]: checked
? [...selected, channel]
: selected.filter((item) => item !== channel),
};
});
}
return (
<div className="w-full max-w-md rounded-xl border border-border bg-card text-card-foreground">
<div className="flex flex-col gap-1 border-b border-border p-3 sm:p-4">
<h3 className="text-base font-semibold">Notifications</h3>
<p className="text-sm text-muted-foreground">
Choose where each kind of update reaches you.
</p>
</div>
<table className="w-full text-sm">
<thead>
<tr className="border-b border-border">
<th scope="col" className="p-2 pl-3 text-left font-medium text-muted-foreground sm:p-3 sm:pl-4">
<span className="sr-only">Event</span>
</th>
{channels.map((channel) => (
<th
key={channel}
scope="col"
className="w-10 px-1 py-3 text-center text-xs font-medium text-muted-foreground sm:w-16 sm:p-3"
>
{channel}
</th>
))}
</tr>
</thead>
<tbody>
{events.map((event) => (
<tr key={event.id} className="border-b border-border last:border-0">
<th scope="row" className="p-2 pl-3 text-left font-normal wrap-break-word sm:p-3 sm:pl-4">
<span className="block font-medium">{event.name}</span>
<span className="block text-xs text-muted-foreground">
{event.hint}
</span>
</th>
{channels.map((channel) => (
<td key={channel} className="p-0 text-center">
<Label className="flex h-full min-h-14 cursor-pointer items-center justify-center hover:bg-muted/60">
<Checkbox
checked={matrix[event.id]?.includes(channel) ?? false}
onCheckedChange={(checked) =>
toggle(event.id, channel, checked)
}
/>
<span className="sr-only">
{channel} for {event.name}
</span>
</Label>
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}
npx shadcn@latest add @sevenui/component/label-08pnpm dlx shadcn@latest add @sevenui/component/label-08yarn dlx shadcn@latest add @sevenui/component/label-08bunx --bun shadcn@latest add @sevenui/component/label-08Upload files
- q3-board-deck.pdf4.8 MB
- revenue-by-region.csv184 KB
"use client";
import { cn } from "cn";
import { CircleCheck, FileText, UploadCloud, X } from "lucide-react";
import * as React from "react";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import {
NativeSelect,
NativeSelectOption,
} from "@/components/ui/native-select";
import { Spinner } from "@/components/ui/spinner";
type QueuedFile = { id: string; name: string; size: number };
const initialQueue: QueuedFile[] = [
{ id: "seed-1", name: "q3-board-deck.pdf", size: 4_812_000 },
{ id: "seed-2", name: "revenue-by-region.csv", size: 184_300 },
];
const folders = ["Finance / Q3 close", "Finance / Board", "Shared with auditors"];
function formatSize(bytes: number) {
if (bytes >= 1_000_000) return `${(bytes / 1_000_000).toFixed(1)} MB`;
return `${Math.max(1, Math.round(bytes / 1_000))} KB`;
}
export default function Label09() {
const [queue, setQueue] = React.useState<QueuedFile[]>(initialQueue);
const [dragging, setDragging] = React.useState(false);
const [folder, setFolder] = React.useState(folders[0]);
const [uploading, setUploading] = React.useState(false);
const [uploaded, setUploaded] = React.useState<string | null>(null);
const nextId = React.useRef(0);
React.useEffect(() => {
if (!uploading) return;
const timer = window.setTimeout(() => {
setUploaded(
`${queue.length === 1 ? "1 file" : `${queue.length} files`} uploaded to ${folder}.`,
);
setQueue([]);
setUploading(false);
}, 1000);
return () => window.clearTimeout(timer);
}, [uploading, queue.length, folder]);
function addFiles(list: FileList | null) {
if (!list || list.length === 0) return;
setUploaded(null);
const next = Array.from(list, (file) => {
nextId.current += 1;
return { id: `file-${nextId.current}`, name: file.name, size: file.size };
});
setQueue((current) => [...current, ...next]);
}
return (
<div className="flex w-full max-w-md flex-col gap-4 rounded-xl border border-border bg-card p-5 text-card-foreground">
<h3 className="text-base font-semibold">Upload files</h3>
<Label
onDragOver={(event) => {
event.preventDefault();
setDragging(true);
}}
onDragLeave={(event) => {
// Moving over the zone's own children also fires dragleave.
if (event.currentTarget.contains(event.relatedTarget as Node | null))
return;
setDragging(false);
}}
onDrop={(event) => {
event.preventDefault();
setDragging(false);
addFiles(event.dataTransfer.files);
}}
className={cn(
"cursor-pointer flex-col justify-center gap-2 rounded-lg border border-dashed border-border px-4 py-8 text-center transition-colors hover:bg-muted/50 has-[:focus-visible]:border-ring has-[:focus-visible]:ring-3 has-[:focus-visible]:ring-ring/50",
dragging && "border-primary bg-muted",
)}
>
<input
type="file"
multiple
accept=".pdf,.csv,.xlsx"
className="sr-only"
onChange={(event) => {
addFiles(event.target.files);
event.target.value = "";
}}
/>
<span className="flex size-10 items-center justify-center rounded-full bg-muted">
<UploadCloud aria-hidden="true" className="size-5 text-muted-foreground" />
</span>
<span>
{dragging ? "Drop to add files" : "Drop files or browse"}
</span>
<span className="text-xs font-normal text-muted-foreground">
PDF, CSV, or XLSX up to 25 MB each
</span>
</Label>
{queue.length > 0 ? (
<ul aria-label="Files to upload" className="flex flex-col gap-2">
{queue.map((file) => (
<li
key={file.id}
className="flex items-center gap-3 rounded-lg border border-border px-3 py-2"
>
<FileText aria-hidden="true" className="size-4 shrink-0 text-muted-foreground" />
<span className="flex min-w-0 flex-1 flex-col sm:flex-row sm:items-center sm:gap-3">
<span className="min-w-0 flex-1 truncate text-sm">{file.name}</span>
<span className="shrink-0 text-xs text-muted-foreground tabular-nums">
{formatSize(file.size)}
</span>
</span>
<Button
variant="ghost"
size="icon-xs"
aria-label={`Remove ${file.name}`}
disabled={uploading}
onClick={() =>
setQueue((current) => current.filter((item) => item.id !== file.id))
}
>
<X aria-hidden="true" />
</Button>
</li>
))}
</ul>
) : null}
<div className="flex flex-col gap-2">
<Label htmlFor="label-09-folder">Upload to</Label>
<NativeSelect
id="label-09-folder"
value={folder}
onChange={(event) => setFolder(event.target.value)}
disabled={uploading}
className="w-full"
>
{folders.map((folder) => (
<NativeSelectOption key={folder} value={folder}>
{folder}
</NativeSelectOption>
))}
</NativeSelect>
</div>
{uploaded ? (
<p className="flex items-center gap-1.5 text-xs text-success">
<CircleCheck aria-hidden="true" className="size-3.5 shrink-0" />
{uploaded}
</p>
) : null}
<Button
disabled={queue.length === 0 || uploading}
className="w-full"
onClick={() => setUploading(true)}
>
{uploading ? (
<>
<Spinner />
Uploading…
</>
) : queue.length === 1 ? (
"Upload 1 file"
) : (
`Upload ${queue.length} files`
)}
</Button>
<p aria-live="polite" className="sr-only">
{uploaded ?? ""}
</p>
</div>
);
}
npx shadcn@latest add @sevenui/component/label-09pnpm dlx shadcn@latest add @sevenui/component/label-09yarn dlx shadcn@latest add @sevenui/component/label-09bunx --bun shadcn@latest add @sevenui/component/label-09"use client";
import { Check, CircleCheck } from "lucide-react";
import * as React from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Progress,
ProgressLabel,
ProgressValue,
} from "@/components/ui/progress";
const maxNameLength = 40;
const useCases = [
"Product roadmap",
"Bug tracking",
"Sprint planning",
"Customer feedback",
"Design reviews",
"Hiring pipeline",
];
const teamSizes = ["Just me", "2–10", "11–50", "51+"];
export default function Label10() {
const [name, setName] = React.useState("Northwind Product");
const [picked, setPicked] = React.useState<string[]>(["Bug tracking"]);
const [teamSize, setTeamSize] = React.useState("2–10");
const [step, setStep] = React.useState<2 | 3>(2);
const trimmed = name.trim();
const canContinue = trimmed.length > 0 && picked.length > 0;
function togglePick(useCase: string) {
setPicked((current) =>
current.includes(useCase)
? current.filter((item) => item !== useCase)
: [...current, useCase],
);
}
return (
<form
onSubmit={(event) => {
event.preventDefault();
if (canContinue) setStep(3);
}}
className="flex w-full max-w-md flex-col gap-6 rounded-xl border border-border bg-card p-5 text-card-foreground"
>
<Progress value={step === 2 ? 66 : 100} className="gap-2">
<ProgressLabel className="text-xs text-muted-foreground">
Step {step} of 3
</ProgressLabel>
<ProgressValue className="text-xs" />
</Progress>
{step === 2 ? (
<>
<div className="flex flex-col gap-1">
<h3 className="text-lg font-semibold text-balance">
Set up your workspace
</h3>
<p className="text-sm text-muted-foreground">
We will tailor templates and views to how your team works.
</p>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="label-10-name" className="justify-between">
Workspace name
<span
aria-hidden="true"
className="text-xs font-normal text-muted-foreground tabular-nums"
>
{name.length}/{maxNameLength}
</span>
</Label>
<Input
id="label-10-name"
value={name}
maxLength={maxNameLength}
autoComplete="organization"
aria-describedby="label-10-name-hint"
onChange={(event) => setName(event.target.value)}
/>
<p id="label-10-name-hint" className="text-xs text-muted-foreground">
Shown in the sidebar and on invite emails. You can change it later.
</p>
</div>
<fieldset className="flex flex-col">
<legend className="mb-3 text-sm font-medium">
What will you use it for?
<span className="ml-1.5 font-normal text-muted-foreground">
Pick any
</span>
</legend>
<div className="flex flex-wrap gap-2">
{useCases.map((useCase) => {
const checked = picked.includes(useCase);
return (
<Label
key={useCase}
className="h-8 cursor-pointer gap-1.5 rounded-full border border-border px-3 font-normal transition-colors hover:bg-muted has-checked:border-primary has-checked:bg-primary has-checked:text-primary-foreground has-[:focus-visible]:ring-3 has-[:focus-visible]:ring-ring/50"
>
<input
type="checkbox"
className="sr-only"
checked={checked}
onChange={() => togglePick(useCase)}
/>
{checked ? <Check aria-hidden="true" className="size-3.5" /> : null}
{useCase}
</Label>
);
})}
</div>
</fieldset>
<fieldset className="flex flex-col">
<legend className="mb-3 text-sm font-medium">Team size</legend>
<div className="grid grid-cols-4 rounded-lg bg-muted p-1">
{teamSizes.map((size) => (
<Label
key={size}
className="h-8 cursor-pointer justify-center rounded-md px-1 text-xs whitespace-nowrap text-muted-foreground transition-colors hover:text-foreground has-checked:bg-background has-checked:text-foreground has-checked:shadow-sm has-[:focus-visible]:ring-3 has-[:focus-visible]:ring-ring/50"
>
<input
type="radio"
name="label-10-team-size"
value={size}
className="sr-only"
checked={teamSize === size}
onChange={() => setTeamSize(size)}
/>
{size}
</Label>
))}
</div>
</fieldset>
</>
) : (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-1">
<h3 className="flex items-center gap-2 text-lg font-semibold text-balance">
<CircleCheck aria-hidden="true" className="size-5 text-success" />
{trimmed} is ready
</h3>
<p className="text-sm text-muted-foreground">
Review your answers. Go back to change anything.
</p>
</div>
<dl className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 rounded-lg border border-border p-3 text-sm">
<dt className="text-muted-foreground">Use cases</dt>
<dd>{picked.join(", ")}</dd>
<dt className="text-muted-foreground">Team size</dt>
<dd>{teamSize}</dd>
</dl>
</div>
)}
<div className="flex items-center justify-between gap-3">
<Button
type="button"
variant="ghost"
disabled={step === 2}
onClick={() => setStep(2)}
>
Back
</Button>
{step === 2 ? (
<Button type="submit" disabled={!canContinue}>
Continue
</Button>
) : null}
</div>
</form>
);
}
npx shadcn@latest add @sevenui/component/label-10pnpm dlx shadcn@latest add @sevenui/component/label-10yarn dlx shadcn@latest add @sevenui/component/label-10bunx --bun shadcn@latest add @sevenui/component/label-10