How to Add a Switch to a React App
Step-by-step guide to add a toggle switch to any React app with the Drivn CLI — a copy-paste, controlled Switch built on an accessible role=switch button.
Reaching for a switch means your React app has a setting that takes effect the moment it flips — dark mode, email notifications, a feature flag in a preferences panel. You could wire up a styled checkbox or a raw button and manage the on/off look and the ARIA yourself, but you would re-solve the same toggle every time. The Switch in Drivn packages it into one small file you paste into your project — a controlled <button> with role="switch" and aria-checked that reads as a real switch to assistive technology out of the box.
Drivn installs by copy-paste: the CLI writes a switch.tsx file into your repository. The component is controlled-only — it holds no internal state, renders exactly the checked boolean you pass, and reports the next value through onChange. Because it renders a <button> with an onClick handler, the file opens with a 'use client' directive and renders inside a client boundary. Its only import past React is the local cn utility — there is not even an icon dependency, because the thumb is a plain <span> that slides across the track with a CSS transition-transform.
This guide adds the Switch to any React app — install the CLI, render your first switch, drive the checked state, and pair it with a label for a settings row. For the App Router walkthrough see the Next.js Switch guide, and for copy-paste layouts see the Switch examples.
Prerequisites
Before installing Switch, confirm your React project has the two things Drivn assumes: Tailwind CSS v4 installed and processing your styles, and a @/ path alias pointing at your source directory — the component ships as a .tsx file, so TypeScript is part of the stack. If you scaffolded with create-next-app, npm create vite, or npx drivn@latest create, both are already wired; for a custom setup the installation page lists the minimal config. The Switch has no third-party import at all past React and the local cn utility — there is no icon package to add, because the sliding thumb is a plain <span>. The one detail worth knowing before you render it is that the Switch is a Client Component: it renders a <button> with an onClick handler, so its source opens with a 'use client' directive and must render inside a client boundary rather than straight in a Server Component.
Step 1 — Install Drivn via the CLI
Run the CLI from your project root to add the Switch source file. The command prompts once for your install directory (defaulting to src/components/ui/) and writes a single switch.tsx — no other files, because the component's only dependency past React is the cn utility. No global config file is created; the result is TypeScript source you edit like any other component. Confirm it landed in your editor, then commit it for a clean baseline. If your project uses a monorepo layout or a non-standard source path, the CLI docs cover the flags for targeting a custom location during install. Because the file is yours after install, future Drivn releases will not overwrite your edits — the whole control, from the role="switch" button to the styling object, is one file you can read top to bottom.
1 # add the Switch source file 2 npx drivn add switch
Step 2 — Render your first switch
Import Switch and render it inside a Client Component. The Switch is controlled-only, so you always hold the checked boolean yourself and hand it back through onChange — there is no uncontrolled mode and no defaultChecked. The smallest working shape owns the value in useState and passes it straight down; onChange fires with the next boolean, so wiring it to the setter is a one-liner. The example below is a 'use client' file that toggles a single boolean. Because the file opens with 'use client', drop this into a client component or a page already inside a client boundary; the Next.js Switch guide covers importing it into a Server Component page. Under the hood the track swaps from bg-border to bg-primary and the thumb slides across — no animation library involved.
1 'use client' 2 3 import { useState } from 'react' 4 import { Switch } from '@/components/ui/switch' 5 6 export function DarkModeToggle() { 7 const [enabled, setEnabled] = useState(false) 8 9 return ( 10 <Switch 11 checked={enabled} 12 onChange={setEnabled} 13 /> 14 ) 15 }
Step 3 — Drive the checked state
Since the Switch keeps no internal state, the checked boolean always lives in your component — which makes it easy to react the moment it flips. Pass a setter to onChange and you can persist the value, fire a side effect, or mirror the state in another element. The onChange callback receives the next value directly — a plain boolean, not a DOM event — so onChange={(next) => ...} gives you the new state without reading it off the DOM. The example toggles a notifications preference and echoes the current state in a caption beside the control. Because the value is yours, a form, a reset button, or a server-loaded default can read and set the same boolean. For more copy-paste patterns see the Switch examples.
1 'use client' 2 3 import { useState } from 'react' 4 import { Switch } from '@/components/ui/switch' 5 6 export function NotificationsToggle() { 7 const [on, setOn] = useState(true) 8 9 return ( 10 <div className="flex items-center gap-3"> 11 <Switch checked={on} onChange={setOn} /> 12 <span className="text-sm text-muted-foreground"> 13 Email notifications {on ? 'on' : 'off'} 14 </span> 15 </div> 16 ) 17 }
Step 4 — Pair the switch with a label for a settings row
A switch rarely stands alone — it sits in a settings row with a label describing what it toggles. Pair it with the Drivn Label and lay the two out in a flex row: the label and a short helper line on the left, the Switch pushed to the trailing edge with justify-between. Because the Switch is a real <button> with role="switch" and aria-checked, screen readers announce it as a switch and read its on/off state with no extra ARIA from you. Wrap the row in a Card to group several toggles into a preferences panel, each with its own boolean in state. Everything visible is a Tailwind class in the styles object inside the file you own, so recoloring the checked track — bg-primary to bg-success for a confirm-style toggle — is a one-line edit; see Theming for the tokens. The example renders a labelled row with a title, a helper line, and the Switch aligned right.
1 'use client' 2 3 import { useState } from 'react' 4 import { Switch } from '@/components/ui/switch' 5 import { Label } from '@/components/ui/label' 6 7 export function MarketingRow() { 8 const [on, setOn] = useState(false) 9 10 return ( 11 <div className="flex items-center justify-between gap-4"> 12 <div className="space-y-0.5"> 13 <Label>Marketing emails</Label> 14 <p className="text-sm text-muted-foreground"> 15 Product news and occasional offers. 16 </p> 17 </div> 18 <Switch checked={on} onChange={setOn} /> 19 </div> 20 ) 21 }
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
Frequently asked questions
Yes. The Switch renders a <button> with an onClick handler, so its source opens with a 'use client' directive and must render inside a Client Component. In the Next.js App Router, put the Switch — and the useState that holds its checked boolean — in a file that starts with 'use client', then import it into your Server Component page. In a Vite or Create React App project the directive is simply ignored and the component works the same.
No. The Drivn Switch is controlled-only — it has no internal state and renders exactly the checked boolean you pass. You hold the value yourself, usually in useState, and update it from the onChange callback, which hands you the next boolean directly. There is no defaultChecked and no uncontrolled mode, so the value always lives in your component and any other element can read or set the same boolean.
Yes. Under the hood it is a <button type="button"> with role="switch" and aria-checked bound to the current value, so assistive technology announces it as a switch and reads its on/off state without any extra ARIA from you. It is keyboard focusable and toggles on Enter or Space like any button, and the focus-visible ring is built into the base class. Add an associated Label for the visible caption.
Pass a disabled prop. The Switch spreads extra props onto the underlying <button>, so disabled reaches the native element and stops it firing onClick — which means onChange never runs while disabled. The button also gains the browser default disabled styling; add disabled:opacity-50 to the base class in the file you own if you want a dimmed look that matches the rest of the Drivn UI.
Yes. The component depends only on React and the local cn utility — no Next.js API and no router — so it works anywhere React and Tailwind reach the DOM. Set the project up with TypeScript and Tailwind v4 first, then run npx drivn add switch and drive it the same way, owning the checked boolean in useState. The 'use client' directive is simply ignored outside the App Router.

