Skip to content
Brok UI

Loading…

No results

Button Group

Open source

Groups related buttons and inputs into a single attached unit.

Version
v1.1.2
Stability
stable
License
MIT
Related
Input Group
Toggle Group
Segmented Nav

Preview

Orientation
previews.components.button-group.default.blade.php Blade
<x-ui.button-group>
    <x-ui.button variant="outline">{{ __('Years') }}</x-ui.button>
    <x-ui.button variant="outline">{{ __('Months') }}</x-ui.button>
    <x-ui.button variant="outline">{{ __('Days') }}</x-ui.button>
</x-ui.button-group>
Horizontal Current
Vertical Current

Installation

terminal
php artisan ui:add button-group

Registry contract

php artisan ui:add button-group writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.

  • blade resources/views/components/ui/button-group.blade.php
  • blade resources/views/components/ui/button-group/text.blade.php
  • blade resources/views/components/ui/button-group/separator.blade.php
Registry dependencies
separator
Packages
composer: jml/brok:^0.2

Use with AI

A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.

button-group.md
# Brok UI: Button Group (`button-group`)

Groups related buttons and inputs into a single attached unit.

Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.

## Install

```bash
php artisan ui:add button-group
```

## Usage

```blade
<x-ui.button-group>
    <x-ui.button variant="outline">{{ __('Years') }}</x-ui.button>
    <x-ui.button variant="outline">{{ __('Months') }}</x-ui.button>
    <x-ui.button variant="outline">{{ __('Days') }}</x-ui.button>
</x-ui.button-group>
```

## Props

- `orientation` (string, default `horizontal`) — Lays children out in a row or a column and trims the matching borders and corners.

## Use when

- Use for explicit actions and commits; keep one action visually primary when several sit together.
- Visually attaching a row or column of related buttons, or a button and an input, so they read as one control.
- Building a toolbar or split-action bar where adjacent controls should share borders and merge corner radii.
- Building a split button: the primary action plus an attached <x-ui.dropdown> whose trigger opens the related actions (see the split example).

## Avoid when

- Do not use button treatment for navigation-only view changes when links, tabs, or radios describe state more clearly.
- Building a mutually-exclusive view switch; use segmented-nav or a toggle group instead.
- Spacing unrelated actions apart; use a plain flex row with gap instead.

## Anti-patterns

- Using a button for navigation-only state changes
- Presenting multiple equally dominant primary actions

## Rules

- Use the `<brok:button-group>` tag (or `<x-ui.button-group>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.

## Links

- Docs: https://brokui.dev/docs/components/button-group
- Registry JSON (files, props, contract): https://brokui.dev/r/open/button-group.json

Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.

Examples

split.blade.php Blade
{{-- A split button: the primary action and an attached menu trigger for the related actions. Two buttons, two tab stops; the trigger names what it opens. --}}
<div class="flex flex-wrap items-center gap-4">
    <x-ui.button-group :aria-label="__('Save options')">
        <x-ui.button type="button">{{ __('Save') }}</x-ui.button>
        <x-ui.dropdown>
            <x-ui.dropdown.trigger variant="default" size="icon" class="border-s border-s-primary-foreground/20" :aria-label="__('More save options')">
                <x-ui.icon name="chevron-down" size="sm" />
            </x-ui.dropdown.trigger>
            <x-ui.dropdown.content align="end">
                <x-ui.dropdown.item>{{ __('Save as draft') }}</x-ui.dropdown.item>
                <x-ui.dropdown.item>{{ __('Save and close') }}</x-ui.dropdown.item>
                <x-ui.dropdown.item>{{ __('Save as template') }}</x-ui.dropdown.item>
            </x-ui.dropdown.content>
        </x-ui.dropdown>
    </x-ui.button-group>

    <x-ui.button-group :aria-label="__('Export options')">
        <x-ui.button type="button" variant="outline">{{ __('Export') }}</x-ui.button>
        <x-ui.dropdown>
            <x-ui.dropdown.trigger variant="outline" size="icon" :aria-label="__('More export formats')">
                <x-ui.icon name="chevron-down" size="sm" />
            </x-ui.dropdown.trigger>
            <x-ui.dropdown.content align="end">
                <x-ui.dropdown.item>{{ __('Export as CSV') }}</x-ui.dropdown.item>
                <x-ui.dropdown.item>{{ __('Export as PDF') }}</x-ui.dropdown.item>
            </x-ui.dropdown.content>
        </x-ui.dropdown>
    </x-ui.button-group>
</div>

API

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
Prop Type Default Description
orientation string horizontal Lays children out in a row or a column and trims the matching borders and corners.

Slots

  • default — Buttons, inputs or other controls to attach together.
  • x-ui.button-group.text — Installed subcomponent from the registry item.
  • x-ui.button-group.separator — Installed subcomponent from the registry item.

Data slots

Stable hooks for CSS overrides and browser tests.

button-group button-group-separator button-group-text

Behavior

  • Removes the rounded corners and doubled border between adjacent children so the group reads as one attached unit.
  • orientation="vertical" stacks children in a column and switches which corners and borders are trimmed.
  • A <x-ui.dropdown> child is a wrapper, so the group also trims the joined corners of the dropdown-trigger inside it; a split button needs no extra classes to attach.
  • The focused child rises above its neighbour, so its focus ring is never covered by the next control.
  • Declares registry capability flags: a11y, responsive, rtl, darkMode, localized.

Guidance

Buttons and direct actions

Commit an explicit user action.

Use when

  • Use for explicit actions and commits; keep one action visually primary when several sit together.
  • Visually attaching a row or column of related buttons, or a button and an input, so they read as one control.
  • Building a toolbar or split-action bar where adjacent controls should share borders and merge corner radii.
  • Building a split button: the primary action plus an attached <x-ui.dropdown> whose trigger opens the related actions (see the split example).

Avoid when

  • Do not use button treatment for navigation-only view changes when links, tabs, or radios describe state more clearly.
  • Building a mutually-exclusive view switch; use segmented-nav or a toggle group instead.
  • Spacing unrelated actions apart; use a plain flex row with gap instead.

Use instead

  • Link for navigation
  • Tabs or radios for view changes

Anti-patterns

  • Using a button for navigation-only state changes
  • Presenting multiple equally dominant primary actions
Anatomy
root button-group-separator button-group-text
Theming hooks
button-group button-group-separator button-group-text

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
Keyboard
No component-owned keyboard interaction; native element behavior applies.
Focus
none
  • The root carries role="group" so assistive technology announces the buttons as a related set.
  • Each child keeps its own accessible name and focus stop; the group adds no extra tab stop.
  • Split button: two real buttons and two tab stops. Give the group an aria-label, and give the icon-only dropdown-trigger an aria-label that names what it opens ("More save options"); the trigger already carries aria-haspopup="menu" and aria-expanded.
  • On a filled primary half, separate the two halves with a hairline on the trigger (border-s border-s-primary-foreground/20) so they read as two controls.
  • Semantic HTML and a stable data-slot attribute for styling and scripting hooks.
  • Focus-visible rings use the ring token, so keyboard focus is always visible.
  • Disabled and invalid states are conveyed to assistive tech, not by color alone.
  • Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
  • Labels go through __() and layout uses logical properties (ms-*, text-start), so it mirrors under dir="rtl" — flip the preview to RTL to confirm.
  • Dark mode uses the same semantic tokens under the dark class; high contrast follows forced-color system tokens.

Livewire

Safe

Livewire can update this component through forwarded wire:* attributes.

Source

The exact, editable files ui:add writes into your app. Previews render this same code; there are no preview-only components.

resources/views/components/ui/button-group.blade.php Blade
@props(['orientation' => 'horizontal'])
@php
    // A direct child trims its joined corners and overlaps the shared border.
    // A dropdown child (the menu half of a split button) is a wrapper around
    // its trigger, so the trigger inside it gets the same trimming. The
    // focused control rises above its neighbour so its ring is never covered.
    $orientations = [
        'horizontal' => 'flex-row [&>*:not(:first-child)]:rounded-s-none [&>*:not(:last-child)]:rounded-e-none [&>*:not(:first-child)]:-ms-px [&>[data-slot=dropdown]:not(:first-child)>[data-slot=dropdown-trigger]]:rounded-s-none [&>[data-slot=dropdown]:not(:last-child)>[data-slot=dropdown-trigger]]:rounded-e-none',
        'vertical' => 'flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:last-child)]:rounded-b-none [&>*:not(:first-child)]:-mt-px [&>[data-slot=dropdown]:not(:first-child)>[data-slot=dropdown-trigger]]:rounded-t-none [&>[data-slot=dropdown]:not(:last-child)>[data-slot=dropdown-trigger]]:rounded-b-none',
    ];
    $classes = trim('inline-flex items-stretch [&>*:focus-visible]:relative [&>*:focus-visible]:z-10 [&>*:has(:focus-visible)]:z-10 '.($orientations[$orientation] ?? $orientations['horizontal']));
@endphp
<div role="group" data-slot="button-group" data-orientation="{{ $orientation }}" {{ $attributes->merge(['class' => $classes]) }}>{{ $slot }}</div>
resources/views/components/ui/button-group/text.blade.php Blade
<div data-slot="button-group-text" {{ $attributes->merge(['class' => 'inline-flex items-center gap-2 border border-input bg-muted px-3 text-sm font-medium text-muted-foreground']) }}>{{ $slot }}</div>
resources/views/components/ui/button-group/separator.blade.php Blade
<div role="separator" data-slot="button-group-separator" {{ $attributes->merge(['class' => 'self-stretch w-px bg-border']) }}></div>

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.1.2
Status
Stable
License
open
Accessibility reviewed
No review date recorded
Last breaking change
No date recorded
Deprecation
Not deprecated
Contract
v6
Foundation
≥ 1.0.0