Button Group
Groups related buttons, shares their styling via context, and optionally welds them into a segmented control.
import { Button, ButtonGroup } from "astralis-ui";
export function ButtonGroupDemo() {
return (
<ButtonGroup variant="outline" colorScheme="gray">
<Button>Save draft</Button>
<Button>Preview</Button>
{/* An explicit prop on a child always wins over the group default. */}
<Button variant="solid" colorScheme="brand">
Publish
</Button>
</ButtonGroup>
);
}Import#
import { ButtonGroup, Button } from "astralis-ui";Usage#
Set variant, colorScheme, size or disabled once on the group and every
child Button inherits it through context — no prop
repetition. An explicit prop on a child always wins, which is how the
"Publish" button above breaks out of its group's outline style.
Attached#
attached collapses the gap, the inner corner radii, and the doubled-up
borders between neighbors, producing a single segmented control. Classic uses:
toolbars and pagination.
"use client";
import { Button, ButtonGroup, Icon, VStack } from "astralis-ui";
import { AlignLeft, AlignCenter, AlignRight, ChevronLeft, ChevronRight } from "lucide-react";
export function ButtonGroupAttached() {
return (
<VStack gap="4">
<ButtonGroup attached variant="outline" colorScheme="gray">
<Button leftIcon={<Icon as={AlignLeft} size="xs" />} aria-label="Align left" />
<Button leftIcon={<Icon as={AlignCenter} size="xs" />} aria-label="Align center" />
<Button leftIcon={<Icon as={AlignRight} size="xs" />} aria-label="Align right" />
</ButtonGroup>
<ButtonGroup attached variant="subtle">
<Button leftIcon={<Icon as={ChevronLeft} size="xs" />}>Previous</Button>
<Button rightIcon={<Icon as={ChevronRight} size="xs" />}>Next</Button>
</ButtonGroup>
</VStack>
);
}Orientation#
orientation="vertical" stacks the buttons. It composes with attached for
vertical segmented controls.
import { Button, ButtonGroup, HStack } from "astralis-ui";
export function ButtonGroupOrientation() {
return (
<HStack gap="8" alignItems="start">
<ButtonGroup orientation="vertical" variant="surface">
<Button>Profile</Button>
<Button>Settings</Button>
<Button>Sign out</Button>
</ButtonGroup>
<ButtonGroup orientation="vertical" attached variant="outline" colorScheme="gray">
<Button>Day</Button>
<Button>Week</Button>
<Button>Month</Button>
</ButtonGroup>
</HStack>
);
}Spacing#
When not attached, spacing controls the gap between buttons.
import { Button, ButtonGroup, Text, VStack } from "astralis-ui";
const spacings = ["none", "sm", "md", "lg"] as const;
export function ButtonGroupSpacing() {
return (
<VStack gap="5" alignItems="stretch">
{spacings.map((spacing) => (
<VStack key={spacing} gap="2" alignItems="stretch">
<Text as="span" size="xs" weight="medium" color="muted">
{spacing}
</Text>
<ButtonGroup spacing={spacing} variant="subtle" size="sm">
<Button>One</Button>
<Button>Two</Button>
<Button>Three</Button>
</ButtonGroup>
</VStack>
))}
</VStack>
);
}Disabled#
Disabling the group disables every button in it — handy while a form section is pending.
import { Button, ButtonGroup } from "astralis-ui";
export function ButtonGroupDisabled() {
return (
<ButtonGroup disabled variant="outline" colorScheme="gray">
<Button>Cut</Button>
<Button>Copy</Button>
<Button>Paste</Button>
</ButtonGroup>
);
}Props#
Button Group renders a div and accepts all its standard attributes.
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | "horizontal" | "vertical" | "horizontal" | Lays the buttons out in a row or a column. |
attached | boolean | false | Welds the buttons into one segmented control: inner radii collapse and adjacent borders merge. spacing is ignored. |
spacing | "none" | "sm" | "md" | "lg" | "md" | Gap between buttons when not attached. |
variant | "solid" | "subtle" | "surface" | "outline" | "text" | "link" | — | Shared visual style pushed onto every child Button. |
colorScheme | "brand" | "gray" | "red" | "orange" | "yellow" | "green" | "teal" | "blue" | "cyan" | "purple" | "pink" | — | Shared hue pushed onto every child Button. |
size | "xs" | "sm" | "md" | "lg" | "xl" | — | Shared size pushed onto every child Button. |
disabled | boolean | false | Disables every child Button at once. |
Accessibility#
- Renders with
role="group"by default; pass arole(e.g.toolbar) and anaria-labelto describe the set when the context isn't obvious. - Attached icon-only buttons still need individual
aria-labels — see the alignment toolbar demo.