Components
Button group
Segmented button controls in horizontal or vertical layouts.
Wrap .ui-button elements in a <ui-button-group> (or the equivalent <div class="ui-button-group">) to combine them into a single segmented control. The container squares inner corners and collapses borders. Add role="group" and aria-label for accessibility.
Horizontal
Horizontal layout is the default:
Loading components…
<div class="flex flex-col items-start gap-md"> <ui-button-group role="group" aria-label="Text alignment"> <button class="ui-button" type="button">Left</button> <button class="ui-button" type="button">Center</button> <button class="ui-button" type="button">Right</button> </ui-button-group> <ui-button-group role="group" aria-label="Pagination"> <button class="ui-button" data-variant="muted" type="button">Previous</button> <button class="ui-button" data-variant="muted" type="button">1</button> <button class="ui-button" data-variant="muted" type="button">2</button> <button class="ui-button" data-variant="muted" type="button">3</button> <button class="ui-button" data-variant="muted" type="button">Next</button> </ui-button-group> <ui-button-group role="group" aria-label="Formatting"> <button class="ui-button" data-size="icon" type="button" aria-label="Bold"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" > <path d="M6 4h8a4 4 0 0 1 0 8H6z" /> <path d="M6 12h9a4 4 0 0 1 0 8H6z" /> </svg> </button> <button class="ui-button" data-size="icon" type="button" aria-label="Italic"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" > <line x1="19" y1="4" x2="10" y2="4" /> <line x1="14" y1="20" x2="5" y2="20" /> <line x1="15" y1="4" x2="9" y2="20" /> </svg> </button> <button class="ui-button" data-size="icon" type="button" aria-label="Underline"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" > <path d="M6 4v6a6 6 0 0 0 12 0V4" /> <line x1="4" y1="20" x2="20" y2="20" /> </svg> </button> </ui-button-group></div>/** * button-group.css: Button group (ui-button-group | .ui-button-group) * * @layer components * @requires layers.css, _variables.css, button.css * @uses :not(): first/last edge selection for shared-border collapse * @uses logical border-radius longhands: RTL-safe corner rounding per orientation * @uses flex-item z-index: raise the active control so its border/ring isn't clipped * @see https://ui.shadcn.com/docs/components/base/button-group */@layer variables { :root { --ui-button-group-radius: var(--ui-button-radius); --ui-button-group-background: var(--card); }}@layer zazz.components { /* =========================================================================== BUTTON GROUP Joins adjacent controls (.ui-button, and any child it wraps) into a single segmented unit. Children keep their own --ui-button-radius, so the outer corners follow the buttons' size variant automatically; only the *inner* corners are squared off and the doubled border is collapsed with a -1px overlap. =========================================================================== */ :where(ui-button-group, .ui-button-group) { display: flex; inline-size: fit-content; /* stretch so mixed-height children share one edge */ align-items: stretch; border-radius: var(--ui-button-group-radius); overflow: clip; background-color: var(--ui-button-group-background); position: relative; isolation: isolate; } /* flex items honour z-index without positioning, but relative keeps the stacking context robust across child types (inputs, selects, …) */ :where(ui-button-group, .ui-button-group) > * { position: relative; } /* the focused/hovered/pressed control floats above its neighbours so its border and box-shadow ring paint over the overlapped edge */ :where(ui-button-group, .ui-button-group) > *:hover:not(:disabled, [aria-disabled="true"]), :where(ui-button-group, .ui-button-group) > *:focus-visible, :where(ui-button-group, .ui-button-group) > *:active:not(:disabled, [aria-disabled="true"]) { z-index: 1; } /* =========================================================================== BUTTON GROUP: HORIZONTAL (default) =========================================================================== */ :where(ui-button-group, .ui-button-group):not([data-orientation="vertical"]) { flex-direction: row; } :where(ui-button-group, .ui-button-group):not([data-orientation="vertical"]) > :not(:last-child) { border-start-end-radius: 0; border-end-end-radius: 0; } :where(ui-button-group, .ui-button-group):not([data-orientation="vertical"]) > :not(:first-child) { /* pull onto the previous control's border so the seam is one line, not two */ margin-inline-start: -1px; border-start-start-radius: 0; border-end-start-radius: 0; } /* =========================================================================== BUTTON GROUP: VERTICAL =========================================================================== */ :where(ui-button-group, .ui-button-group)[data-orientation="vertical"] { flex-direction: column; } :where(ui-button-group, .ui-button-group)[data-orientation="vertical"] > :not(:last-child) { border-end-start-radius: 0; border-end-end-radius: 0; } :where(ui-button-group, .ui-button-group)[data-orientation="vertical"] > :not(:first-child) { margin-block-start: -1px; border-start-start-radius: 0; border-start-end-radius: 0; }}Vertical
Add data-orientation="vertical" to stack buttons:
Loading components…
<ui-button-group data-orientation="vertical" role="group" aria-label="View options"> <button class="ui-button" type="button">List</button> <button class="ui-button" type="button">Board</button> <button class="ui-button" type="button">Calendar</button> <button class="ui-button" type="button">Timeline</button></ui-button-group>/** * button-group.css: Button group (ui-button-group | .ui-button-group) * * @layer components * @requires layers.css, _variables.css, button.css * @uses :not(): first/last edge selection for shared-border collapse * @uses logical border-radius longhands: RTL-safe corner rounding per orientation * @uses flex-item z-index: raise the active control so its border/ring isn't clipped * @see https://ui.shadcn.com/docs/components/base/button-group */@layer variables { :root { --ui-button-group-radius: var(--ui-button-radius); --ui-button-group-background: var(--card); }}@layer zazz.components { /* =========================================================================== BUTTON GROUP Joins adjacent controls (.ui-button, and any child it wraps) into a single segmented unit. Children keep their own --ui-button-radius, so the outer corners follow the buttons' size variant automatically; only the *inner* corners are squared off and the doubled border is collapsed with a -1px overlap. =========================================================================== */ :where(ui-button-group, .ui-button-group) { display: flex; inline-size: fit-content; /* stretch so mixed-height children share one edge */ align-items: stretch; border-radius: var(--ui-button-group-radius); overflow: clip; background-color: var(--ui-button-group-background); position: relative; isolation: isolate; } /* flex items honour z-index without positioning, but relative keeps the stacking context robust across child types (inputs, selects, …) */ :where(ui-button-group, .ui-button-group) > * { position: relative; } /* the focused/hovered/pressed control floats above its neighbours so its border and box-shadow ring paint over the overlapped edge */ :where(ui-button-group, .ui-button-group) > *:hover:not(:disabled, [aria-disabled="true"]), :where(ui-button-group, .ui-button-group) > *:focus-visible, :where(ui-button-group, .ui-button-group) > *:active:not(:disabled, [aria-disabled="true"]) { z-index: 1; } /* =========================================================================== BUTTON GROUP: HORIZONTAL (default) =========================================================================== */ :where(ui-button-group, .ui-button-group):not([data-orientation="vertical"]) { flex-direction: row; } :where(ui-button-group, .ui-button-group):not([data-orientation="vertical"]) > :not(:last-child) { border-start-end-radius: 0; border-end-end-radius: 0; } :where(ui-button-group, .ui-button-group):not([data-orientation="vertical"]) > :not(:first-child) { /* pull onto the previous control's border so the seam is one line, not two */ margin-inline-start: -1px; border-start-start-radius: 0; border-end-start-radius: 0; } /* =========================================================================== BUTTON GROUP: VERTICAL =========================================================================== */ :where(ui-button-group, .ui-button-group)[data-orientation="vertical"] { flex-direction: column; } :where(ui-button-group, .ui-button-group)[data-orientation="vertical"] > :not(:last-child) { border-end-start-radius: 0; border-end-end-radius: 0; } :where(ui-button-group, .ui-button-group)[data-orientation="vertical"] > :not(:first-child) { margin-block-start: -1px; border-start-start-radius: 0; border-start-end-radius: 0; }}API
| Attribute | Values |
|---|---|
data-orientation | vertical (omit = horizontal default) |
Outer corners adapt to child button --ui-button-radius tokens automatically.