component

Group

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".

Basic

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.

Full width — search bar

Full width — segmented control

Input with button

Button on both sides

Multiple inputs

Button group

Mixed tone buttons

Tab horizontal

Tab vertical

Vertical button group

Vertical checkbox group

Vertical radio button group

Checkbox button group

Radio button group

---
// GENERATED from packages/demos/group/basic.demo.tsx — edit the source, not this file.
import Group from "@pindoba/astro-group";
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="astro-demo-basic-selected-fw-env-dev"
        appearance="button"
        checked
        value="dev"
        fullWidth
        name="astro-demo-basic-selected-fw-env"
      >
        Dev
      </Radio>
      <Radio
        id="astro-demo-basic-selected-fw-env-staging"
        appearance="button"
        value="staging"
        fullWidth
        name="astro-demo-basic-selected-fw-env"
      >
        Staging
      </Radio>
      <Radio
        id="astro-demo-basic-selected-fw-env-prod"
        appearance="button"
        value="prod"
        fullWidth
        name="astro-demo-basic-selected-fw-env"
      >
        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 tone buttons</h3>
    <Group variant="controls">
      <Button tone="success">Save</Button>
      <Button tone="warning" emphasis="secondary">
        Draft
      </Button>
      <Button tone="danger" emphasis="secondary">
        Delete
      </Button>
    </Group>
  </div>

  <div>
    <h3>Tab horizontal</h3>
    <Group variant="controls">
      <Radio
        id="astro-demo-basic-selected-tab-all"
        appearance="tab"
        checked
        value="all"
        name="astro-demo-basic-selected-tab"
      >
        All
      </Radio>
      <Radio
        id="astro-demo-basic-selected-tab-active"
        appearance="tab"
        value="active"
        name="astro-demo-basic-selected-tab"
      >
        Active
      </Radio>
      <Radio
        id="astro-demo-basic-selected-tab-archived"
        appearance="tab"
        value="archived"
        name="astro-demo-basic-selected-tab"
      >
        Archived
      </Radio>
    </Group>
  </div>

  <div>
    <h3>Tab vertical</h3>
    <Group variant="controls" orientation="vertical">
      <Radio
        id="astro-demo-basic-selected-nav-dashboard"
        appearance="tab"
        orientation="vertical"
        checked
        value="dashboard"
        name="astro-demo-basic-selected-nav"
      >
        Dashboard
      </Radio>
      <Radio
        id="astro-demo-basic-selected-nav-settings"
        appearance="tab"
        orientation="vertical"
        value="settings"
        name="astro-demo-basic-selected-nav"
      >
        Settings
      </Radio>
      <Radio
        id="astro-demo-basic-selected-nav-profile"
        appearance="tab"
        orientation="vertical"
        value="profile"
        name="astro-demo-basic-selected-nav"
      >
        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="astro-demo-basic-selected-view-list"
        appearance="button"
        size="md"
        checked
        value="list"
        name="astro-demo-basic-selected-view"
      >
        List
      </Radio>
      <Radio
        id="astro-demo-basic-selected-view-grid"
        appearance="button"
        size="md"
        value="grid"
        name="astro-demo-basic-selected-view"
      >
        Grid
      </Radio>
      <Radio
        id="astro-demo-basic-selected-view-table"
        appearance="button"
        size="md"
        value="table"
        name="astro-demo-basic-selected-view"
      >
        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="astro-demo-basic-selected-view-list"
        appearance="button"
        size="md"
        checked
        value="list"
        name="astro-demo-basic-selected-view"
      >
        List
      </Radio>
      <Radio
        id="astro-demo-basic-selected-view-grid"
        appearance="button"
        size="md"
        value="grid"
        name="astro-demo-basic-selected-view"
      >
        Grid
      </Radio>
      <Radio
        id="astro-demo-basic-selected-view-table"
        appearance="button"
        size="md"
        value="table"
        name="astro-demo-basic-selected-view"
      >
        Table
      </Radio>
    </Group>
  </div>
</div>

Tone

With variant="controls", apply a semantic color to the merged frame and pass matching tone to the inner components.

---
// GENERATED from packages/demos/group/tone.demo.tsx — edit the source, not this file.
import Group from "@pindoba/astro-group";
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" tone="neutral">
    <Input placeholder="Neutral" />
    <Button>Go</Button>
  </Group>

  <Group variant="controls" tone="primary">
    <Input tone="primary" placeholder="Primary" />
    <Button>Go</Button>
  </Group>

  <Group variant="controls" tone="success">
    <Input tone="success" placeholder="Success" />
    <Button tone="success">Go</Button>
  </Group>

  <Group variant="controls" tone="danger">
    <Input tone="danger" placeholder="Danger" />
    <Button tone="danger">Go</Button>
  </Group>

  <Group variant="controls" tone="warning">
    <Input tone="warning" placeholder="Warning" />
    <Button tone="warning">Go</Button>
  </Group>
</div>

Size

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.

---
// GENERATED from packages/demos/group/size.demo.tsx — edit the source, not this file.
import Group from "@pindoba/astro-group";
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>

Badges and buttons

controls also merges badges — and a trailing button — into a single chip. Mix emphasis (primary / secondary / tertiary) and tone 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.

React 18 issues
v2.1 Released
Filter 12 results
Pending 3 in queue
PR #247 merged
CI failed retry in 30s
Topic design-systems
Author @ed
Draft 12 files
---
// GENERATED from packages/demos/group/badges.demo.tsx — edit the source, not this file.
import Badge from "@pindoba/astro-badge";
import Group from "@pindoba/astro-group";
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" })}>
  <div
    class={stack({
      gap: "md",
      direction: "row",
      align: "center",
      flexWrap: "wrap",
    })}
  >
    <Group variant="controls">
      <Badge tone="primary" emphasis="primary">
        React
      </Badge>
      <Badge tone="primary" emphasis="secondary">
        18 issues
      </Badge>
    </Group>
    <Group variant="controls">
      <Badge tone="success" emphasis="secondary">
        v2.1
      </Badge>
      <Badge tone="success" emphasis="primary">
        Released
      </Badge>
    </Group>
    <Group variant="controls">
      <Badge tone="neutral" emphasis="tertiary">
        Filter
      </Badge>
      <Badge tone="primary" emphasis="secondary">
        12 results
      </Badge>
    </Group>
    <Group variant="controls">
      <Badge tone="warning" emphasis="primary">
        Pending
      </Badge>
      <Badge tone="warning" emphasis="tertiary">
        3 in queue
      </Badge>
    </Group>
  </div>

  <div
    class={stack({
      gap: "md",
      direction: "row",
      align: "center",
      flexWrap: "wrap",
    })}
  >
    <Group variant="controls">
      <Badge tone="primary" emphasis="primary">
        PR
      </Badge>
      <Badge tone="primary" emphasis="secondary">
        #247
      </Badge>
      <Badge tone="success" emphasis="tertiary">
        merged
      </Badge>
    </Group>
    <Group variant="controls">
      <Badge tone="danger" emphasis="primary">
        CI
      </Badge>
      <Badge tone="danger" emphasis="secondary">
        failed
      </Badge>
      <Badge tone="neutral" emphasis="tertiary">
        retry in 30s
      </Badge>
    </Group>
  </div>

  <div
    class={stack({
      gap: "md",
      direction: "row",
      align: "center",
      flexWrap: "wrap",
    })}
  >
    <Group variant="controls" size="md">
      <Badge tone="primary" emphasis="primary">
        Topic
      </Badge>
      <Badge tone="primary" emphasis="secondary">
        design-systems
      </Badge>
      <Button aria-label="Dismiss" size="sm" shape="square" emphasis="ghost">
        <X />
      </Button>
    </Group>
    <Group variant="controls" size="sm">
      <Badge size="sm" tone="neutral" emphasis="tertiary">
        Author
      </Badge>
      <Badge size="sm" tone="primary" emphasis="secondary">
        @ed
      </Badge>
      <Button
        aria-label="Dismiss"
        size="xs"
        shape="square"
        emphasis="secondary"
      >
        <X />
      </Button>
    </Group>
    <Group variant="controls" size="md">
      <Badge tone="warning" emphasis="primary">
        Draft
      </Badge>
      <Badge tone="neutral" emphasis="tertiary">
        12 files
      </Badge>
      <Button aria-label="Dismiss" size="sm" shape="square" emphasis="ghost">
        <X />
      </Button>
    </Group>
  </div>
</div>

Keyboard shortcuts

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.

⌘K
⌃⇧P
⌥Space
CtrlAltDel
---
// GENERATED from packages/demos/group/kbd.demo.tsx — edit the source, not this file.
import Kbd from "@pindoba/astro-kbd";
import Group from "@pindoba/astro-group";
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>

Custom Styling

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.

Custom style via passThrough

Custom props via passThrough

Panel chrome — bordered, padded, filled container

---
// GENERATED from packages/demos/group/custom.demo.tsx — edit the source, not this file.
import Group from "@pindoba/astro-group";
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>
  • Components
  • Blocks