component
Joins related controls — inputs, buttons, checkboxes, radios, badges — into one
connected unit. It uses the children’s own borders: the corners where they
meet are flattened and adjacent borders overlap into a single seam, so the group
reads as one element with no double border and no frame of its own. Need a real
bordered/padded container around the joined children? Opt in with the Panel
chrome props (border, padding, background, emphasis, …). For overlapping
avatars, use variant="stack".
The default controls variant joins inputs, buttons, checkboxes, and radios into
a single connected element by flattening the inner corners and merging each
child’s own border with its neighbour’s.
---
import Group from "../Group.astro";
import Input from "@pindoba/astro-input";
import Button from "@pindoba/astro-button";
import Checkbox from "@pindoba/astro-checkbox";
import Radio from "@pindoba/astro-radio";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "xl", direction: "column", width: "full" })}>
<div>
<h3>Full width — search bar</h3>
<Group variant="controls" fullWidth>
<Input fullWidth placeholder="Search..." />
<Button>Search</Button>
</Group>
</div>
<div>
<h3>Full width — segmented control</h3>
<Group variant="controls" fullWidth>
<Radio
id="group-fw-env-dev"
appearance="button"
name="group-fw-env"
value="dev"
fullWidth
checked>Dev</Radio
>
<Radio
id="group-fw-env-staging"
appearance="button"
name="group-fw-env"
value="staging"
fullWidth>Staging</Radio
>
<Radio
id="group-fw-env-prod"
appearance="button"
name="group-fw-env"
value="prod"
fullWidth>Prod</Radio
>
</Group>
</div>
<div>
<h3>Input with button</h3>
<Group variant="controls">
<Input placeholder="Search..." />
<Button>Search</Button>
</Group>
</div>
<div>
<h3>Button on both sides</h3>
<Group variant="controls">
<Button emphasis="secondary">-</Button>
<Input placeholder="Enter value" />
<Button emphasis="secondary">+</Button>
</Group>
</div>
<div>
<h3>Multiple inputs</h3>
<Group variant="controls">
<Input placeholder="First name" />
<Input placeholder="Last name" />
</Group>
</div>
<div>
<h3>Button group</h3>
<Group variant="controls">
<Button emphasis="secondary">Cut</Button>
<Button emphasis="secondary">Copy</Button>
<Button emphasis="secondary">Paste</Button>
</Group>
</div>
<div>
<h3>Mixed feedback buttons</h3>
<Group variant="controls">
<Button feedback="success">Save</Button>
<Button feedback="warning" emphasis="secondary">Draft</Button>
<Button feedback="danger" emphasis="secondary">Delete</Button>
</Group>
</div>
<div>
<h3>Tab horizontal</h3>
<Group variant="controls">
<Radio
id="group-tab-h-all"
appearance="tab"
name="group-tab-h"
value="all"
checked>All</Radio
>
<Radio
id="group-tab-h-active"
appearance="tab"
name="group-tab-h"
value="active">Active</Radio
>
<Radio
id="group-tab-h-archived"
appearance="tab"
name="group-tab-h"
value="archived">Archived</Radio
>
</Group>
</div>
<div>
<h3>Tab vertical</h3>
<Group variant="controls" orientation="vertical">
<Radio
id="group-tab-v-dashboard"
appearance="tab"
orientation="vertical"
name="group-tab-v"
value="dashboard"
checked>Dashboard</Radio
>
<Radio
id="group-tab-v-settings"
appearance="tab"
orientation="vertical"
name="group-tab-v"
value="settings">Settings</Radio
>
<Radio
id="group-tab-v-profile"
appearance="tab"
orientation="vertical"
name="group-tab-v"
value="profile">Profile</Radio
>
</Group>
</div>
<div>
<h3>Vertical button group</h3>
<Group variant="controls" orientation="vertical">
<Button emphasis="secondary">Cut</Button>
<Button emphasis="secondary">Copy</Button>
<Button emphasis="secondary">Paste</Button>
</Group>
</div>
<div>
<h3>Vertical checkbox group</h3>
<Group variant="controls" orientation="vertical">
<Checkbox appearance="button" size="md">Bold</Checkbox>
<Checkbox appearance="button" size="md">Italic</Checkbox>
<Checkbox appearance="button" size="md">Underline</Checkbox>
</Group>
</div>
<div>
<h3>Vertical radio button group</h3>
<Group variant="controls" orientation="vertical">
<Radio
id="group-view-v-list"
appearance="button"
size="md"
name="view-v"
value="list">List</Radio
>
<Radio
id="group-view-v-grid"
appearance="button"
size="md"
name="view-v"
value="grid">Grid</Radio
>
<Radio
id="group-view-v-table"
appearance="button"
size="md"
name="view-v"
value="table">Table</Radio
>
</Group>
</div>
<div>
<h3>Checkbox button group</h3>
<Group variant="controls">
<Checkbox appearance="button" size="md">Bold</Checkbox>
<Checkbox appearance="button" size="md">Italic</Checkbox>
<Checkbox appearance="button" size="md">Underline</Checkbox>
</Group>
</div>
<div>
<h3>Radio button group</h3>
<Group variant="controls">
<Radio
id="group-view-list"
appearance="button"
size="md"
name="view"
value="list">List</Radio
>
<Radio
id="group-view-grid"
appearance="button"
size="md"
name="view"
value="grid">Grid</Radio
>
<Radio
id="group-view-table"
appearance="button"
size="md"
name="view"
value="table">Table</Radio
>
</Group>
</div>
</div>With variant="controls", apply a semantic color to the merged frame and pass
matching feedback to the inner components.
---
import Group from "../Group.astro";
import Input from "@pindoba/astro-input";
import Button from "@pindoba/astro-button";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "md", direction: "column", width: "full" })}>
<Group variant="controls" feedback="neutral">
<Input placeholder="Neutral" />
<Button>Go</Button>
</Group>
<Group variant="controls" feedback="primary">
<Input feedback="primary" placeholder="Primary" />
<Button>Go</Button>
</Group>
<Group variant="controls" feedback="success">
<Input feedback="success" placeholder="Success" />
<Button feedback="success">Go</Button>
</Group>
<Group variant="controls" feedback="danger">
<Input feedback="danger" placeholder="Danger" />
<Button feedback="danger">Go</Button>
</Group>
<Group variant="controls" feedback="warning">
<Input feedback="warning" placeholder="Warning" />
<Button feedback="warning">Go</Button>
</Group>
</div>Use size to control the merged frame’s border radius (controls). Match it to
the size of the inner controls so the rounding is proportional.
---
import Group from "../Group.astro";
import Input from "@pindoba/astro-input";
import Button from "@pindoba/astro-button";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "md", direction: "column", width: "full" })}>
<Group variant="controls" size="xs">
<Input size="xs" placeholder="Extra small" />
<Button size="xs">Go</Button>
</Group>
<Group variant="controls" size="sm">
<Input size="sm" placeholder="Small" />
<Button size="sm">Go</Button>
</Group>
<Group variant="controls" size="md">
<Input size="md" placeholder="Medium" />
<Button size="md">Go</Button>
</Group>
<Group variant="controls" size="lg">
<Input size="lg" placeholder="Large" />
<Button size="lg">Go</Button>
</Group>
</div>controls also merges badges — and a trailing button — into a single chip. Mix
emphasis (primary / secondary / tertiary) and feedback colors freely;
Group suppresses each badge’s own outline and emits a palette-aware divider
between siblings. Pair button sizes with the badge size (sm badge → xs
button, md badge → sm button) so nothing renders at the default button
height.
---
import Badge from "@pindoba/astro-badge";
import Group from "../Group.astro";
import Button from "@pindoba/astro-button";
import { X } from "@lucide/astro";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "xl", direction: "column" })}>
<!-- Two-emphasis chips: mix primary / secondary / tertiary / adaptive so
the same composition can play every visual role the old label slot did. -->
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Group variant="controls">
<Badge feedback="primary" emphasis="primary">React</Badge>
<Badge feedback="primary" emphasis="secondary">18 issues</Badge>
</Group>
<Group variant="controls">
<Badge feedback="success" emphasis="secondary">v2.1</Badge>
<Badge feedback="success" emphasis="primary">Released</Badge>
</Group>
<Group variant="controls">
<Badge feedback="neutral" emphasis="tertiary">Filter</Badge>
<Badge feedback="primary" emphasis="secondary">12 results</Badge>
</Group>
<Group variant="controls">
<Badge feedback="warning" emphasis="primary">Pending</Badge>
<Badge feedback="warning" emphasis="tertiary">3 in queue</Badge>
</Group>
</div>
<!-- Three-section chips: emphasis cascades across the group. -->
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Group variant="controls">
<Badge feedback="primary" emphasis="primary">PR</Badge>
<Badge feedback="primary" emphasis="secondary">#247</Badge>
<Badge feedback="success" emphasis="tertiary">merged</Badge>
</Group>
<Group variant="controls">
<Badge feedback="danger" emphasis="primary">CI</Badge>
<Badge feedback="danger" emphasis="secondary">failed</Badge>
<Badge feedback="neutral" emphasis="tertiary">retry in 30s</Badge>
</Group>
</div>
<!-- Badges + a trailing dismiss button. Button size matches the badge size
(sm-badge ↔ xs-button, md-badge ↔ sm-button) so nothing renders at the
default 30 px button height. -->
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Group variant="controls" size="md">
<Badge feedback="primary" emphasis="primary">Topic</Badge>
<Badge feedback="primary" emphasis="secondary">design-systems</Badge>
<Button size="sm" shape="square" emphasis="ghost"><X /></Button>
</Group>
<Group variant="controls" size="sm">
<Badge size="sm" feedback="neutral" emphasis="tertiary">Author</Badge>
<Badge size="sm" feedback="primary" emphasis="secondary">@ed</Badge>
<Button size="xs" shape="square" emphasis="secondary"><X /></Button>
</Group>
<Group variant="controls" size="md">
<Badge feedback="warning" emphasis="primary">Draft</Badge>
<Badge feedback="neutral" emphasis="tertiary">12 files</Badge>
<Button size="sm" shape="square" emphasis="ghost"><X /></Button>
</Group>
</div>
</div>controls also fuses keycaps into a single shortcut cluster — each Kbd keeps
its own base edge while the shared borders collapse into one seam. Match the
group size to the kbd size so the corner rounding stays proportional.
---
import Kbd from "@pindoba/astro-kbd";
import Group from "../Group.astro";
import { stack } from "@pindoba/styled-system/patterns";
---
<div
class={stack({
gap: "lg",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Group>
<Kbd>⌘</Kbd>
<Kbd>K</Kbd>
</Group>
<Group>
<Kbd>⌃</Kbd>
<Kbd>⇧</Kbd>
<Kbd>P</Kbd>
</Group>
<Group size="lg">
<Kbd size="lg">⌥</Kbd>
<Kbd size="lg">Space</Kbd>
</Group>
<Group size="sm">
<Kbd size="sm">Ctrl</Kbd>
<Kbd size="sm">Alt</Kbd>
<Kbd size="sm">Del</Kbd>
</Group>
</div>The passThrough prop provides an escape hatch for the root element. The last
example shows the exposed Panel chrome props turning the plain group into a
bordered, padded, filled container.
---
import Group from "../Group.astro";
import Input from "@pindoba/astro-input";
import Button from "@pindoba/astro-button";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "xl", direction: "column" })}>
<div>
<h3>Custom style via passThrough</h3>
<Group
variant="controls"
passThrough={{ root: { style: { borderRadius: "full" } } }}
>
<Input
placeholder="Search..."
passThrough={{ root: { style: { borderRadius: "full" } } }}
/>
<Button shape="pill">Go</Button>
</Group>
</div>
<div>
<h3>Custom props via passThrough</h3>
<Group
variant="controls"
passThrough={{
root: {
props: { "data-testid": "search-group" },
},
}}
>
<Input placeholder="Search..." />
<Button>Search</Button>
</Group>
</div>
<div>
<h3>Panel chrome — bordered, padded, filled container</h3>
<Group border="default" padding="md" background="surface.peak">
<Button>Save</Button>
<Button emphasis="secondary">Cancel</Button>
</Group>
</div>
</div>"transparent"Panel surface background of the group container.
"none"Panel chrome — opt into a bordered/padded/filled container drawn AROUND the joined children. By default Group is chrome-less (the outline is the children's own borders).
Panel interactive border treatment applied to the opt-in container frame.
No description yet.
Additional class name(s) merged onto the group's root element.
No description yet.
Panel emphasis (primary / secondary / tertiary) for the opt-in container frame.
"neutral"Semantic color palette for the group, inherited by children and any opt-in frame.
falseStretch the group to fill its container's width.
Enable Panel interactive (hover/press) affordances on the container frame.
"horizontal"Lay children out in a row or a column.
"none"Panel padding — inner spacing of the group container.
Per-slot style and attribute override bag for the group's root element.
Corner radius of the framed container. By default the radius is owned by the `size` variant (so joined children nest concentrically). Pass an explicit `radius` to override that size-derived default — useful when the Group frames a chrome'd container (e.g. a segmented nav) that must match a sibling's radius rather than its own size scale.
trueOpt out of the Panel's reserved 1px border footprint. The default keeps the reserved space so toggling border variants never shifts layout; set `false` on a chrome-less group that must not add 1px around the joined set (e.g. a structural join-group nested inside a parent that already owns the frame).
Panel shadow elevation applied to the opt-in container frame.
"md"Border radius matching the size of the inner controls (`controls` variant).
Render the container frame with a translucent (frosted) surface.
"controls"Layout mode. `controls` (default) joins children into one connected unit using their OWN borders — it flattens the corners where they meet and overlaps adjacent borders into a single seam (no group frame, no double border). `stack` renders children as an avatar face pile with overlap and leftmost-on-top cascade.
Plus all standard <div> HTML
attributes.