Input Group
Puts icons, text and buttons inside a single input frame.
Installation
Install the following dependencies:
Set up the token layer and the StyleX compiler, if you have not already. See
Installation, and paste states.ts from the same page.
The frame's field surface, its focus ring, its invalid ring and its disabled
dim come from it. The
group also needs interop.css, which sizes the icons its addons do not render.
Paste field-state.ts and state-styles.ts from the same installation page.
Add Input and Button. The
group imports both, and InputGroupContext is declared in input.tsx so the
input stays pasteable without the group.
Copy and paste the following code into your project.
Update the import paths to match your project setup.
Usage
Composition
There is no InputGroupInput. Input finds the group through context and drops
its own border, fill and ring, so only the group draws a frame. Any other control
joins the frame when it has data-slot="input-group-control".
There is no InputGroupTextarea either. A Textarea
joins the frame through the same data-slot, plus textareaStyles.inGroup, which
removes the textarea's own frame.
Align
Use the align prop on InputGroupAddon to place the addon. The inline values
put the addon beside the control. The block values put it on its own row and
make the frame taller.
Render every addon after the control. align positions it with order, so
the control stays first in the DOM and therefore first in the tab order. An
addon written before the control appears in the same place but comes before
the control in the tab order.
Icon
Text
Use InputGroupText for a prefix, a unit or a counter.
Button
Use InputGroupButton for an action inside the frame. It defaults to the
ghost variant at size xs and shifts toward its addon's edge so its padding
does not stack with the addon's.
Dropdown
Pass InputGroupButton to a menu trigger through render.
Disabled and invalid
Set disabled or invalid on the enclosing Field or directly on the group. The group passes both to the
control through context, so one prop styles the frame and marks the input.
Setting disabled on the control alone greys the text but leaves the frame
undimmed. The frame cannot read its children's state, because :has() is below
this project's browser baseline. Set the state on the group instead.
API reference
Pass render to replace a part's element or compose it with another component.
Every part renders a div (or a span, for InputGroupText) and takes
xstyle and className.
InputGroup
| Prop | Type | Default |
|---|---|---|
disabled | boolean | false |
invalid | boolean | false |
xstyle | StyleXStyles | — |
className | string | — |
The group draws its focus ring from React state. It sets that state from a
focus event that bubbles up from an element with
data-slot="input-group-control". A button inside an addon keeps its own ring
and does not light the frame's ring.
InputGroupAddon
| Prop | Type | Default |
|---|---|---|
align | "inline-start" | "inline-end" | "block-start" | "block-end" | "inline-start" |
Clicking an addon focuses the group's control, unless the click landed on a button.
InputGroupButton
| Prop | Type | Default |
|---|---|---|
variant | every Button variant | "ghost" |
size | "xs" | "sm" | "icon-xs" | "icon-sm" | "xs" |
Takes every other Button prop. The larger Button sizes are left out because
they are taller than the frame.
InputGroupText
A muted run of text at the control's size, with a 0.5rem gap for an icon.
inputGroupStyles
The style maps behind the component. Spread them onto any element.
| Key | What it is |
|---|---|
base | The frame: field surface, wrap layout, min height |
focused | The ring, with no pseudo-class on it, over the drop |
invalid | The destructive border and ring |
disabled | The disabled fill, dimmed and inert |
addon | An addon's row, type and cursor |
aligns | One key per align value, each an order and inset |
text | InputGroupText |
buttons | An in-frame button, flat and concentric, xs/sm |
base is the frame's own box plus states.fieldSurface. focused is ring
with the field's shadow kept under it. invalid is invalidRing. All three
come from states in theme/states.ts. disabled is two styles, the group's own fill and
the shared disabled. buttons.xs and buttons.sm set
--carbon-button-radius to the frame's radius less the button's inset, for a
24px and a 28px button. The frame tracks focus itself, from a focus event
the control raises, so its ring has no pseudo-class.
Data attributes
| Attribute | On | Effect |
|---|---|---|
data-slot="input-group" | the frame | Targets the frame from a parent |
data-disabled | the frame | Present when disabled is set |
data-slot="input-group-addon" | an addon | Targets an addon from a parent |
data-align | an addon | Reflects the align prop |
data-slot="input-group-button" | a button | Targets a button from a parent |
data-slot="input-group-text" | a text run | Targets a text run from a parent |
data-slot="input-group-control" | the control | What the frame watches to draw its focus ring |