Input group
Input with company — a prefix, a suffix, a leading icon, a button — inside one box that behaves like Input.
Beta · Forms · packages/css/src/components/input-group.css
.pui-input-group · __icon __prefix __control __suffix __button · size --small --large · --error --disabled · ground --on-dark. The nested button sits at the 4 px inset, radius 4.
Sizes across, states down.
What it is for
Section titled “What it is for”- Input’s properties — Placeholder, Value, Has value, Has placeholder, Has icon leading, Icon leading — plus Prefix / Has prefix, Suffix / Has suffix (text/subtle at the text’s size) and Has button; the Button is exposed and stretches to the box
- Three sizes: small has no button (no 20px Button exists); medium takes Button small; large takes Button medium
- Five states on the box, Input’s: hover border/strong, focus and error with the ring, disabled at .5 with its button
- One box, one border: never two bordered controls side by side pretending to be one (that is a row of Input + Button at space/8)
- The nesting rule: a control inside a radius-8 box takes radius 4 and an equal inset on every side — the inset is the size step (36 − 28 = 8, so 4 each side), never the text padding
- Ground on-dark — the same box on surface/inverse, the footer’s email field: the wash color/alpha/white-8, the line nav/item/bg-active, the ink nav/item/fg-active, the placeholder nav/section/fg, and the Button in it turned over — text/on-inverse under text/default. Hover, focus and error are the box’s own. Drawn at medium, the footer’s size; large takes the same colours
Use when an input needs a unit, a scheme or a domain beside its text (prefix / suffix), an icon in front of it, or one action attached to it — subscribe, search, copy, apply a code.
Do not use when the field stands alone (that is Input, in a Field); when two or more actions belong to the input (that is a toolbar); for a select beside an input (Select stays its own control).
Relates to Input, Field, Button, Icon button, Select, Combobox.
Anti-patterns a button of the box’s own height (it sits one size below, inside the padding) · a nested button at the box’s own radius 8 · a right inset larger than the top and bottom · a second border around the inner text · a prefix that repeats the label · a suffix used as a placeholder · a fill changed on the instance.
Keywords: input group, addon, prefix, suffix, search, subscribe, input with button
Figma — page Input group: Input group (20 variants)
shadcn: input-group
The markup — copy this
Section titled “The markup — copy this”The class, the parts, and nothing that is not in the package. The same block opens input-group.css.
<label class="pui-input-group"> <span class="pui-icon pui-input-group__icon">…search…</span> <span class="pui-input-group__prefix">https://</span> <input class="pui-input-group__control" type="text" placeholder="Your email"> <span class="pui-input-group__suffix">.com</span> <button class="pui-button pui-button--small pui-input-group__button">Subscribe</button></label>The modifiers
Section titled “The modifiers”| What it is | Note | |
|---|---|---|
| Size | --small 28 (12 px, no button — no 20 px Button exists) · (none) medium 36 (button small) · --large 44 (16 px, button medium) | — |
| State | :hover · :focus-within · error = --error or :has([aria-invalid=“true”]) (+ the error ring when focused) · disabled = --disabled or :has(:disabled) | — |
| Ground | (none) light · --on-dark — the same box on surface/inverse (the footer’s email field, request 33): the wash color/alpha/white-8, the line nav/item/bg-active, the ink nav/item/fg-active, the placeholder nav/section/fg, and the Button in it turned over — text/on-inverse under text/default, with the inset ring | — |
| Parts | __icon (16, text/muted) · __prefix / __suffix (the caption ink, the text’s size) · __control · __button | — |
Rule — one box, one border — never two bordered controls pretending to be one; the nested button takes radius 4 and the 4 px inset, never the text padding.
Accessibility — measured, not assumed
Section titled “Accessibility — measured, not assumed”Every number here is measured on the rendered component: contrast (npm run contrast:render), the focus ring (npm run focus) and the keyboard (npm run keyboard). The floors are principle 12’s.
Contrast
Section titled “Contrast”| ink on ground | reads | floor | the matrix calls it | |
|---|---|---|---|---|
#544d4d on #ffffff |
8.24 | 4.5 | text.muted on surface.page (promised) | ok |
#0b0202 on #ffffff |
20.51 | 4.5 | text.default on surface.page (promised) | ok |
#ffffff on #0b0202 |
20.51 | 4.5 | Primary button label (promised) | ok |
3 distinct pair(s) rendered, 0 that axe calls a violation.
The focus ring
Section titled “The focus ring”| primitive | on | the indicator | over | the change | |
|---|---|---|---|---|---|
| Input group | surface/page | shadow #6a66e4 2 px |
#ffffff |
4.53 | ok |
| Input group | surface/card | shadow #6a66e4 2 px |
#f2f2f7 |
4.06 | ok |
| Input group | surface/sunken | shadow #6a66e4 2 px |
#f2f2f2 |
4.05 | ok |
15 of 15 focus stop(s) in this block meet WCAG 2.4.13 on screen — the same crop screenshotted focused and unfocused, and the difference measured.
Keyboard
Section titled “Keyboard”Nothing in this component claims an ARIA role with a keyboard contract; its controls are ordinary ones and the browser already gives them their keys.
Tab order across the whole library is document order over 344 stops, and Shift+Tab is its exact reverse — no keyboard trap anywhere (WCAG 2.1.2).
Pepper UI v1.5.0
The Mobupps design system. 386 tokens, 88 components, 65 pages in the workshop — every page of this site generated from the repository, so nothing here is typed twice.