Input Group

One bordered field made of a control and its additions: icons, prefixes, shortcuts, and buttons. It powers search boxes, the workspace URL field, combobox inputs, picker searches, and the chat composer.

Sizes and inline additions

Default is 32px with 14px text, for toolbars and lists. Large is 44px with 16px text, for onboarding forms. Additions sit before or after the input.

⌘ K

default · leading icon · trailing shortcut

⌘K

lg · leading icon · trailing shortcut

Search with a clear button

Chat and list searches put a magnifier first and an icon-xs clear button last. Clear empties the text and puts focus back in the input.

  • Q3 board deck review
  • Northwind renewal terms
  • Hiring plan for support
  • Weekly metrics digest

Chats · 14px icon · 640 characters

No agents match. Clear the search to see all.

Search field · long text · 120 characters

Workspace URL

Large group with a text prefix. Invalid input turns the whole group red, and the field is locked while the workspace saves.

app.tulipfarm.ai/

Choose carefully. This address cannot be changed after creation.

app.tulipfarm.ai/

Use 3 to 48 lowercase letters, numbers, or hyphens.

Invalid · aria-invalid on the input

app.tulipfarm.ai/

Disabled while saving

Button sizes

xs and sm fit a short label; icon-xs and icon-sm are square. Buttons are ghost and type button unless you say otherwise.

https://

xs · ghost · label

sm · secondary · icon and label

icon-xs · ghost

lg · icon-sm · outline

No button pressed yet.

Combobox input

A Base UI Combobox renders its input and buttons through the group. The trigger and clear buttons are icon-xs; once there is a choice the clear button replaces the trigger.

Trigger · type or press ↓ to open

Trigger and clear · chosen value

Disabled

Command search

Pickers put a quiet, filled group at the top of their list. The input is any element; the leading icon can follow it in markup.

Message composer

The composer variant stacks attachments, the message, and a toolbar. Toolbar buttons are sm with a label or icon-sm; send turns from secondary to default once there is text.

Q3-forecast.xlsx

Nothing has been sent.

Question composer and custom controls

While an agent waits on a question, the composer border goes quiet. Any element marked data-slot input-group-control, like this rich text box, drives the group's focus and invalid states.

The agent asked: Which region should the Q3 report cover?

question-composer · rich text control

composer · disabled while read-only

Block additions

Block additions span a row above or below a textarea. Here a label sits on top and an icon-xs button and an sm default button sit underneath.

Draft message

Nothing has been sent.

Usage

Input Group usageTSX
import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupInput,
  InputGroupText,
  InputGroupTextarea,
} from "@tulipfarm/sprout/input-group";

// Search with a clear button
<InputGroup>
  <InputGroupAddon><Search3 size={14} aria-hidden="true" /></InputGroupAddon>
  <InputGroupInput ref={input} aria-label="Search chat titles" placeholder="Search chat titles"
    maxLength={640} value={query} onChange={event => setQuery(event.target.value)} />
  <InputGroupAddon align="inline-end">
    {query && (
      <InputGroupButton size="icon-xs" aria-label="Clear search"
        onClick={() => { setQuery(""); input.current?.focus(); }}>
        <X aria-hidden="true" />
      </InputGroupButton>
    )}
  </InputGroupAddon>
</InputGroup>

// Large, with a text prefix
<InputGroup size="lg">
  <InputGroupAddon><InputGroupText>app.tulipfarm.ai/</InputGroupText></InputGroupAddon>
  <InputGroupInput id="workspace-slug" required maxLength={48}
    aria-invalid={Boolean(error)} aria-describedby="workspace-slug-hint" />
</InputGroup>

// Composer
<InputGroup variant={questionPending ? "question-composer" : "composer"}>
  <InputGroupAddon align="block-start">{attachments}</InputGroupAddon>
  <InputGroupTextarea aria-label="Message" placeholder="Ask anything…" />
  <InputGroupAddon align="block-end" className="border-t">
    <DropdownMenuTrigger render={<InputGroupButton size="sm" aria-label="Add a file" />}>
      <Paperclip aria-hidden="true" /> Files
    </DropdownMenuTrigger>
    <InputGroupButton type="submit" size="icon-sm"
      variant={canSend ? "default" : "secondary"} aria-label="Send message">
      <ArrowUp aria-hidden="true" />
    </InputGroupButton>
  </InputGroupAddon>
</InputGroup>

// Base UI Combobox renders through the group parts
<InputGroup>
  <Combobox.Input render={<InputGroupInput />} aria-label="Choose a fruit" />
  <InputGroupAddon align="inline-end">
    <InputGroupButton size="icon-xs" render={<Combobox.Trigger />} aria-label="Show fruits">
      <ChevronDown aria-hidden="true" />
    </InputGroupButton>
    <Combobox.Clear render={<InputGroupButton size="icon-xs" />} aria-label="Clear selection">
      <X aria-hidden="true" />
    </Combobox.Clear>
  </InputGroupAddon>
</InputGroup>

Props

InputGroup takes size: default(32px, 14px text) or lg (44px, 16px text), andvariant: default, composer (a stacked message box with a stronger border and panel radius), orquestion-composer (the same box with a quieter border while an agent waits on an answer). It is a div withrole="group" and takes every div prop.

InputGroupAddon takes align:inline-start (default), inline-end,block-start, or block-end. It can come before or after the control in markup. Block additions turn the group into a column; add border-b or border-t for a divider row.

InputGroupButton is a Sprõut Button withsizexs (default), sm,icon-xs, or icon-sm, any Buttonvariant (default ghost), andtype="button" unless set. Use render to make it a menu, popover, tooltip, or combobox trigger.

InputGroupInput and InputGroupTextarea are SprõutInput and Textarea without their own border and fill. They take every native prop and read a surroundingField. InputGroupText is muted text for prefixes, units, and shortcuts.

Behavior

The group draws the border, focus ring, and invalid and disabled states for any element marked data-slot="input-group-control".InputGroupInput and InputGroupTextarea set it; add it to a rich text editor to get the same states. A control without it, like a command list's search input, keeps the group quiet.

Clicking an addon anywhere except a button focuses the control, like a label. Inside a combobox popup the group skips its focus ring, since the list shows where focus is. In the composer, toolbar buttons grow to 44px on touch screens.

Accessibility

Name the control with a label or aria-label, and name icon-only buttons with aria-label. Icons in addons should be aria-hidden. Mark errors witharia-invalid and point aria-describedby at the message. Keyboard focus shows a 2px ring around the whole group; the composer swaps it for a focus-colored border.

Border, fill, and ring changes take 120ms and stop when reduced motion is requested. In forced colors mode the group keeps a system border and a highlight focus ring.

Theme tokens

The border uses --sprout-input (composer:--sprout-border), the fill --sprout-surface, and addon text --sprout-muted. Focus uses--sprout-focus, errors --sprout-danger, and disabled --sprout-hover. Radius follows--sprout-radius-control, or--sprout-radius-panel for composers. The opt-in TulipFarm theme tints the focus ring and send button through these tokens.