Toggle
Two-state press-button (`aria-pressed`) built on Radix Toggle, for stateful actions like text formatting and view modes.
Overview
Toggle is built on @radix-ui/react-toggle. A two-state press-button (aria-pressed) for stateful actions outside of form submission - bold / italic in a text editor, view-mode switches, filter chips.
For form-bound boolean values use Checkbox or Switch. Don't use Toggle as navigation - that's Tab or Link. ToggleGroup is the coordinated multi-button equivalent when you have several related toggles.
Preview
Installation
bash npx gremorie@latest add rx-toggle bash pnpm dlx gremorie@latest add rx-toggle bash yarn dlx gremorie@latest add rx-toggle bash bunx --bun gremorie@latest add rx-toggle Usage
import { Toggle } from "@gremorie/rx-forms";
import { Bold } from "lucide-react";
export function Example() {
return (
<Toggle aria-label="Bold">
<Bold />
</Toggle>
);
}Angular edition planned for Phase 5h. Star the repo to track progress.
API
<Toggle>
| Prop | Type | Default | Description |
|---|---|---|---|
pressed | boolean | - | Controlled pressed state. |
defaultPressed | boolean | - | Uncontrolled initial state. |
onPressedChange | (pressed: boolean) => void | - | Fires when the user toggles. |
disabled | boolean | false | Disables interaction. |
variant | "default" | "outline" | "default" | default is transparent; outline adds a border for standalone toolbar use. |
size | "default" | "sm" | "lg" | "default" | Footprint preset. |
Forwards to TogglePrimitive.Root. The pressed state is mirrored via data-state (on / off), which drives the accent background.
toggleVariants
Exported CVA factory. Reused by ToggleGroupItem so children inherit the same surface via context.
import { toggleVariants } from '@gremorie/rx-forms';
<div className={toggleVariants({ variant: 'outline', size: 'sm' })}>
Custom host
</div>;Composition
<Toggle>is a leaf. Always providearia-labelfor icon-only toggles.- Children are typically icons (formatting, view mode). For text + icon, follow the same pattern as
Button. - Use
<ToggleGroup>when you have 2-5 related toggles that should behave like a coordinated set.
Variations
Icon-only formatting toggle
The canonical text-editor pattern. Always pair with aria-label.
import { Bold } from 'lucide-react';
<Toggle aria-label="Bold">
<Bold />
</Toggle>;Default pressed
For toggles whose default state should be "on" (e.g. autosave indicator, an applied filter).
import { Italic } from 'lucide-react';
<Toggle aria-label="Italic" defaultPressed>
<Italic />
</Toggle>;Outline variant
When the toggle stands alone (not inside a ToggleGroup), the outline variant adds a visible boundary so it reads as an interactive control on flat surfaces.
import { Underline } from 'lucide-react';
<Toggle aria-label="Underline" variant="outline">
<Underline />
</Toggle>;Controlled with effect
Bind the pressed state to your editor / view state.
function BoldToggle({ editor }) {
const isBold = editor.isActive('bold');
return (
<Toggle
aria-label="Bold"
pressed={isBold}
onPressedChange={(pressed) => editor.chain().focus().toggleBold().run()}
>
<Bold />
</Toggle>
);
}Accessibility
- ARIA: Radix renders
role="button"witharia-pressedreflecting state (true/false). - Keyboard:
SpaceandEntertoggle the button;Tab/Shift+Tabmove focus. - Focus: 3px focus-visible ring driven by
focus-visible:ring-ring/50. - Icon-only: always provide
aria-label. Without it, screen readers announce the toggle with no name. - Distinction from Switch / Checkbox: Toggle is for stateful actions (apply a format, change a view). Switch and Checkbox are for stateful values (a setting, a form field). The visual treatment differs because the mental model differs.
Related
- Toggle Group - coordinated cluster of toggles
- Button - stateless single-action sibling
- Switch - boolean setting (immediate effect)
- Checkbox - boolean form value (on submit)