Forms · astro-ui · InputGroup
Input Group
Input with attached addons — icons, text prefixes, and buttons around the control.
InputGroup composes a text control with addons on either side: a leading icon, an https:// prefix, a trailing button, or anything else you slot in. The wrapper owns the border, focus ring, and height (mirroring Input sizes) while the inner control renders bare — focus styling is CSS :focus-within, so no JavaScript ships. Works with Input and Textarea alike; use multiline for a Textarea so addons stretch the full height.
Features
- Named start/end addon slots plus any control in the default slot
- Wrapper owns border + focus ring via :focus-within — zero JS
- Five sizes matching Input; addons scale with the size
- invalid / disabled state hooks mirroring Input
- multiline mode for Textarea controls
- Addons accept any markup: icons, text, Button, Kbd, Tag
Usage
---
import { InputGroup, Input } from "astro-ui";
---
<InputGroup>
<svg slot="start" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
<Input name="search" placeholder="Search…" />
</InputGroup>Demos
Icon + result count
<InputGroup>
<svg slot="start" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
<Input name="search" placeholder="Search…" />
<Fragment slot="end"><span class="aui-text-sm">12 results</span></Fragment>
</InputGroup>Text prefix + button
<InputGroup>
<Fragment slot="start">https://</Fragment>
<Input name="site" placeholder="example.com" />
<Fragment slot="end"><Button size="xs" variant="solid" color="carbon">Check</Button></Fragment>
</InputGroup>Textarea + actions
<InputGroup multiline>
<Textarea id="prompt" name="prompt" placeholder="Ask, Search or Chat..." rows={3} maxlength={280} />
<Fragment slot="bottom">
<Button size="icon" variant="ghost" ariaLabel="Add attachment">
<svg …><path d="M12 5v14M5 12h14"/></svg>
</Button>
<Button size="xs" variant="ghost">Auto</Button>
<span class="aui-input-group__spacer" />
<span id="count" style="font-variant-numeric:tabular-nums">0 / 280</span>
<span aria-hidden="true" style="inline-size:1px;block-size:1rem;background:var(--ui-input)" />
<Button size="icon" variant="solid" color="primary" style="border-radius:9999px" ariaLabel="Send">
<svg …><path d="M12 19V5M5 12l7-7 7 7"/></svg>
</Button>
</Fragment>
</InputGroup>
<script>
const ta = document.getElementById("prompt");
const out = document.getElementById("count");
const max = Number(ta.getAttribute("maxlength")) || 0;
const sync = () => { out.textContent = max ? ta.value.length + " / " + max : String(ta.value.length); };
ta.addEventListener("input", sync);
sync();
</script>Sizes
<InputGroup size="sm"><Input size="sm" placeholder="sm" /></InputGroup>
<InputGroup size="md"><Input placeholder="md" /></InputGroup>
<InputGroup size="lg"><Input size="lg" placeholder="lg" /></InputGroup>Invalid & disabled
<InputGroup invalid>
<Input name="url" invalid placeholder="Required field…" />
</InputGroup>
<InputGroup disabled>
<Input disabled value="Cannot edit" />
</InputGroup>API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | Group height; mirrors Input sizes. |
invalid | boolean | false | Destructive border on the wrapper. |
disabled | boolean | false | Dim + mute the whole group. |
multiline | boolean | false | Auto height for a Textarea control. |
slot start / slot end | Astro slots | — | Addon markup on each edge. |
class | string | — | Merged via cn(); Tailwind classes welcome. |
id | string | — | Element id. |
ariaLabel | string | — | Accessible name. |
Accessibility
Implemented accessibility features — semantics, ARIA wiring, and keyboard behavior that ship with the component.
- Native input semantics are untouched — label the control with Label + id as usual
- Decorative addon icons should be aria-hidden; addon text stays readable to screen readers
- Focus ring uses :focus-within so keyboard focus is always visible
FAQ
How is this different from just an Input?
InputGroup joins extra markup to the control so the border and focus ring wrap everything — an icon, a currency prefix, a submit button — without you hand-tuning borders or focus styles. The control itself stays a plain Input.
Does it work without JavaScript?
Fully. Addons are static markup and focus styling is CSS :focus-within — nothing ships or runs client-side.