Build websites 10x faster with HextaUI Blocks — Learn more
UI/UI

Toggle

A two-state button that can be either on or off with smooth transitions and multiple variants.

<div className="flex gap-4 flex-wrap">
  <Toggle>Default</Toggle>
  <Toggle variant="outline">Outline</Toggle>
  <Toggle variant="ghost">Ghost</Toggle>
  <Toggle variant="secondary">Secondary</Toggle>
</div>

Installation

Install following dependencies:

npm install @radix-ui/react-toggle class-variance-authority
pnpm add @radix-ui/react-toggle class-variance-authority
yarn add @radix-ui/react-toggle class-variance-authority
bun add @radix-ui/react-toggle class-variance-authority

Copy and paste the following code into your project.

components/ui/toggle.tsx
"use client";

import * as React from "react";
import * as TogglePrimitive from "@radix-ui/react-toggle";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const toggleVariants = cva(
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[var(--radius)] text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
  {
    variants: {
      variant: {
        default:
          "border border-[hsl(var(--hu-border))] text-[hsl(var(--hu-foreground))] hover:bg-[hsl(var(--hu-accent))] hover:text-[hsl(var(--hu-accent-foreground))] data-[state=on]:bg-[hsl(var(--hu-primary))] data-[state=on]:text-[hsl(var(--hu-primary-foreground))] focus-visible:ring-[hsl(var(--hu-ring))]",
        outline:
          "border border-[hsl(var(--hu-border))] text-[hsl(var(--hu-foreground))] hover:bg-[hsl(var(--hu-accent))] hover:text-[hsl(var(--hu-accent-foreground))] data-[state=on]:bg-[hsl(var(--hu-accent))] data-[state=on]:text-[hsl(var(--hu-accent-foreground))] data-[state=on]:border-[hsl(var(--hu-primary))] focus-visible:ring-[hsl(var(--hu-ring))]",
        ghost:
          "text-[hsl(var(--hu-foreground))] hover:bg-[hsl(var(--hu-accent))] hover:text-[hsl(var(--hu-accent-foreground))] data-[state=on]:bg-[hsl(var(--hu-accent))] data-[state=on]:text-[hsl(var(--hu-accent-foreground))] focus-visible:ring-[hsl(var(--hu-ring))]",
        secondary:
          "bg-[hsl(var(--hu-secondary))] text-[hsl(var(--hu-secondary-foreground))] hover:bg-[hsl(var(--hu-secondary))]/80 data-[state=on]:bg-[hsl(var(--hu-primary))] data-[state=on]:text-[hsl(var(--hu-primary-foreground))] focus-visible:ring-[hsl(var(--hu-ring))]",
      },
      size: {
        default: "h-9 px-4 py-2",
        sm: "h-8 px-3 text-xs",
        lg: "h-10 px-8",
        xl: "h-12 px-10 text-base",
        icon: "h-9 w-9",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  },
);

export interface ToggleProps
  extends React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root>,
    VariantProps<typeof toggleVariants> {
  leftIcon?: React.ReactNode;
  rightIcon?: React.ReactNode;
}

const Toggle = React.forwardRef<
  React.ElementRef<typeof TogglePrimitive.Root>,
  ToggleProps
>(
  (
    { className, variant, size, leftIcon, rightIcon, children, ...props },
    ref,
  ) => (
    <TogglePrimitive.Root
      ref={ref}
      className={cn(toggleVariants({ variant, size, className }))}
      {...props}
    >
      {leftIcon && leftIcon}
      {children}
      {rightIcon && rightIcon}
    </TogglePrimitive.Root>
  ),
);

Toggle.displayName = TogglePrimitive.Root.displayName;

export { Toggle, toggleVariants };
npx hextaui@latest add toggle
pnpm dlx hextaui@latest add toggle
yarn dlx hextaui@latest add toggle
bun x hextaui@latest add toggle

Usage

import { Toggle } from "@/components/ui/Toggle";
<Toggle>Toggle me</Toggle>

Examples

Default

<Toggle>Toggle me</Toggle>

Variants

<Toggle variant="default">Default</Toggle>
<Toggle variant="outline">Outline</Toggle>
<Toggle variant="ghost">Ghost</Toggle>
<Toggle variant="secondary">Secondary</Toggle>

Sizes

<Toggle size="sm">Small</Toggle>
<Toggle size="default">Default</Toggle>
<Toggle size="lg">Large</Toggle>
<Toggle size="xl">Extra Large</Toggle>

With Icons

<Toggle leftIcon={<Bold />}>Bold</Toggle>
<Toggle rightIcon={<Italic />}>Italic</Toggle>
<Toggle variant="outline" leftIcon={<Underline />}>
  Underline
</Toggle>
<Toggle variant="secondary" rightIcon={<Heart />}>
  Like
</Toggle>

Icon Only

<Toggle variant="outline" size="icon">
  <AlignLeft />
</Toggle>
<Toggle variant="ghost" size="icon">
  <AlignCenter />
</Toggle>
<Toggle variant="secondary" size="icon">
  <AlignRight />
</Toggle>

Text Formatting

Text Formatting

Toggle buttons for text formatting options

Text Alignment

Toggle buttons for text alignment with icons only

// Text Formatting
<div className="flex gap-2">
  <Toggle leftIcon={<Bold />} aria-label="Toggle bold">
    Bold
  </Toggle>
  <Toggle leftIcon={<Italic />} aria-label="Toggle italic">
    Italic
  </Toggle>
  <Toggle leftIcon={<Underline />} aria-label="Toggle underline">
    Underline
  </Toggle>
</div>

// Text Alignment
<div className="flex gap-2">
  <Toggle variant="outline" size="icon" aria-label="Align left">
    <AlignLeft />
  </Toggle>
  <Toggle variant="outline" size="icon" aria-label="Align center">
    <AlignCenter />
  </Toggle>
  <Toggle variant="outline" size="icon" aria-label="Align right">
    <AlignRight />
  </Toggle>
</div>

Interactive Toggles

Interactive Toggles

Toggle buttons with state management and different variants

const [isLiked, setIsLiked] = React.useState(false);
const [isStarred, setIsStarred] = React.useState(false);

<Toggle
  variant="outline"
  pressed={isLiked}
  onPressedChange={setIsLiked}
  leftIcon={<Heart className={isLiked ? "fill-current" : ""} />}
  aria-label="Toggle like"
>
  {isLiked ? "Liked" : "Like"}
</Toggle>

<Toggle
  variant="ghost"
  pressed={isStarred}
  onPressedChange={setIsStarred}
  leftIcon={<Star className={isStarred ? "fill-current" : ""} />}
  aria-label="Toggle star"
>
  {isStarred ? "Starred" : "Star"}
</Toggle>

Controlled State

<Toggle pressed={false}>Always Off</Toggle>
<Toggle defaultPressed={true}>Default On</Toggle>

Toggle Groups

Toggles can be grouped together to create more complex interactive components like toolbars and formatting controls.

Rich Text Toolbar

Text Formatting Group

Multiple toggles can be active at once

Active formats: none

Text Alignment Group

Only one alignment can be active at a time

Current alignment: left

List Type Group

Toggle between different list types or none

Current list type: none

Rich Text Toolbar

Complete text editor toolbar with grouped toggles

const [formatting, setFormatting] = React.useState<string[]>([]);
const [alignment, setAlignment] = React.useState("left");
const [listType, setListType] = React.useState("");

// Text Formatting Group (Multiple Selection)
<div className="flex gap-1 border rounded-lg p-1">
  <Toggle
    size="sm"
    variant="ghost"
    pressed={formatting.includes("bold")}
    onPressedChange={(pressed) => {
      setFormatting(prev =>
        pressed
          ? [...prev, "bold"]
          : prev.filter(f => f !== "bold")
      );
    }}
  >
    <Bold className="h-4 w-4" />
  </Toggle>
  <Toggle
    size="sm"
    variant="ghost"
    pressed={formatting.includes("italic")}
    onPressedChange={(pressed) => {
      setFormatting(prev =>
        pressed
          ? [...prev, "italic"]
          : prev.filter(f => f !== "italic")
      );
    }}
  >
    <Italic className="h-4 w-4" />
  </Toggle>
  <Toggle
    size="sm"
    variant="ghost"
    pressed={formatting.includes("underline")}
    onPressedChange={(pressed) => {
      setFormatting(prev =>
        pressed
          ? [...prev, "underline"]
          : prev.filter(f => f !== "underline")
      );
    }}
  >
    <Underline className="h-4 w-4" />
  </Toggle>
</div>

// Text Alignment Group (Single Selection)
<div className="flex gap-1 border rounded-lg p-1">
  <Toggle
    size="sm"
    variant="ghost"
    pressed={alignment === "left"}
    onPressedChange={() => setAlignment("left")}
  >
    <AlignLeft className="h-4 w-4" />
  </Toggle>
  <Toggle
    size="sm"
    variant="ghost"
    pressed={alignment === "center"}
    onPressedChange={() => setAlignment("center")}
  >
    <AlignCenter className="h-4 w-4" />
  </Toggle>
  <Toggle
    size="sm"
    variant="ghost"
    pressed={alignment === "right"}
    onPressedChange={() => setAlignment("right")}
  >
    <AlignRight className="h-4 w-4" />
  </Toggle>
</div>

// List Type Group (Toggle Off)
<div className="flex gap-1 border rounded-lg p-1">
  <Toggle
    size="sm"
    variant="ghost"
    pressed={listType === "bulleted"}
    onPressedChange={(pressed) => {
      setListType(pressed ? "bulleted" : "");
    }}
  >
    <List className="h-4 w-4" />
  </Toggle>
  <Toggle
    size="sm"
    variant="ghost"
    pressed={listType === "numbered"}
    onPressedChange={(pressed) => {
      setListType(pressed ? "numbered" : "");
    }}
  >
    <ListOrdered className="h-4 w-4" />
  </Toggle>
</div>

Props

PropTypeDefault
className?
string
undefined
disabled?
boolean
false
rightIcon?
React.ReactNode
undefined
leftIcon?
React.ReactNode
undefined
onPressedChange?
(pressed: boolean) => void
undefined
defaultPressed?
boolean
false
pressed?
boolean
undefined
size?
"sm" | "default" | "lg" | "xl" | "icon"
"default"
variant?
"default" | "outline" | "ghost" | "secondary"
"default"
Edit on GitHub

Last updated on