Skip to content
hightouchUI

Design system

v40.8.0

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.

Initial
Active
Long label
Disabled
Picker
Compact · primary
Compact · warning

PromptInputToggle

Initial
Active
Long label
Disabled
Primary
Warning

PromptInputSubmitButton

Empty
Ready
Disabled
Loading, cancellable
Loading

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.
  • Badge and BadgeButton are 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.

NameDefaultDescription
label

string

Pill 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

ReactNode

Custom 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

string

Hover hint. The pill owns its `Tooltip`.

maxLabelWidth"130px"string

Width at which the label starts fading out.

isDisabledfalseboolean

Toggles the disabled state.

variant

"picker" | "compact"

A `picker` always shows its label plus a chevron.

isPlaceholderfalseboolean

Render the label muted, for a not-yet-selected prompt.

isActivefalseboolean

Whether the pill is expanded and tinted.

tone"primary""primary" | "warning"

Tint used while active.

onClear

() => void

Active only: hover-revealed X that clears without opening a menu.

clearAriaLabel"Remove"string

Label of the clear X for screen readers.

ariaLabellabelstring

Label 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.

NameDefaultDescription
label

string

Pill 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

ReactNode

Custom 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

string

Hover hint. The pill owns its `Tooltip`.

maxLabelWidth"130px"string

Width at which the label starts fading out.

isDisabledfalseboolean

Toggles the disabled state.

variant

"picker" | "compact"

A `picker` always shows its label plus a chevron.

isPlaceholderfalseboolean

Render the label muted, for a not-yet-selected prompt.

isActivefalseboolean

Whether the pill is expanded and tinted.

tone"primary""primary" | "warning"

Tint used while active.

onClear

() => void

Active only: hover-revealed X that clears without opening a menu.

clearAriaLabel"Remove"string

Label of the clear X for screen readers.

isLoadingfalseboolean

Accepted 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.

NameDefaultDescription
label

string

Pill text, revealed while active.

icon

ComponentType<SVGAttributes<SVGElement>>

Leading glyph, always visible.

isActive

boolean

Whether the toggle is on.

onToggle

() => void

Callback for when the toggle is clicked.

ariaLabel

string

Label for screen readers, needed because the label is hidden while inactive.

tooltip

string

Hover hint.

isDisabledfalseboolean

Toggles 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.

NameDefaultDescription
hasContent

boolean

Whether the prompt input has something to send. An empty prompt input renders the button idle and disabled.

isDisabledfalseboolean

Blocks sending even when the prompt input has content.

isLoadingfalseboolean

Whether a response is in flight.

onSubmit

() => void

Callback for when the prompt is submitted.

onCancel

() => void

While loading: with `onCancel` the button becomes a Stop control; without it, a spinner.

disabledMessage

string

Why sending is blocked; shown as the tooltip instead of the send hint.