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

12 results
<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

https://
<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

0 / 280
<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

PropTypeDefaultDescription
size"xs" | "sm" | "md" | "lg" | "xl""md"Group height; mirrors Input sizes.
invalidbooleanfalseDestructive border on the wrapper.
disabledbooleanfalseDim + mute the whole group.
multilinebooleanfalseAuto height for a Textarea control.
slot start / slot endAstro slots—Addon markup on each edge.
classstring—Merged via cn(); Tailwind classes welcome.
idstring—Element id.
ariaLabelstring—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.