ControlGroup
One flush surface that groups buttons, selects, and inputs into a strip with hairline dividers — the navbar preferences cluster as a component.
Usage
svelte
<script lang="ts">
import { ListFilter } from "@lucide/svelte";
import { css } from "@milandekruijf/pandacss/css";
import {
Button,
ControlGroup,
Input,
InputGroup,
Select,
} from "@milandekruijf/ui/svelte";
let query = $state("");
let sort = $state("newest");
const sorts = [
{ value: "newest", label: "Newest first" },
{ value: "oldest", label: "Oldest first" },
];
const fieldClass = css({ flex: "1 1 0", minW: 0 });
const iconClass = css({ w: 4, h: 4 });
</script>
<!-- Group flush buttons, selects, and inputs on one shared surface. -->
<ControlGroup ariaLabel="Document actions">
<Button color="flush">Copy</Button>
<Button color="flush">Export</Button>
</ControlGroup>
<ControlGroup full ariaLabel="Filter and sort">
<InputGroup class={fieldClass}>
{#snippet leading()}
<ListFilter class={iconClass} aria-hidden="true" />
{/snippet}
<Input bind:value={query} placeholder="Filter…" />
</InputGroup>
<Select
bind:value={sort}
options={sorts}
triggerVariant="flush"
showChevron={false}
ariaLabel="Sort by"
/>
</ControlGroup>Examples
Grouped controls
<script lang="ts">
import { ListFilter } from "@lucide/svelte";
import { css } from "@milandekruijf/pandacss/css";
import {
Button,
ControlGroup,
Input,
InputGroup,
Select,
} from "@milandekruijf/ui/svelte";
let query = $state("");
let sort = $state("newest");
const sorts = [
{ value: "newest", label: "Newest first" },
{ value: "oldest", label: "Oldest first" },
];
const stackClass = css({
display: "flex",
flexDirection: "column",
gap: 4,
minW: 0,
});
const fieldClass = css({ flex: "1 1 0", minW: 0 });
const iconClass = css({ w: 4, h: 4 });
</script>
<div class={stackClass}>
<ControlGroup ariaLabel="Document actions">
<Button color="flush">Copy</Button>
<Button color="flush">Export</Button>
</ControlGroup>
<ControlGroup full ariaLabel="Filter and sort">
<InputGroup class={fieldClass}>
{#snippet leading()}
<ListFilter class={iconClass} aria-hidden="true" />
{/snippet}
<Input bind:value={query} placeholder="Filter…" />
</InputGroup>
<Select
bind:value={sort}
options={sorts}
triggerVariant="flush"
showChevron={false}
ariaLabel="Sort by"
class={css({ flexShrink: 0 })}
/>
</ControlGroup>
</div>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| full? | boolean | false | Stretches to the available width (e.g. a sidebar) instead of hugging its controls. |
| dividers? | boolean | true | Draws a hairline between adjacent controls. |
| ariaLabel? | string | — | Accessible name for the group; also turns on `role="group"`. |
| rootRef? | HTMLDivElement | null | — | Bindable handle to the root element (`bind:rootRef`), e.g. to size an open menu. |
| class? | string | — | Extra classes merged after the recipe classes. |
| children | Snippet | — | The controls to group — buttons, selects, inputs, or a mix. Size flexible children yourself (e.g. `flex: 1`). |
The group owns the `slate.3` surface; children stay flush (`Button color="flush"`, `Select triggerVariant="flush"`, an `Input` on the shared surface). Dividers are drawn on each control's start edge, so a full-width group never paints past the last control.