Skip to main content
Gremorie

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

'use client';import { Toggle } from '@gremorie/rx-forms';import { Bold, Italic, Underline } from 'lucide-react';export function TogglePreview() {  return (    <div className="flex gap-2">      <Toggle aria-label="Bold">        <Bold className="size-4" />      </Toggle>      <Toggle aria-label="Italic" defaultPressed>        <Italic className="size-4" />      </Toggle>      <Toggle aria-label="Underline">        <Underline className="size-4" />      </Toggle>    </div>  );}

Anatomy

Toggle   single two-state button (aria-pressed) styled by toggleVariants; data-[state=on] paints the active look

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>
  );
}

The Angular edition of this component ships from source today (see the workbench for the side-by-side); its registry entry is coming next.

API

<Toggle>

PropTypeDefaultDescription
pressedboolean-Controlled pressed state.
defaultPressedboolean-Uncontrolled initial state.
onPressedChange(pressed: boolean) => void-Fires when the user toggles.
disabledbooleanfalseDisables 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

  1. <Toggle> is a leaf. Always provide aria-label for icon-only toggles.
  2. Children are typically icons (formatting, view mode). For text + icon, follow the same pattern as Button.
  3. 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.

'use client';import { Toggle } from '@gremorie/rx-forms';import { Underline } from 'lucide-react';export function ToggleOutlinePreview() {  return (    <Toggle aria-label="Underline" variant="outline">      <Underline />    </Toggle>  );}

Sizes

Three footprint presets - sm, default, lg - keep toggles aligned with neighbouring controls in a toolbar.

'use client';import { Toggle } from '@gremorie/rx-forms';import { Bold } from 'lucide-react';export function ToggleSizesPreview() {  return (    <div className="flex flex-wrap items-center gap-3">      <Toggle aria-label="Bold" size="sm">        <Bold />      </Toggle>      <Toggle aria-label="Bold" size="default">        <Bold />      </Toggle>      <Toggle aria-label="Bold" size="lg">        <Bold />      </Toggle>    </div>  );}

Disabled

disabled removes the toggle from the tab order and drops its opacity. The pressed state is still visible so users can see what was active.

'use client';import { Toggle } from '@gremorie/rx-forms';import { Italic } from 'lucide-react';export function ToggleDisabledPreview() {  return (    <Toggle aria-label="Italic" disabled defaultPressed>      <Italic />    </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" with aria-pressed reflecting state (true / false).
  • Keyboard: Space and Enter toggle the button; Tab / Shift+Tab move 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.
  • Toggle Group - coordinated cluster of toggles
  • Button - stateless single-action sibling
  • Switch - boolean setting (immediate effect)
  • Checkbox - boolean form value (on submit)

On this page