Input Group
An input or textarea with icons, text or buttons attached inside its border.
@zenoxai/ui/components/input-group Markdownfunction InputGroupDemo() {
const [query, setQuery] = React.useState("")
return (
<InputGroup className="max-w-xs">
<InputGroupInput
type="search"
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search alerts"
aria-label="Search alerts"
/>
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
{query ? (
<InputGroupButton
size="icon-xs"
aria-label="Clear search"
onClick={() => setQuery("")}
>
<XIcon />
</InputGroupButton>
) : (
<Kbd>/</Kbd>
)}
</InputGroupAddon>
</InputGroup>
)
}Usage
import { SearchIcon } from "lucide-react"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@zenoxai/ui/components/input-group"
<InputGroup>
<InputGroupInput placeholder="Search alerts" aria-label="Search alerts" />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>
InputGroup draws one border around a control and its addons, and moves the focus ring and
the invalid ring from the control to the group. Use InputGroupInput or InputGroupTextarea
inside it, not the plain Input or Textarea,
which would draw a second border.
Put the control first and the addons after it. align on InputGroupAddon places the addon,
whatever its order in the markup:
align |
Places the addon |
|---|---|
inline-start (default) |
Before the control, on the same line. |
inline-end |
After the control, on the same line. |
block-start |
Above the control, full width. The group grows in height. |
block-end |
Below the control, full width. The group grows in height. |
A click on an addon outside its buttons focuses the input. Keep addons short: an icon, a unit, a prefix, a keyboard hint or one or two buttons. For a label and help text, use a Field around the group.
The example at the top of the page is the search field of a list: a search icon at the start,
and at the end the / shortcut hint, replaced by a “Clear search” button once there is text.
Browsers’ own clear buttons on type="search" are hidden by the base styles, so this button is
the only one.
Examples
Sizes
size sets the height of a one-line group: sm (28px), default (32px) and lg (36px), the
heights of Input and Button. Icons in an addon are
16px, 14px in an sm group.
<div className="flex w-full max-w-xs flex-col gap-3">
<InputGroup size="sm">
<InputGroupInput
placeholder="Filter assets"
aria-label="Filter assets"
/>
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput
placeholder="Search alerts"
aria-label="Search alerts"
/>
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>
<InputGroup size="lg">
<InputGroupInput
placeholder="Search domains"
aria-label="Search domains"
/>
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>
</div>Prefix and suffix text
InputGroupText shows fixed text, such as a protocol, a domain or a unit, that is part of the
value’s format but not typed by the user.
<InputGroup className="max-w-sm">
<InputGroupInput placeholder="acme" aria-label="Workspace subdomain" />
<InputGroupAddon>
<InputGroupText>https://</InputGroupText>
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<InputGroupText>.zenox.io</InputGroupText>
</InputGroupAddon>
</InputGroup>With a button
InputGroupButton is a Button sized for the group: xs (24px, the
default) and sm (28px) for buttons with text, icon-xs and icon-sm for icon-only buttons.
Each size has the text, icon and radius of the Button size of the same name. The 28px sizes
fit the default and lg groups; in an sm group use the xs sizes. It defaults to
variant="ghost" and type="button", so it does not submit the form.
<InputGroup className="max-w-sm">
<InputGroupInput
readOnly
defaultValue="https://hooks.zenox.io/t/acme/alerts"
aria-label="Alert webhook URL"
/>
<InputGroupAddon align="inline-end">
<InputGroupButton size="icon-xs" aria-label="Copy webhook URL">
<CopyIcon />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>With a textarea
A block-end addon under an InputGroupTextarea holds a hint and the submit button of a
comment or note box.
<InputGroup className="max-w-md">
<InputGroupTextarea
placeholder="Add a note for the next analyst"
aria-label="Investigation note"
rows={3}
/>
<InputGroupAddon align="block-end">
<InputGroupText>Visible to everyone in the tenant</InputGroupText>
<InputGroupButton variant="default" size="xs" className="ml-auto">
<SendIcon data-icon="inline-start" />
Add note
</InputGroupButton>
</InputGroupAddon>
</InputGroup>Accessibility
- The control needs its own name:
aria-label, or aFieldLabelpointing at itsid. Text in an addon, such ashttps://, is not part of that name; repeat it in the label when it matters. - Icon-only
InputGroupButtons need anaria-label. Lucide icons render witharia-hidden="true", so an icon addon adds nothing to what screen readers announce. - The addon’s click-to-focus is for pointer users only; keyboard users tab to the input directly.
- To mark the group invalid, set
aria-invalidon the control. The group draws the error ring.
API reference
Generated from the TypeScript source. className, style and children are accepted everywhere and omitted below.
InputGroup
InputGroup attaches icons, text, key hints or buttons to an input or a textarea inside one bordered box: a search field with its icon, a URL with its prefix, a message box with a send button. Use InputGroupInput or InputGroupTextarea as the control and an InputGroupAddon per attachment.
| Prop | Type | Default |
|---|---|---|
size | "default" | "sm" | "lg"Height of the group on one row: `sm` 28px, `default` 32px, `lg` 36px, the heights of Input and Button. An `sm` group fits `xs` buttons only. | default |
InputGroupAddon
InputGroupAddon holds the icons, text or buttons attached to the control. A click on its empty area focuses the input.
| Prop | Type | Default |
|---|---|---|
align | "inline-end" | "inline-start" | "block-start" | "block-end" | nullWhere the addon sits: `inline-start` (default) and `inline-end` before and after the control on its row; `block-start` and `block-end` as full-width rows above and below it, as around a textarea. | inline-start |
InputGroupButton
InputGroupButton is a compact button sized to sit in an addon. It defaults to type="button" and the ghost variant, so it never submits the form by accident.
| Prop | Type | Default |
|---|---|---|
focusableWhenDisabled | booleanWhether the button should be focusable when disabled. | false |
nativeButton | booleanWhether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). | true |
render | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, ButtonState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. | - |
size | "xs" | "sm" | "icon-xs" | "icon-sm" | xs |
type | "button" | "submit" | "reset" | button |
variant | "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | nullVisual weight. `default` is the filled primary action, one per view; `outline` and `secondary` are for the other actions; `ghost` has no fill until hover, for toolbars and icon buttons; `destructive` is for delete and other irreversible actions; `link` looks like inline text. | ghost |
InputGroupText
InputGroupText is static text or an icon inside an addon, such as a currency sign or a unit.
No props of its own. Accepts the props of the element it renders.
InputGroupInput
InputGroupInput is the Input to use inside an InputGroup: it drops its own border and focus ring, which the group draws instead.
No props of its own. Accepts the props of the element it renders.
InputGroupTextarea
InputGroupTextarea is the Textarea to use inside an InputGroup: it drops its own border and focus ring, which the group draws instead.
No props of its own. Accepts the props of the element it renders.