Per-event switches across email, push, and SMS, grouped by area.
Install with the CLI, or add the packages and paste the source below.
Add Notification preferences to your app with its source-owned dependencies.
npx cronus-ui add notification-preferences
Install the Cronus UI packages, then paste the block below.
Copy the source for Channel matrix. Every class is a semantic token, so it re-themes with your app.
import {Button,Card,CardContent,CardDescription,CardFooter,CardHeader,CardTitle,Separator,Switch,} from "@cronus-ui/ui";interface NotificationRule {id: string;label: string;description: string;email: boolean;push: boolean;sms: boolean;}interface NotificationGroup {id: string;label: string;rules: NotificationRule[];}const notificationGroups: NotificationGroup[] = [{id: "group-sales",label: "Sales",rules: [{id: "rule-new-order",label: "New order",description: "Every successful checkout.",email: true,push: true,sms: true,},{id: "rule-payout-sent",label: "Payout sent",description: "When a payout leaves your balance.",email: true,push: true,sms: false,},{id: "rule-refund-requested",label: "Refund requested",description: "A customer asks for their money back.",email: true,push: false,sms: false,},],},{id: "group-team",label: "Team",rules: [{id: "rule-member-joined",label: "Member joined",description: "Someone accepts a workspace invite.",email: true,push: false,sms: false,},{id: "rule-mentions",label: "Mentions & comments",description: "Replies and @mentions on your work.",email: true,push: true,sms: false,},],},{id: "group-security",label: "Security",rules: [{id: "rule-new-sign-in",label: "New sign-in",description: "A sign-in from an unrecognized device.",email: true,push: true,sms: true,},{id: "rule-password-changed",label: "Password changed",description: "Your password or 2FA settings change.",email: true,push: true,sms: true,},],},];export function NotificationPreferencesMatrixBlock() {return (<Card className="mx-auto w-full max-w-2xl gap-0 pb-0 shadow-md"><CardHeader><CardTitle className="font-display text-lg">Notification preferences</CardTitle><CardDescription>Choose how you hear about activity, per channel.</CardDescription></CardHeader><CardContent className="px-0 pt-4"><div className="grid grid-cols-[minmax(0,1fr)_repeat(3,2.75rem)] gap-x-3 px-4 pb-2 sm:grid-cols-[minmax(0,1fr)_repeat(3,3.5rem)] sm:px-6"><span aria-hidden="true" /><span className="text-center text-xs font-medium text-fg-tertiary">Email</span><span className="text-center text-xs font-medium text-fg-tertiary">Push</span><span className="text-center text-xs font-medium text-fg-tertiary">SMS</span></div>{notificationGroups.map((group) => (<div key={group.id}><div className="border-t border-border bg-surface-inset px-4 py-2 sm:px-6"><span className="text-xs font-medium uppercase tracking-wide text-fg-tertiary">{group.label}</span></div>{group.rules.map((rule) => (<divkey={rule.id}className="grid grid-cols-[minmax(0,1fr)_repeat(3,2.75rem)] items-center gap-x-3 border-t border-border px-4 py-3 sm:grid-cols-[minmax(0,1fr)_repeat(3,3.5rem)] sm:px-6"><div className="flex min-w-0 flex-col gap-0.5"><span className="text-sm font-medium text-fg">{rule.label}</span><span className="text-xs text-fg-tertiary">{rule.description}</span></div><span className="flex justify-center"><Switch defaultChecked={rule.email} aria-label={`${rule.label} by email`} /></span><span className="flex justify-center"><Switch defaultChecked={rule.push} aria-label={`${rule.label} by push`} /></span><span className="flex justify-center"><Switch defaultChecked={rule.sms} aria-label={`${rule.label} by SMS`} /></span></div>))}</div>))}</CardContent><Separator /><CardFooter className="justify-between gap-3 py-5"><span className="text-xs text-fg-tertiary">SMS is reserved for critical alerts.</span><Button variant="primary" size="sm">Save preferences</Button></CardFooter></Card>);}