Composables

Button Group

A layout component for grouping buttons with joined borders and shared orientation. Supports horizontal and vertical layouts plus full-width block mode through the recipe system.

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:

src/components/button-group.styleframe.ts
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:

src/components/ButtonGroup.tsx
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>
    );
}

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

OrientationDirectionBorder Collapsing
horizontalLeft to right (row)Right border + right/left radius removed at joins
verticalTop 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.

Pro tip: The block variant works with both orientations. Combine 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

ColorUse Case
primaryDefault actions, key information
secondarySecondary actions, neutral states
successPositive actions, confirmations
infoInformational actions, tips
warningCaution actions, pending states
errorDestructive 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.

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

SizeTokenUse Case
xsExtra smallCompact toolbars, dense UIs
smSmallSecondary action groups, table rows
mdMedium (default)General purpose button groups
lgLargeProminent action groups, hero sections
xlExtra largeFull-width mobile actions, marketing CTAs

Accessibility

  • Use role="group". Wrap buttons in an element with role="group" and an aria-label describing 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), consider role="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:

src/components/button-group.styleframe.ts
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:

src/components/button-group.styleframe.ts
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;
Good to know: Filtering also removes compound variants and adjusts default variants that reference filtered-out values, so your recipe stays consistent.

API Reference

useButtonGroupRecipe(s, options?)

Creates a button group recipe with orientation and block mode variants plus automatic border collapsing.

Parameters:

ParameterTypeDescription
sStyleframeThe Styleframe instance
optionsDeepPartial<RecipeConfig>Optional overrides for the recipe configuration
options.baseVariantDeclarationsBlockCustom base styles for the button group
options.variantsVariantsCustom variant definitions for the recipe
options.defaultVariantsRecord<keyof Variants, string>Default variant values for the recipe
options.compoundVariantsCompoundVariant[]Custom compound variant definitions for the recipe
options.filterRecord<string, string[]>Limit which variant values are generated

Variants:

VariantOptionsDefault
orientationhorizontal, verticalhorizontal
blocktrue, falsefalse

Learn more about recipes →

Best Practices

  • Pair with the Button recipe: The Button Group recipe targets child elements with the .button class. 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 solid primary action with outline secondary actions) so users can quickly identify the primary action.
  • Add an aria-label: Always describe the group's purpose with aria-label so screen readers can communicate the relationship between buttons.

FAQ