Toggle Switch Row
Free · MITStacked list of labeled on/off switches for notification preferences, built on native checkboxes with zero JS.
- Category
- Forms
- Framework
- astro
- Version
- 1.0.0
- Peer deps
- none
Install
velocityx add toggle-switch-row
Free and MIT licensed — no account needed. The file lands under src/components/velocity/toggle-switch-row/ by default (override with --dest), and it's yours to edit from the moment it's written.
Demo
View the live demo on the showcase site ↗
Source
Pulled live from the registry — this is the exact file velocityx add toggle-switch-row writes into your project right now.
components/velocity/toggle-switch-row/ToggleSwitchRow.astro
---
// VELOCITY COMPONENT · theming
//
// Colours here are Tailwind `primary-*` / `accent-*`, which resolve through
// the Velocity preset. Out of the box they are a neutral indigo — NOT the
// Aidxn brand — so this drops into your project without dragging our look in.
//
// ⚡ NOT USING VELOCITY? Run this once, in your project root:
//
// velocityx theme
//
// It writes velocity-preset.cjs + velocity-theme.css. Add the preset to your
// tailwind.config, import the css, then set your whole brand in ONE place:
//
// :root {
// --vx-brand: custom;
// --vx-primary-600: 220 38 38; <- your action colour
// --vx-primary-700: 185 28 28;
// }
//
// ⚠️ Raw RGB channels, space separated — NOT hex. `#DC2626` here silently
// breaks every /opacity variant in the library (bg-primary-600/20 etc).
//
// Prefer not to install anything? Find-and-replace `primary-` with your own
// scale name. Nothing else in this file depends on Velocity.
/**
* ToggleSwitchRow — a stacked list of labeled on/off switches (notification
* preferences, feature flags). Native checkbox + `peer-checked` styling, no
* JS. Each row is its own <label> so the whole row is clickable.
*/
export interface ToggleRow {
name: string;
label: string;
description?: string;
defaultChecked?: boolean;
disabled?: boolean;
}
export interface Props {
rows?: ToggleRow[];
heading?: string;
className?: string;
}
const {
rows = [
{ name: 'email_updates', label: 'Product updates', description: 'New features and improvements', defaultChecked: true },
{ name: 'email_marketing', label: 'Marketing emails', description: 'Tips, offers, and promotions' },
{ name: 'email_security', label: 'Security alerts', description: 'Sign-ins and account changes', defaultChecked: true, disabled: true },
],
heading = 'Email notifications',
className = '',
} = Astro.props;
---
<div class:list={['rounded-2xl border border-primary-500/15 bg-white/95 dark:bg-primary-950/60 overflow-hidden', className]}>
{heading && (
<div class="px-5 py-4 border-b border-primary-500/10">
<h3 class="font-bold text-primary-950 dark:text-white">{heading}</h3>
</div>
)}
<div class="divide-y divide-primary-500/10">
{rows.map((row) => {
const id = `tsw-${row.name}`;
return (
<label for={id} class:list={['flex items-center justify-between gap-4 px-5 py-4', row.disabled ? 'opacity-60 cursor-not-allowed' : 'cursor-pointer']}>
<span class="min-w-0">
<span class="block text-sm font-semibold text-primary-950 dark:text-white">{row.label}</span>
{row.description && <span class="block body-sm text-primary-950/55 dark:text-primary-200/55 mt-0.5">{row.description}</span>}
</span>
<span class="relative shrink-0 inline-flex h-6 w-11 items-center">
<input
id={id}
type="checkbox"
name={row.name}
checked={row.defaultChecked}
disabled={row.disabled}
class="peer sr-only"
/>
<span class="absolute inset-0 rounded-full bg-primary-200 dark:bg-primary-800 transition-colors peer-checked:bg-primary-500 peer-focus-visible:ring-4 peer-focus-visible:ring-primary-500/25"></span>
<span class="absolute left-0.5 top-0.5 h-5 w-5 rounded-full bg-white shadow transition-transform peer-checked:translate-x-5 motion-reduce:transition-none"></span>
</span>
</label>
);
})}
</div>
</div>
Theming
Uses the shared primary-* / accent-* scale — see Theming if colours look unstyled after install.