Skip to content

Prompt bar

The composer: autosizing input, @ mentions, / commands, model, attachments, dictation and send.

Status
Beta
Category
AI
Adoption
Not used yet
import { PromptBar } from "@oration/canon/components/ai/prompt-bar";
packages/canon/src/components/ai/prompt-bar.tsx
Looking atNorthwind Freight

Type @ to add context. Type / at the start for commands. Enter sends, Shift+Enter adds a new line.

import { type PromptAttachment, PromptBar } from "@oration/canon/components/ai/prompt-bar";import { toast } from "@oration/canon/components/toast";import { BotIcon, Building2Icon, FileTextIcon, TicketIcon } from "lucide-react";import * as React from "react";export function Hero() {    const samples = [        { name: "remittance-PMT-58213.pdf", meta: "182 KB" },        { name: "w9-orchard-street.pdf", meta: "4.1 MB" },        { name: "payment-run-oct-2.csv", meta: "36 KB" },    ];    const [attachments, setAttachments] = React.useState<PromptAttachment[]>(        [],    );    const added = React.useRef(0);    const attach = () => {        const sample = samples[added.current % samples.length];        added.current += 1;        if (!sample) return;        const id = `att-${added.current}`;        setAttachments((list) => [...list, { id, ...sample, uploading: true }]);        window.setTimeout(() => {            setAttachments((list) =>                list.map((file) =>                    file.id === id ? { ...file, uploading: false } : file,                ),            );        }, 1100);    };    return (        <div className="flex min-h-80 w-full max-w-xl flex-col justify-end gap-2 text-left">            <div className="flex h-6 w-fit items-center gap-1.5 rounded-md bg-muted/70 px-1.5 text-xs">                <Building2Icon                    aria-hidden="true"                    className="size-3.5 text-muted-foreground"                />                <span className="text-muted-foreground">Looking at</span>                <span className="font-medium text-foreground">                    Northwind Freight                </span>            </div>            <PromptBar                label="Message Copilot"                placeholder="Ask about Northwind Freight"                mentions={[                    {                        id: "sup_northwind",                        label: "Northwind Freight",                        description: "Supplier",                        icon: <Building2Icon aria-hidden="true" />,                    },                    {                        id: "sup_halcyon",                        label: "Halcyon",                        description: "Supplier",                        icon: <Building2Icon aria-hidden="true" />,                    },                    {                        id: "inv_20931",                        label: "INV-20931",                        description: "Invoice, Northwind Freight",                        icon: <FileTextIcon aria-hidden="true" />,                    },                    {                        id: "ag_nora",                        label: "Nora",                        description: "Voice agent",                        icon: <BotIcon aria-hidden="true" />,                    },                    {                        id: "tck_1184",                        label: "TCK-1184 Short payment on INV-20877",                        description: "Ticket",                        icon: <TicketIcon aria-hidden="true" />,                    },                ]}                commands={[                    {                        id: "summarize",                        label: "summarize",                        description: "Summarize this page or record",                    },                    {                        id: "draft",                        label: "draft",                        description: "Draft a reply or an email to a supplier",                    },                    {                        id: "create-ticket",                        label: "create-ticket",                        description: "Open a ticket with fields filled in",                    },                ]}                models={[                    {                        id: "auto",                        label: "Auto",                        description: "Picks a model per step",                    },                    {                        id: "gpt-4-1",                        label: "GPT-4.1",                        description: "OpenAI, best for drafting",                    },                    {                        id: "mistral-large",                        label: "Mistral Large",                        description: "Mistral, lowest cost",                    },                ]}                attachments={attachments}                onAttach={attach}                onRemoveAttachment={(id) =>                    setAttachments((list) =>                        list.filter((file) => file.id !== id),                    )                }                dictationText="Why did Northwind Freight call three times this week?"                onSubmit={(text, details) => {                    const context = [                        details.command ? `/${details.command.label}` : null,                        ...details.mentions.map(                            (mention) => `@${mention.label}`,                        ),                        ...details.attachments.map((file) => file.name),                    ].filter(Boolean);                    toast.add({                        title: "Sent to Copilot",                        description: context.length                            ? `${text || "No text"}, with ${context.join(", ")}`                            : text,                    });                    setAttachments([]);                }}            />        </div>    );}

Usage#

Prompt bar is the composer under a Copilot or agent conversation: an autosizing textarea with @ mentions that attach records, / commands, a model picker, attachments, dictation and a round Send that turns into Stop while a response runs. Mentions, the command and files become removable chips above the text and travel with the submit as structured details, so the answer is grounded in the right supplier or invoice. Send is the view's one filled button, which is why nothing else near the composer should be filled. The common mistake is wiring it up without mentions or a specific placeholder, which leaves people guessing what Copilot can see.

When to use

  • As the composer of the Copilot panel, at md, with mentions, commands, models and attachments.
  • As the single-line composer of a side panel at sm, such as the Contact Center assist panel or a web-call chat with Nora.
  • For a one-shot natural-language request that starts a generation, such as describing a report to build.
  • Whenever people should be able to pull a supplier, invoice, ticket or agent into the prompt with @.

When not to use

  • For a long prompt or agent instructions that people edit like a document, with variables. Use Prompt editor
  • To generate the value of one form field, such as a tool description. Use AI generate button
  • For a plain multi-line form field such as a note on a supplier. Use Textarea
  • For searching or filtering a list. Use Search field
  • For a command palette that runs actions without a conversation. Use Command menu

The One Filled Button Rule

Send is a filled indigo button, so the composer's view can't have another one. Attach, Add context, the model picker and Dictate are ghost buttons in Slate Meta.

The Quiet Indigo Rule

Indigo appears on Send, on the live dot beside Listening while dictating, and on the focus ring. Chips are Well Gray.

The Hairline-and-Lift Rule

The bar is a card surface, rounded-xl bg-card shadow-border, with no CSS border. Focus adds a soft ring around the whole bar instead of a border on the textarea.

Anatomy#

remittance-PMT-58213.pdf182 KB

Type @ to add context. Enter sends, Shift+Enter adds a new line.

  1. Chips row. Appears once there is a command, a mention or a file: 24px Well Gray chips with an icon, a label, optional meta and a remove button.
  2. Textarea. Starts at one line and grows to maxHeight (240px), then scrolls. 16px text below 768px so mobile browsers don't zoom, 14px above.
  3. Attach. A 28px ghost icon button, shown when onAttach is passed. At sm it sits before the text.
  4. Add context. Inserts @ at the caret and opens the mention menu. md only, when mentions is passed.
  5. Model picker. An extra-small ghost button with the current model and a chevron, opening a radio menu. md only, when models is passed.
  6. Dictate. A ghost mic button that swaps to a check while listening. On by default.
  7. Send. A 28px round filled button with an up arrow, disabled until there is something to send. Becomes Stop, a filled square, while isRunning.

Examples#

Sizes

md has a toolbar row for attachments, context, the model and dictation. sm is one line, for the Contact Center assist panel and agent test chats.

md, the Copilot panel

Enter sends, Shift+Enter adds a new line.

sm, a side panel

Enter sends, Shift+Enter adds a new line.

import { PromptBar } from "@oration/canon/components/ai/prompt-bar";import { toast } from "@oration/canon/components/toast";export function Sizes() {    const send = (text: string) =>        toast.add({ title: "Message sent", description: text });    return (        <div className="flex w-full max-w-xl flex-col gap-6 text-left">            <div className="flex flex-col gap-2">                <span className="text-xs text-muted-foreground">                    md, the Copilot panel                </span>                <PromptBar                    label="Message Copilot"                    placeholder="Ask about suppliers or payments"                    onAttach={() =>                        toast.add({ title: "Choose files to attach" })                    }                    models={[                        { id: "auto", label: "Auto" },                        { id: "gpt-4-1", label: "GPT-4.1" },                    ]}                    dictationText="Which suppliers are in Friday's payment run?"                    onSubmit={send}                />            </div>            <div className="flex max-w-sm flex-col gap-2">                <span className="text-xs text-muted-foreground">                    sm, a side panel                </span>                <PromptBar                    size="sm"                    label="Ask AI about this conversation"                    placeholder="Ask about policy, payments or this supplier"                    dictationText="What's the early-payment discount on Halcyon's terms?"                    onSubmit={send}                />            </div>        </div>    );}

Mentions and commands

Type @ anywhere to attach a record, or / as the first character for a command. Both become chips, and onSubmit receives them as structured details.

Send a message to see the details onSubmit receives.

Type @ to add context. Type / at the start for commands. Enter sends, Shift+Enter adds a new line.

import { PromptBar } from "@oration/canon/components/ai/prompt-bar";import { Building2Icon, FileTextIcon, TicketIcon } from "lucide-react";import * as React from "react";export function MentionsAndCommands() {    const [last, setLast] = React.useState<string | null>(null);    return (        <div className="flex min-h-80 w-full max-w-xl flex-col justify-end gap-3 text-left">            <pre className="min-h-16 rounded-[10px] bg-muted/70 px-3 py-2 font-mono text-xs whitespace-pre-wrap text-muted-foreground">                {last ?? "Send a message to see the details onSubmit receives."}            </pre>            <PromptBar                label="Message Copilot"                placeholder="Type @ to add a record, or / at the start for a command"                dictation={false}                mentions={[                    {                        id: "sup_orchard",                        label: "Orchard Street",                        description: "Supplier",                        icon: <Building2Icon aria-hidden="true" />,                    },                    {                        id: "sup_northwind",                        label: "Northwind Freight",                        description: "Supplier",                        icon: <Building2Icon aria-hidden="true" />,                    },                    {                        id: "inv_os4471",                        label: "OS-4471",                        description: "Invoice, Orchard Street",                        icon: <FileTextIcon aria-hidden="true" />,                    },                    {                        id: "tck_1184",                        label: "TCK-1184 Short payment on INV-20877",                        description: "Ticket",                        icon: <TicketIcon aria-hidden="true" />,                    },                ]}                commands={[                    {                        id: "summarize",                        label: "summarize",                        description: "Summarize this page or record",                    },                    {                        id: "create-ticket",                        label: "create-ticket",                        description: "Open a ticket with fields filled in",                    },                ]}                onSubmit={(text, details) =>                    setLast(                        [                            details.command                                ? `Command: ${details.command.id}`                                : null,                            details.mentions.length                                ? `Mentions: ${details.mentions.map((m) => m.id).join(", ")}`                                : null,                            `Text: ${text || "none"}`,                        ]                            .filter(Boolean)                            .join("\n"),                    )                }            />        </div>    );}

Attachments

You own the list. Add an item with uploading while it uploads (Send stays disabled), flip it off when done, and clear the list on submit.

w9-orchard-street.pdf4.1 MB

Enter sends, Shift+Enter adds a new line.

import { type PromptAttachment, PromptBar } from "@oration/canon/components/ai/prompt-bar";import { toast } from "@oration/canon/components/toast";import * as React from "react";export function Attachments() {    const [files, setFiles] = React.useState<PromptAttachment[]>([        { id: "f1", name: "w9-orchard-street.pdf", meta: "4.1 MB" },    ]);    const count = React.useRef(1);    return (        <div className="w-full max-w-xl text-left">            <PromptBar                label="Message Copilot"                placeholder="Ask about these files"                dictation={false}                attachments={files}                onAttach={() => {                    count.current += 1;                    const id = `f${count.current}`;                    setFiles((list) => [                        ...list,                        {                            id,                            name: `remittance-PMT-5821${count.current}.pdf`,                            meta: "182 KB",                            uploading: true,                        },                    ]);                    window.setTimeout(() => {                        setFiles((list) =>                            list.map((file) =>                                file.id === id                                    ? { ...file, uploading: false }                                    : file,                            ),                        );                    }, 1500);                }}                onRemoveAttachment={(id) =>                    setFiles((list) => list.filter((file) => file.id !== id))                }                onSubmit={(_text, details) => {                    toast.add({                        title: "Sent with attachments",                        description: details.attachments                            .map((file) => file.name)                            .join(", "),                    });                    setFiles([]);                }}            />        </div>    );}

Send and stop

While isRunning is true, Send becomes Stop generating and calls onStop. Enter does nothing until the response finishes.

Enter sends, Shift+Enter adds a new line.

Send a message to start.

import { PromptBar } from "@oration/canon/components/ai/prompt-bar";import * as React from "react";export function RunningAndStop() {    const [running, setRunning] = React.useState(false);    const [status, setStatus] = React.useState("Send a message to start.");    const timer = React.useRef<number | undefined>(undefined);    React.useEffect(() => () => window.clearTimeout(timer.current), []);    return (        <div className="flex w-full max-w-xl flex-col gap-2 text-left">            <PromptBar                label="Message Copilot"                placeholder="Ask about Friday's payment run"                dictation={false}                isRunning={running}                onSubmit={(text) => {                    setRunning(true);                    setStatus(`Copilot is answering "${text}"`);                    timer.current = window.setTimeout(() => {                        setRunning(false);                        setStatus("Answer finished.");                    }, 5000);                }}                onStop={() => {                    window.clearTimeout(timer.current);                    setRunning(false);                    setStatus("You stopped the response.");                }}            />            <p role="status" className="px-1 text-xs text-muted-foreground">                {status}            </p>        </div>    );}

Dictation

The mic swaps the textarea for a listening row. The check inserts dictationText; the X or Escape cancels. Dictation is simulated in this prototype.

Enter sends, Shift+Enter adds a new line.

Enter sends, Shift+Enter adds a new line.

import { PromptBar } from "@oration/canon/components/ai/prompt-bar";import { toast } from "@oration/canon/components/toast";export function Dictation() {    return (        <div className="flex w-full max-w-xl flex-col gap-6 text-left">            <PromptBar                label="Message Copilot"                placeholder="Press the mic to dictate"                dictationText="Draft a reply to Aisha at Halcyon explaining the early-payment discount."                onSubmit={(text) =>                    toast.add({ title: "Message sent", description: text })                }            />            <div className="max-w-sm">                <PromptBar                    size="sm"                    label="Message Nora"                    placeholder="Message Nora"                    dictationText="Hi, I'm calling about invoice INV-20931."                    onSubmit={(text) =>                        toast.add({ title: "Sent to Nora", description: text })                    }                />            </div>        </div>    );}

Controlled value

Pass value and onValueChange to fill the composer from outside, the way Copilot drops an approval's edit draft into it.

Enter sends, Shift+Enter adds a new line.

0 characters

import { PromptBar } from "@oration/canon/components/ai/prompt-bar";import { Button } from "@oration/canon/components/button";import { toast } from "@oration/canon/components/toast";import * as React from "react";export function ControlledValue() {    const [value, setValue] = React.useState("");    const templates = [        "Draft a remittance reply to Halcyon",        "Summarize Northwind Freight's open invoices",        "Which suppliers are missing a W-9?",    ];    return (        <div className="flex w-full max-w-xl flex-col gap-3 text-left">            <div className="flex flex-wrap gap-1.5">                {templates.map((template) => (                    <Button                        key={template}                        type="button"                        variant="outline"                        size="sm"                        onClick={() => setValue(template)}                    >                        {template}                    </Button>                ))}            </div>            <PromptBar                label="Message Copilot"                placeholder="Pick a starter or type your own"                dictation={false}                value={value}                onValueChange={setValue}                onSubmit={(text) =>                    toast.add({ title: "Message sent", description: text })                }            />            <p className="px-1 text-xs text-muted-foreground tabular-nums">                {value.length} characters            </p>        </div>    );}

States#

Empty

Enter sends, Shift+Enter adds a new line.

Focused

Enter sends, Shift+Enter adds a new line.

Ready to send

Enter sends, Shift+Enter adds a new line.

Uploading
payment-run-oct-2.csvUploading

Enter sends, Shift+Enter adds a new line.

Running

Enter sends, Shift+Enter adds a new line.

Disabled

Enter sends, Shift+Enter adds a new line.

import { PromptBar } from "@oration/canon/components/ai/prompt-bar";import { toast } from "@oration/canon/components/toast";export function StatesMatrix() {    const send = (text: string) =>        toast.add({ title: "Message sent", description: text });    return (        <div className="grid w-full gap-x-6 gap-y-5 text-left sm:grid-cols-2">            <div className="flex min-w-0 flex-col gap-2">                <span className="text-xs text-muted-foreground">Empty</span>                <PromptBar                    size="sm"                    label="Empty example"                    placeholder="Ask about Northwind Freight"                    dictation={false}                    onSubmit={send}                />            </div>            <div className="flex min-w-0 flex-col gap-2">                <span className="text-xs text-muted-foreground">Focused</span>                <PromptBar                    size="sm"                    label="Focused example"                    placeholder="Ask about Northwind Freight"                    dictation={false}                    className="ring-3 ring-ring/20"                    onSubmit={send}                />            </div>            <div className="flex min-w-0 flex-col gap-2">                <span className="text-xs text-muted-foreground">                    Ready to send                </span>                <PromptBar                    size="sm"                    label="Ready example"                    defaultValue="Why is INV-20931 on hold?"                    dictation={false}                    onSubmit={send}                />            </div>            <div className="flex min-w-0 flex-col gap-2">                <span className="text-xs text-muted-foreground">Uploading</span>                <PromptBar                    size="sm"                    label="Uploading example"                    placeholder="Ask about this file"                    dictation={false}                    attachments={[                        {                            id: "u1",                            name: "payment-run-oct-2.csv",                            uploading: true,                        },                    ]}                    onSubmit={send}                />            </div>            <div className="flex min-w-0 flex-col gap-2">                <span className="text-xs text-muted-foreground">Running</span>                <PromptBar                    size="sm"                    label="Running example"                    placeholder="Ask a follow-up"                    dictation={false}                    isRunning                    onStop={() => toast.add({ title: "Response stopped" })}                    onSubmit={send}                />            </div>            <div className="flex min-w-0 flex-col gap-2">                <span className="text-xs text-muted-foreground">Disabled</span>                <PromptBar                    size="sm"                    label="Disabled example"                    placeholder="Copilot is off for this workspace"                    dictation={false}                    disabled                    onSubmit={send}                />            </div>        </div>    );}
States
StateTreatment
EmptyPlaceholder in Slate Meta, Send disabled.
FocusedKeyboard focus in the textarea draws a 3px Focus Indigo ring at 20% around the whole bar.
ReadySend enables once there is text, a command or an attachment, and no file is still uploading.
Menu openTyping @ (or / at the very start) opens a popover above the bar (menuSide="top") labelled Add context or Commands, with the first match highlighted. No matches shows No matching records or No matching commands.
With chipsPicked mentions, the command and attachments sit in a row above the text. The command can only be picked once.
UploadingAn attachment with uploading shimmers its name, shows Uploading and keeps Send disabled.
ListeningThe textarea is replaced by a row: an indigo pulsing dot and Listening, an ink waveform, an elapsed clock and a cancel button. The mic becomes a check that inserts the text.
RunningSend becomes Stop generating, enabled when onStop is passed. Pressing Enter does nothing until it finishes.
DisabledThe whole bar dims to 60%. The textarea, Attach, Add context, Dictate and Send are disabled; the model picker is not (see known gaps).

Behavior#

  • @ opens the mention menu anywhere after a space or at the start; / opens commands only as the first character, and only until a command is picked. Both menus filter by label or id as you type.
  • The mention menu shows up to eight matches and leaves out records already picked. Commands aren't capped.
  • Picking an item removes the typed trigger and query from the text and adds a chip. Focus stays in the textarea throughout; the menu is driven with aria-activedescendant.
  • Enter sends, Shift+Enter adds a line. Enter or Tab picks the highlighted menu item instead while a menu is open. IME composition is respected.
  • Backspace with the caret at the very start removes the last mention chip, then the command.
  • On submit, onSubmit(text, { mentions, command, model, attachments }) receives trimmed text, then the bar clears its text, mentions and command. Attachments belong to you: clear them in onSubmit.
  • value with onValueChange (or its alias onChange) makes the text controlled, which is how Copilot drops an edit draft into the composer. model with onModelChange controls the model the same way.
  • Dictation is simulated: the mic shows the listening row, and the check inserts dictationText after the current text. Escape or the X cancels without inserting.
  • onAttach is only a callback; the bar has no file input. You add PromptAttachment items, flip uploading off when they finish, and remove them in onRemoveAttachment.
  • The menus open without animation because typing opens them. The model picker is a dropdown menu and animates like one.

Do and don't#

Enter sends, Shift+Enter adds a new line.

Do. Write a placeholder that says what Copilot can see: Ask about Northwind Freight.

Enter sends, Shift+Enter adds a new line.

Don't. Use filler such as Type your message here. It's the one line that tells people what to ask.

Enter sends, Shift+Enter adds a new line.

Do. Let Send be the only filled button around the composer.

Enter sends, Shift+Enter adds a new line.

Don't. Add a filled Ask AI or Generate button beside it. Two filled buttons compete, and Enter already sends.

Content#

  • Placeholder: Ask about plus what's in context, such as Ask about Northwind Freight or Ask about policy, payments or this supplier. Default Ask anything.
  • label is the textarea's accessible name and should name the recipient: Message Copilot, Message Nora. Default Message.
  • Mention labels are the record's name as people see it in the CRM: Northwind Freight, INV-20931, TCK-1184 Short payment on INV-20877. Use description for the kind: Supplier, Invoice, Voice agent.
  • Command labels are the bare command, such as summarize or create-ticket. The chip and the welcome copy add the slash.
  • Command descriptions say what comes back: Open a ticket with fields filled in.
  • Attachment meta is the size, 182 KB. While uploading it reads Uploading.

Accessibility#

  • The textarea is named by label and described by a hidden hint that lists what works: Type @ to add context. Type / at the start for commands. Enter sends, Shift+Enter adds a new line.
  • While a menu is open the textarea sets aria-controls and aria-activedescendant; the menu is a role="listbox" of options with aria-selected.
  • Every icon button has a name and a tooltip: Attach files, Add context (with an @ key hint), Dictate, Insert dictation, Cancel dictation, Send, Stop generating. The model picker reads Model: Auto.
  • Chip remove buttons are named Remove Northwind Freight, and focus returns to the textarea after a removal.
  • Dictate sets aria-pressed while listening, and Listening is a role="status".
  • Text is 16px below 768px so iOS doesn't zoom on focus.
Keyboard interactions
KeysAction
@Opens the mention menu at the caret.
/As the first character, opens the command menu.
↓↑Moves the highlight in an open menu, wrapping.
EnterPicks the highlighted item, or sends when no menu is open.
TabPicks the highlighted item while a menu is open.
ShiftEnterAdds a new line.
EscCloses the menu, or cancels dictation.
BackspaceAt the start of the text, removes the last chip.

Design tokens#

Design tokens
TokenUsed for
--cardBar surface
shadow-borderHairline lift around the bar
--ringFocus ring at 20% around the bar
--mutedChips, menu highlight
--muted-foregroundPlaceholder, toolbar icons, chip icons and meta
--primarySend and Stop fill, the listening dot
--popoverMention and command menu
shadow-popoverMenu lift
--radius-xl12px bar corners
text-shimmerUploading attachment name

API reference#

PromptBar

The composer, rendered as a <form>. Also exported: the PromptMention, PromptCommand, PromptAttachment, PromptSubmitDetails and PromptModel types.

Other props spread onto Nothing. Only the props below are read..

Props of PromptBar
PropTypeDefaultDescription
valuestringNo defaultControlled text.
defaultValuestring""Initial text when uncontrolled.
onValueChange(value: string) => voidNo defaultCalled on every text change. onChange is an alias.
onSubmit(text: string, details: PromptSubmitDetails) => voidNo defaultTrimmed text plus { mentions, command, model, attachments }.
onStop() => voidNo defaultEnables Stop while running.
isRunningbooleanfalseTurns Send into Stop and blocks sending.
placeholderstring"Ask anything"Say what Copilot can see.
labelstring"Message"Accessible name of the textarea.
mentions{ id: string; label: string; description?: string; meta?: string; icon?: React.ReactNode }[]No defaultEnables @ and the Add context button.
commands{ id: string; label: string; description?: string; meta?: string; icon?: React.ReactNode }[]No defaultEnables / at the start.
models{ id: string; label: string; description?: string }[]No defaultShows the model picker at md. Defaults to the first.
modelstringNo defaultControlled model id.
onModelChange(id: string) => voidNo defaultCalled when the model changes.
attachments{ id: string; name: string; meta?: string; uploading?: boolean; icon?: React.ReactNode }[]No defaultFiles to show as chips. You own this list.
onAttach() => voidNo defaultShows Attach files.
onRemoveAttachment(id: string) => voidNo defaultShows a remove button on each file chip.
dictationbooleantrueShows the mic.
dictationTextstring"Draft a follow-up to Priya about the revised pricing."What the simulated dictation inserts.
size"sm" | "md""md"md has a toolbar row; sm is one line for side panels.
menuSide"top" | "bottom""top"Where the menus and model picker open.
maxHeightnumber240Pixels the textarea grows to before scrolling.
autoFocusbooleanNo defaultFocuses the textarea on mount.
disabledbooleanNo defaultDims the bar and disables the textarea and its buttons.
classNamestringNo defaultMerged onto the form.

Known gaps#

Where the implementation and the system disagree today. Follow the system, not the gap.

dictationText defaults to product copy (Draft a follow-up to Priya about the revised pricing.), and dictation defaults to on, so any prompt bar that forgets either shows a mic that inserts that sentence.

Dictation and attachments are simulated: there is no speech recognition and no file input.

Focus draws a 3px ring at 20% and no indigo border, softer than the field focus in DESIGN.md (indigo border plus a 3px ring at 50%). Disabled dims to 60% rather than the system's 50%.

The app's Copilot command labels start with / (/summarize), and the chip adds a slash icon, so the chip reads as two slashes.

The menus are positioned inside the form, not portaled. A parent with overflow-hidden clips them.

At sm there is no model picker and no Add context button; mentions still work by typing @.

disabled doesn't reach the model picker, which stays clickable on a dimmed bar.