A settings-style panel of animated toggle switches, grouped under labeled sections. Each row has a label and optional description. The thumb slides smoothly on toggle. Fully accessible: role='switch', aria-checked, keyboard-toggled with Space and Enter.
Click or Space / Enter to toggle · Smooth thumb animation
// Dependencies: react ^18
import React, { useState } from 'react';
function cn(...classes: (string | false | null | undefined)[]) {
return classes.filter(Boolean).join(' ');
}
type ToggleItem = {
id: string;
label: string;
description?: string;
enabled: boolean;
};
type ToggleGroup = {
id: string;
label: string;
items: ToggleItem[];
};
type Props = {
groups: ToggleGroup[];
onChange?: (groupId: string, itemId: string, enabled: boolean) => void;
};
function Switch({ enabled, onToggle }: { enabled: boolean; onToggle: () => void }) {
return (
<button
role="switch"
aria-checked={enabled}
onClick={onToggle}
onKeyDown={(e) => {
if (e.key === 'Enter') { e.preventDefault(); onToggle(); }
}}
className={cn(
'relative flex-shrink-0 w-9 h-5 rounded-full transition-colors duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1 focus-visible:ring-offset-[#0a0a0a]',
enabled ? 'bg-indigo-500' : 'bg-zinc-700'
)}
>
<span
className={cn(
'pointer-events-none absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white shadow-sm transition-transform duration-200',
enabled ? 'translate-x-4' : 'translate-x-0'
)}
/>
</button>
);
}
export function ToggleSwitchGroup({ groups, onChange }: Props) {
const [state, setState] = useState<Record<string, Record<string, boolean>>>(
() => Object.fromEntries(
groups.map((g) => [g.id, Object.fromEntries(g.items.map((i) => [i.id, i.enabled]))])
)
);
const toggle = (groupId: string, itemId: string) => {
const next = !state[groupId][itemId];
setState((prev) => ({
...prev,
[groupId]: { ...prev[groupId], [itemId]: next },
}));
onChange?.(groupId, itemId, next);
};
return (
<div className="w-full flex flex-col gap-4">
{groups.map((group) => (
<div
key={group.id}
className="flex flex-col bg-[#0a0a0a] border border-[#1a1a1a] rounded-xl overflow-hidden"
>
<div className="px-4 py-2.5 border-b border-[#1a1a1a]">
<span className="text-xs font-semibold text-zinc-500 uppercase tracking-wide">
{group.label}
</span>
</div>
<div className="divide-y divide-[#1a1a1a]">
{group.items.map((item) => (
<div key={item.id} className="flex items-center justify-between px-4 py-3">
<div className="flex flex-col gap-0.5 min-w-0 mr-4">
<span className="text-sm font-medium text-white">{item.label}</span>
{item.description && (
<span className="text-xs text-zinc-500 leading-snug">{item.description}</span>
)}
</div>
<Switch
enabled={state[group.id][item.id]}
onToggle={() => toggle(group.id, item.id)}
/>
</div>
))}
</div>
</div>
))}
</div>
);
}
// Usage:
// const GROUPS = [
// {
// id: 'notifications',
// label: 'Notifications',
// items: [
// { id: 'email', label: 'Email', description: 'Receive updates via email', enabled: true },
// { id: 'push', label: 'Push', description: 'Browser push alerts', enabled: false },
// ],
// },
// ];
// <ToggleSwitchGroup groups={GROUPS} onChange={(gId, iId, val) => console.log(gId, iId, val)} />Unlock to copy
Free access to all patterns
Command Palette
Linear
Global Search with Results Grouping
Linear
Recent + Suggested Search
Raycast
Filter Builder (AND / OR)
Linear
Sidebar Navigation
Linear
Collapsible Nested Tree Nav
Notion
Tab Bar with Overflow Menu
Vercel
Breadcrumb with Dropdowns
Linear
Workspace Switcher
Slack
Deployment Status Card
Vercel
Empty State
Vercel
Error State with Retry
Notion
Slash Command Menu
Notion
Stats & Metrics Row
Vercel
Single Big Metric Card
Stripe
Usage / Quota Meter
Vercel
Skeleton Loading Grid
Linear
Activity Heatmap
Notion
Confirmation Dialog
Linear
Slide-Over Panel
Stripe
Toast / Snackbar Stack
Vercel
Issue / Task Card
Linear
Activity Feed
Linear + Slack
Comment Thread
Linear
Audit Log Timeline
Vercel
Properties Panel
Notion + Linear
Multi-Step Form Wizard
Stripe
Tag Multi-Select
Linear
Toggle Switch Group
Stripe
Inline Data Table
Notion + Linear
Pagination
Vercel
Sortable / Selectable Table
Linear
Status Badge System
Linear
Segmented Control
Notion