Tessina UI
Tessina UI
GitHubIntroduction
InstallationUsageTheming
Components
ButtonIconButtonLinkSpinnerBadgeAvatarStatusChipShortcutSkeletonSurfaceProgressMeterRating
LabelFieldFieldsetCheckboxRadioSwitchSliderSelectComboboxSearchTextareaNumberFieldDate PickerTime PickerOTP InputFile UploadCalendar
AlertBannerToastTooltip
TabsAccordionCollapsibleBreadcrumbPaginationStepperSegmentedControlButtonGroupToggleButtonToggleGroupToolbarNavigation MenuMenubarBottom NavSidebar
Split ButtonFABDropdown MenuContextMenuCommandPopoverHoverCard
ContainerStackFlexGridAspectRatioSpacerCardCarouselDividerScroll Area
Table
ChatBubblePromptInputCodeBlock
ModalAlertDialogDrawerAction SheetTop Header MobileTop Header DesktopEmptyStateForm
Contributing
ComponentsAI

PromptInput

AI chat input with auto-grow textarea, send/stop swap, a "+" options menu, model selector, suggestion chips, drag-drop attachments, voice controls, inline & stacked layouts, RTL and a loading skeleton.

Playground

Installation

pnpm add @tessinaui/ui

Usage

import { PromptInputBar } from "@tessinaui/ui";
<PromptInputBar
  placeholder="Ask anything…"
  onSend={(value, attachments) => console.log(value, attachments)}
/>

Showcase

APIs

Two APIs are available depending on how much control you need.

APIWhen to use
PromptInputBarDrop-in single component for the 90% use case
PromptInput + sub-componentsFull control over toolbar layout and custom actions

PromptInputBar

The batteries-included compound. Manages value and attachment state, auto-clears on send, and composes every sub-component behind opt-in props.

<PromptInputBar
  placeholder="Message…"
  showMenu
  showVoice
  models={MODELS}
  suggestions={["Summarize", "Translate", "Explain"]}
  accept="image/*,.pdf"
  maxLength={500}
  isGenerating={streaming}
  onSend={(value, files) => send(value, files)}
  onStop={() => abort()}
/>

Compound API

Use PromptInput as the root and compose sub-components for full layout control.

import {
  PromptInput,
  PromptInputAttachments,
  PromptInputTextarea,
  PromptInputToolbar,
  PromptInputMenu,
  PromptInputModelSelect,
  PromptInputSpacer,
  PromptInputVoice,
  PromptInputCounter,
  PromptInputSend,
} from "@tessinaui/ui";

function CustomInput() {
  const [value, setValue] = React.useState("");

  return (
    <PromptInput
      value={value}
      onChange={setValue}
      onSend={(v) => { console.log(v); setValue(""); }}
      accept="image/*,.pdf"
      rounded="xl"
    >
      <PromptInputAttachments />
      <PromptInputTextarea placeholder="Message…" maxRows={6} />
      <PromptInputToolbar>
        <PromptInputMenu />
        <PromptInputModelSelect models={MODELS} defaultValue="sonnet-4-6" />
        <PromptInputSpacer />
        <PromptInputVoice />
        <PromptInputCounter />
        <PromptInputSend />
      </PromptInputToolbar>
    </PromptInput>
  );
}

Layout

layout="stacked" (default) places the toolbar in a row beneath the textarea. layout="inline" collapses the textarea and controls onto a single row — the compact ChatGPT-style pill. In the compound API, wrap the row in PromptInputRow.

<PromptInputBar layout="inline" rounded="full" showMenu showVoice />

Options menu

showMenu adds a "+" dropdown. With no menuItems it ships a default AI-tool set (Add photos & files, Create image, Thinking, Deep research, Web search). When the input is attachment-enabled, "Add photos & files" opens the file picker.

<PromptInputBar
  showMenu
  menuItems={[
    { label: "Attach image", icon: <ImageIcon />, onSelect: pickImage },
    { label: "Search the web", icon: <Globe />, onSelect: searchWeb },
  ]}
/>

Model selector

Pass a models list to render a model-picker pill in the toolbar. It is controlled with model / onModelChange, or uncontrolled with defaultModel.

const MODELS = [
  { value: "opus-4-7", label: "Opus 4.7", description: "Most capable" },
  { value: "sonnet-4-6", label: "Sonnet 4.6", description: "Balanced" },
  { value: "haiku-4-5", label: "Haiku 4.5", description: "Fastest" },
];

<PromptInputBar models={MODELS} defaultModel="sonnet-4-6" onModelChange={setModel} />

Suggestions

suggestions renders a horizontally-scrollable chip row below the bar. suggestionBehavior="fill" (default) drops the text into the input; "send" submits it immediately. The chips inherit the bar's size and rounded so they stay visually consistent with the input.

<PromptInputBar
  suggestions={["Summarize a document", "Write a function", "Brainstorm ideas"]}
  suggestionBehavior="fill"
/>

Attachments

Set accept (and optionally maxFiles / maxFileSize) to enable attachments. Files can be added by drag-and-drop, clipboard paste, or the "+" menu. Previews render above the textarea with a remove button, and a message can be sent with attachments only — no text required.

<PromptInputBar
  showMenu
  accept="image/*,.pdf,.txt"
  maxFiles={4}
  maxFileSize={5 * 1024 * 1024}
  onSend={(text, files) => send(text, files)}
  onFilesRejected={(rejected) => toast(`${rejected.length} file(s) rejected`)}
/>

Attachment state can also be controlled with attachments / onAttachmentsChange.

Voice & voice mode

showVoice adds a microphone (dictation) button — pass isRecording for the pulsing state. showVoiceMode adds the solid voice-mode button.

<PromptInputBar
  showVoice
  showVoiceMode
  isRecording={recording}
  onVoice={toggleDictation}
  onVoiceMode={openVoiceMode}
/>

Send / Stop swap

Pass isGenerating={true} to swap the send button for a stop button. The textarea stays editable while generating so the next message can be composed.

<PromptInputBar
  isGenerating={isStreaming}
  onSend={(v) => startStream(v)}
  onStop={() => abortStream()}
/>

Character counter

maxLength enables a live counter that turns warning past 80% and error at the limit. The count is announced to screen readers only near the limit.

<PromptInputBar maxLength={500} showCounter />

Banner

banner renders a dismissible notice at the top of the bar — handy for usage notes or model warnings.

<PromptInputBar
  banner="Opus consumes usage limits faster than other models."
  bannerIcon={<Sparkles />}
  onBannerDismiss={dismiss}
/>

Skeleton

Render PromptInputSkeleton while the interface loads. It mirrors the bar's size, rounded and layout.

<PromptInputSkeleton layout="stacked" />
<PromptInputSkeleton layout="inline" rounded="full" />

RTL

Pass dir="rtl" to PromptInput or PromptInputBar. The layout, toolbar, menu, attachments and suggestion rows all mirror automatically.

<PromptInputBar dir="rtl" placeholder="اكتب رسالتك…" onSend={handleSend} />

Keyboard behaviour

KeyAction
EnterSubmit — calls onSend
Shift + EnterInsert newline
Alt + EnterInsert newline (alternative)
IME composingEnter submit is suppressed until composition ends

Accessibility

  • Every interactive control exposes a ≥44×44px touch target (WCAG 2.5.5), so the toolbar is comfortable on mobile without enlarging the visual icons.
  • The textarea uses a ≥16px font on touch viewports to prevent iOS focus-zoom.
  • The send/stop button, voice buttons and menu trigger carry descriptive aria-labels; the counter announces politely only near the limit.

Sub-components

ComponentDescription
PromptInputRoot context provider — value, attachments, callbacks, size, layout, direction
PromptInputBannerDismissible notice row at the top of the bar
PromptInputTextareaAuto-growing textarea — expands up to maxRows before scrolling
PromptInputToolbarBottom action row (stacked layout)
PromptInputRowSingle-row wrapper for the inline layout
PromptInputSpacerflex-1 spacer to push items to either end
PromptInputAttachPaperclip icon button — opens the file picker
PromptInputMenu"+" dropdown of options
PromptInputModelSelectModel-picker pill
PromptInputVoiceMicrophone / dictation button with pulsing recording state
PromptInputVoiceModeSolid voice-mode button
PromptInputCounterCharacter count with limit colouring
PromptInputSendSend / Stop button — swaps based on isGenerating
PromptInputDropOverlayFull-surface drag-to-attach overlay (auto-rendered)
PromptInputAttachmentsInline attachment previews with remove buttons
PromptInputSuggestionsPredefined suggestion chip row
PromptInputSkeletonLoading placeholder mirroring the bar
PromptInputBarAll-in-one compound wrapping every sub-component

API Reference

PromptInputBar — core

PropTypeDefaultDescription
valuestring—Controlled value
defaultValuestring""Uncontrolled initial value
onChange(value: string) => void—Fires on every keystroke
onSend(value: string, attachments: PromptInputAttachment[]) => void—Called on Enter or Send; value + attachments auto-clear
onStop() => void—Called when the Stop button is pressed
isGeneratingbooleanfalseSwaps Send for Stop
size"sm" | "md" | "lg""md"Textarea and button sizing
rounded"sm" | "md" | "lg" | "xl" | "full""xl"Container border-radius
layout"stacked" | "inline""stacked"Toolbar-below vs single-row layout
disabledbooleanfalseDisables all interactions
dir"ltr" | "rtl""ltr"Text direction
placeholderstring"Message…"Textarea placeholder
maxRowsnumber8Lines before the textarea scrolls
maxLengthnumber—Hard character limit; enables the counter
showCounterbooleanfalseForce-show the counter without maxLength

PromptInputBar — toolbar slots

PropTypeDefaultDescription
showMenubooleanfalseShow the "+" options menu
menuItemsPromptInputMenuItem[]default setCustom menu items
showAttachbooleanfalseShow the paperclip attach button
onAttach() => void—Overrides the attach button click
showVoicebooleanfalseShow the microphone button
onVoice() => void—Called when the mic is clicked
isRecordingbooleanfalsePulsing recording state on the mic
showVoiceModebooleanfalseShow the solid voice-mode button
onVoiceMode() => void—Called when voice mode is clicked
isVoiceModeActivebooleanfalseActive state on the voice-mode button
modelsPromptInputModelOption[]—Renders the model-selector pill
model / defaultModelstring—Controlled / uncontrolled model value
onModelChange(value: string) => void—Fires when the model changes

PromptInputBar — suggestions, banner & attachments

PropTypeDefaultDescription
suggestionsPromptInputSuggestion[]—Suggestion chips below the bar
suggestionBehavior"fill" | "send""fill"Fill the input vs submit on click
onSuggestionSelect(value: string) => void—Fires when a chip is clicked
bannerReact.ReactNode—Dismissible banner content
bannerIconReact.ReactNode—Leading icon for the banner
onBannerDismiss() => void—Called when the banner is dismissed
acceptstring—Accepted file types; enables attachments
maxFilesnumber—Maximum attachment count
maxFileSizenumber—Maximum size per file, in bytes
attachments / defaultAttachmentsPromptInputAttachment[]—Controlled / uncontrolled attachment list
onAttachmentsChange(attachments: PromptInputAttachment[]) => void—Fires when attachments change
onFilesRejected(files: File[]) => void—Files rejected by the accept / size / count limits
attachablebooleanautoForce-enable drag-drop + paste handling

PromptInput (root)

Accepts all PromptInputBar props except the show* slot-visibility flags and the model / suggestion / banner conveniences. Context is consumed by every sub-component.

PromptInputMenu

PropTypeDefaultDescription
itemsPromptInputMenuItem[]default setMenu items
childrenReact.ReactNode—Custom menu content — overrides items
iconReact.ReactNode<Plus />Trigger icon
side"top" | "bottom""top"Menu open direction
align"start" | "center" | "end""start"Menu alignment

PromptInputModelSelect

PropTypeDefaultDescription
modelsPromptInputModelOption[]—Selectable models (required)
value / defaultValuestring—Controlled / uncontrolled selection
onValueChange(value: string) => void—Fires on selection

PromptInputSuggestions

PropTypeDefaultDescription
suggestionsPromptInputSuggestion[]—Chips to render (required)
behavior"fill" | "send""fill"Fill the input vs submit on click
onSelect(value: string) => void—Fires when a chip is clicked
chipVariant"solid" | "soft" | "outline" | "ghost""outline"Chip fill style
size"sm" | "md" | "lg"inheritedChip size — inherits the bar's size
rounded"sm" | "md" | "lg" | "xl" | "full"inheritedChip rounding — inherits the bar's rounded

PromptInputSkeleton

PropTypeDefaultDescription
size"sm" | "md" | "lg""md"Mirrors the bar size
rounded"sm" | "md" | "lg" | "xl" | "full""xl"Mirrors the bar radius
layout"stacked" | "inline""stacked"Mirrors the bar layout
showToolbarbooleantrueRender the toolbar placeholder row

PromptInputDropOverlay

PropTypeDefaultDescription
isVisibleboolean—Shows the overlay
labelstring"Drop files to attach"Overlay label text

ChatBubble

AI chat message bubbles with role-aware alignment, streaming state, action buttons, and RTL support.

CodeBlock

Syntax-highlighted code display with single-file and multi-tab modes, copy button, line highlighting, and collapsible long blocks — always rendered in a dark palette.

On this page

PlaygroundInstallationUsageShowcaseAPIsPromptInputBarCompound APILayoutOptions menuModel selectorSuggestionsAttachmentsVoice & voice modeSend / Stop swapCharacter counterBannerSkeletonRTLKeyboard behaviourAccessibilitySub-componentsAPI ReferencePromptInputBar — corePromptInputBar — toolbar slotsPromptInputBar — suggestions, banner & attachmentsPromptInput (root)PromptInputMenuPromptInputModelSelectPromptInputSuggestionsPromptInputSkeletonPromptInputDropOverlay