Updated
AI
Message is one row of a conversation, and Prompt composer is the input under it. Together they cover a thread, an answer that is still arriving, an answer that failed, and the five states of the input.
Updated
On this page
Preview
Two surfaces an AI product cannot borrow from anything else in the system: the conversation row and the prompt input. Everything standing around them, suggestion chips, source cards, the tools menu and the model picker, is Chip, Card, Context Menu and Dropdown.
Source: the AI / Message and AI / Prompt Composer sets of the Appetite UI Figma file, read on October 2, 2026.
A thread is a column of Message rows and the composer is pinned under it, so neither component knows what the other holds. The exchange above is sample copy from the example screen, in use on the AI screens.
The three dots under a streaming answer are drawn inside Message. For an indicator that stands on its own, use Typing Indicator on the Progress page.
Which roles and states does Message have?
Message has two Roles, User and Assistant, and three States, Default, Streaming and Error. Role decides the shape and State decides what an unfinished answer looks like, so a thread is one component repeated.
State Streaming, 361 x 42
State Error, 361 x 112
| Property | Values | What it changes |
|---|---|---|
Role | User, Assistant | User draws a 280 bubble in bg/soft-100 pushed right. Assistant draws no container and runs the full 361. |
State | Default, Streaming, Error | Streaming hangs a 28 x 6 row of three dots under the answer. Error swaps the answer for a 12 radius panel with an icon, a reason and a Retry button. |
Message | Text | The body copy, and the only text property the set exposes. It defaults to Message text. |
Four of the 6 Role by State cells exist, because a user turn cannot stream or fail. Handle a failed send on the composer or as a Toast.
The Retry button is a Button at Type Error, Style Weak and Size sm: a state/error/base-500 label on a red tint at 10 percent.
The Answer frame uses gap 8 in Default and gap 12 in Streaming and Error, and the Actions frame under the error carries 30 of left padding to line the button up with the copy.
Which states does the Prompt composer have?
The Prompt composer has five states: Empty, Typing, Generating, Disabled and Attachment. One shell carries all five, and the state says what the input is doing: waiting, ready, working, shut or carrying a file.
Typing, 361 x 96
Generating, 361 x 96
Disabled, 361 x 96
Attachment, 361 x 157
Empty draws the send disc dormant, bg/soft-100 with an fg/disabled-400 arrow. Typing turns the disc brand/base as soon as the field holds text.
Generating clears the field, because the prompt has been sent, and puts Stop on the same disc. Disabled covers offline and over the limit, every glyph on fg/disabled-400 over a bg/weak-50 shell.
Attachment puts the file above the prompt with its own remove control. The plus opens the tools menu, a Context Menu instance, so the tool list is not fixed.
The shell is padded 16 on every side at radius 32, the same 16 as platform/screen-padding in the Platform collection. It is 96 tall at one line and 157 with a file and grows with its value, so set the ceiling in code.
Anatomy
Every part is measured and written as drawn. Where a value sits off the spacing scale it is written as it stands.
| Part | Measured | How it resolves |
|---|---|---|
| User bubble | 280 wide, padding 12 and 16, radii 20 20 6 20, 48 tall at one line | Fixed 280 in a 361 row aligned right. 16 + 248 + 16 = 280, and the clipped corner is the near one. |
| Assistant answer | 361 wide, 24 tall at one line | No frame, no fill. The Answer stack is gap 8 in Default and gap 12 in Streaming and Error. |
| Streaming dots | 28 x 6, three dots of 6 at gap 5 | fg/soft-500, 12 under the text. |
| Error panel | 361 x 112, padding 12, radius 12 | Row 337 x 40 at gap 10 with a 20 triangle-alert, then a 100 x 36 Retry. Actions carries 30 of left padding, off the scale, to align the button with the copy. |
| Composer shell | 361 wide, padding 16, radius 32, gap 8 | Content is 329 in every state. Heights run 96 and 157 with a file. |
| Controls row | 329 x 32, gap 8, tools gap 6 | Discs are 32 at radius 9999 holding an 18 glyph at 1.75. Stop is an 11 square at radius 2.5. |
| Attachment row | 329 x 53, padding 8 10 8 8, radius 16 | Tile 36 at radius 8, remove 20 and round, meta gap 1. 8 + 36 + 10 + 235 + 10 + 20 + 10 = 329. |
Both 1 pt strokes are drawn inside, so a CSS border would make the shell 363 wide and the attachment row 55. Draw them as inset shadows or inside strokes, which add nothing.
Tokens
Nothing on either component carries a raw color. Each token is resolved in Light and Dark.
| Where | Token | Light | Dark |
|---|---|---|---|
| Composer surface | bg/white-0 | #FFFFFF | #0E0E0F |
| Bubble, discs, tile | bg/soft-100 | #E9E9EA | #48484A |
| Disabled shell | bg/weak-50 | #F4F5F5 | #242427 |
| Remove control | bg/sub-200 | #D1D1D6 | #636366 |
| Both 1 pt strokes | border/soft-100 | #E9E9EA | #2F2F31 |
| Answer, bubble, typed value, file name, paperclip | text/strong-950, fg/strong-950 | #0E0E0F | #FFFFFF |
| Plus glyph | fg/sub-700 | #48484A | #C7C7CC |
| Placeholder, meta, dots | text/soft-500, fg/soft-500 | #8E8E93 | #AEAEB2 |
| Dormant and disabled glyphs | fg/disabled-400 | #AEAEB2 | #8E8E93 |
| Send and Stop disc | brand/base | #155DFC | #2B7FFF |
| Glyph on that disc | text/on-color/strong | #FFFFFF | #FFFFFF |
| Error panel | state/error/lighter-50 | #FEF2F2 | #FB2C36 at 16% |
| Error icon and Retry label | state/error/base-500 | #FB2C36 | #FB2C36 |
| Error reason | state/error/dark-950 | #460809 | #FFA2A2 |
| Retry fill | colors/alpha/red/alpha-10 | #FB2C36 at 10% | #FB2C36 at 10% |
| Composer shadow | dropShadow/sm | effect | effect |
In dark mode bg/white-0 becomes #0E0E0F and text/strong-950 becomes #FFFFFF, so surface and copy swap ends of the ramp together. The error tint is the one that does not simply invert: #FEF2F2 becomes red at 16 percent alpha, and the reason text moves from the darkest red to a light one so it stays readable.
How does the Prompt composer differ from the Input Composer?
The Prompt composer is a multi-line shell with a tools row, a send or stop disc and an optional attachment. The Input Composer is a single 40 high bar with a field, an optional avatar and a send circle.
The Input Composer, on the Input page, is for messages and comments. The field, the avatar and the send circle share one 40 pt height, in a Default or an Overlay style, with Empty and Typing states.
Use the Prompt composer where the person writes a longer instruction to a model, needs tools, attaches a file, or must be able to stop a running answer. Neither contains the other.
How do the AI components differ on iOS and Material 3?
Message and Prompt composer have no iOS or Material 3 counterpart, because neither platform ships a conversation or a prompt component. Build them from this set.
| Appetite UI | iOS counterpart | Material 3 counterpart | What differs |
|---|---|---|---|
| Message | None | None | The platforms leave conversation rows to the app. Assistant text runs the full 361 with no bubble. |
| Prompt composer | The nearest is iOS Text Field | The nearest is M3 Text Field | The native fields draw one field and nothing else. The composer adds the tools row, send and stop, and the attachment row. |
The send, stop and tools discs are 32, under the minimum touch target of 44 pt on iOS and 48 dp on Android set by platform/min-touch-target, so extend their hit area in code. See Platform Bridge, iOS and Material 3.
How should a conversation be laid out?
Run the assistant as plain text on the full 361, keep Generating reachable, and announce what arrives.
Do
Let the assistant run as plain text on the full 361. The reader scans one column and a long answer is not squeezed into a 280 bubble.
Don't
Do not give the assistant a bubble as well. Two bubbles read as two people, and the answer loses the width it was built for.
Keep Generating reachable, because Stop is the only control on either component that ends a running answer. The set carries no Streaming and no Error on the User side, so handle a failed send on the composer or as a Toast.
Announce what arrives. Put a streaming answer in a polite live region, move focus to Retry after an error, and give the send and stop disc one accessible name that changes with the state, Send then Stop.