Studio A is free from 15:00 to 18:00. Room 2 is held
Kit part · AI chats
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.
- Element
- <tui-chat>
- Needs
- kit.css + kit.mjs
- Rules
- chat.rules.md · 5
- Finishes
- Aluminium · Graphite · Porcelain · Black
- Volumes
- Tactile · Quiet
Variants
Ask about rooms, sessions and bookings. Answers print here.
node kit/build.mjsEvery state, at once
Both finishes, side by side. Hover, pressed and focus are forced with .is-hover, .is-down and .is-focus; checked and disabled are real.
Aluminium
| Part | Rest | Focus | Printing | Disabled |
|---|---|---|---|---|
| Send key | ||||
| Composer | ||||
| Slip | Studio A is free. | — | Studio A is | — |
Graphite
| Part | Rest | Focus | Printing | Disabled |
|---|---|---|---|---|
| Send key | ||||
| Composer | ||||
| Slip | Studio A is free. | — | Studio A is | — |
API
| Name | Kind | Values | What it does |
|---|---|---|---|
| label | attribute | required | Who answers. Names the log (“Conversation with Studio assistant”), the text area and each slip. |
| placeholder | attribute | an example question | The composer’s placeholder: an example, never the label. |
| demo | attribute | — | Print a sample answer when nobody cancels tui-chat-send. For specimens and static pages. |
| .tui-chat__empty | class | child | The empty state, shown until the first message: an unlit lamp, one sentence, suggestions. |
| [data-suggest] | attribute | on a key | Sends its own words as a message. |
| .tui-chat__msg[data-from] | class | "user" · "assistant" | A message. aria-label “Name, time” becomes the printed stamp on a slip. |
| pre[data-label] | element | in an answer | Becomes a code block on glass, with its label and a copy key. |
| say(text, files) | method | — | Add your own message to the thread. |
| reply() | method | → { write, code, end, stop } | Start a slip. write(text) prints (a blank line starts a paragraph), code(text, label) adds a block, end() stamps it, stop() ends it early. |
| tui-chat-send | event | cancelable · { text, files } | A message was sent. Cancel it to answer yourself. |
| tui-chat-stop | event | bubbles | Stop was pressed while an answer printed. |
| --tui-chat-h | CSS property | 540px | The chat’s height; the thread scrolls inside it. |
| Enter · Shift+Enter | keyboard | in the composer | Send · a new line. |
Markup
<tui-chat label="Studio assistant" placeholder="e.g. Which room is free at 3?">
<!-- The empty state: shown until the first message. -->
<div class="tui-chat__empty">
<span class="tui-status"><span class="tui-lamp" data-lamp="amber"></span>Ready</span>
<p>Ask about rooms, sessions and bookings.</p>
<div class="tui-chat__suggest">
<button type="button" class="tui-key tui-key--sm" data-suggest>Which room is free at 3?</button>
</div>
</div>
<!-- Messages you already have. <pre> in an answer becomes a code block with a copy key. -->
<article class="tui-chat__msg" data-from="user" aria-label="You, 14:18">
<div class="tui-chat__body"><p>Is anything free this afternoon?</p></div>
</article>
<article class="tui-chat__msg" data-from="assistant" aria-label="Studio assistant, 14:18">
<div class="tui-chat__body"><p>Studio A is free from 15:00 to 18:00.</p>
<pre data-label="JavaScript"><code>await fetch('/api/holds', …)</code></pre></div>
</article>
</tui-chat>import '/kit/kit.mjs'; // defines <tui-chat>
const chat = document.querySelector('tui-chat');
chat.addEventListener('tui-chat-send', async (e) => {
e.preventDefault(); // we answer, not the demo
const out = chat.reply(); // a slip feeds out; the lamp blinks
for await (const piece of ask(e.detail.text, e.detail.files)) {
piece.code ? out.code(piece.code, piece.language) : out.write(piece.text);
}
out.end(); // lamp out, time stamped, Copy added
});
chat.addEventListener('tui-chat-stop', () => abort());Needs kit.css and kit.mjs. How to install the kit.
Accessibility
- The thread is a
role="log", a polite live region, and focusable so it scrolls from the keyboard. A slip isaria-busywhile it prints, so it is read once, whole, not letter by letter. - Every message is an
<article>named by who and when (“Studio assistant, 14:18”). - The send key names what it will do (“Send” or “Stop the reply”) and is disabled while there is nothing to send.
- Attached files are listed as tags, each with a remove key that names the file.
- Printing is shown by words (“printing”) beside the amber lamp; the lamp stops blinking under reduced motion.
How it’s built
A custom element that builds a scrolling log and a composer around your markup. A slip is a raised face with a perforation drawn as one repeating radial gradient along its top (holes in the face colour with a shaded rim) and it feeds out with a 180ms clip and a 6px drop. The printer appends text nodes as they come, keeping the amber lamp as the last thing on the line; end() removes it, stamps the time and adds the copy key. The text area grows with field-sizing: content up to 140px; no script measures it.
Rules
The lamp at the print head is the whole loading state (no dots, no spinner) and Stop is always one key away.
5 rules in this file, with the foundations they rest on and the anti-patterns that break them. Full rules ship in the brain with the Pass.
Related parts
Glass
Dark display glass for plots and live readouts, whatever the finish around it. Everything printed inside it is lit.
Lamp
The only colour in the kit: a lamp that is on. Green is on, amber is waiting, red is recording or failing, blue is linked, white is standing by. Always next to words.
Field
Text entry in a recess, the label printed above it, and a hint that says how to fix an error.