Button Group
Overview
The Button Group is a layout component that visually connects related buttons into a single, cohesive unit. The useButtonGroupRecipe() composable creates a fully configured recipe with orientation and block mode options — plus automatic border-radius collapsing so adjacent buttons share a seamless edge.
The Button Group recipe integrates directly with the Button recipe and generates type-safe utility classes at build time with zero runtime CSS.
Why use the Button Group recipe?
The Button Group recipe helps you:
- Join buttons seamlessly: Adjacent buttons automatically collapse their border-radius and borders at shared edges, creating a clean, unified control.
- Support both orientations: Switch between horizontal and vertical layouts with a single variant prop — border collapsing adjusts automatically.
- Fill available space: The block variant stretches the group to full width and distributes buttons equally, perfect for mobile layouts and form actions.
- Stay type-safe: Full TypeScript support means your editor catches invalid orientation or block values at compile time.
Usage
Register the recipe
Add the Button Group recipe to a local Styleframe instance. The global styleframe.config.ts provides design tokens and utilities, while the component-level file registers the recipe itself:
import { styleframe } from 'virtual:styleframe';
import { useButtonRecipe, useButtonGroupRecipe } from '@styleframe/theme';
const s = styleframe();
const button = useButtonRecipe(s);
const buttonGroup = useButtonGroupRecipe(s);
export default s;
Build the component
Import the buttonGroup runtime function from the virtual module and pass variant props to compute class names:
import { buttonGroup } from "virtual:styleframe";
interface ButtonGroupProps {
orientation?: "horizontal" | "vertical";
block?: boolean;
children?: React.ReactNode;
}
export function ButtonGroup({
orientation = "horizontal",
block = false,
children,
}: ButtonGroupProps) {
const classes = buttonGroup({ orientation, block: block ? "true" : "false" });
return (
<div className={classes} role="group">
{children}
</div>
);
}
<script setup lang="ts">
import { buttonGroup } from "virtual:styleframe";
const props = withDefaults(
defineProps<{
orientation?: "horizontal" | "vertical";
block?: boolean;
}>(),
{
orientation: "horizontal",
block: false,
},
);
</script>
<template>
<div
:class="buttonGroup({ orientation: props.orientation, block: props.block ? 'true' : 'false' })"
role="group"
>
<slot />
</div>
</template>
See it in action
Orientation
The Button Group supports two orientation variants that control the layout direction and which edges have their borders collapsed.
Horizontal
The default orientation. Buttons are laid out side-by-side in a row. Border-radius is removed from the right edge of each button except the last, and from the left edge of each button except the first. The right border is removed from each button except the last to prevent doubled borders.
Vertical
Buttons are stacked top-to-bottom in a column. Border-radius is removed from the bottom edge of each button except the last, and from the top edge of each button except the first. The bottom border is removed from each button except the last to prevent doubled borders.
Orientation Reference
| Orientation | Direction | Border Collapsing |
|---|---|---|
horizontal | Left to right (row) | Right border + right/left radius removed at joins |
vertical | Top to bottom (column) | Bottom border + bottom/top radius removed at joins |
Block
The block variant stretches the button group to fill the full width of its container. Each child button is given equal flex sizing (flex-basis: 0; flex-grow: 1), distributing the available space evenly.
block with vertical for a full-width stacked layout, such as a mobile action menu.Colors
The Button Group works with all Button color variants. Each button within the group retains its own color styling while the group handles layout and border collapsing.
Color Reference
| Color | Use Case |
|---|---|
primary | Default actions, key information |
secondary | Secondary actions, neutral states |
success | Positive actions, confirmations |
info | Informational actions, tips |
warning | Caution actions, pending states |
error | Destructive actions, error states |
Variants
The Button Group showcases how each Button visual style looks when buttons are joined together. Border collapsing is most visible with variants that have explicit borders, like outline and subtle.
Solid
Filled background buttons joined seamlessly. The most prominent grouping style, ideal for primary action sets.
Outline
Bordered buttons with transparent backgrounds. Border collapsing is clearly visible here — shared edges merge into a single border line.
Soft
Light tinted background buttons. The soft backgrounds blend together at the join points.
Subtle
Light tinted background with visible borders. Like outline, the border collapsing creates clean shared edges.
Ghost
Transparent buttons that only show background on hover. Minimal visual weight for secondary action groups.
Link
Styled as inline text links. Useful for navigation-style button groups with minimal chrome.
Sizes
The Button Group works with all Button sizes. All buttons within a group should use the same size for consistent alignment.
Size Reference
| Size | Token | Use Case |
|---|---|---|
xs | Extra small | Compact toolbars, dense UIs |
sm | Small | Secondary action groups, table rows |
md | Medium (default) | General purpose button groups |
lg | Large | Prominent action groups, hero sections |
xl | Extra large | Full-width mobile actions, marketing CTAs |
Accessibility
- Use
role="group". Wrap buttons in an element withrole="group"and anaria-labeldescribing the group's purpose so screen readers announce the context.
<!-- Correct: semantic group with label -->
<div role="group" aria-label="Text alignment">
<button class="...">Left</button>
<button class="...">Center</button>
<button class="...">Right</button>
</div>
<!-- Avoid: no group semantics -->
<div class="...">
<button class="...">Left</button>
<button class="...">Center</button>
<button class="...">Right</button>
</div>
- Keyboard navigation. Native
<button>elements provide Tab key support. For single-selection groups (segmented controls), considerrole="toolbar"with arrow-key navigation.
Customization
Overriding Defaults
The useButtonGroupRecipe() composable accepts an optional second argument to override any part of the recipe configuration. Overrides are deep-merged with the defaults, so you only need to specify the properties you want to change:
import { styleframe } from 'virtual:styleframe';
import { useButtonGroupRecipe } from '@styleframe/theme';
const s = styleframe();
const buttonGroup = useButtonGroupRecipe(s, {
defaultVariants: {
orientation: 'vertical',
block: 'true',
},
});
export default s;
Filtering Variants
If you only need a subset of the available variants, use the filter option to limit which values are generated. This reduces the output CSS and keeps your component API focused:
import { styleframe } from 'virtual:styleframe';
import { useButtonGroupRecipe } from '@styleframe/theme';
const s = styleframe();
// Only generate horizontal orientation
const buttonGroup = useButtonGroupRecipe(s, {
filter: {
orientation: ['horizontal'],
},
});
export default s;
API Reference
useButtonGroupRecipe(s, options?)
Creates a button group recipe with orientation and block mode variants plus automatic border collapsing.
Parameters:
| Parameter | Type | Description |
|---|---|---|
s | Styleframe | The Styleframe instance |
options | DeepPartial<RecipeConfig> | Optional overrides for the recipe configuration |
options.base | VariantDeclarationsBlock | Custom base styles for the button group |
options.variants | Variants | Custom variant definitions for the recipe |
options.defaultVariants | Record<keyof Variants, string> | Default variant values for the recipe |
options.compoundVariants | CompoundVariant[] | Custom compound variant definitions for the recipe |
options.filter | Record<string, string[]> | Limit which variant values are generated |
Variants:
| Variant | Options | Default |
|---|---|---|
orientation | horizontal, vertical | horizontal |
block | true, false | false |
Best Practices
- Pair with the Button recipe: The Button Group recipe targets child elements with the
.buttonclass. Always use it alongside the Button recipe for consistent border collapsing. - Keep groups small: Limit button groups to 2–5 buttons. Larger groups are harder to scan and may overwhelm the user with choices.
- Use block mode for mobile: On narrow screens, the block variant gives buttons equal width and prevents awkward wrapping.
- Maintain a visual hierarchy within the group: Combine different button variants (e.g., one
solidprimary action withoutlinesecondary actions) so users can quickly identify the primary action. - Add an aria-label: Always describe the group's purpose with
aria-labelso screen readers can communicate the relationship between buttons.
FAQ
.button elements based on their position. In horizontal mode, each button except the last has its right border-radius and right border removed; each button except the first has its left border-radius removed. In vertical mode, the same logic applies to the bottom and top edges. This creates a seamless join between adjacent buttons without any additional markup.solid, outline, soft, and other button variants within the same group.display: inline-flex and only takes up as much horizontal space as its children need. In block mode, the group switches to display: flex with width: 100%, and each child button is given equal flex sizing so they distribute the full width evenly..button class, which is the class name generated by the Button recipe. If you want to use the Button Group with custom buttons, make sure your buttons use the .button class, or override the selectors in the recipe setup callback.filter option, compound variants that reference filtered-out values are automatically removed. For example, if you filter orientation to only ['horizontal'], the vertical border-collapsing selectors are still registered by the setup callback but the vertical compound variant class name won't be generated, so those selectors won't match any elements.Button
An interactive control component for actions and navigation. Supports multiple colors, visual styles, sizes, and states through the recipe system.
Callout
A contextual feedback component for alerts, notifications, and inline messages. Supports multiple colors, visual styles, sizes, and orientations through the recipe system.