Input group
Puts icons, text or buttons inside an input or textarea.
Installation
If your project doesn't have Wisty UI's theme yet, add it once with npx shadcn@latest add @wisty/cosmos. Until then, components keep shadcn's default look. Installation has the details.
When to use
Use it
- An input needs an icon inside it, like a search field with a magnifying glass.
- An input needs fixed text before or after the value, like
https://orUSD. - An input needs a button inside it, like clear, copy or show password.
- A textarea needs a row of actions below the text, like a count and a send button.
Don't use it
- A plain field with no icon, text or button. Use Input or Textarea.
- People pick from suggestions as they type. Use Combobox, or Lookup for records.
- Several separate controls sit side by side. Use Button group or a flex row.
Usage
InputGroup
├── InputGroupInput
└── InputGroupAddon
├── InputGroupText
└── InputGroupButtonInputGroup is the field. Put an InputGroupInput or InputGroupTextarea in it, then the addons. An addon's align sets where it shows, before or after the control or on a row above or below it, whatever its place in the markup.
import { SearchIcon } from "lucide-react"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"<InputGroup>
<InputGroupInput type="search" aria-label="Search accounts" />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>Clicking an addon outside a button focuses an InputGroupInput, but not a textarea. A click on a button keeps focus there, so a button that hides itself, like Clear shown only while there's text, should move focus back to the input first.
Examples
Text
Fixed text in an `InputGroupText` before and after the input.
Buttons
A copy `InputGroupButton` with `size="icon-xs"` on a read-only link, and a text Apply button inside a promo code field.
Textarea
A count and a send button below an `InputGroupTextarea`, in an addon with `align="block-end"`.
States
One field with `aria-invalid` and an error, and one `disabled`, both set on `InputGroupInput`.
API reference
| Part | Built on |
|---|---|
InputGroup | <div> (opens in a new tab) |
InputGroupAddon | <div> (opens in a new tab) |
InputGroupButton | Button |
InputGroupText | <span> (opens in a new tab) |
InputGroupInput | <input> (opens in a new tab) |
InputGroupTextarea | <textarea> (opens in a new tab) |
InputGroupAddon
| Prop | Type | Default |
|---|---|---|
align | "inline-start" | "inline-end" | "block-start" | "block-end" | "inline-start" |
InputGroupButton
| Prop | Type | Default |
|---|---|---|
size | "xs" | "sm" | "icon-xs" | "icon-sm" | "xs" |
type | "button" | "submit" | "reset" | "button" |
variant | "default" | "outline" | "secondary" | "ghost" | "destructive" | "link" | "outline-brand" | "destructive-text" | "success" | "inverse" | "ghost" |
SLDS mapping
InputGroup is an SLDS input with icons, slds-input-has-icon, with its frame and states read from the control inside. Addon icons are slds-input__icon, and InputGroupText is SLDS's fixed text, slds-form-element__addon.
Known gaps:
- SLDS puts fixed text outside the field, beside it. Here it sits inside.
- SLDS has no rows above or below a control. Block addons follow SLDS's spacing, not a blueprint.
| Prop | Value | SLDS | Notes |
|---|---|---|---|
InputGroupAddon align | inline-start | slds-input-has-icon_left | Icons sit inside the field, as SLDS's do. Text sits inside too; SLDS's fixed text (slds-form-element__addon) sits outside. |
InputGroupAddon align | inline-end | slds-input-has-icon_right | Icon buttons sit inside, like SLDS's clear button (slds-input__icon_right). |
InputGroupAddon align | block-start | none | A row above the control. SLDS has none. |
InputGroupAddon align | block-end | none | A row below the control. SLDS has none. |
InputGroupButton size | xs | slds-button | 24px tall. |
InputGroupButton size | sm | slds-button | 28px tall. |
InputGroupButton size | icon-xs | slds-button_icon slds-input__icon | 24px square, like SLDS's clear button. |
InputGroupButton size | icon-sm | slds-button_icon | 28px square. |
Accessibility
- Name the control with a visible label. Fixed text isn't part of its name, so repeat
https://orUSDin the label or help text when people need it. - Give an icon button an
aria-label, like "Clear". - Tab follows the markup, not
align. A button in aninline-startorblock-startaddon placed after the control shows first but takes focus after it, so put that addon before the control. InputGroupButtondoesn't submit a form. Settype="submit"on the one that should.- In Cosmos dark, text in an invalid field is about 4.3:1 against its background, below AA. Salesforce's own stylesheet fails the same way.
Responsive behavior
- The control takes the room left after the addons, so long fixed text squeezes the value. Set the width on the parent.
Rules
- Use
InputGroupInputandInputGroupTextareainside a group, neverInputorTextarea. The group reads its state from them. - Put icons straight into
InputGroupAddonand actions intoInputGroupButton. Wrap text in anInputGroupTextinside the addon: bare text takes the icon color. - Set
aria-invalidanddisabledon the control, not on the group.