# Message

One chat message as a plate, with no chat around it, and made the way the chat is made. What you wrote is typed into a recess and sits to the right; what the assistant said is printed on a slip of paper torn along a perforation; what someone else wrote is a small raised plate. Along the top run the sender, printed as a legend, and the time; a lamp and a word say what has become of your message (sending, sent or not sent). A message that did not go grows a Retry key. Use it in a thread you build yourself, in a notification or in a log of what was said.

Kit part · Free · https://tactileui.dev/parts/message

- Element: `<article class="tui-message" data-from="user">`
- Needs: `kit.css` and `kit.mjs` ([how to install the kit](https://tactileui.dev/docs/installation#the-kit))
- Categories: AI chats
- Finishes: Aluminium, Graphite, Oak, Walnut, Porcelain, Black, Daylight, Night, Paper, Phosphor, E-paper, AMOLED
- Volumes: Tactile, Quiet

## Variants

### Yours · sent

```html
<div style="width:min(440px,calc(100vw - 96px));text-align:left"><article class="tui-message" data-from="user" data-state="sent" aria-labelledby="ms22-who"><header class="tui-message__meta"><span class="tui-label tui-message__who" id="ms22-who">You</span><span class="tui-message__state" role="status"><span class="tui-lamp" data-on aria-hidden="true"></span><span class="tui-message__words">Sent</span></span><time class="tui-message__time" datetime="2026-10-07T14:02">14:02</time></header><div class="tui-message__body"><p>Is Studio A free from 15:00 today?</p></div><button class="tui-key tui-key--flat tui-key--sm tui-message__retry" type="button" data-message-retry>Retry</button></article></div>
```

### Yours · sending, the lamp blinking

```html
<div style="width:min(440px,calc(100vw - 96px));text-align:left"><article class="tui-message" data-from="user" data-state="sending" aria-labelledby="ms23-who"><header class="tui-message__meta"><span class="tui-label tui-message__who" id="ms23-who">You</span><span class="tui-message__state" role="status"><span class="tui-lamp" data-on aria-hidden="true"></span><span class="tui-message__words">Sending</span></span><time class="tui-message__time" datetime="2026-10-07T14:02">14:02</time></header><div class="tui-message__body"><p>Is Studio A free from 15:00 today?</p></div><button class="tui-key tui-key--flat tui-key--sm tui-message__retry" type="button" data-message-retry>Retry</button></article></div>
```

### Yours · not sent, with Retry

```html
<div style="width:min(440px,calc(100vw - 96px));text-align:left"><article class="tui-message" data-from="user" data-state="failed" aria-labelledby="ms24-who"><header class="tui-message__meta"><span class="tui-label tui-message__who" id="ms24-who">You</span><span class="tui-message__state" role="status"><span class="tui-lamp" data-on aria-hidden="true"></span><span class="tui-message__words">Not sent</span></span><time class="tui-message__time" datetime="2026-10-07T14:02">14:02</time></header><div class="tui-message__body"><p>Is Studio A free from 15:00 today?</p></div><button class="tui-key tui-key--flat tui-key--sm tui-message__retry" type="button" data-message-retry>Retry</button></article></div>
```

### The assistant’s · a torn slip

```html
<div style="width:min(440px,calc(100vw - 96px));text-align:left"><article class="tui-message" data-from="assistant" aria-labelledby="ms25-who"><header class="tui-message__meta"><span class="tui-label tui-message__who" id="ms25-who">Studio assistant</span><time class="tui-message__time" datetime="2026-10-07T14:02">14:02</time></header><div class="tui-message__body"><p>Studio A is free from 15:00 to 18:00 today. Room 2 is held for Tom until 16:30.</p></div></article></div>
```

### Someone else’s · a plate

```html
<div style="width:min(440px,calc(100vw - 96px));text-align:left"><article class="tui-message" data-from="other" aria-labelledby="ms26-who"><header class="tui-message__meta"><span class="tui-label tui-message__who" id="ms26-who">Tom Hale</span><time class="tui-message__time" datetime="2026-10-07T14:02">14:02</time></header><div class="tui-message__body"><p>I can take Room 2 after 16:30 if that helps.</p></div></article></div>
```

### Several paragraphs

```html
<div style="width:min(440px,calc(100vw - 96px));text-align:left"><article class="tui-message" data-from="assistant" aria-labelledby="ms27-who"><header class="tui-message__meta"><span class="tui-label tui-message__who" id="ms27-who">Studio assistant</span><time class="tui-message__time" datetime="2026-10-07T14:02">14:02</time></header><div class="tui-message__body"><p>Studio A is free from 15:00 to 18:00.</p><p>Room 2 is held for Tom until 16:30, then free.
I have not booked anything.</p></div></article></div>
```

### No delivery to speak of · no data-state

```html
<div style="width:min(440px,calc(100vw - 96px));text-align:left"><article class="tui-message" data-from="user" aria-labelledby="ms28-who"><header class="tui-message__meta"><span class="tui-label tui-message__who" id="ms28-who">You</span><span class="tui-message__state" role="status"><span class="tui-lamp" data-on aria-hidden="true"></span><span class="tui-message__words"></span></span><time class="tui-message__time" datetime="2026-10-07T14:02">14:02</time></header><div class="tui-message__body"><p>Is Studio A free from 15:00 today?</p></div><button class="tui-key tui-key--flat tui-key--sm tui-message__retry" type="button" data-message-retry>Retry</button></article></div>
```

## Every state

- Sending
- Sent
- Not sent
- Retry focused

Hover, pressed and focus are forced with `.is-hover`, `.is-down` and `.is-focus`; checked and disabled are real.

## API

| Name | Kind | Values | What it does |
| - | - | - | - |
| article.tui-message[data-from][aria-labelledby] | element | user · assistant · other | The message. Yours is a recess to the right, the assistant’s a torn slip, anyone else’s a raised plate. Named by its sender. |
| [data-state] | attribute | sending · sent · failed | What became of it. The lamp follows it in CSS. Leave it off for a message with no delivery to report. |
| .tui-message__meta > .tui-message__who | element | the sender | A printed legend, which is also the message’s name. |
| .tui-message__state[role="status"] | element | lamp and words | A polite status region: a lit lamp (hidden from a screen reader) and the words “Sending”, “Sent” or “Not sent”. |
| time.tui-message__time[datetime] | element | the time | A real time element with a machine-readable datetime. |
| .tui-message__body | element | any text | The words. Paragraphs, line breaks kept, long words broken so nothing overflows. |
| button.tui-message__retry[data-message-retry] | element | shown when failed | Retry. Pressing it sets the message back to sending and fires tui-message-retry; focus stays with the message. |
| setMessageState(message, state) | function | needs kit.mjs | Sets data-state and the words, and fires tui-message-state. null removes the state. |
| messageStateText(state) | function | pure | The words for a state. |

## Markup

```html
<!-- data-from: user · assistant · other. data-state: sending · sent · failed (leave it off when delivery is not in question). -->
<article class="tui-message" data-from="user" data-state="failed" aria-labelledby="m1-who">
  <header class="tui-message__meta">
    <span class="tui-label tui-message__who" id="m1-who">You</span>
    <span class="tui-message__state" role="status">
      <span class="tui-lamp" data-on aria-hidden="true"></span><span class="tui-message__words">Not sent</span>
    </span>
    <time class="tui-message__time" datetime="2026-10-07T14:02">14:02</time>
  </header>
  <div class="tui-message__body"><p>Is Studio A free from 15:00 today?</p></div>
  <button class="tui-key tui-key--flat tui-key--sm tui-message__retry" type="button" data-message-retry>Retry</button>
</article>
```

```js
import { setMessageState } from '/kit/kit.mjs';

setMessageState(message, 'sending');          // the lamp follows data-state; the words change with it
document.addEventListener('tui-message-retry', async (e) => {
  try { await resend(e.detail.message); setMessageState(e.detail.message, 'sent'); }
  catch { setMessageState(e.detail.message, 'failed'); }
});
```

The demo on the page:

```html
<div class="tui-panel" style="width:min(100%,560px);display:grid;gap:16px;text-align:left"><div class="tui-panel__head" style="margin-bottom:0"><span class="tui-label">Thread · Studio A</span></div><div style="display:grid;gap:16px"><article class="tui-message" data-ms-demo data-from="user" data-state="sent" aria-labelledby="ms-d1-who"><header class="tui-message__meta"><span class="tui-label tui-message__who" id="ms-d1-who">You</span><span class="tui-message__state" role="status"><span class="tui-lamp" data-on aria-hidden="true"></span><span class="tui-message__words">Sent</span></span><time class="tui-message__time" datetime="2026-10-07T14:02">14:02</time></header><div class="tui-message__body"><p>Is Studio A free from 15:00 today?</p></div><button class="tui-key tui-key--flat tui-key--sm tui-message__retry" type="button" data-message-retry>Retry</button></article><article class="tui-message" data-from="assistant" aria-labelledby="ms-d2-who"><header class="tui-message__meta"><span class="tui-label tui-message__who" id="ms-d2-who">Studio assistant</span><time class="tui-message__time" datetime="2026-10-07T14:03">14:03</time></header><div class="tui-message__body"><p>Studio A is free from 15:00 to 18:00 today. Room 2 is held for Tom until 16:30.</p></div></article><article class="tui-message" data-from="other" aria-labelledby="ms-d3-who"><header class="tui-message__meta"><span class="tui-label tui-message__who" id="ms-d3-who">Tom Hale</span><time class="tui-message__time" datetime="2026-10-07T14:05">14:05</time></header><div class="tui-message__body"><p>I can take Room 2 after 16:30 if that helps.</p></div></article></div>
  <div style="display:flex;gap:12px;flex-wrap:wrap;align-items:center"><button class="tui-key tui-key--sm" type="button" data-ms-send>Send it</button><button class="tui-key tui-key--sm" type="button" data-ms-fail>Make it fail</button><output class="tui-muted" style="font-size:var(--tui-t-small)" data-ms-out aria-live="polite"></output></div></div>
```

## Usage notes

Press Send it: the message goes to sending, with its amber lamp blinking, then to sent. Press Make it fail and then Retry: the key goes back to sending and the focus stays on the message. The words and the lamp always agree; the lamp is never the news.

## Accessibility

- The message is an `<article>` named by its sender. The sender, the time, the state and the text are all real text, read in that order; the time is a `<time>` with a datetime.
- The state is a polite `role="status"` region, so changing its words says them aloud without moving focus. The lamp is hidden from a screen reader and always sits beside the word that says the same.
- A failure is also bold and says “Not sent”, so it is never a red lamp alone, and Retry appears as a real key in the reading order straight after the text. The words on the paper are in the paper’s own ink, above 6:1.
- When Retry is pressed with the keyboard, the key goes away, so the focus is moved to the message, which takes a visible 2px ring. The blink stops under reduced motion and the lamp stays lit.
- The key is 44px tall on a phone and the plate never scrolls sideways: long words break inside it.

## How it’s built

The recess is the one the chat uses for what you type, and the slip is the chat’s: a paper layer cut by a mask with small bites along the top and teeth at the foot, lifted by a shadow filter that follows the torn edge. It is made lighter here, with the perforation as two rows of gradients and no fibres, so a page can hold many. In the flat finishes the paper’s edge is drawn as a line, as the chat’s is.

## Rules

A single message with its sender, its time and what became of it, in the same hand as the chat: recessed for you, a torn slip for the assistant, a plate for anyone else.

The full rules are in `message.rules.md` in the brain, which ships with the Kit.

## Related parts

- [Chat](https://tactileui.dev/parts/chat): An AI chat as a teleprinter, with restraint: what you type sits in a recess; the answer prints on a perforated slip with an amber lamp at the print head; code comes up on display glass. Composer with attachments and a send key that becomes Stop.
- [Sync status](https://tactileui.dev/parts/sync-status): A small legend that says whether your work is safe: Saving, Saved or Offline, with a lamp that matches (amber and blinking while it saves, green when it has, red when it cannot). The words are the state and the lamp only agrees with them. It is a polite status region, so changing its words is what a screen reader hears.
- [Avatar](https://tactileui.dev/parts/avatar): A person’s mark, struck like a coin: initials stamped into the field, or a photo set under the rim, with a status lamp mounted at five o’clock. Stacks into a group with a count.
