Button Group
Groups related buttons and inputs into a single attached unit.
Preview
<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>
Installation
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.
-
resources/views/components/ui/button-group.blade.php -
resources/views/components/ui/button-group/text.blade.php -
resources/views/components/ui/button-group/separator.blade.php
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.
# 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
{{-- 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
Props
| 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.
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
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
- Theming hooks
Accessibility
- 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-slotattribute for styling and scripting hooks. - Focus-visible rings use the
ringtoken, 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 underdir="rtl"— flip the preview to RTL to confirm. - Dark mode uses the same semantic tokens under the
darkclass; high contrast follows forced-color system tokens.
Livewire
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.
@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>
<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>
<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