# Switch cards

> Three settings cards built on a springy switch — notification preferences with a saved cue, plan features with dependent options and a save bar, and privacy toggles that ask before turning protection off.

- Collection: Examples
- Tier: free
- Docs: https://geturui.com/docs/switch-cards
- Install: `npx shadcn@latest add @geturui/switch-cards`
- Dependencies: motion, lucide-react

## Variants

### Notifications

Email and push preferences in labelled groups; the thumb stretches on press and a Saved chip blurs in after each change.

Install: `npx shadcn@latest add @geturui/switch-cards-notifications`

```tsx
import { NotificationSwitchCard } from "@/components/examples/switch-cards/notifications";

export function Preferences() {
  return <NotificationSwitchCard onChange={(id, on) => api.setPreference(id, on)} />;
}
```

| Prop | Type | Default | Description |
|---|---|---|---|
| title | `string` | "Notifications" | Card heading. |
| settings | `{ id; label; description; channel: "email" | "push"; enabled }[]` | — | Rows, grouped by channel. |
| onChange | `(id: string, enabled: boolean) => void` | — | Fires on every toggle. |
| className | `string` | — | Extra classes on the root element. |

### Plan features

Workspace feature toggles where child options unlock with their parent, a locked row names its plan, and a save bar springs in once anything changes.

Install: `npx shadcn@latest add @geturui/switch-cards-plan-features`

```tsx
import { PlanFeatureCard } from "@/components/examples/switch-cards/plan-features";

export function WorkspaceFeatures() {
  return <PlanFeatureCard onSave={(values) => api.saveFeatures(values)} onUpgrade={(plan) => openUpgrade(plan)} />;
}
```

| Prop | Type | Default | Description |
|---|---|---|---|
| workspace | `string` | — | Named in the subtitle. |
| features | `{ id; label; description; enabled; parent?; requires? }[]` | — | parent nests a row; requires locks it behind a plan. |
| onSave | `(values: Record<string, boolean>) => void | Promise<void>` | — | Save changes button. |
| onUpgrade | `(plan: string) => void` | — | Upgrade link on locked rows. |
| className | `string` | — | Extra classes on the root element. |

### Privacy

Privacy and security switches with a Protected or At risk badge; turning off a sensitive one opens an inline confirmation first.

Install: `npx shadcn@latest add @geturui/switch-cards-privacy`

```tsx
import { PrivacySwitchCard } from "@/components/examples/switch-cards/privacy";

export function Privacy() {
  return <PrivacySwitchCard onChange={(id, on) => api.setPrivacy(id, on)} />;
}
```

| Prop | Type | Default | Description |
|---|---|---|---|
| settings | `{ id; label; description; enabled; confirmOff? }[]` | — | confirmOff text makes turning it off ask first. |
| onChange | `(id: string, enabled: boolean) => void` | — | Fires after a change is confirmed. |
| className | `string` | — | Extra classes on the root element. |

## Accessibility

- Each switch is a button with role switch and aria-checked, labelled and described by its row text.
- Dependent switches are disabled with a visible reason; locked features name the plan they need.
- Confirmations are inline alertdialogs that take focus, close on Escape and hand focus back to the switch.

## Source

### components/examples/switch-cards/notifications.tsx

```tsx
"use client";

import * as React from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { BellOff, Check, Mail, Smartphone } from "lucide-react";
import { cn } from "@/lib/utils";

export interface NotificationSetting {
  id: string;
  label: string;
  description: string;
  channel: "email" | "push";
  enabled: boolean;
}

export interface NotificationSwitchCardProps {
  title?: string;
  settings?: NotificationSetting[];
  onChange?: (id: string, enabled: boolean) => void;
  className?: string;
}

const SETTINGS: NotificationSetting[] = [
  { id: "mentions", label: "Mentions and replies", description: "When someone @mentions you or replies to your thread.", channel: "email", enabled: true },
  { id: "digest", label: "Weekly digest", description: "A Monday summary of activity in your projects.", channel: "email", enabled: true },
  { id: "product", label: "Product updates", description: "New features and changes, about once a month.", channel: "email", enabled: false },
  { id: "assigned", label: "Assigned to you", description: "New tasks and reviews that need your input.", channel: "push", enabled: true },
  { id: "deploys", label: "Deploy status", description: "Builds that fail or finish on production.", channel: "push", enabled: false },
];
const GROUPS = [
  { channel: "email", label: "Email", Icon: Mail },
  { channel: "push", label: "Push", Icon: Smartphone },
] as const;
const spring = { type: "spring", stiffness: 520, damping: 32 } as const;

/** Accessible switch with a springy thumb that stretches while pressed. */
function Switch({ checked, onToggle, labelledBy, describedBy }: { checked: boolean; onToggle: () => void; labelledBy: string; describedBy: string }) {
  return (
    <motion.button
      type="button"
      role="switch"
      aria-checked={checked}
      aria-labelledby={labelledBy}
      aria-describedby={describedBy}
      onClick={onToggle}
      whileTap="press"
      className={cn(
        "relative inline-flex h-6 w-10 shrink-0 items-center rounded-full border p-[2px] outline-none transition-colors duration-200 focus-visible:shadow-[var(--focus-ring)]",
        checked ? "border-transparent bg-volt" : "border-border-strong bg-surface-sunken",
      )}
    >
      <motion.span
        animate={{ x: checked ? 16 : 0 }}
        variants={{ press: { scaleX: 1.25 } }}
        transition={spring}
        style={{ originX: checked ? 1 : 0 }}
        className={cn("block size-[18px] rounded-full shadow-sm", checked ? "bg-on-volt" : "bg-ink-subtle")}
      />
    </motion.button>
  );
}

/** Notification preferences grouped by channel; each switch springs and a quiet "Saved" confirmation fades in. */
export function NotificationSwitchCard({ title = "Notifications", settings = SETTINGS, onChange, className }: NotificationSwitchCardProps) {
  const reduce = useReducedMotion();
  const uid = React.useId();
  const [state, setState] = React.useState(() => Object.fromEntries(settings.map((s) => [s.id, s.enabled])));
  const [saved, setSaved] = React.useState(0);
  const allOff = settings.every((s) => !state[s.id]);

  React.useEffect(() => {
    if (!saved) return;
    const t = setTimeout(() => setSaved(0), 1800);
    return () => clearTimeout(t);
  }, [saved]);

  function toggle(id: string) {
    const next = !state[id];
    setState((s) => ({ ...s, [id]: next }));
    setSaved((n) => n + 1);
    onChange?.(id, next);
  }

  return (
    <section aria-labelledby={`${uid}-title`} className={cn("w-full max-w-[460px] rounded-lg border border-border bg-surface-raised shadow-sm", className)}>
      <header className="flex items-start justify-between gap-3 border-b border-border px-5 py-4">
        <div>
          <h2 id={`${uid}-title`} className="font-display text-[16px] font-semibold text-ink">{title}</h2>
          <p className="mt-0.5 text-[12.5px] text-ink-muted">Choose what reaches you and where.</p>
        </div>
        <p role="status" className="h-6 text-[12px]">
          <AnimatePresence>
            {saved > 0 && (
              <motion.span key={saved} initial={{ opacity: 0, y: reduce ? 0 : 4, filter: "blur(4px)" }} animate={{ opacity: 1, y: 0, filter: "blur(0px)" }} exit={{ opacity: 0 }} className="inline-flex items-center gap-1 rounded-full bg-surface-sunken px-2 py-1 font-medium text-ink-muted ring-1 ring-border">
                <Check aria-hidden className="size-3 text-success" />
                Saved
              </motion.span>
            )}
          </AnimatePresence>
        </p>
      </header>

      {GROUPS.map(({ channel, label, Icon }) => (
        <div key={channel} role="group" aria-labelledby={`${uid}-${channel}`} className="px-5 py-3">
          <p id={`${uid}-${channel}`} className="flex items-center gap-1.5 py-1 text-[11.5px] font-medium uppercase tracking-wide text-ink-subtle">
            <Icon aria-hidden className="size-3.5" />
            {label}
          </p>
          <ul>
            {settings.filter((s) => s.channel === channel).map((s) => (
              <li key={s.id} className="flex items-center justify-between gap-4 py-2.5">
                <div className="min-w-0">
                  <p id={`${uid}-${s.id}`} className={cn("text-[13.5px] font-medium transition-colors", state[s.id] ? "text-ink" : "text-ink-muted")}>{s.label}</p>
                  <p id={`${uid}-${s.id}-d`} className="text-[12px] text-ink-subtle">{s.description}</p>
                </div>
                <Switch checked={!!state[s.id]} onToggle={() => toggle(s.id)} labelledBy={`${uid}-${s.id}`} describedBy={`${uid}-${s.id}-d`} />
              </li>
            ))}
          </ul>
        </div>
      ))}

      <AnimatePresence initial={false}>
        {allOff && (
          <motion.p initial={{ height: 0, opacity: 0 }} animate={{ height: "auto", opacity: 1 }} exit={{ height: 0, opacity: 0 }} transition={reduce ? { duration: 0 } : spring} className="overflow-hidden">
            <span className="flex items-center gap-2 border-t border-border px-5 py-3 text-[12.5px] text-warning">
              <BellOff aria-hidden className="size-4 shrink-0" />
              All notifications are off. You may miss mentions and failed deploys.
            </span>
          </motion.p>
        )}
      </AnimatePresence>
    </section>
  );
}

```

### components/examples/switch-cards/plan-features.tsx

```tsx
"use client";

import * as React from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { Check, LoaderCircle, Lock } from "lucide-react";
import { cn } from "@/lib/utils";

export interface PlanFeature {
  id: string;
  label: string;
  description: string;
  enabled: boolean;
  /** Id of the feature this one depends on. */
  parent?: string;
  /** Plan needed to use it; renders a locked row. */
  requires?: string;
}

export interface PlanFeatureCardProps {
  workspace?: string;
  features?: PlanFeature[];
  onSave?: (values: Record<string, boolean>) => void | Promise<void>;
  onUpgrade?: (plan: string) => void;
  className?: string;
}

const FEATURES: PlanFeature[] = [
  { id: "public", label: "Public links", description: "Anyone with the link can view shared pages.", enabled: true },
  { id: "password", label: "Require a password", description: "Visitors enter a password before viewing.", enabled: false, parent: "public" },
  { id: "index", label: "Allow search engines", description: "Let public pages appear in search results.", enabled: false, parent: "public" },
  { id: "ai", label: "Writing assistant", description: "Draft, summarize and rewrite inside docs.", enabled: false },
  { id: "ai-train", label: "Learn from workspace", description: "Use your docs to improve suggestions for your team.", enabled: false, parent: "ai" },
  { id: "sso", label: "SAML single sign-on", description: "Sign in through Okta, Entra ID or Google Workspace.", enabled: false, requires: "Business" },
];
const spring = { type: "spring", stiffness: 520, damping: 32 } as const;
const soft = { type: "spring", stiffness: 320, damping: 30 } as const;

function Switch({ checked, disabled, onToggle, labelledBy, describedBy }: { checked: boolean; disabled?: boolean; onToggle: () => void; labelledBy: string; describedBy: string }) {
  return (
    <motion.button
      type="button"
      role="switch"
      aria-checked={checked}
      aria-labelledby={labelledBy}
      aria-describedby={describedBy}
      disabled={disabled}
      onClick={onToggle}
      whileTap={disabled ? undefined : "press"}
      className={cn(
        "relative inline-flex h-6 w-10 shrink-0 items-center rounded-full border p-[2px] outline-none transition-[background-color,border-color,opacity] duration-200 focus-visible:shadow-[var(--focus-ring)] disabled:cursor-not-allowed disabled:opacity-40",
        checked ? "border-transparent bg-volt" : "border-border-strong bg-surface-sunken",
      )}
    >
      <motion.span animate={{ x: checked ? 16 : 0 }} variants={{ press: { scaleX: 1.25 } }} transition={spring} style={{ originX: checked ? 1 : 0 }} className={cn("block size-[18px] rounded-full shadow-sm", checked ? "bg-on-volt" : "bg-ink-subtle")} />
    </motion.button>
  );
}

/** Workspace feature toggles where child options unlock with their parent, locked features show the plan they need, and a save bar springs in once something changes. */
export function PlanFeatureCard({ workspace = "Northwind Studio", features = FEATURES, onSave = () => new Promise((r) => setTimeout(r, 900)), onUpgrade, className }: PlanFeatureCardProps) {
  const reduce = useReducedMotion();
  const uid = React.useId();
  const initial = React.useMemo(() => Object.fromEntries(features.map((f) => [f.id, f.enabled])), [features]);
  const [base, setBase] = React.useState(initial);
  const [values, setValues] = React.useState(initial);
  const [status, setStatus] = React.useState<"idle" | "saving" | "saved">("idle");
  const dirty = features.filter((f) => values[f.id] !== base[f.id]).length;

  async function save() {
    setStatus("saving");
    await onSave(values);
    setBase(values);
    setStatus("saved");
    setTimeout(() => setStatus("idle"), 1600);
  }

  return (
    <section aria-labelledby={`${uid}-t`} className={cn("w-full max-w-[480px] overflow-hidden rounded-lg border border-border bg-surface-raised shadow-sm", className)}>
      <header className="border-b border-border px-5 py-4">
        <h2 id={`${uid}-t`} className="font-display text-[16px] font-semibold text-ink">Features</h2>
        <p className="mt-0.5 text-[12.5px] text-ink-muted">Applies to everyone in {workspace}.</p>
      </header>
      <ul className="divide-y divide-border">
        {features.filter((f) => !f.parent).map((f) => {
          const children = features.filter((c) => c.parent === f.id);
          const on = !!values[f.id];
          return (
            <li key={f.id} className="px-5 py-3.5">
              <Row f={f} uid={uid} checked={on} disabled={!!f.requires} onToggle={() => setValues((v) => ({ ...v, [f.id]: !on }))} onUpgrade={onUpgrade} />
              {children.length > 0 && (
                <motion.ul
                  aria-label={`${f.label} options`}
                  animate={{ opacity: on ? 1 : 0.55 }}
                  transition={{ duration: 0.2 }}
                  className="relative ml-1 mt-2 space-y-2.5 border-l border-border-strong/40 pl-4"
                >
                  {children.map((c) => (
                    <li key={c.id}>
                      <Row f={c} uid={uid} checked={on && !!values[c.id]} disabled={!on} onToggle={() => setValues((v) => ({ ...v, [c.id]: !v[c.id] }))} />
                    </li>
                  ))}
                  <AnimatePresence initial={false}>
                    {!on && (
                      <motion.li initial={{ height: 0, opacity: 0 }} animate={{ height: "auto", opacity: 1 }} exit={{ height: 0, opacity: 0 }} transition={reduce ? { duration: 0 } : soft} className="overflow-hidden text-[11.5px] text-ink-subtle">
                        Turn on {f.label.toLowerCase()} to change these.
                      </motion.li>
                    )}
                  </AnimatePresence>
                </motion.ul>
              )}
            </li>
          );
        })}
      </ul>
      <AnimatePresence initial={false}>
        {(dirty > 0 || status !== "idle") && (
          <motion.div initial={{ height: 0 }} animate={{ height: "auto" }} exit={{ height: 0 }} transition={reduce ? { duration: 0 } : soft} className="overflow-hidden">
            <div className="flex items-center justify-between gap-3 border-t border-border bg-surface px-5 py-3">
              <p aria-live="polite" className="text-[12.5px] text-ink-muted">
                {status === "saved" ? (
                  <span className="inline-flex items-center gap-1.5 font-medium text-success"><Check aria-hidden className="size-3.5" />Changes saved</span>
                ) : (
                  `${dirty} unsaved ${dirty === 1 ? "change" : "changes"}`
                )}
              </p>
              {status !== "saved" && (
                <div className="flex gap-2">
                  <button type="button" disabled={status === "saving"} onClick={() => setValues(base)} className="h-8 rounded-full px-3 text-[12.5px] font-medium text-ink-muted outline-none hover:bg-surface-hover hover:text-ink focus-visible:shadow-[var(--focus-ring)]">
                    Discard
                  </button>
                  <motion.button type="button" disabled={status === "saving"} whileTap={{ scale: 0.96 }} onClick={save} className="inline-flex h-8 items-center gap-1.5 rounded-full bg-volt px-3.5 text-[12.5px] font-semibold text-on-volt outline-none focus-visible:shadow-[var(--focus-ring)] disabled:opacity-80">
                    {status === "saving" && <LoaderCircle aria-hidden className="size-3.5 animate-spin" />}
                    Save changes
                  </motion.button>
                </div>
              )}
            </div>
          </motion.div>
        )}
      </AnimatePresence>
    </section>
  );
}

function Row({ f, uid, checked, disabled, onToggle, onUpgrade }: { f: PlanFeature; uid: string; checked: boolean; disabled: boolean; onToggle: () => void; onUpgrade?: (plan: string) => void }) {
  return (
    <div className="flex items-center justify-between gap-4">
      <div className="min-w-0">
        <p id={`${uid}-${f.id}`} className={cn("flex items-center gap-2 font-medium", f.parent ? "text-[13px]" : "text-[13.5px]", disabled ? "text-ink-muted" : "text-ink")}>
          {f.label}
          {f.requires && (
            <span className="inline-flex items-center gap-1 rounded-full bg-ember-soft px-1.5 py-0.5 text-[10.5px] font-semibold text-ember-ink">
              <Lock aria-hidden className="size-2.5" />
              {f.requires}
            </span>
          )}
        </p>
        <p id={`${uid}-${f.id}-d`} className="text-[12px] text-ink-subtle">
          {f.description}
          {f.requires && (
            <>
              {" "}
              <button type="button" onClick={() => onUpgrade?.(f.requires ?? "")} className="rounded-xs font-medium text-ink underline underline-offset-2 outline-none focus-visible:shadow-[var(--focus-ring)]">
                Upgrade to {f.requires}
              </button>
            </>
          )}
        </p>
      </div>
      <Switch checked={checked} disabled={disabled} onToggle={onToggle} labelledBy={`${uid}-${f.id}`} describedBy={`${uid}-${f.id}-d`} />
    </div>
  );
}

```

### components/examples/switch-cards/privacy.tsx

```tsx
"use client";

import * as React from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { ShieldAlert, ShieldCheck } from "lucide-react";
import { cn } from "@/lib/utils";

export interface PrivacySetting {
  id: string;
  label: string;
  description: string;
  enabled: boolean;
  /** Ask before turning this off; the text explains the consequence. */
  confirmOff?: string;
}

export interface PrivacySwitchCardProps {
  settings?: PrivacySetting[];
  onChange?: (id: string, enabled: boolean) => void;
  className?: string;
}

const SETTINGS: PrivacySetting[] = [
  { id: "presence", label: "Show when I’m online", description: "Teammates see a green dot next to your name.", enabled: true },
  { id: "receipts", label: "Read receipts", description: "Let people know when you’ve read their message.", enabled: false },
  { id: "analytics", label: "Share usage data", description: "Anonymous diagnostics that help us fix bugs faster.", enabled: true },
  { id: "alerts", label: "Sign-in alerts", description: "Email me when a new device signs in.", enabled: true, confirmOff: "You won’t hear about sign-ins from new devices, including ones that aren’t yours." },
  { id: "2fa", label: "Two-step verification", description: "Ask for a code from your authenticator app.", enabled: true, confirmOff: "Anyone with your password could sign in. We recommend keeping this on." },
];
const spring = { type: "spring", stiffness: 520, damping: 32 } as const;
const soft = { type: "spring", stiffness: 320, damping: 30 } as const;

/** Privacy settings where risky switches ask first: turning one off springs open an inline confirmation that holds focus until you choose. */
export function PrivacySwitchCard({ settings = SETTINGS, onChange, className }: PrivacySwitchCardProps) {
  const reduce = useReducedMotion();
  const uid = React.useId();
  const [state, setState] = React.useState(() => Object.fromEntries(settings.map((s) => [s.id, s.enabled])));
  const [pending, setPending] = React.useState<string | null>(null);
  const switchRefs = React.useRef<Record<string, HTMLButtonElement | null>>({});
  const keepRef = React.useRef<HTMLButtonElement>(null);
  const protectedCount = settings.filter((s) => s.confirmOff && state[s.id]).length;
  const totalProtected = settings.filter((s) => s.confirmOff).length;

  React.useEffect(() => {
    if (pending) requestAnimationFrame(() => keepRef.current?.focus());
  }, [pending]);

  function set(id: string, value: boolean) {
    setState((s) => ({ ...s, [id]: value }));
    onChange?.(id, value);
  }

  function toggle(s: PrivacySetting) {
    if (state[s.id] && s.confirmOff) return setPending(pending === s.id ? null : s.id);
    setPending(null);
    set(s.id, !state[s.id]);
  }

  function resolve(turnOff: boolean) {
    if (!pending) return;
    if (turnOff) set(pending, false);
    switchRefs.current[pending]?.focus();
    setPending(null);
  }

  const secure = protectedCount === totalProtected;

  return (
    <section aria-labelledby={`${uid}-t`} className={cn("w-full max-w-[460px] overflow-hidden rounded-lg border border-border bg-surface-raised shadow-sm", className)}>
      <header className="flex items-center justify-between gap-3 border-b border-border px-5 py-4">
        <div>
          <h2 id={`${uid}-t`} className="font-display text-[16px] font-semibold text-ink">Privacy and security</h2>
          <p className="mt-0.5 text-[12.5px] text-ink-muted">Control what others see and how you sign in.</p>
        </div>
        <AnimatePresence mode="wait" initial={false}>
          <motion.span
            key={String(secure)}
            role="status"
            initial={{ opacity: 0, scale: reduce ? 1 : 0.85 }}
            animate={{ opacity: 1, scale: 1 }}
            exit={{ opacity: 0, scale: reduce ? 1 : 0.85 }}
            transition={spring}
            className={cn("inline-flex shrink-0 items-center gap-1 rounded-full px-2 py-1 text-[11.5px] font-medium ring-1", secure ? "text-success ring-success/30" : "text-warning ring-warning/40")}
          >
            {secure ? <ShieldCheck aria-hidden className="size-3.5" /> : <ShieldAlert aria-hidden className="size-3.5" />}
            {secure ? "Protected" : "At risk"}
          </motion.span>
        </AnimatePresence>
      </header>
      <ul className="divide-y divide-border">
        {settings.map((s) => {
          const on = !!state[s.id];
          const asking = pending === s.id;
          return (
            <li key={s.id} className="px-5 py-3">
              <div className="flex items-center justify-between gap-4">
                <div className="min-w-0">
                  <p id={`${uid}-${s.id}`} className="text-[13.5px] font-medium text-ink">{s.label}</p>
                  <p id={`${uid}-${s.id}-d`} className="text-[12px] text-ink-subtle">{s.description}</p>
                </div>
                <motion.button
                  ref={(el) => {
                    switchRefs.current[s.id] = el;
                  }}
                  type="button"
                  role="switch"
                  aria-checked={on}
                  aria-labelledby={`${uid}-${s.id}`}
                  aria-describedby={`${uid}-${s.id}-d`}
                  aria-expanded={s.confirmOff ? asking : undefined}
                  aria-controls={s.confirmOff ? `${uid}-${s.id}-c` : undefined}
                  onClick={() => toggle(s)}
                  whileTap="press"
                  className={cn(
                    "relative inline-flex h-6 w-10 shrink-0 items-center rounded-full border p-[2px] outline-none transition-colors duration-200 focus-visible:shadow-[var(--focus-ring)]",
                    on ? "border-transparent bg-volt" : "border-border-strong bg-surface-sunken",
                    asking && "ring-2 ring-danger/50",
                  )}
                >
                  <motion.span animate={{ x: asking ? 8 : on ? 16 : 0 }} variants={{ press: { scaleX: 1.25 } }} transition={spring} style={{ originX: on ? 1 : 0 }} className={cn("block size-[18px] rounded-full shadow-sm", on ? "bg-on-volt" : "bg-ink-subtle")} />
                </motion.button>
              </div>
              <AnimatePresence initial={false}>
                {asking && (
                  <motion.div
                    id={`${uid}-${s.id}-c`}
                    role="alertdialog"
                    aria-labelledby={`${uid}-${s.id}-q`}
                    aria-describedby={`${uid}-${s.id}-w`}
                    initial={{ height: 0, opacity: 0 }}
                    animate={{ height: "auto", opacity: 1 }}
                    exit={{ height: 0, opacity: 0 }}
                    transition={reduce ? { duration: 0 } : soft}
                    onKeyDown={(e) => {
                      if (e.key !== "Escape") return;
                      e.stopPropagation();
                      resolve(false);
                    }}
                    className="overflow-hidden"
                  >
                    <div className="mt-3 rounded-md border border-danger/30 bg-surface-sunken p-3">
                      <p id={`${uid}-${s.id}-q`} className="flex items-center gap-1.5 text-[13px] font-medium text-danger">
                        <ShieldAlert aria-hidden className="size-4" />
                        Turn off {s.label.toLowerCase()}?
                      </p>
                      <p id={`${uid}-${s.id}-w`} className="mt-1 text-[12.5px] text-ink-muted">{s.confirmOff}</p>
                      <div className="mt-3 flex justify-end gap-2">
                        <button ref={keepRef} type="button" onClick={() => resolve(false)} className="h-8 rounded-full bg-ink px-3.5 text-[12.5px] font-semibold text-bg outline-none focus-visible:shadow-[var(--focus-ring)]">
                          Keep it on
                        </button>
                        <motion.button type="button" whileTap={{ scale: 0.96 }} onClick={() => resolve(true)} className="h-8 rounded-full border border-danger/50 px-3.5 text-[12.5px] font-medium text-danger outline-none transition-colors hover:bg-danger/10 focus-visible:shadow-[var(--focus-ring)]">
                          Turn off
                        </motion.button>
                      </div>
                    </div>
                  </motion.div>
                )}
              </AnimatePresence>
            </li>
          );
        })}
      </ul>
    </section>
  );
}

```
