Installation
pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/toggle.jsonInstalls to @/components/ui/toggle.
Manual installation
Install the dependencies:
pnpm add @base-ui/react@^1.8.0 class-variance-authority@^0.7.1Copy the source into your project:
'use client'
import { Toggle as TogglePrimitive } from '@base-ui/react/toggle'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const toggleVariants = cva(
"group/toggle inline-flex items-center justify-center gap-1 rounded-lg text-sm font-medium whitespace-nowrap transition-all outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-accent aria-pressed:text-accent-foreground data-pressed:bg-accent data-pressed:text-accent-foreground dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
default: 'bg-transparent',
outline: 'border border-input bg-transparent hover:bg-accent hover:text-accent-foreground',
},
size: {
default: 'h-10 min-w-10 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5',
sm: "h-8 min-w-8 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
lg: 'h-12 min-w-12 px-4 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
)
function Toggle({
className,
variant = 'default',
size = 'default',
...props
}: TogglePrimitive.Props & VariantProps<typeof toggleVariants>) {
return <TogglePrimitive data-slot="toggle" className={cn(toggleVariants({ variant, size, className }))} {...props} />
}
export { Toggle, toggleVariants }A two-state button that can be either on or off. Built on the Base UI Toggle primitive with customizable variants and sizes.
Features
- Two-state toggle functionality (pressed/unpressed)
- Built on the Base UI Toggle primitive for accessibility
- Two visual variants:
defaultandoutline - Three sizes:
sm,default,lg - Icon spacing via
data-icon="inline-start"/data-icon="inline-end"; SVGs are sized automatically - Controlled (
pressed) and uncontrolled (defaultPressed) modes - Disabled state and
aria-invalidstyling toggleVariantsexported for reuse (also used byToggleGroup)
Usage
import { Toggle } from "@/components/ui/toggle"
import { Bold } from "lucide-react"
// Icon only
<Toggle aria-label="Toggle bold">
<Bold />
</Toggle>
// With text
<Toggle aria-label="Toggle bold">Bold</Toggle>
// With icon and text
<Toggle aria-label="Toggle bold">
<Bold data-icon="inline-start" />
Bold
</Toggle>
// Initially pressed (uncontrolled)
<Toggle defaultPressed aria-label="Toggle bold">
<Bold />
</Toggle>
// Controlled toggle
const [isPressed, setIsPressed] = useState(false)
<Toggle pressed={isPressed} onPressedChange={setIsPressed} aria-label="Toggle bold">
<Bold />
</Toggle>
// Variant and size
<Toggle variant="outline" size="lg" aria-label="Toggle bold">
<Bold />
</Toggle>Examples
Default
An outline toggle with an icon and text. The pin icon is filled while the toggle is on, using the group/toggle state.
Variants
default is transparent until pressed; outline adds a border so the toggle reads as a control on busy backgrounds.
Sizes
Three sizes: sm, default and lg.
With Text
Small outline toggles with icons and counts, used as map layer filters for municipal facilities.
Formatting Toolbar
Independent toggles in an editor toolbar for a decision letter. The preview text follows the pressed state.
Follow Case
A toggle per row to follow public cases. The icon and label change with the state; aria-pressed tells screen readers whether it's on.
Disabled
Disabled toggles in both states and variants.
Controlled
A controlled toggle (pressed / onPressedChange) that filters a case list to overdue cases.
Props
Toggle
variant?: "default" | "outline"- The visual style variant (default"default")size?: "sm" | "default" | "lg"- The size of the toggle (default"default")pressed?: boolean- The pressed state of the toggle (controlled)defaultPressed?: boolean- The default pressed state (uncontrolled)onPressedChange?: (pressed: boolean, eventDetails) => void- Callback when pressed state changesdisabled?: boolean- Whether the toggle is disabledclassName?: string- Additional CSS classes- Extends all Base UI Toggle props
Styling
The pressed state is styled with data-pressed / aria-pressed (accent background). Override it with className:
<Toggle className="data-pressed:bg-primary data-pressed:text-primary-foreground" aria-label="Toggle bold">
<Bold />
</Toggle>Accessibility
- Built on the Base UI Toggle primitive with full keyboard support
- Renders a
buttonwitharia-pressedreflecting the state Space/Entertoggle the pressed state- Always provide an
aria-labelfor icon-only toggles
Best Practices
-
Labeling
- Always provide meaningful
aria-labelattributes - Consider tooltips for icon-only toggles
- Ensure labels describe the action, not the current state
- Always provide meaningful
-
State Management
- Use controlled mode when the state lives elsewhere
- Use
ToggleGroupfor sets of related options
-
Visual Design
- Use consistent sizing within a toolbar
- Use
outlinewhen toggles sit on a busy background
Common Use Cases
- Text Formatting: Bold, italic, underline toggles in editors
- Layout Controls: Text alignment, view modes, display options
- Filter Controls: Show/hide content categories
- Media Controls: Mute/unmute, shuffle/repeat
Integration Examples
With React Hook Form
import { useForm, Controller } from 'react-hook-form'
import { Toggle } from '@/components/ui/toggle'
function SettingsForm() {
const { control, handleSubmit } = useForm()
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="notifications"
control={control}
render={({ field }) => (
<Toggle
pressed={field.value}
onPressedChange={field.onChange}
aria-label="Enable notifications"
>
Enable Notifications
</Toggle>
)}
/>
</form>
)
}Related Components
- Toggle Group
- Button
- Checkbox
- Switch
- Radio Group
- Tabs