For the complete documentation index, see llms.txt. Markdown variants are available by appending .md to documentation URLs. Homepage markdown is available at /llms.md/content.md. An agent skill is available at /.well-known/agent-skills/site-skill.md.

Toggle

The Toggle component from this design system, built on Base UI.

Installation

$ pnpm dlx shadcn@latest add @cobalt/toggle

Options

  • variant="ghost" has no resting surface. It fills with the muted surface on hover and while pressed, for toolbars and settings rows.
  • variant="choice" is a selectable card or chip: a hairline border that turns primary, with a faint primary tint, while pressed. ToggleGroupItem takes it too.
  • variant="swatch" is a round colour swatch with no fill: pressed shows a ring around the child. Use it with size="icon-sm" and a coloured dot inside.
  • Icon sizes icon-xs (24px), icon-sm (32px), icon (36px) and icon-lg (40px) give a square toggle with no horizontal padding, matching Button. ToggleGroupItem takes the same sizes.

Source

"use client";

import { Toggle as TogglePrimitive } from "@base-ui/react/toggle";
import { cva } from "class-variance-authority";
import type { VariantProps } from "class-variance-authority";
import { cn } from "cn";

const toggleVariants = cva(
  "group/toggle relative isolate inline-flex shrink-0 cursor-pointer items-center justify-center gap-0.5 rounded-[10px] text-sm before:pointer-events-none before:absolute before:inset-0 before:-z-10 before:rounded-[inherit] font-medium whitespace-nowrap text-foreground outline-none select-none [transition:background-color_150ms_ease,border-color_150ms_ease,box-shadow_150ms_ease,color_150ms_ease,scale_420ms_cubic-bezier(0.4,0,0.2,1)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background active:scale-[0.98] active:[transition-duration:150ms,150ms,150ms,150ms,220ms] data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 motion-reduce:transition-none motion-reduce:active:scale-100 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-5",
  {
    defaultVariants: {
      size: "default",
      variant: "default",
    },
    variants: {
      size: {
        default:
          "h-9 min-w-9 px-2.75 has-data-[icon=inline-end]:pr-1.75 has-data-[icon=inline-start]:pl-1.75",
        icon: "size-9 min-w-9 px-0",
        "icon-lg": "size-10 min-w-10 px-0",
        "icon-sm":
          "size-8 min-w-8 rounded-[8px] px-0 [&_svg:not([class*='size-'])]:size-[18px]",
        "icon-xs":
          "size-6 min-w-6 rounded-[4px] px-0 [&_svg:not([class*='size-'])]:size-3.5",
        lg: "h-10 min-w-10 px-2.75 has-data-[icon=inline-end]:pr-1.75 has-data-[icon=inline-start]:pl-1.75",
        sm: "h-8 min-w-8 rounded-[8px] px-2.25 has-data-[icon=inline-end]:pr-1.75 has-data-[icon=inline-start]:pl-1.75 [&_svg:not([class*='size-'])]:size-[18px]",
      },
      variant: {
        // The pressed fill, border and ring sit on the `before:` layer, which
        // has no transition, so switching between toggles is instant instead
        // of the old one fading out. Hover keeps its fade on the element.
        default:
          "bg-transparent hover:bg-muted data-pressed:before:bg-[var(--surface-strong,var(--muted))] data-pressed:text-foreground",
        ghost:
          "bg-transparent hover:bg-muted data-pressed:before:bg-muted data-pressed:text-foreground",
        // A selectable card or chip: hairline border, primary border and tint when pressed.
        choice:
          "border border-border bg-transparent hover:not-data-pressed:bg-muted data-pressed:before:-inset-px data-pressed:before:border data-pressed:before:border-primary data-pressed:before:bg-primary/5 data-pressed:text-foreground",
        // A round colour swatch: no fill, a ring when pressed.
        swatch:
          "rounded-full border-0 bg-transparent p-0 hover:bg-transparent data-pressed:before:ring-2 data-pressed:before:ring-foreground/50 data-pressed:before:ring-offset-1 data-pressed:before:ring-offset-background",
        outline:
          "border border-border bg-popover shadow-[shadow:var(--shadow-control,0_1px_2px_0_rgb(0_0_0/0.05))] hover:border-[var(--border-hover,var(--border))] hover:bg-[var(--surface-hover,var(--muted))] active:border-[var(--foreground-subtle,var(--border))] active:bg-[var(--surface-strong,var(--muted))] data-pressed:before:bg-[var(--surface-strong,var(--muted))] data-disabled:shadow-none",
      },
    },
  }
);

function Toggle({
  className,
  variant = "default",
  size = "default",
  ...props
}: TogglePrimitive.Props & VariantProps<typeof toggleVariants>) {
  return (
    <TogglePrimitive
      data-slot="toggle"
      className={cn(toggleVariants({ className, size, variant }))}
      {...props}
    />
  );
}

export { Toggle, toggleVariants };