Toggle

A button-like component for toggling between on and off states, commonly used for formatting options like bold, italic, or other binary settings.

import { Toggle } from "~/components/toggle";

export default function ToggleBasicDemo() {
  return (
    <Toggle aria-label="Toggle bold">
      <svg
        xmlns="http://www.w3.org/2000/svg"
        width="18"
        height="18"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
      >
        <path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" />
        <path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" />
      </svg>
    </Toggle>
  );
}

Installation

CLI

Run the following command to add the component to your project:

npx @ark-preset/cli@latest add toggle

Manual

Create the recipe file at src/components/recipes/toggle.ts:

import { tv, type VariantProps } from "tailwind-variants";

export const toggleVariants = tv({
  slots: {
    root: "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium border border-input bg-transparent text-foreground hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 transition duration-300 motion-safe:hover:-translate-y-0.5 motion-safe:active:translate-y-0.5 active:duration-150 data-[state=on]:bg-primary data-[state=on]:text-primary-foreground data-[state=on]:border-transparent data-[disabled]:pointer-events-none data-[disabled]:opacity-50 w-fit",
    indicator: "inline-flex items-center justify-center",
  },
  variants: {
    size: {
      sm: { root: "h-8 px-2" },
      md: { root: "h-9 px-2.5" },
      lg: { root: "h-10 px-3" },
    },
  },
  defaultVariants: {
    size: "sm",
  },
});

export type ToggleVariants = VariantProps<typeof toggleVariants>;

Create the component directory and files.

src/components/toggle/toggle.base.tsx:

import { Toggle as ArkToggle } from "@ark-ui/solid/toggle";
import { splitProps, type Component } from "solid-js";
import { toggleVariants, type ToggleVariants } from "../recipes/toggle";

const styles = toggleVariants();

const Root: Component<ArkToggle.RootProps & ToggleVariants> = (props) => {
  const [local, others] = splitProps(props, ["class", "size"]);
  return (
    <ArkToggle.Root class={styles.root({ class: local.class, size: local.size })} {...others} />
  );
};

const Indicator: Component<ArkToggle.IndicatorProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return <ArkToggle.Indicator class={styles.indicator({ class: local.class })} {...others} />;
};

const Context = ArkToggle.Context;

export const Toggle = { Root, Indicator, Context };

src/components/toggle/index.tsx:

import { Toggle as ToggleBase } from "./toggle.base";

const Toggle = ToggleBase.Root;
const ToggleIndicator = ToggleBase.Indicator;

export { Toggle, ToggleIndicator, ToggleBase };

export { toggleVariants, type ToggleVariants } from "../recipes/toggle";

Note: Make sure your project has the Tailwind CSS theme variables set up (--background, --foreground, --ring, --border, etc.) or override the utility classes to match your design system.

Usage

Basic Usage

import { Toggle } from "~/components/toggle";

export default function ToggleBasicDemo() {
  return (
    <Toggle aria-label="Toggle bold">
      <svg
        xmlns="http://www.w3.org/2000/svg"
        width="18"
        height="18"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
      >
        <path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" />
        <path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" />
      </svg>
    </Toggle>
  );
}

Default Pressed

Use the defaultPressed prop to set the initial pressed state.

import { Toggle } from "~/components/toggle";

export default function ToggleDefaultPressedDemo() {
  return (
    <Toggle defaultPressed aria-label="Toggle bold">
      <svg
        xmlns="http://www.w3.org/2000/svg"
        width="18"
        height="18"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
      >
        <path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" />
        <path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" />
      </svg>
    </Toggle>
  );
}

Controlled

Control the pressed state programmatically using pressed and onPressedChange.

import { createSignal } from "solid-js";
import { Toggle } from "~/components/toggle";

export default function ToggleControlledDemo() {
  const [pressed, setPressed] = createSignal(false);

  return (
    <Toggle pressed={pressed()} onPressedChange={setPressed} aria-label="Toggle bold">
      <svg
        xmlns="http://www.w3.org/2000/svg"
        width="18"
        height="18"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
      >
        <path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" />
        <path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" />
      </svg>
    </Toggle>
  );
}

Disabled

Add the disabled prop to disable interaction.

import { Toggle } from "~/components/toggle";

export default function ToggleDisabledDemo() {
  return (
    <Toggle disabled aria-label="Toggle bold">
      <svg
        xmlns="http://www.w3.org/2000/svg"
        width="18"
        height="18"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
      >
        <path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" />
        <path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" />
      </svg>
    </Toggle>
  );
}

With Indicator

ToggleIndicator renders different content based on the toggle state — the fallback is shown when off, and the children are shown when on.

import { Toggle, ToggleIndicator } from "~/components/toggle";

export default function ToggleIndicatorDemo() {
  return (
    <div class="flex items-center gap-4">
      <Toggle>
        <ToggleIndicator
          fallback={
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="18"
              height="18"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <circle cx="12" cy="12" r="5" />
              <line x1="12" y1="1" x2="12" y2="3" />
              <line x1="12" y1="21" x2="12" y2="23" />
              <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
              <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
              <line x1="1" y1="12" x2="3" y2="12" />
              <line x1="21" y1="12" x2="23" y2="12" />
              <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
              <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
            </svg>
          }
        >
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="18"
            height="18"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
          >
            <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
          </svg>
        </ToggleIndicator>
      </Toggle>
      <Toggle defaultPressed>
        <ToggleIndicator
          fallback={
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="18"
              height="18"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <circle cx="12" cy="12" r="5" />
              <line x1="12" y1="1" x2="12" y2="3" />
              <line x1="12" y1="21" x2="12" y2="23" />
              <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
              <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
              <line x1="1" y1="12" x2="3" y2="12" />
              <line x1="21" y1="12" x2="23" y2="12" />
              <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
              <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
            </svg>
          }
        >
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="18"
            height="18"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
          >
            <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
          </svg>
        </ToggleIndicator>
      </Toggle>
    </div>
  );
}

Sizes

Use the size prop to change the toggle size.

import { Toggle } from "~/components/toggle";

export default function ToggleSizesDemo() {
  return (
    <div class="flex flex-wrap items-center gap-4">
      <Toggle size="sm">Sm</Toggle>
      <Toggle size="md">Md</Toggle>
      <Toggle size="lg">Lg</Toggle>
    </div>
  );
}

API Reference

See the Ark UI Toggle documentation.