Xzero UI
Pro library

Button group

Related actions joined into one surface with hairline dividers: a hover highlight glides between segments, the pressed one answers in place, and an attached menu can close the row.

$ pnpm dlx shadcn@4.23.0 add @xzero-ui/button-group
Live · keyboard ready

When to use

When not to use

Installation

Register both Xzero UI namespaces in your project's components.json:

{
  "registries": {
    "@xzero-ui": "https://mcp.xzero.co.uk/r/{name}.json",
    "@xzero-ui-pro": "https://mcp.xzero.co.uk/r/pro/{name}.json"
  }
}
npx shadcn@4.23.0 add @xzero-ui/button-group

Install xzero-foundation once, and import @/components/xzero/foundation.css in your app root. No Xzero token is used by this snapshot. For sample portraits, photos, fonts, or logos, download https://mcp.xzero.co.uk/install-assets.mjs and run node xzero-install-assets.mjs <receiving-project>.

Usage

import { Archive, Check, CopyPlus, Link, Pin, Trash2 } from "lucide-react";
import { ButtonGroup } from "@/components/xzero/button-group/button-group";

export function DocumentActions({ doc }: { doc: Doc }) {
  const [copied, setCopied] = useState(false);
  return (
    <ButtonGroup
      label="Document actions"
      items={[
        {
          id: "share",
          label: copied ? "Copied" : "Share",
          reserve: ["Share", "Copied"],
          icon: copied ? <Check /> : <Link />,
          onSelect: async () => {
            await navigator.clipboard.writeText(doc.url);
            setCopied(true);
            setTimeout(() => setCopied(false), 1800);
          },
        },
        { id: "duplicate", label: "Duplicate", icon: <CopyPlus />, onSelect: () => duplicate(doc.id) },
        { id: "archive", label: "Archive", icon: <Archive />, onSelect: () => archive(doc.id) },
      ]}
      menu={{
        label: "More actions",
        items: [
          { id: "pin", label: "Pin to sidebar", icon: <Pin />, onSelect: () => pin(doc.id) },
          { id: "delete", label: "Delete", icon: <Trash2 />, destructive: true, onSelect: () => remove(doc.id) },
        ],
      }}
    />
  );
}

API reference

ButtonGroup

Related actions joined into one surface with hairline dividers: a hover highlight glides between segments, the pressed one answers in place, and an attached menu can close the row.

PropTypeDefaultDescription
items (required){ id: string; label: string; reserve?: string[]; icon?: ReactNode; iconOnly?: boolean; content?: ReactNode; onSelect?: () => void; href?: string; disabled?: boolean }[]–The segments in order. label is the visible text and the accessible name; changing it right after a press ("Share" to "Copied") crossfades in place and is announced. reserve lists every other label the segment can show, so it is sized to the widest and never resizes; a label nobody reserved springs the width instead. iconOnly shows only the icon and moves the label to aria-label and the tooltip. content replaces the visible label with a live value, such as a zoom level. href renders a link.
label (required)string–Accessible name of the group, such as "Document actions".
menu{ label: string; items: { id: string; label: string; icon?: ReactNode; onSelect?: () => void; href?: string; disabled?: boolean; destructive?: boolean }[] }–Adds a trailing chevron segment that opens more actions. menu.label names the chevron, such as "More actions". The menu aligns to the group's end edge (start edge when vertical).
variant"outline" | "solid""outline"outline sits on a neutral surface with a quiet border. solid takes the primary button's fill, for the main actions of a view.
size"sm" | "md""md"Matches the small and medium button heights.
orientation"horizontal" | "vertical""horizontal"Vertical stacks the segments, such as zoom controls on a canvas; arrow up and down then move between them.
collapseLabelsbooleantrueWhen a horizontal row no longer fits its container, segments with an icon drop their label and keep the icon (the label moves to aria-label and the tooltip). Labels return once the container is wide enough again.
disabledbooleanfalseDisables every segment and the menu.
classNamestring–Extra class on the group.
styleCSSProperties–Inline styles on the group.

Keyboard interactions

KeysAction
Tab / Shift+TabMoves into, through and out of the group. Every segment is a tab stop, so the group behaves like the buttons it replaces.
Arrow right / Arrow leftAlso moves between segments in a horizontal group, wrapping at the ends (mirrored in right to left layouts).
Arrow down / Arrow upMoves between segments in a vertical group. On the chevron segment, Arrow down opens the menu instead.
Home / EndMoves to the first or last segment.
Enter / SpaceActivates the focused segment. Link segments follow the link with Enter.
Enter / Space / Arrow downOn the chevron segment: opens the menu with the first item highlighted.
Arrow keys / Home / EndIn the menu: moves between items, looping at the ends; disabled items are skipped.
EscapeCloses the menu and returns focus to the chevron.
Tab (menu open)Closes the menu and moves on, since the menu is non-modal.

Accessibility

Motion

Responsive behavior

Performance

Notes for AI

Also in buttons

Guidance for AI tools

Button group: Related actions joined into one surface with hairline dividers: a hover highlight glides between segments, the pressed one answers in place, and an attached menu can close the row. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.

Full library index: https://ui.xzero.co.uk/llms.txt

Browse all components