---
title: Chat Marker
description: In-thread meta rows for streaming status, tool activity, system notes, and date breaks
visibility: guest
draft: false
---

# Chat Marker

The rows in a conversation that **are not messages**. Interleave them with
[Message](/components/message) bubbles to show what is happening between the
turns: an assistant thinking, a tool that ran, a system note, a date break.

It is what makes an agent thread feel complete — without it, a transcript is
just bubbles with no sense of activity.

Despite the name, this is **not** a text highlighter: it is a *row* in a
conversation. To mark words inside a sentence, reach for
[Mark](/components/mark) (manual) or [Highlight](/components/highlight)
(query-driven) instead.

## Basic Usage

:::preview{title="A system note between messages"}
<div style="width: 24rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::stack gap="md">
        <x-wirekit::message :author="['name' => 'Ada']" timestamp="2026-07-17 09:14">Can you re-run the import?</x-wirekit::message>
        <x-wirekit::chat-marker icon="refresh">Re-ran the nightly import</x-wirekit::chat-marker>
        <x-wirekit::message :author="['name' => 'You']" side="right" timestamp="2026-07-17 09:16">Done — 412 rows.</x-wirekit::message>
    </x-wirekit::stack>
</div>
:::

:::source{language="blade"}
<x-wirekit::stack gap="md">
    <x-wirekit::message :author="['name' => 'Ada']" timestamp="2026-07-17 09:14">Can you re-run the import?</x-wirekit::message>
    <x-wirekit::chat-marker icon="refresh">Re-ran the nightly import</x-wirekit::chat-marker>
    <x-wirekit::message :author="['name' => 'You']" side="right" timestamp="2026-07-17 09:16">Done — 412 rows.</x-wirekit::message>
</x-wirekit::stack>
:::

## Streaming status

`status` makes the row a **live region**, so a text swap inside it is announced.
Add `shimmer` while work is in flight — it shimmers the label and marks the row
`aria-busy`.

:::preview{title="A live, shimmering status row"}
<div style="width: 24rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::stack gap="md">
        <x-wirekit::chat-marker status shimmer>Generating response…</x-wirekit::chat-marker>
        <x-wirekit::chat-marker status icon="check">Explored 4 files</x-wirekit::chat-marker>
    </x-wirekit::stack>
</div>
:::

:::source{language="blade"}
<x-wirekit::stack gap="md">
    <x-wirekit::chat-marker status shimmer>Generating response…</x-wirekit::chat-marker>
    <x-wirekit::chat-marker status icon="check">Explored 4 files</x-wirekit::chat-marker>
</x-wirekit::stack>
:::

The live region is rendered **up front** and stays in the DOM. That matters: a
region that appears at the same moment its text does is inert to assistive
technology — so a `wire:stream` swap into a conditionally-rendered region would
never announce. This one always announces.

```blade
{{-- 1. The row is always present; only its TEXT changes as work progresses --}}
<x-wirekit::chat-marker status :shimmer="$working">
    {{ $statusLine }}
</x-wirekit::chat-marker>
```

## System notes

`intent` colors a note with its semantic state token — and the text always says
what the color says, so the meaning never depends on color alone.

:::preview{title="System notes by intent"}
<div style="width: 24rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::stack gap="sm">
        <x-wirekit::chat-marker intent="info" icon="info">Model switched to the long-context variant</x-wirekit::chat-marker>
        <x-wirekit::chat-marker intent="success" icon="check">Connection restored</x-wirekit::chat-marker>
        <x-wirekit::chat-marker intent="warning" icon="warning">Approaching the rate limit</x-wirekit::chat-marker>
        <x-wirekit::chat-marker intent="danger" icon="danger">Rate limit reached — retrying in 30s</x-wirekit::chat-marker>
    </x-wirekit::stack>
</div>
:::

:::source{language="blade"}
<x-wirekit::stack gap="sm">
    <x-wirekit::chat-marker intent="info" icon="info">Model switched to the long-context variant</x-wirekit::chat-marker>
    <x-wirekit::chat-marker intent="success" icon="check">Connection restored</x-wirekit::chat-marker>
    <x-wirekit::chat-marker intent="warning" icon="warning">Approaching the rate limit</x-wirekit::chat-marker>
    <x-wirekit::chat-marker intent="danger" icon="danger">Rate limit reached — retrying in 30s</x-wirekit::chat-marker>
</x-wirekit::stack>
:::

## Variants

`border` draws a hairline under the row (a boundary between activity groups).
`separator` renders a label flanked by rules — the date break.

:::preview{title="Border and separator variants"}
<div style="width: 24rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::stack gap="md">
        <x-wirekit::chat-marker variant="border" icon="check">Opened implementation notes</x-wirekit::chat-marker>
        <x-wirekit::chat-marker variant="separator">Today</x-wirekit::chat-marker>
        <x-wirekit::message :author="['name' => 'Ada']" timestamp="2026-07-17 08:02">Morning!</x-wirekit::message>
    </x-wirekit::stack>
</div>
:::

:::source{language="blade"}
<x-wirekit::stack gap="md">
    <x-wirekit::chat-marker variant="border" icon="check">Opened implementation notes</x-wirekit::chat-marker>
    <x-wirekit::chat-marker variant="separator">Today</x-wirekit::chat-marker>
    <x-wirekit::message :author="['name' => 'Ada']" timestamp="2026-07-17 08:02">Morning!</x-wirekit::message>
</x-wirekit::stack>
:::

## Interactive markers

Chat Marker is presentational on purpose. When a marker should be clickable,
compose a [link component](/components/link) or a [button component](/components/button)
**inside** it — they already carry the correct semantics, focus handling, and the
`target="_blank"` protection.

```blade
{{-- 1. The link brings its own semantics + rel protection --}}
<x-wirekit::chat-marker icon="external-link">
    <x-wirekit::link href="https://github.com/example/pr/42" target="_blank">
        View the pull request
    </x-wirekit::link>
</x-wirekit::chat-marker>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | string | `'default'` | `default` (inline row), `border` (hairline under), `separator` (label between rules) |
| `icon` | string\|null | `null` | Decorative leading glyph |
| `status` | bool | `false` | Render as an always-present polite live region |
| `shimmer` | bool | `false` | Shimmer the label (implies `aria-busy`) |
| `intent` | string | `'neutral'` | `neutral`, `info`, `success`, `warning`, `danger` |
| `scope` | string\|null | `null` | Scoped personalization name |

## Accessibility

- `status` renders `role="status"` + `aria-live="polite"` on a row that is
  **always in the DOM**, so streamed text swaps actually announce.
- `shimmer` sets `aria-busy="true"` — assistive technology knows work is in
  flight, and the shimmer itself is disabled under reduced-motion.
- A row **without** `status` is not a live region: a static note must not
  interrupt the reader.
- `intent` is never the only signal — the row text always states the condition
  (WCAG 1.4.1).
- The `icon` is decorative (`aria-hidden`); the row text carries the meaning.
- `variant="separator"` delegates to [Divider](/components/divider) and gives it
  an explicit accessible name, so a date break is announced ("Today, separator")
  rather than silently skipped.

## Keyboard Interaction

This component is presentational and does not respond to keyboard input. Any
interactive content you compose inside it keeps its own keyboard behavior.

## Pitfalls

- **Do not make every row a live region.** Only `status` rows should announce;
  a thread full of live regions is unusable with a screen reader.
- **Do not use it for text highlighting** — that is [Mark](/components/mark) /
  [Highlight](/components/highlight).
- **Keep labels short.** A marker is a glance, not a paragraph.

## Design Tokens

| Element | Token |
| --- | --- |
| Row text size | `--text-wk-xs` |
| Neutral text | `--color-wk-text-muted` |
| `intent` text | `--color-wk-{info,success,warning,danger}-text` |
| `border` hairline | `--color-wk-border` |
| Gap / spacing | `--gap-wk-sm`, `--space-wk-sm` |
| Shimmer sweep | `--shimmer-wk-duration` |

## Further Reading

- [WAI-ARIA: `status` role](https://www.w3.org/TR/wai-aria-1.2/#status)
- [WAI-ARIA: `aria-busy`](https://www.w3.org/TR/wai-aria-1.2/#aria-busy)
- [WCAG 1.4.1: Use of Color](https://www.w3.org/WAI/WCAG21/Understanding/use-of-color.html)
