1 GitHub star = 1 new component.

Star the repo
App & Dashboardappdashboardsaasswitchtogglesettingsform

Switch Row

Settings row with label, description and an accessible on/off switch.

Live Preview

Get an email for new bookings and cancellations.

Usage

Copy and paste this code into your React component:

tsx
import { SwitchRow } from 'react-component-kit-ui/app';

<SwitchRow name="weekly" label="Weekly report" description="Every Monday at 9:00." checked />

Manual Installation

If you are not using the npm package, create a new file and paste the following code: src/components/SwitchRow.tsx + SwitchRow.css:

SwitchRow.tsx
'use client';

import './SwitchRow.css';

/** class:list โ†’ className */
function cx(...args: unknown[]): string {
  const out: string[] = [];
  const walk = (v: unknown) => {
    if (!v) return;
    if (typeof v === 'string' || typeof v === 'number') out.push(String(v));
    else if (Array.isArray(v)) v.forEach(walk);
    else if (typeof v === 'object') for (const [k, on] of Object.entries(v as Record<string, unknown>)) if (on) out.push(k);
  };
  args.forEach(walk);
  return out.join(' ');
}

/**
 * SwitchRow โ€” Settings row with label, description and an accessible on/off switch.
 *
 * @param {string} label - Setting name. Default 'Email notifications'.
 * @param {string} description - Help text.
 * @param {string} name - Form field name (submitted as "on" when checked). Default 'setting'.
 * @param {boolean} checked - Initial state. Default true.
 * @param {boolean} disabled - Disable the switch. Default false.
 * @param {string} id - Input id (unique). Defaults to `sw-${name}`.
 * @param {string} size - Switch size (sm|md). Default 'md'.
 * @param {string} className - Extra class.
 *
 * Accessibility: native checkbox with role="switch", associated label and description
 * (aria-describedby), visible focus ring, 44px touch target.
 */
export interface SwitchRowProps {
  label?: string;
  description?: string;
  name?: string;
  checked?: boolean;
  disabled?: boolean;
  id?: string;
  size?: 'sm' | 'md';
  className?: string;
}

export function SwitchRow({
  label = 'Email notifications',
  description = 'Get an email for new bookings and cancellations.',
  name = 'setting',
  checked = true,
  disabled = false,
  id,
  size = 'md',
  className = '',
}: SwitchRowProps) {
  const inputId = id ?? `sw-${name}`;
  return (
    <>
      <div className={cx(['sw-row', `sw-row--${size}`, { 'is-disabled': disabled }, className])} data-rk-22e298="">
        <div className="sw-row__text" data-rk-22e298="">
          <label className="sw-row__label" htmlFor={inputId} data-rk-22e298="">{label}</label>
          {description && <p className="sw-row__desc" id={`${inputId}-desc`} data-rk-22e298="">{description}</p>}
        </div>
        <span className="sw-row__switch" data-rk-22e298="">
          <input type="checkbox" role="switch" id={inputId} name={name} defaultChecked={checked} disabled={disabled} aria-describedby={description ? `${inputId}-desc` : undefined} data-rk-22e298="" />
          <span className="sw-row__track" aria-hidden="true" data-rk-22e298=""></span>
        </span>
      </div>
    </>
  );
}

export default SwitchRow;
SwitchRow.css
/* SwitchRow โ€” ported from astro-component-kit (app/SwitchRow.astro) */

.sw-row:where([data-rk-22e298]) { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4, 1rem); min-height: 44px; }
.sw-row:where([data-rk-22e298]) + .sw-row { padding-top: var(--sp-4, 1rem); border-top: 1px solid var(--c-border, rgba(255, 255, 255, 0.06)); }
.sw-row__label:where([data-rk-22e298]) { font-size: 0.9rem; font-weight: 600; color: var(--c-text-1, #fff); cursor: pointer; }
.sw-row__desc:where([data-rk-22e298]) { margin: 2px 0 0; font-size: 0.8rem; line-height: 1.5; color: var(--c-text-2, #a3a3a3); }
.sw-row__switch:where([data-rk-22e298]) { position: relative; flex-shrink: 0; width: 44px; height: 26px; }
.sw-row--sm:where([data-rk-22e298]) .sw-row__switch:where([data-rk-22e298]) { width: 36px; height: 22px; }
.sw-row__switch:where([data-rk-22e298]) input:where([data-rk-22e298]) { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; z-index: 1; }
.sw-row__track:where([data-rk-22e298]) { position: absolute; inset: 0; border-radius: 999px; background: rgb(var(--ink-rgb, 255 255 255) / 0.16); transition: background 0.2s ease; }
.sw-row__track:where([data-rk-22e298])::after { content: ''; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); transition: transform 0.2s cubic-bezier(0.34, 1.4, 0.64, 1); }
.sw-row--sm:where([data-rk-22e298]) .sw-row__track:where([data-rk-22e298])::after { width: 16px; height: 16px; }
.sw-row__switch:where([data-rk-22e298]) input:checked:where([data-rk-22e298]) + .sw-row__track:where([data-rk-22e298]) { background: var(--app-accent, var(--c-primary-light, #d4a017)); }
.sw-row__switch:where([data-rk-22e298]) input:checked:where([data-rk-22e298]) + .sw-row__track:where([data-rk-22e298])::after { transform: translateX(18px); }
.sw-row--sm:where([data-rk-22e298]) .sw-row__switch:where([data-rk-22e298]) input:checked:where([data-rk-22e298]) + .sw-row__track:where([data-rk-22e298])::after { transform: translateX(14px); }
.sw-row__switch:where([data-rk-22e298]) input:focus-visible:where([data-rk-22e298]) + .sw-row__track:where([data-rk-22e298]) { outline: 2px solid var(--app-accent, var(--c-primary-light, #d4a017)); outline-offset: 2px; }
.sw-row.is-disabled:where([data-rk-22e298]) { opacity: 0.5; }
.sw-row.is-disabled:where([data-rk-22e298]) input:where([data-rk-22e298]) { cursor: not-allowed; }
@media (prefers-reduced-motion: reduce) { .sw-row__track:where([data-rk-22e298]), .sw-row__track:where([data-rk-22e298])::after { transition: none; } }

Quick Info

Category
App & Dashboard
Filename
SwitchRow.tsx
Dependencies
None โ€” pure React + CSS
Tags
appdashboardsaasswitchtogglesettingsform