Toggle Group
Free, copy-and-go Toggle Group components built on the SevenUI Toggle Group primitive.Read the primitive docs.
Interface theme
Follows your operating system and switches automatically at sunset.
"use client";
import * as React from "react";
import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
const themes = [
{
value: "light",
label: "Light",
icon: SunIcon,
hint: "Always use the light interface, even at night.",
},
{
value: "dark",
label: "Dark",
icon: MoonIcon,
hint: "Always use the dark interface, easier on the eyes in low light.",
},
{
value: "system",
label: "System",
icon: MonitorIcon,
hint: "Follows your operating system and switches automatically at sunset.",
},
];
export default function ToggleGroup01() {
const [theme, setTheme] = React.useState("system");
const active = themes.find((item) => item.value === theme) ?? themes[2];
return (
<div className="flex w-full max-w-sm flex-col gap-2">
<span id="interface-theme-label" className="text-sm font-medium">
Interface theme
</span>
<ToggleGroup
aria-labelledby="interface-theme-label"
variant="outline"
spacing={0}
value={[theme]}
onValueChange={(next) => {
// Keep exactly one segment selected: ignore attempts to clear it.
if (next.length > 0) setTheme(next[0]);
}}
className="w-full"
>
{themes.map((item) => (
<ToggleGroupItem
key={item.value}
value={item.value}
className="flex-1 aria-pressed:bg-accent aria-pressed:text-accent-foreground"
>
<item.icon aria-hidden="true" />
{item.label}
</ToggleGroupItem>
))}
</ToggleGroup>
<p className="text-sm text-muted-foreground" aria-live="polite">
{active.hint}
</p>
</div>
);
}
npx shadcn@latest add @sevenui/component/toggle-group-01pnpm dlx shadcn@latest add @sevenui/component/toggle-group-01yarn dlx shadcn@latest add @sevenui/component/toggle-group-01bunx --bun shadcn@latest add @sevenui/component/toggle-group-01SmallDense dashboard widgets
DefaultReport toolbars
LargeTouch and kiosk screens
"use client";
import { ChartAreaIcon, ChartColumnIcon, ChartLineIcon } from "lucide-react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
const sizes = [
{ size: "sm", label: "Small", hint: "Dense dashboard widgets" },
{ size: "default", label: "Default", hint: "Report toolbars" },
{ size: "lg", label: "Large", hint: "Touch and kiosk screens" },
] as const;
const charts = [
{ value: "line", label: "Line", icon: ChartLineIcon },
{ value: "bar", label: "Bar", icon: ChartColumnIcon },
{ value: "area", label: "Area", icon: ChartAreaIcon },
];
export default function ToggleGroup02() {
return (
<div className="flex w-full max-w-md flex-col gap-5">
{sizes.map((row) => (
<div
key={row.size}
className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"
>
<div className="flex flex-col">
<span className="text-sm font-medium">{row.label}</span>
<span className="text-xs text-muted-foreground">{row.hint}</span>
</div>
<ToggleGroup
aria-label={`Chart type, ${row.label.toLowerCase()} size`}
size={row.size}
spacing={0.5}
defaultValue={["bar"]}
className="rounded-lg bg-muted p-0.5"
>
{charts.map((chart) => (
<ToggleGroupItem
key={chart.value}
value={chart.value}
className="text-muted-foreground hover:bg-transparent hover:text-foreground aria-pressed:bg-background aria-pressed:text-foreground aria-pressed:shadow-sm dark:aria-pressed:bg-input/50"
>
<chart.icon aria-hidden="true" />
{chart.label}
</ToggleGroupItem>
))}
</ToggleGroup>
</div>
))}
</div>
);
}
npx shadcn@latest add @sevenui/component/toggle-group-02pnpm dlx shadcn@latest add @sevenui/component/toggle-group-02yarn dlx shadcn@latest add @sevenui/component/toggle-group-02bunx --bun shadcn@latest add @sevenui/component/toggle-group-02Filter by label
Showing 15 of 30 open issues.
"use client";
import * as React from "react";
import { CheckIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
const labels = [
{ value: "bug", label: "Bug", count: 12 },
{ value: "feature", label: "Feature", count: 8 },
{ value: "performance", label: "Performance", count: 5 },
{ value: "docs", label: "Docs", count: 3 },
{ value: "security", label: "Security", count: 2 },
];
export default function ToggleGroup03() {
const [selected, setSelected] = React.useState<string[]>(["bug", "docs"]);
const total = labels
.filter((item) => selected.includes(item.value))
.reduce((sum, item) => sum + item.count, 0);
return (
<div className="flex w-full max-w-md flex-col gap-3">
<div className="flex items-center justify-between gap-2">
<span id="label-filter" className="text-sm font-medium">
Filter by label
</span>
<Button
variant="ghost"
size="sm"
disabled={selected.length === 0}
onClick={() => setSelected([])}
>
Clear
</Button>
</div>
<ToggleGroup
aria-labelledby="label-filter"
multiple
variant="outline"
size="sm"
value={selected}
onValueChange={setSelected}
className="w-full flex-wrap"
>
{labels.map((item) => (
<ToggleGroupItem
key={item.value}
value={item.value}
className="rounded-full px-3 aria-pressed:border-primary aria-pressed:bg-primary/10 aria-pressed:text-foreground"
>
<CheckIcon
aria-hidden="true"
className="-ml-0.5 hidden group-aria-pressed/toggle:block"
/>
{item.label}
<span className="text-muted-foreground tabular-nums">
{item.count}
</span>
</ToggleGroupItem>
))}
</ToggleGroup>
<p className="text-sm text-muted-foreground" aria-live="polite">
{selected.length === 0
? "Showing all 30 open issues."
: `Showing ${total} of 30 open issues.`}
</p>
</div>
);
}
npx shadcn@latest add @sevenui/component/toggle-group-03pnpm dlx shadcn@latest add @sevenui/component/toggle-group-03yarn dlx shadcn@latest add @sevenui/component/toggle-group-03bunx --bun shadcn@latest add @sevenui/component/toggle-group-03Default role for new membersOwner can only be granted from member settings.
Guest link accessSet by your organization admin for all workspaces.
Managed"use client";
import * as React from "react";
import { CheckIcon, LockIcon } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Spinner } from "@/components/ui/spinner";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
const roles = [
{ value: "viewer", label: "Viewer" },
{ value: "commenter", label: "Commenter" },
{ value: "editor", label: "Editor" },
];
const guestAccess = [
{ value: "none", label: "No access" },
{ value: "view", label: "View" },
{ value: "comment", label: "Comment" },
];
type Status = "idle" | "saving" | "saved";
export default function ToggleGroup04() {
const [role, setRole] = React.useState("commenter");
const [status, setStatus] = React.useState<Status>("idle");
const timeout = React.useRef<ReturnType<typeof setTimeout> | null>(null);
React.useEffect(() => {
return () => {
if (timeout.current) clearTimeout(timeout.current);
};
}, []);
function handleChange(next: string[]) {
if (next.length === 0) return;
setRole(next[0]);
setStatus("saving");
if (timeout.current) clearTimeout(timeout.current);
// Simulate a request to persist the new default role.
timeout.current = setTimeout(() => setStatus("saved"), 900);
}
return (
<div className="flex w-full max-w-md flex-col divide-y divide-border rounded-xl border border-border bg-card text-card-foreground">
<div className="flex flex-col gap-3 p-4">
<div className="flex items-start justify-between gap-3">
<div className="flex flex-col gap-0.5">
<span id="default-role" className="text-sm font-medium">
Default role for new members
</span>
<span className="text-xs text-muted-foreground">
Owner can only be granted from member settings.
</span>
</div>
<span
className="flex h-5 shrink-0 items-center gap-1.5 text-xs text-muted-foreground"
aria-live="polite"
>
{status === "saving" && (
<>
<Spinner className="size-3.5" aria-hidden="true" />
Saving
</>
)}
{status === "saved" && (
<>
<CheckIcon
className="size-3.5 text-success"
aria-hidden="true"
/>
Saved
</>
)}
</span>
</div>
<ToggleGroup
aria-labelledby="default-role"
variant="outline"
size="sm"
spacing={0}
value={[role]}
onValueChange={handleChange}
aria-busy={status === "saving"}
className="w-full"
>
{roles.map((item) => (
<ToggleGroupItem
key={item.value}
value={item.value}
className="flex-1 max-sm:flex-auto max-sm:px-1 aria-pressed:bg-accent aria-pressed:text-accent-foreground"
>
{item.label}
</ToggleGroupItem>
))}
<ToggleGroupItem
value="owner"
disabled
className="flex-1 max-sm:flex-auto max-sm:px-1"
>
<LockIcon aria-hidden="true" />
Owner
</ToggleGroupItem>
</ToggleGroup>
</div>
<div className="flex flex-col gap-3 p-4">
<div className="flex items-start justify-between gap-3">
<div className="flex flex-col gap-0.5">
<span id="guest-access" className="text-sm font-medium">
Guest link access
</span>
<span className="text-xs text-muted-foreground">
Set by your organization admin for all workspaces.
</span>
</div>
<Badge variant="secondary" className="shrink-0 gap-1">
<LockIcon aria-hidden="true" />
Managed
</Badge>
</div>
<ToggleGroup
aria-labelledby="guest-access"
variant="outline"
size="sm"
spacing={0}
defaultValue={["view"]}
disabled
className="w-full"
>
{guestAccess.map((item) => (
<ToggleGroupItem
key={item.value}
value={item.value}
className="flex-1 aria-pressed:bg-accent aria-pressed:text-accent-foreground"
>
{item.label}
</ToggleGroupItem>
))}
</ToggleGroup>
</div>
</div>
);
}
npx shadcn@latest add @sevenui/component/toggle-group-04pnpm dlx shadcn@latest add @sevenui/component/toggle-group-04yarn dlx shadcn@latest add @sevenui/component/toggle-group-04bunx --bun shadcn@latest add @sevenui/component/toggle-group-04Assignees
14 tasks assigned to Maya and Theo.
"use client";
import * as React from "react";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
const people = [
{ value: "maya", name: "Maya Patel", initials: "MP", tasks: 9 },
{ value: "theo", name: "Theo Laurent", initials: "TL", tasks: 5 },
{ value: "ines", name: "Ines Moreau", initials: "IM", tasks: 7 },
{ value: "kofi", name: "Kofi Mensah", initials: "KM", tasks: 3 },
{ value: "sara", name: "Sara Lindqvist", initials: "SL", tasks: 6 },
];
function joinNames(names: string[]) {
if (names.length <= 1) return names.join("");
return `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
}
export default function ToggleGroup05() {
const [selected, setSelected] = React.useState<string[]>(["maya", "theo"]);
const chosen = people.filter((person) => selected.includes(person.value));
const total = chosen.reduce((sum, person) => sum + person.tasks, 0);
return (
<div className="flex w-full max-w-sm flex-col gap-3">
<div className="flex items-center justify-between gap-2">
<span id="assignee-filter" className="text-sm font-medium">
Assignees
</span>
<Button
variant="link"
size="sm"
className="h-auto px-0"
onClick={() =>
setSelected(
selected.length === people.length
? []
: people.map((person) => person.value),
)
}
>
{selected.length === people.length ? "Clear all" : "Select all"}
</Button>
</div>
<ToggleGroup
aria-labelledby="assignee-filter"
multiple
spacing={1.5}
value={selected}
onValueChange={setSelected}
className="flex-wrap"
>
{people.map((person) => (
<ToggleGroupItem
key={person.value}
value={person.value}
aria-label={person.name}
title={person.name}
className="relative size-auto min-w-0 rounded-full p-0.5 hover:bg-transparent aria-pressed:bg-transparent"
>
<Avatar
size="lg"
className="opacity-50 grayscale transition-[opacity,filter] duration-150 group-hover/toggle:opacity-80 group-aria-pressed/toggle:opacity-100 group-aria-pressed/toggle:grayscale-0 motion-reduce:transition-none"
>
<AvatarFallback className="bg-secondary font-medium text-secondary-foreground">
{person.initials}
</AvatarFallback>
</Avatar>
<span
aria-hidden="true"
className="pointer-events-none absolute inset-0 rounded-full ring-2 ring-transparent transition-[box-shadow] duration-150 group-aria-pressed/toggle:ring-primary motion-reduce:transition-none"
/>
</ToggleGroupItem>
))}
</ToggleGroup>
<p className="text-sm text-muted-foreground" aria-live="polite">
{chosen.length === 0
? "No assignees selected. Showing unassigned tasks only."
: `${total} tasks assigned to ${joinNames(
chosen.map((person) => person.name.split(" ")[0]),
)}.`}
</p>
</div>
);
}
npx shadcn@latest add @sevenui/component/toggle-group-05pnpm dlx shadcn@latest add @sevenui/component/toggle-group-05yarn dlx shadcn@latest add @sevenui/component/toggle-group-05bunx --bun shadcn@latest add @sevenui/component/toggle-group-05Press Ctrl + B, I, or U while typing to toggle styles.
"use client";
import * as React from "react";
import {
AlignCenterIcon,
AlignLeftIcon,
AlignRightIcon,
BoldIcon,
ItalicIcon,
StrikethroughIcon,
UnderlineIcon,
} from "lucide-react";
import { cn } from "cn";
import { Kbd, KbdGroup } from "@/components/ui/kbd";
import { Separator } from "@/components/ui/separator";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
const marks = [
{ value: "bold", label: "Bold", key: "B", icon: BoldIcon },
{ value: "italic", label: "Italic", key: "I", icon: ItalicIcon },
{ value: "underline", label: "Underline", key: "U", icon: UnderlineIcon },
{
value: "strike",
label: "Strikethrough",
key: "X",
shift: true,
icon: StrikethroughIcon,
},
];
const alignments = [
{ value: "left", label: "Align left", icon: AlignLeftIcon },
{ value: "center", label: "Align center", icon: AlignCenterIcon },
{ value: "right", label: "Align right", icon: AlignRightIcon },
];
const alignClass: Record<string, string> = {
left: "text-left",
center: "text-center",
right: "text-right",
};
export default function ToggleGroup06() {
const [formats, setFormats] = React.useState<string[]>(["bold"]);
const [align, setAlign] = React.useState("left");
function toggleFormat(value: string) {
setFormats((current) =>
current.includes(value)
? current.filter((item) => item !== value)
: [...current, value],
);
}
function handleKeyDown(event: React.KeyboardEvent<HTMLTextAreaElement>) {
if (!(event.metaKey || event.ctrlKey)) return;
const mark = marks.find(
(item) =>
item.key.toLowerCase() === event.key.toLowerCase() &&
Boolean(item.shift) === event.shiftKey,
);
if (!mark) return;
event.preventDefault();
toggleFormat(mark.value);
}
return (
<TooltipProvider>
<div className="w-full max-w-md overflow-hidden rounded-xl border border-border bg-card text-card-foreground shadow-xs">
<div
role="toolbar"
aria-label="Text formatting"
className="flex flex-wrap items-center gap-1 border-b border-border bg-muted/40 p-1.5"
>
<ToggleGroup
aria-label="Text style"
multiple
size="sm"
spacing={0.5}
value={formats}
onValueChange={setFormats}
>
{marks.map((mark) => (
<Tooltip key={mark.value}>
<TooltipTrigger
render={
<ToggleGroupItem
value={mark.value}
aria-label={mark.label}
className="max-sm:px-2"
>
<mark.icon aria-hidden="true" />
</ToggleGroupItem>
}
/>
<TooltipContent>
{mark.label}{" "}
<KbdGroup>
<Kbd>Ctrl</Kbd>
{mark.shift && <Kbd>Shift</Kbd>}
<Kbd>{mark.key}</Kbd>
</KbdGroup>
</TooltipContent>
</Tooltip>
))}
</ToggleGroup>
<Separator orientation="vertical" className="mx-1 my-1" />
<ToggleGroup
aria-label="Text alignment"
size="sm"
spacing={0.5}
value={[align]}
onValueChange={(next) => {
if (next.length > 0) setAlign(next[0]);
}}
>
{alignments.map((item) => (
<Tooltip key={item.value}>
<TooltipTrigger
render={
<ToggleGroupItem
value={item.value}
aria-label={item.label}
className="max-sm:px-2"
>
<item.icon aria-hidden="true" />
</ToggleGroupItem>
}
/>
<TooltipContent>{item.label}</TooltipContent>
</Tooltip>
))}
</ToggleGroup>
</div>
<label htmlFor="release-note" className="sr-only">
Release note
</label>
<textarea
id="release-note"
rows={4}
onKeyDown={handleKeyDown}
defaultValue="Dark mode now follows your system setting. Toggle it anytime from Settings, then Appearance."
className={cn(
"block w-full resize-none bg-transparent px-4 py-3 text-sm leading-relaxed outline-none placeholder:text-muted-foreground focus-visible:bg-muted/20",
alignClass[align],
formats.includes("bold") && "font-semibold",
formats.includes("italic") && "italic",
formats.includes("underline") && "underline underline-offset-4",
formats.includes("strike") && "line-through",
formats.includes("underline") &&
formats.includes("strike") &&
"[text-decoration-line:underline_line-through]",
)}
/>
<p className="border-t border-border px-4 py-2 text-xs text-muted-foreground">
Press <Kbd>Ctrl</Kbd> + <Kbd>B</Kbd>, <Kbd>I</Kbd>, or <Kbd>U</Kbd>{" "}
while typing to toggle styles.
</p>
</div>
</TooltipProvider>
);
}
npx shadcn@latest add @sevenui/component/toggle-group-06pnpm dlx shadcn@latest add @sevenui/component/toggle-group-06yarn dlx shadcn@latest add @sevenui/component/toggle-group-06bunx --bun shadcn@latest add @sevenui/component/toggle-group-06"use client";
import * as React from "react";
import { Check } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
type Cycle = "monthly" | "yearly";
const pricing: Record<Cycle, { perSeat: number; note: string }> = {
monthly: { perSeat: 18, note: "Billed monthly. Cancel anytime." },
yearly: { perSeat: 14, note: "Billed $168 per seat once a year." },
};
const features = [
"Unlimited projects and guests",
"Version history for 365 days",
"SAML single sign-on",
];
export default function ToggleGroup07() {
const [cycle, setCycle] = React.useState<Cycle>("yearly");
const plan = pricing[cycle];
return (
<Card className="w-full max-w-sm">
<CardHeader>
<CardTitle>Team plan</CardTitle>
<CardDescription>For growing teams shipping every week.</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-5">
<ToggleGroup
aria-label="Billing cycle"
variant="outline"
spacing={0}
value={[cycle]}
onValueChange={(next) => {
if (next[0]) setCycle(next[0] as Cycle);
}}
className="w-full"
>
<ToggleGroupItem value="monthly" className="flex-1">
Monthly
</ToggleGroupItem>
<ToggleGroupItem value="yearly" className="flex-1 gap-2">
Yearly
<Badge variant="secondary" className="h-4 px-1.5 text-[0.65rem]">
Save 22%
</Badge>
</ToggleGroupItem>
</ToggleGroup>
<div aria-live="polite" className="flex flex-col gap-1">
<p className="flex items-baseline gap-1">
<span className="text-4xl font-semibold tracking-tight tabular-nums">
${plan.perSeat}
</span>
<span className="text-sm text-muted-foreground">
per seat / month
</span>
</p>
<p className="text-xs text-muted-foreground">{plan.note}</p>
</div>
<ul className="flex flex-col gap-2 text-sm">
{features.map((feature) => (
<li key={feature} className="flex items-center gap-2">
<Check className="size-4 text-primary" aria-hidden="true" />
{feature}
</li>
))}
</ul>
</CardContent>
<CardFooter>
<Button className="w-full">
{cycle === "yearly" ? "Start yearly plan" : "Start monthly plan"}
</Button>
</CardFooter>
</Card>
);
}
npx shadcn@latest add @sevenui/component/toggle-group-07pnpm dlx shadcn@latest add @sevenui/component/toggle-group-07yarn dlx shadcn@latest add @sevenui/component/toggle-group-07bunx --bun shadcn@latest add @sevenui/component/toggle-group-07"use client";
import * as React from "react";
import { Bell, Mail, MessageSquare } from "lucide-react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
const channels = [
{ value: "email", label: "Email", icon: Mail },
{ value: "push", label: "Push", icon: Bell },
{ value: "slack", label: "Slack", icon: MessageSquare },
];
const events = [
{
id: "mentions",
title: "Mentions",
description: "Someone @mentions you in a comment.",
defaults: ["email", "push", "slack"],
},
{
id: "assigned",
title: "Assigned to you",
description: "An issue or review lands on your plate.",
defaults: ["push", "slack"],
},
{
id: "deploys",
title: "Failed deploys",
description: "A production deploy you triggered fails.",
defaults: ["email", "push"],
},
{
id: "digest",
title: "Weekly digest",
description: "A Monday summary of activity in your projects.",
defaults: ["email"],
},
];
export default function ToggleGroup08() {
const [prefs, setPrefs] = React.useState<Record<string, string[]>>(() =>
Object.fromEntries(events.map((event) => [event.id, event.defaults])),
);
return (
<Card className="w-full max-w-lg">
<CardHeader>
<CardTitle>Notifications</CardTitle>
<CardDescription>
Pick where each kind of update reaches you.
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col divide-y divide-border">
{events.map((event) => {
const selected = prefs[event.id] ?? [];
return (
<div
key={event.id}
className="flex flex-col gap-3 py-3 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between"
>
<div className="flex min-w-0 flex-col gap-0.5">
<span id={`toggle-group-08-${event.id}`} className="text-sm font-medium">
{event.title}
</span>
<span className="text-xs text-muted-foreground">
{selected.length === 0 ? "Muted. " : ""}
{event.description}
</span>
</div>
<ToggleGroup
multiple
size="sm"
variant="outline"
spacing={1}
className="shrink-0"
aria-labelledby={`toggle-group-08-${event.id}`}
value={selected}
onValueChange={(next) =>
setPrefs((current) => ({ ...current, [event.id]: next }))
}
>
{channels.map((channel) => (
<ToggleGroupItem
key={channel.value}
value={channel.value}
className="aria-pressed:border-primary/40 aria-pressed:bg-primary/10 aria-pressed:text-primary"
>
<channel.icon aria-hidden="true" />
{channel.label}
</ToggleGroupItem>
))}
</ToggleGroup>
</div>
);
})}
</CardContent>
</Card>
);
}
npx shadcn@latest add @sevenui/component/toggle-group-08pnpm dlx shadcn@latest add @sevenui/component/toggle-group-08yarn dlx shadcn@latest add @sevenui/component/toggle-group-08bunx --bun shadcn@latest add @sevenui/component/toggle-group-08"use client";
import * as React from "react";
import { TrendingDown, TrendingUp } from "lucide-react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
type Range = "24h" | "7d" | "30d" | "90d";
const ranges: Record<
Range,
{ label: string; revenue: string; change: number; series: number[]; axis: [string, string] }
> = {
"24h": {
label: "last 24 hours",
revenue: "$4,218",
change: 6.2,
series: [22, 18, 12, 9, 14, 28, 41, 52, 48, 57, 63, 44],
axis: ["00:00", "22:00"],
},
"7d": {
label: "last 7 days",
revenue: "$31,940",
change: 12.4,
series: [38, 46, 51, 44, 62, 29, 24],
axis: ["Mon", "Sun"],
},
"30d": {
label: "last 30 days",
revenue: "$128,502",
change: -3.1,
series: [48, 52, 61, 58, 44, 40, 55, 63, 57, 49, 46, 42, 51, 60, 54],
axis: ["Aug 27", "Sep 25"],
},
"90d": {
label: "last 90 days",
revenue: "$402,117",
change: 18.9,
series: [28, 31, 35, 33, 40, 44, 47, 45, 52, 58, 61, 66, 70],
axis: ["Jun 27", "Sep 25"],
},
};
export default function ToggleGroup09() {
const [range, setRange] = React.useState<Range>("7d");
const data = ranges[range];
const max = Math.max(...data.series);
const up = data.change >= 0;
const Trend = up ? TrendingUp : TrendingDown;
return (
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>Net revenue</CardTitle>
<CardDescription>After refunds and payment fees.</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-5">
<ToggleGroup
aria-label="Date range"
size="sm"
spacing={0}
value={[range]}
onValueChange={(next) => {
if (next[0]) setRange(next[0] as Range);
}}
className="w-full rounded-lg bg-muted p-0.5"
>
{(Object.keys(ranges) as Range[]).map((key) => (
<ToggleGroupItem
key={key}
value={key}
className="flex-1 rounded-md! text-muted-foreground aria-pressed:bg-background aria-pressed:text-foreground aria-pressed:shadow-sm"
>
{key}
</ToggleGroupItem>
))}
</ToggleGroup>
<div aria-live="polite" className="flex flex-wrap items-end justify-between gap-2">
<div className="flex flex-col">
<span className="text-3xl font-semibold tracking-tight tabular-nums">
{data.revenue}
</span>
<span className="text-xs text-muted-foreground">{data.label}</span>
</div>
<span
className={`inline-flex items-center gap-1 text-sm font-medium tabular-nums ${
up ? "text-success" : "text-destructive"
}`}
>
<Trend className="size-4" aria-hidden="true" />
{up ? "+" : ""}
{data.change}%
<span className="sr-only">versus the previous period</span>
</span>
</div>
<div className="flex flex-col gap-2">
<div
role="img"
aria-label={`Revenue trend for the ${data.label}, ${up ? "up" : "down"} ${Math.abs(data.change)}% overall.`}
className="flex h-28 items-end gap-1"
>
{data.series.map((point, index) => (
<div
// biome-ignore lint/suspicious/noArrayIndexKey: static series keyed by position
key={index}
className="flex-1 rounded-t-sm bg-chart-1 transition-[height] duration-300 ease-out motion-reduce:transition-none"
style={{ height: `${(point / max) * 100}%` }}
/>
))}
</div>
<div className="flex justify-between text-xs text-muted-foreground">
<span>{data.axis[0]}</span>
<span>{data.axis[1]}</span>
</div>
</div>
</CardContent>
</Card>
);
}
npx shadcn@latest add @sevenui/component/toggle-group-09pnpm dlx shadcn@latest add @sevenui/component/toggle-group-09yarn dlx shadcn@latest add @sevenui/component/toggle-group-09bunx --bun shadcn@latest add @sevenui/component/toggle-group-09Ridgeline Trail Runner
Men's · Slate grey
$148.00
Width
Size (US)
Select a size to see delivery time.
"use client";
import * as React from "react";
import { Check, ShoppingBag } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Popover,
PopoverContent,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
} from "@/components/ui/popover";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
const widths = [
{ value: "standard", label: "Standard" },
{ value: "wide", label: "Wide (2E)" },
];
const sizes = [
{ value: "7", stock: 4 },
{ value: "7.5", stock: 0 },
{ value: "8", stock: 12 },
{ value: "8.5", stock: 9 },
{ value: "9", stock: 2 },
{ value: "9.5", stock: 15 },
{ value: "10", stock: 7 },
{ value: "10.5", stock: 0 },
{ value: "11", stock: 5 },
{ value: "12", stock: 1 },
];
const sizeChart = [
{ us: "7", eu: "40", cm: "25" },
{ us: "8", eu: "41", cm: "26" },
{ us: "9", eu: "42.5", cm: "27" },
{ us: "10", eu: "44", cm: "28" },
{ us: "11", eu: "45", cm: "29" },
{ us: "12", eu: "46", cm: "30" },
];
export default function ToggleGroup10() {
const [width, setWidth] = React.useState("standard");
const [size, setSize] = React.useState<string[]>([]);
const [added, setAdded] = React.useState(false);
React.useEffect(() => {
if (!added) return;
const timeout = window.setTimeout(() => setAdded(false), 2000);
return () => window.clearTimeout(timeout);
}, [added]);
const picked = sizes.find((entry) => entry.value === size[0]);
return (
<div className="flex w-full max-w-sm flex-col gap-5">
<div className="flex gap-4">
<img
src="/placeholder.svg"
alt="Trail runner in slate grey, side view"
className="size-20 shrink-0 rounded-lg border border-border bg-muted object-cover"
/>
<div className="flex flex-col gap-1">
<h3 className="font-medium">Ridgeline Trail Runner</h3>
<p className="text-sm text-muted-foreground">Men's · Slate grey</p>
<p className="text-sm font-medium tabular-nums">$148.00</p>
</div>
</div>
<div className="flex flex-col gap-2">
<span id="toggle-group-10-width" className="text-sm font-medium">
Width
</span>
<ToggleGroup
aria-labelledby="toggle-group-10-width"
variant="outline"
value={[width]}
onValueChange={(next) => {
if (next[0]) setWidth(next[0]);
}}
className="w-full"
>
{widths.map((option) => (
<ToggleGroupItem
key={option.value}
value={option.value}
className="flex-1 aria-pressed:border-foreground aria-pressed:bg-transparent"
>
{option.label}
</ToggleGroupItem>
))}
</ToggleGroup>
</div>
<div className="flex flex-col gap-2">
<div className="flex items-baseline justify-between">
<span id="toggle-group-10-size" className="text-sm font-medium">
Size (US)
</span>
<Popover>
<PopoverTrigger className="text-xs text-muted-foreground underline underline-offset-4 hover:text-foreground focus-visible:rounded-sm focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-none">
Size guide
</PopoverTrigger>
<PopoverContent align="end" className="w-64">
<PopoverHeader>
<PopoverTitle>Size guide</PopoverTitle>
<PopoverDescription className="text-xs">
Measure your foot heel to toe. Runs true to size.
</PopoverDescription>
</PopoverHeader>
<table className="w-full text-xs tabular-nums">
<thead>
<tr className="text-left text-muted-foreground">
<th className="py-1 font-medium">US</th>
<th className="py-1 font-medium">EU</th>
<th className="py-1 font-medium">Foot (cm)</th>
</tr>
</thead>
<tbody>
{sizeChart.map((row) => (
<tr key={row.us} className="border-t border-border">
<td className="py-1">{row.us}</td>
<td className="py-1">{row.eu}</td>
<td className="py-1">{row.cm}</td>
</tr>
))}
</tbody>
</table>
</PopoverContent>
</Popover>
</div>
<ToggleGroup
aria-labelledby="toggle-group-10-size"
variant="outline"
value={size}
onValueChange={(next) => {
setSize(next);
setAdded(false);
}}
className="grid w-full grid-cols-5"
>
{sizes.map((option) => (
<ToggleGroupItem
key={option.value}
value={option.value}
disabled={option.stock === 0}
aria-label={
option.stock === 0
? `Size ${option.value}, sold out`
: `Size ${option.value}`
}
className="tabular-nums aria-pressed:border-foreground aria-pressed:bg-foreground aria-pressed:text-background disabled:line-through data-disabled:line-through"
>
{option.value}
</ToggleGroupItem>
))}
</ToggleGroup>
<p aria-live="polite" className="min-h-4 text-xs text-muted-foreground">
{picked
? picked.stock <= 2
? `Only ${picked.stock} left in size ${picked.value}. Order soon.`
: `Size ${picked.value} ships in 1-2 business days.`
: "Select a size to see delivery time."}
</p>
</div>
<Button
size="lg"
className="w-full"
disabled={!picked}
onClick={() => setAdded(true)}
>
{added ? (
<Check aria-hidden="true" />
) : (
<ShoppingBag aria-hidden="true" />
)}
{added ? "Added to bag" : "Add to bag"}
</Button>
<span aria-live="polite" className="sr-only">
{added && picked ? `Size ${picked.value} added to your bag.` : ""}
</span>
</div>
);
}
npx shadcn@latest add @sevenui/component/toggle-group-10pnpm dlx shadcn@latest add @sevenui/component/toggle-group-10yarn dlx shadcn@latest add @sevenui/component/toggle-group-10bunx --bun shadcn@latest add @sevenui/component/toggle-group-10Shared with marketing
6 of 6 files
Q3 board deck.pdf
4.2 MB
Launch hero.png
1.8 MB
Onboarding walkthrough.mp4
86 MB
Hiring plan 2027.xlsx
312 KB
Brand guidelines.pdf
9.6 MB
Team offsite.jpg
3.1 MB
"use client";
import * as React from "react";
import {
FileImage,
FileSpreadsheet,
FileText,
FileVideo,
LayoutGrid,
List,
} from "lucide-react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
type Kind = "doc" | "image" | "video" | "sheet";
const files: { name: string; kind: Kind; size: string; edited: string }[] = [
{ name: "Q3 board deck.pdf", kind: "doc", size: "4.2 MB", edited: "2h ago" },
{ name: "Launch hero.png", kind: "image", size: "1.8 MB", edited: "Yesterday" },
{ name: "Onboarding walkthrough.mp4", kind: "video", size: "86 MB", edited: "Sep 21" },
{ name: "Hiring plan 2027.xlsx", kind: "sheet", size: "312 KB", edited: "Sep 19" },
{ name: "Brand guidelines.pdf", kind: "doc", size: "9.6 MB", edited: "Sep 12" },
{ name: "Team offsite.jpg", kind: "image", size: "3.1 MB", edited: "Sep 8" },
];
const kinds: { value: Kind; label: string; icon: typeof FileText; tone: string }[] = [
{ value: "doc", label: "Docs", icon: FileText, tone: "text-chart-1" },
{ value: "image", label: "Images", icon: FileImage, tone: "text-chart-2" },
{ value: "video", label: "Video", icon: FileVideo, tone: "text-chart-4" },
{ value: "sheet", label: "Sheets", icon: FileSpreadsheet, tone: "text-chart-3" },
];
const kindMap = Object.fromEntries(kinds.map((kind) => [kind.value, kind]));
export default function ToggleGroup11() {
const [view, setView] = React.useState<"grid" | "list">("grid");
const [filters, setFilters] = React.useState<string[]>([]);
const visible = filters.length
? files.filter((file) => filters.includes(file.kind))
: files;
return (
<section
aria-labelledby="toggle-group-11-title"
className="flex w-full max-w-xl flex-col gap-4 rounded-xl border border-border bg-card p-4 text-card-foreground"
>
<div className="flex items-center justify-between gap-3">
<div>
<h3 id="toggle-group-11-title" className="text-sm font-medium">
Shared with marketing
</h3>
<p aria-live="polite" className="text-xs text-muted-foreground">
{visible.length} of {files.length} files
</p>
</div>
<ToggleGroup
aria-label="Layout"
variant="outline"
size="sm"
spacing={0}
value={[view]}
onValueChange={(next) => {
if (next[0]) setView(next[0] as "grid" | "list");
}}
>
<ToggleGroupItem value="grid" aria-label="Grid view">
<LayoutGrid aria-hidden="true" />
</ToggleGroupItem>
<ToggleGroupItem value="list" aria-label="List view">
<List aria-hidden="true" />
</ToggleGroupItem>
</ToggleGroup>
</div>
<ToggleGroup
multiple
aria-label="Filter by file type"
size="sm"
spacing={1}
value={filters}
onValueChange={setFilters}
className="flex-wrap"
>
{kinds.map((kind) => (
<ToggleGroupItem
key={kind.value}
value={kind.value}
className="rounded-full! border border-border px-3 aria-pressed:border-foreground/30"
>
<kind.icon className={kind.tone} aria-hidden="true" />
{kind.label}
</ToggleGroupItem>
))}
</ToggleGroup>
{view === "grid" ? (
<ul className="grid grid-cols-2 gap-2 sm:grid-cols-3">
{visible.map((file) => {
const kind = kindMap[file.kind];
return (
<li
key={file.name}
className="flex flex-col gap-3 rounded-lg border border-border p-3"
>
<div className="flex h-14 items-center justify-center rounded-md bg-muted">
<kind.icon className={`size-6 ${kind.tone}`} aria-hidden="true" />
</div>
<div className="min-w-0">
<p className="truncate text-sm font-medium">{file.name}</p>
<p className="text-xs text-muted-foreground tabular-nums">
{file.size}
</p>
</div>
</li>
);
})}
</ul>
) : (
<ul className="flex flex-col divide-y divide-border rounded-lg border border-border">
{visible.map((file) => {
const kind = kindMap[file.kind];
return (
<li key={file.name} className="flex items-center gap-3 px-3 py-2">
<kind.icon className={`size-4 shrink-0 ${kind.tone}`} aria-hidden="true" />
<span className="min-w-0 flex-1 truncate text-sm">{file.name}</span>
<span className="hidden text-xs text-muted-foreground sm:inline">
{file.edited}
</span>
<span className="w-14 text-right text-xs text-muted-foreground tabular-nums">
{file.size}
</span>
</li>
);
})}
</ul>
)}
</section>
);
}
npx shadcn@latest add @sevenui/component/toggle-group-11pnpm dlx shadcn@latest add @sevenui/component/toggle-group-11yarn dlx shadcn@latest add @sevenui/component/toggle-group-11bunx --bun shadcn@latest add @sevenui/component/toggle-group-11"use client";
import * as React from "react";
import { CalendarClock, Check, Moon, Sun, Sunrise } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
const days = [
{ value: "mon", short: "M", label: "Monday" },
{ value: "tue", short: "T", label: "Tuesday" },
{ value: "wed", short: "W", label: "Wednesday" },
{ value: "thu", short: "T", label: "Thursday" },
{ value: "fri", short: "F", label: "Friday" },
{ value: "sat", short: "S", label: "Saturday" },
{ value: "sun", short: "S", label: "Sunday" },
];
const windows = [
{ value: "morning", label: "Morning", hours: "8am-12pm", span: 4, icon: Sunrise },
{ value: "afternoon", label: "Afternoon", hours: "12-5pm", span: 5, icon: Sun },
{ value: "evening", label: "Evening", hours: "5-8pm", span: 3, icon: Moon },
];
const durations = ["15", "30", "45", "60"];
export default function ToggleGroup12() {
const [selectedDays, setSelectedDays] = React.useState<string[]>([
"tue",
"wed",
"thu",
]);
const [selectedWindows, setSelectedWindows] = React.useState<string[]>([
"afternoon",
]);
const [duration, setDuration] = React.useState("30");
const [saved, setSaved] = React.useState(false);
const hoursPerDay = windows
.filter((period) => selectedWindows.includes(period.value))
.reduce((total, period) => total + period.span, 0);
const slots = Math.floor(
(selectedDays.length * hoursPerDay * 60) / Number(duration),
);
const dayNames = days
.filter((day) => selectedDays.includes(day.value))
.map((day) => day.label.slice(0, 3))
.join(", ");
const ready = selectedDays.length > 0 && selectedWindows.length > 0;
return (
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>Office hours</CardTitle>
<CardDescription>
Set when customers can book a call with you.
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-5">
<div className="flex flex-col gap-2">
<span id="toggle-group-12-days" className="text-sm font-medium">
Repeats on
</span>
<ToggleGroup
multiple
aria-labelledby="toggle-group-12-days"
spacing={1}
value={selectedDays}
onValueChange={(next) => {
setSelectedDays(next);
setSaved(false);
}}
className="w-full justify-between"
>
{days.map((day) => (
<ToggleGroupItem
key={day.value}
value={day.value}
aria-label={day.label}
className="size-8 rounded-full! border border-border aria-pressed:border-primary aria-pressed:bg-primary aria-pressed:text-primary-foreground sm:size-9"
>
{day.short}
</ToggleGroupItem>
))}
</ToggleGroup>
</div>
<div className="flex flex-col gap-2">
<span id="toggle-group-12-windows" className="text-sm font-medium">
Time of day
</span>
<ToggleGroup
multiple
aria-labelledby="toggle-group-12-windows"
variant="outline"
spacing={2}
value={selectedWindows}
onValueChange={(next) => {
setSelectedWindows(next);
setSaved(false);
}}
className="grid w-full grid-cols-3"
>
{windows.map((period) => (
<ToggleGroupItem
key={period.value}
value={period.value}
className="h-auto flex-col gap-1 py-2.5 aria-pressed:border-primary aria-pressed:bg-primary/5"
>
<period.icon aria-hidden="true" />
<span>{period.label}</span>
<span className="text-xs font-normal text-muted-foreground tabular-nums">
{period.hours}
</span>
</ToggleGroupItem>
))}
</ToggleGroup>
</div>
<div className="flex flex-wrap items-center justify-between gap-2">
<span id="toggle-group-12-duration" className="text-sm font-medium">
Slot length
</span>
<ToggleGroup
aria-labelledby="toggle-group-12-duration"
variant="outline"
size="sm"
spacing={0}
value={[duration]}
onValueChange={(next) => {
if (next[0]) {
setDuration(next[0]);
setSaved(false);
}
}}
>
{durations.map((minutes) => (
<ToggleGroupItem
key={minutes}
value={minutes}
aria-label={`${minutes} minutes`}
className="tabular-nums"
>
{minutes}m
</ToggleGroupItem>
))}
</ToggleGroup>
</div>
<div
aria-live="polite"
className="flex items-start gap-3 rounded-lg bg-muted p-3 text-sm"
>
<CalendarClock
className="mt-0.5 size-4 shrink-0 text-muted-foreground"
aria-hidden="true"
/>
{ready ? (
<p>
<span className="font-medium tabular-nums">{slots} slots</span>{" "}
a week on {dayNames}.
</p>
) : (
<p className="text-muted-foreground">
Pick at least one day and one time of day to open bookings.
</p>
)}
</div>
</CardContent>
<CardFooter className="justify-end gap-3">
<span aria-live="polite" className="text-xs text-muted-foreground">
{saved ? "Bookings open with these hours." : ""}
</span>
<Button disabled={!ready || saved} onClick={() => setSaved(true)}>
{saved && <Check aria-hidden="true" data-icon="inline-start" />}
{saved ? "Saved" : "Save availability"}
</Button>
</CardFooter>
</Card>
);
}
npx shadcn@latest add @sevenui/component/toggle-group-12pnpm dlx shadcn@latest add @sevenui/component/toggle-group-12yarn dlx shadcn@latest add @sevenui/component/toggle-group-12bunx --bun shadcn@latest add @sevenui/component/toggle-group-12pnpm dlx shadcn@latest add @sevenui/toggle-groupcomponents/ui."use client";
import * as React from "react";
import { Check, Copy, Terminal } from "lucide-react";
import { Button } from "@/components/ui/button";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
type Manager = "pnpm" | "npm" | "yarn" | "bun";
const commands: Record<Manager, string> = {
pnpm: "pnpm dlx shadcn@latest add @sevenui/toggle-group",
npm: "npx shadcn@latest add @sevenui/toggle-group",
yarn: "yarn dlx shadcn@latest add @sevenui/toggle-group",
bun: "bunx --bun shadcn@latest add @sevenui/toggle-group",
};
export default function ToggleGroup13() {
const [manager, setManager] = React.useState<Manager>("pnpm");
const [copied, setCopied] = React.useState(false);
React.useEffect(() => {
if (!copied) return;
const timeout = window.setTimeout(() => setCopied(false), 2000);
return () => window.clearTimeout(timeout);
}, [copied]);
const copy = async () => {
try {
await navigator.clipboard.writeText(commands[manager]);
setCopied(true);
} catch {
setCopied(false);
}
};
return (
<figure className="w-full max-w-lg overflow-hidden rounded-xl border border-border bg-card text-card-foreground">
<div className="flex items-center justify-between gap-2 border-b border-border bg-muted/50 px-2 py-1.5">
<ToggleGroup
aria-label="Package manager"
size="sm"
spacing={0}
value={[manager]}
onValueChange={(next) => {
if (next[0]) {
setManager(next[0] as Manager);
setCopied(false);
}
}}
>
{(Object.keys(commands) as Manager[]).map((key) => (
<ToggleGroupItem
key={key}
value={key}
className="rounded-md! px-2 font-mono text-xs text-muted-foreground hover:bg-transparent hover:text-foreground aria-pressed:bg-background aria-pressed:text-foreground aria-pressed:shadow-xs"
>
{key}
</ToggleGroupItem>
))}
</ToggleGroup>
<Button
variant="ghost"
size="icon-sm"
onClick={copy}
aria-label={copied ? "Copied" : "Copy command"}
>
{copied ? <Check aria-hidden="true" /> : <Copy aria-hidden="true" />}
</Button>
</div>
<div className="flex items-start gap-2 overflow-x-auto px-4 py-3.5">
<Terminal
className="mt-0.5 size-4 shrink-0 text-muted-foreground"
aria-hidden="true"
/>
<code className="font-mono text-sm whitespace-nowrap">
{commands[manager]}
</code>
</div>
<figcaption className="border-t border-border px-4 py-2 text-xs text-muted-foreground">
Adds the component source to <code className="font-mono">components/ui</code>.
</figcaption>
<span aria-live="polite" className="sr-only">
{copied ? "Command copied to clipboard" : ""}
</span>
</figure>
);
}
npx shadcn@latest add @sevenui/component/toggle-group-13pnpm dlx shadcn@latest add @sevenui/component/toggle-group-13yarn dlx shadcn@latest add @sevenui/component/toggle-group-13bunx --bun shadcn@latest add @sevenui/component/toggle-group-13Step 2 of 3
What will your workspace be for?
Pick all that apply. We will set up views and templates to match.
How many people will join?
Choose a goal and team size
"use client";
import * as React from "react";
import {
ArrowLeft,
ArrowRight,
BarChart3,
Bug,
CircleCheck,
Megaphone,
Rocket,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Progress } from "@/components/ui/progress";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
const goals = [
{
value: "roadmap",
title: "Plan the roadmap",
description: "Prioritize initiatives by quarter.",
icon: Rocket,
},
{
value: "bugs",
title: "Triage bugs",
description: "Route reports to the right owner.",
icon: Bug,
},
{
value: "launches",
title: "Coordinate launches",
description: "Align product, sales, and support.",
icon: Megaphone,
},
{
value: "metrics",
title: "Track outcomes",
description: "Tie shipped work to adoption.",
icon: BarChart3,
},
];
const teamSizes = ["Just me", "2-10", "11-50", "51+"];
export default function ToggleGroup14() {
const [selectedGoals, setSelectedGoals] = React.useState<string[]>([
"roadmap",
]);
const [teamSize, setTeamSize] = React.useState<string[]>([]);
const canContinue = selectedGoals.length > 0 && teamSize.length > 0;
return (
<div className="flex w-full max-w-lg flex-col gap-6 rounded-xl border border-border bg-card p-5 text-card-foreground sm:p-6">
<Progress value={66} aria-label="Setup progress, step 2 of 3" />
<div className="flex flex-col gap-1">
<p className="text-xs text-muted-foreground tabular-nums">Step 2 of 3</p>
<h3 id="toggle-group-14-goals" className="text-lg font-semibold tracking-tight">
What will your workspace be for?
</h3>
<p className="text-sm text-muted-foreground">
Pick all that apply. We will set up views and templates to match.
</p>
</div>
<ToggleGroup
multiple
aria-labelledby="toggle-group-14-goals"
variant="outline"
spacing={2}
value={selectedGoals}
onValueChange={setSelectedGoals}
className="grid w-full grid-cols-1 items-stretch sm:grid-cols-2"
>
{goals.map((goal) => (
<ToggleGroupItem
key={goal.value}
value={goal.value}
className="group/goal relative h-auto items-start justify-start gap-3 rounded-xl! p-3 text-left whitespace-normal aria-pressed:border-primary aria-pressed:bg-primary/5 aria-pressed:ring-1 aria-pressed:ring-primary"
>
<span className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted group-aria-pressed/goal:bg-primary group-aria-pressed/goal:text-primary-foreground">
<goal.icon aria-hidden="true" />
</span>
<span className="flex min-w-0 flex-col gap-0.5 pr-5">
<span className="text-sm font-medium">{goal.title}</span>
<span className="text-xs font-normal text-muted-foreground">
{goal.description}
</span>
</span>
<CircleCheck
className="absolute top-3 right-3 size-4! text-primary opacity-0 transition-opacity group-aria-pressed/goal:opacity-100 motion-reduce:transition-none"
aria-hidden="true"
/>
</ToggleGroupItem>
))}
</ToggleGroup>
<div className="flex flex-col gap-2">
<span id="toggle-group-14-size" className="text-sm font-medium">
How many people will join?
</span>
<ToggleGroup
aria-labelledby="toggle-group-14-size"
variant="outline"
spacing={0}
value={teamSize}
onValueChange={setTeamSize}
className="w-full"
>
{teamSizes.map((size) => (
<ToggleGroupItem
key={size}
value={size}
className="flex-1 tabular-nums aria-pressed:bg-primary/5 aria-pressed:text-primary"
>
{size}
</ToggleGroupItem>
))}
</ToggleGroup>
</div>
<div className="flex items-center justify-between gap-3 border-t border-border pt-4">
<Button variant="ghost">
<ArrowLeft aria-hidden="true" data-icon="inline-start" />
Back
</Button>
<div className="flex items-center gap-3">
<span aria-live="polite" className="hidden text-xs text-muted-foreground sm:inline">
{canContinue
? `${selectedGoals.length} selected`
: "Choose a goal and team size"}
</span>
<Button disabled={!canContinue}>
Continue
<ArrowRight aria-hidden="true" data-icon="inline-end" />
</Button>
</div>
</div>
</div>
);
}
npx shadcn@latest add @sevenui/component/toggle-group-14pnpm dlx shadcn@latest add @sevenui/component/toggle-group-14yarn dlx shadcn@latest add @sevenui/component/toggle-group-14bunx --bun shadcn@latest add @sevenui/component/toggle-group-14