Switch Card
Bordered settings row driven by a switch.
Cards
A settings row driven by a switch.
function CardControls() {
return (
<div className="grid w-full max-w-lg gap-3">
<CheckboxCard
defaultSelected
title="Usage analytics"
description="Share anonymized interaction data to help prioritize components."
/>
<SwitchCard
icon={RiMailLine}
defaultSelected
title="Weekly digest"
description="A summary of new components and breaking changes."
/>
</div>
);
}function CardControls() {
return (
<div className="grid w-full max-w-lg gap-3">
<CheckboxCard
defaultSelected
title="Usage analytics"
description="Share anonymized interaction data to help prioritize components."
/>
<SwitchCard
icon={RiMailLine}
defaultSelected
title="Weekly digest"
description="A summary of new components and breaking changes."
/>
</div>
);
}Installation
npx shadcn@latest add https://boardcn.dev/r/switch-card.jsonnpx shadcn@latest add https://boardcn.dev/r/switch-card.jsonnpm packages
- react-aria-components
BoardCN dependencies
The CLI installs these for you — you do not need to add them yourself.
Source
The file the CLI copies into your project.
"use client";
import type { ComponentType, ReactNode, Ref } from "react";
import { Switch as AriaSwitch } from "react-aria-components";
import type { SwitchProps as AriaSwitchProps } from "react-aria-components";
import { cx } from "@/utils/cx";
import { SwitchTrack } from "./switch";
import type { SwitchShape, SwitchSize } from "./switch";
type IconComponent = ComponentType<{
className?: string;
"aria-hidden"?: boolean | "true" | "false";
}>;
/**
* Selectable settings card: title + description on the left, Switch on the
* right. The whole card toggles (react-aria Switch renders the label), so a
* click anywhere flips the state — the switch counterpart to CheckboxCard.
*
* Card chrome mirrors CheckboxCard, but with rounded-xl (12px) corners:
* card rounded-xl (12px), 1px border/button/default, pl 16 pr 20 py 12
* hover bg background/primary/hover (#f7f7f7)
* title Body 1/Medium, text/primary
* desc Body 1/Regular, text/secondary
*/
export interface SwitchCardProps extends Omit<AriaSwitchProps, "children"> {
title: ReactNode;
description?: ReactNode;
/** Optional 24×24 leading icon — pass a Remix Icon component (`RiMailLine`, not `<RiMailLine />`). */
icon?: IconComponent;
size?: SwitchSize;
shape?: SwitchShape;
ref?: Ref<HTMLLabelElement>;
}
export function SwitchCard({
className,
title,
description,
icon: Icon,
size = "md",
shape = "pill",
ref,
...props
}: SwitchCardProps) {
return (
<AriaSwitch
ref={ref}
{...props}
className={(state) =>
cx(
"group flex w-full items-center justify-between gap-3 rounded-xl border border-border-button-default py-3 pr-5 pl-4 select-none",
"transition-colors duration-150 ease",
state.isHovered && !state.isDisabled
? "bg-background-primary-hover"
: "bg-background-primary-default",
state.isDisabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
typeof className === "function" ? className(state) : className,
)
}
>
{(state) => (
<>
<span className="flex min-w-0 items-center gap-3">
{Icon && <Icon className="size-6 shrink-0 text-foreground-icon-secondary" aria-hidden />}
<span className="flex min-w-0 flex-col gap-0.5">
<span className="truncate text-body-medium text-text-primary">{title}</span>
{description !== undefined && description !== null && (
<span className="truncate text-body-regular text-text-secondary">{description}</span>
)}
</span>
</span>
<span className="flex shrink-0 items-center">
<SwitchTrack state={state} size={size} shape={shape} />
</span>
</>
)}
</AriaSwitch>
);
}"use client";
import type { ComponentType, ReactNode, Ref } from "react";
import { Switch as AriaSwitch } from "react-aria-components";
import type { SwitchProps as AriaSwitchProps } from "react-aria-components";
import { cx } from "@/utils/cx";
import { SwitchTrack } from "./switch";
import type { SwitchShape, SwitchSize } from "./switch";
type IconComponent = ComponentType<{
className?: string;
"aria-hidden"?: boolean | "true" | "false";
}>;
/**
* Selectable settings card: title + description on the left, Switch on the
* right. The whole card toggles (react-aria Switch renders the label), so a
* click anywhere flips the state — the switch counterpart to CheckboxCard.
*
* Card chrome mirrors CheckboxCard, but with rounded-xl (12px) corners:
* card rounded-xl (12px), 1px border/button/default, pl 16 pr 20 py 12
* hover bg background/primary/hover (#f7f7f7)
* title Body 1/Medium, text/primary
* desc Body 1/Regular, text/secondary
*/
export interface SwitchCardProps extends Omit<AriaSwitchProps, "children"> {
title: ReactNode;
description?: ReactNode;
/** Optional 24×24 leading icon — pass a Remix Icon component (`RiMailLine`, not `<RiMailLine />`). */
icon?: IconComponent;
size?: SwitchSize;
shape?: SwitchShape;
ref?: Ref<HTMLLabelElement>;
}
export function SwitchCard({
className,
title,
description,
icon: Icon,
size = "md",
shape = "pill",
ref,
...props
}: SwitchCardProps) {
return (
<AriaSwitch
ref={ref}
{...props}
className={(state) =>
cx(
"group flex w-full items-center justify-between gap-3 rounded-xl border border-border-button-default py-3 pr-5 pl-4 select-none",
"transition-colors duration-150 ease",
state.isHovered && !state.isDisabled
? "bg-background-primary-hover"
: "bg-background-primary-default",
state.isDisabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
typeof className === "function" ? className(state) : className,
)
}
>
{(state) => (
<>
<span className="flex min-w-0 items-center gap-3">
{Icon && <Icon className="size-6 shrink-0 text-foreground-icon-secondary" aria-hidden />}
<span className="flex min-w-0 flex-col gap-0.5">
<span className="truncate text-body-medium text-text-primary">{title}</span>
{description !== undefined && description !== null && (
<span className="truncate text-body-regular text-text-secondary">{description}</span>
)}
</span>
</span>
<span className="flex shrink-0 items-center">
<SwitchTrack state={state} size={size} shape={shape} />
</span>
</>
)}
</AriaSwitch>
);
}Props
Generated from the component's TypeScript types. Standard DOM and React Aria props are omitted.
SwitchCard
| Prop | Type | Default | Description |
|---|---|---|---|
| titlerequired | ReactNode | — | — |
| description | ReactNode | — | — |
| icon | IconComponent | — | Optional 24×24 leading icon — pass a Remix Icon component (`RiMailLine`, not `<RiMailLine />`). |
| shape | "pill" | "rectangle" | pill | — |
| size | "sm" | "md" | "lg" | md | — |