ButtonGroup & Toolbar

Segmented single-choice button set, and a Toolbar container for grouped actions.

import { ButtonGroup, Toolbar, ButtonGroupOption }from "@/components/ui"

Segmented + toolbar

Full source

The complete component, read from src/components/ui/button-group.tsx.

button-group.tsx
"use client";

import { cn } from "@/lib/utils";

export interface ButtonGroupOption {
  value: string;
  label: React.ReactNode;
  disabled?: boolean;
}

interface ButtonGroupProps {
  options: ButtonGroupOption[];
  value: string;
  onChange: (value: string) => void;
  "aria-label"?: string;
  className?: string;
}

export function ButtonGroup({ options, value, onChange, className, ...rest }: ButtonGroupProps) {
  return (
    <div
      role="group"
      aria-label={rest["aria-label"]}
      className={cn("inline-flex overflow-hidden rounded-lg border border-border", className)}
    >
      {options.map((opt, i) => {
        const on = opt.value === value;
        return (
          <button
            key={opt.value}
            type="button"
            aria-pressed={on}
            disabled={opt.disabled}
            onClick={() => onChange(opt.value)}
            className={cn(
              "px-3 py-1.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-accent",
              i > 0 && "border-l border-border",
              on ? "bg-accent text-on-accent" : "bg-surface text-text-secondary hover:bg-bg-hover hover:text-text",
              opt.disabled && "cursor-not-allowed opacity-50"
            )}
          >
            {opt.label}
          </button>
        );
      })}
    </div>
  );
}

export function Toolbar({ children, className, "aria-label": ariaLabel }: { children: React.ReactNode; className?: string; "aria-label"?: string }) {
  return (
    <div
      role="toolbar"
      aria-label={ariaLabel}
      className={cn("flex flex-wrap items-center gap-2 rounded-lg border border-border bg-surface p-2", className)}
    >
      {children}
    </div>
  );
}

Dependencies

To use ButtonGroup & Toolbar you also need the following. Copy these into your project alongside it.

Related components

Recommended folder structure

project structure
src/components/ui/
  ├─ button-group.tsx
  ├─ button.tsx
  └─ separator.tsx
src/hooks/
  ├─ use-mounted.ts
  └─ use-popover-position.ts
src/lib/
  └─ utils.ts