Input Group
Free · MITHeadless, accessible, and styled entirely through the attribute surface below — bring your own CSS system, or start from the reference theme.
Install
Without a flag you get no styling at all — plain HTML carrying the attribute surface below. --theme copies the same theme in two dialects;--bridge lumos re-points either one onto Lumos for Astro's tokens (it needs a theme to re-point, and Lumos itself, already in your project).
Unstyled — behavior only, you write the CSS
npx astrocraft-ui add forms/input-groupPlain-CSS theme — no build step
npx astrocraft-ui add forms/input-group --theme cssTailwind theme — needs Tailwind v4
npx astrocraft-ui add forms/input-group --theme tailwindPlain-CSS theme, re-pointed onto Lumos tokens
npx astrocraft-ui add forms/input-group --theme css --bridge lumosTailwind theme, re-pointed onto Lumos tokens
npx astrocraft-ui add forms/input-group --theme tailwind --bridge lumosLive demo
InputGroup & SearchField
An addon takes up width; an element sits on top of the field. The search field’s clear button appears once there is something to clear, and Escape empties it without leaving it.
Attribute surface
With no class-merging machinery, these attributes are the whole extension point. State is a data-attribute or a native attribute; visibility is the hiddenattribute. Generated from source by pnpm slots.
| Component | Slots | Variants | Runtime state | Native state |
|---|---|---|---|---|
| `InputAddon.astro` | `input-addon` | `data-side`: `start` · `end` | — | — |
| `InputElement.astro` | `input-element` | `data-side`: `start` · `end` | — | — |
| `InputGroup.astro` | `input-group` | `data-size`: `sm` · `md` · `lg` | — | — |
Source
What the command copies — 4 files, alias-free so the copy lands standing alone. This is the whole component; there is nothing else.
---
// src/components/ui/forms/input-group/InputAddon.astro — InputGroup compound part (see ../../README.md).
// An attached, in-flow prefix or suffix — "https://", ".com", a unit, a currency picker. `side` says
// which end it belongs to, as `data-side`, so a theme can round the correct corners.
//
// Decoration, not a control: text in here is announced with the field only if you say so. For a unit
// that changes meaning ("$" vs "€") give the FIELD the accessible name that includes it.
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"span"> & { side?: "start" | "end" };
const { side = "start", class: className, ...rest } = Astro.props;
---
<span class={className} data-slot="input-addon" data-side={side} {...rest}><slot /></span>
---
// src/components/ui/forms/input-group/InputElement.astro — InputGroup compound part (see ../../README.md).
// An element that sits ON TOP of the field — the search glyph, a loading spinner, a clear button.
// `side` is which end, as `data-side`; the theme does the positioning and the matching field padding.
//
// One rule when you theme it: an overlay holding only an icon needs `pointer-events: none`, or it
// eats the click that should have focused the field. An overlay holding a real control (a button)
// must NOT have it. That is why the rule is not in structure.css — the right answer depends on what
// you put inside, and the library cannot know.
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"span"> & { side?: "start" | "end" };
const { side = "start", class: className, ...rest } = Astro.props;
---
<span class={className} data-slot="input-element" data-side={side} {...rest}><slot /></span>
---
// src/components/ui/forms/input-group/InputGroup.astro — headless primitive (see ../../README.md).
// The wrapper a field needs when something sits beside it or on top of it. Structure only, zero-JS —
// it renders no CSS, it renders the element a theme's CSS has to hook.
//
// Two different things go inside it, and confusing them is the usual bug:
// • InputAddon — attached and IN FLOW ("https://", a currency select). It takes up width, so the
// field shrinks beside it and nothing overlaps.
// • InputElement — OVERLAID (a search glyph, a spinner). The theme positions it absolutely and
// pads the field to clear it; the field keeps its full width.
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"div"> & { size?: "sm" | "md" | "lg" };
const { size = "md", class: className, ...rest } = Astro.props;
---
<div class={className} data-slot="input-group" data-size={size} {...rest}>
<slot />
</div>
import InputAddon from "./InputAddon.astro";
import InputElement from "./InputElement.astro";
import InputGroup from "./InputGroup.astro";
export { InputAddon, InputElement, InputGroup };
export default InputGroup;
What you get
The component source, copied into your project by npx astrocraft-ui add forms/input-group — no package dependency, no CSS to fight. Whatever the paid blocks compose, this is it.