Prompt input
Prompt input is the family of standalone pill-shaped pickers, toggles, and the submit button that make up the bottom action row of an LLM prompt input (also called a chat composer).
Example
PromptInput in this example is docs-only scaffolding, not a library export — the production prompt input container lives in the Hightouch app, so don't import it in product code. How it draws the chrome is documented in its source, docs/components/demos/prompt-input.js.
Usage
Picker
A PromptInputMenuButton with variant="picker" always shows its label and a chevron, so the current selection stays readable. It must be rendered inside a <Menu>, and the caller supplies the <MenuList> — the pill only styles the trigger. Use isPlaceholder for a prompt that hasn't been answered yet, and maxLabelWidth to change where a long label starts fading out (the label is masked with a fade rather than an ellipsis, so a truncated value never looks like part of the name).
A pill holds a selection, so fill the menu with a filter menu FilterMenuGroup of type="radio" rather than plain menu items — the open menu then checks the current value, and the pill's label and its menu can't disagree. Keep the <Menu> root: it closes the menu on select, which a picker wants and FilterMenu deliberately doesn't do.
Compact
A PromptInputMenuButton with variant="compact" is icon-only until it's active, then it expands to reveal its label and picks up a tint. Pass onClear to add a hover-revealed X that clears the choice without opening the menu, and tone="warning" when the active state is something the user should notice.
Pill button
PromptInputPillButton is the pill itself, without the menu wiring. Reach for it when the pill drives something other than a Menu — an onClick that opens a drawer, or a custom trigger.
It's also the shape to pass to a combobox's trigger={{ type: "component", ... }} when the picker needs search. The combobox hands the trigger everything it needs (onClick, aria-label, disabled and loading state), so spread those props onto the pill — isLoading is accepted and ignored, since the pill has no loading treatment.
Pass isLazy={false} with any non-input trigger: it renders the search field inside the popover, and a lazily mounted popover focuses that field before it has been positioned, which scrolls the page to the top on first open. popoverWidth keeps the option list from being sized to the pill.
Toggle
PromptInputToggle is a pill that turns a mode on and off. It renders as aria-pressed, so it's announced as a toggle button rather than a menu, and ariaLabel is required: the visible label only appears while the toggle is active, so the accessible name has to stay constant.
Submit button
PromptInputSubmitButton owns every state of sending, plus the tooltip that explains it. An empty prompt input (hasContent false) renders idle and disabled with no tooltip; content makes it active. isDisabled with a disabledMessage blocks sending and says why. While isLoading, passing onCancel turns the button into a Stop control, and omitting it shows a spinner instead.
Combinations
PromptInputMenuButton
One scenario throughout — each cell's menu holds the real option list with the shown value checked. For a picker, initial is the unanswered placeholder and active is a made selection; long label shows the overflow fade, and hovering any active compact cell reveals its clear ✕. PromptInputPillButton renders the same pixels as the menu button, so its states are covered here.
PromptInputToggle
PromptInputSubmitButton
Guidelines
When to use
- In the footer of a prompt input, for the row of controls that sits under the textarea.
- For compact inline pickers and toggles that belong to the prompt being written — a scope selector, a mode switch, an output format.
- For the send control of any prompt input, so submitting, blocking, and cancelling look and read the same across prompt surfaces.
When not to use
- For selection in a general form, use a select or a combobox — those carry labels, validation, and form-field sizing that a pill deliberately drops.
- For a page-level mode switch outside a prompt input, use a toggle button, a segmented control, or a switch.
- For a generic dropdown trigger, use a menu with a plain
MenuButton— the pill's shape only reads correctly inside a prompt input row. - For non-interactive status, use a badge.
BadgeandBadgeButtonare pill-shaped too, but they're labels: no expanding label, no menu, no toggle semantics, and no clear affordance. Reach for a prompt input pill only when the pill is a control in a prompt input.
Props
Every component inherits margin props.
PromptInputMenuButton
The menu-backed pill: the trigger inside a <Menu>, for choices that live in a dropdown. variant="picker" always shows the current selection with a chevron; variant="compact" is icon-only until active, then expands to reveal its label.
| Name | Default | Description |
|---|---|---|
label | — | stringPill text. A `compact` pill reveals it only while active. |
icon | — | ComponentType<SVGAttributes<SVGElement>>Leading glyph. A `compact` pill needs this or `leadingAccessory`; when both are passed, the glyph renders first. |
leadingAccessory | — | ReactNodeCustom leading element, for content a plain glyph can't express. Size it to `promptInputPillIconSize` — an inactive `compact` pill is a fixed 32px disc, so a wider accessory overflows it. |
tooltip | — | stringHover hint. The pill owns its `Tooltip`. |
maxLabelWidth | "130px" | stringWidth at which the label starts fading out. |
isDisabled | false | booleanToggles the disabled state. |
variant | — | "picker" | "compact"A `picker` always shows its label plus a chevron. |
isPlaceholder | false | booleanRender the label muted, for a not-yet-selected prompt. |
isActive | false | booleanWhether the pill is expanded and tinted. |
tone | "primary" | "primary" | "warning"Tint used while active. |
onClear | — | () => voidActive only: hover-revealed X that clears without opening a menu. |
clearAriaLabel | "Remove" | stringLabel of the clear X for screen readers. |
ariaLabel | label | stringLabel for screen readers. |
PromptInputPillButton
The plain action pill: the same shape without menu wiring, for an onClick that drives something other than a Menu — a drawer, a custom trigger.
| Name | Default | Description |
|---|---|---|
label | — | stringPill text. A `compact` pill reveals it only while active. |
icon | — | ComponentType<SVGAttributes<SVGElement>>Leading glyph. A `compact` pill needs this or `leadingAccessory`; when both are passed, the glyph renders first. |
leadingAccessory | — | ReactNodeCustom leading element, for content a plain glyph can't express. Size it to `promptInputPillIconSize` — an inactive `compact` pill is a fixed 32px disc, so a wider accessory overflows it. |
tooltip | — | stringHover hint. The pill owns its `Tooltip`. |
maxLabelWidth | "130px" | stringWidth at which the label starts fading out. |
isDisabled | false | booleanToggles the disabled state. |
variant | — | "picker" | "compact"A `picker` always shows its label plus a chevron. |
isPlaceholder | false | booleanRender the label muted, for a not-yet-selected prompt. |
isActive | false | booleanWhether the pill is expanded and tinted. |
tone | "primary" | "primary" | "warning"Tint used while active. |
onClear | — | () => voidActive only: hover-revealed X that clears without opening a menu. |
clearAriaLabel | "Remove" | stringLabel of the clear X for screen readers. |
isLoading | false | booleanAccepted so the pill can be a `Combobox` `trigger.type: "component"`, and ignored — the pill has no loading treatment. |
PromptInputToggle
The on/off pill: switches a mode and announces itself as a toggle (aria-pressed), revealing its label only while active.
| Name | Default | Description |
|---|---|---|
label | — | stringPill text, revealed while active. |
icon | — | ComponentType<SVGAttributes<SVGElement>>Leading glyph, always visible. |
isActive | — | booleanWhether the toggle is on. |
onToggle | — | () => voidCallback for when the toggle is clicked. |
ariaLabel | — | stringLabel for screen readers, needed because the label is hidden while inactive. |
tooltip | — | stringHover hint. |
isDisabled | false | booleanToggles the disabled state. |
tone | "primary" | "primary" | "warning"Tint used while active. |
PromptInputSubmitButton
The send control: owns every state of sending — idle, active, blocked with a disabledMessage, and loading with an optional Stop — plus the tooltip that explains it.
| Name | Default | Description |
|---|---|---|
hasContent | — | booleanWhether the prompt input has something to send. An empty prompt input renders the button idle and disabled. |
isDisabled | false | booleanBlocks sending even when the prompt input has content. |
isLoading | false | booleanWhether a response is in flight. |
onSubmit | — | () => voidCallback for when the prompt is submitted. |
onCancel | — | () => voidWhile loading: with `onCancel` the button becomes a Stop control; without it, a spinner. |
disabledMessage | — | stringWhy sending is blocked; shown as the tooltip instead of the send hint. |