Chat
Displays an inline status, system note, bordered row, or labeled separator in a conversation.
Playground
MarkerIcon and MarkerContent; both are optional.import {
Marker,
MarkerContent,
MarkerIcon,
} from "@workspace/ui/components/marker"
<Marker>
<MarkerIcon>
<MagnifyingGlassIcon />
</MarkerIcon>
<MarkerContent>Explored 4 files</MarkerContent>
</Marker>
<Marker variant="separator">
<MarkerContent>Today</MarkerContent>
</Marker>variant is mirrored to data-variant on the root, which is how MarkerContent knows to stop stretching and center itself inside a separator.<Marker> <MarkerContent>A default marker for inline notes.</MarkerContent> </Marker> <Marker variant="separator"> <MarkerContent>A separator marker</MarkerContent> </Marker> <Marker variant="border"> <MarkerContent>A border marker for row boundaries.</MarkerContent> </Marker>
| Variant | Description |
|---|---|
| default | An inline marker for status, notes, and actions. |
| separator | A centered label with divider lines on each side. Use it for dates and for anything that closes a stretch of thread. |
| border | A default marker with a bottom border under the row. Use it to stack several markers as a list of boundaries. |
MarkerIcon is a fixed size-4 slot that sizes any unsized SVG inside it, so Phosphor icons drop in without a class. The slot never shrinks — long content wraps around it. Stack the icon above the text with className="flex-col" on the root when the marker stands alone.<Marker>
<MarkerIcon>
<GitBranchIcon />
</MarkerIcon>
<MarkerContent>Switched to a new branch</MarkerContent>
</Marker>
<Marker variant="separator">
<MarkerIcon>
<MagnifyingGlassIcon />
</MarkerIcon>
<MarkerContent>Explored 4 files</MarkerContent>
</Marker>
<Marker className="flex-col">
<MarkerIcon>
<BookOpenTextIcon />
</MarkerIcon>
<MarkerContent>Syncing completed</MarkerContent>
</Marker>Spinner into the icon slot for work still in flight, and set role="status" on the root so the text is announced when it changes. Note that the icon slot is aria-hidden, so the spinner's own label never reaches the accessibility tree — the marker text carries the whole message.<Marker role="status">
<MarkerIcon>
<Spinner />
</MarkerIcon>
<MarkerContent>Compacting conversation</MarkerContent>
</Marker>
<Marker variant="separator" role="status">
<MarkerIcon>
<Spinner />
</MarkerIcon>
<MarkerContent>Running tests</MarkerContent>
</Marker>shimmer utility animates a highlight across the text itself, which reads as streaming without spending an icon slot on it. It clips the background to the glyphs, so put it on MarkerContent — never on the root, where it would swallow the separator lines.<Marker role="status"> <MarkerContent className="shimmer">Thinking…</MarkerContent> </Marker> <Marker variant="separator" role="status"> <MarkerContent className="shimmer">Reading 4 files</MarkerContent> </Marker>
::before and ::after pseudo-elements that split the leftover width between them, so the label stays centered at any content length. They use logical margins and mirror under dir="rtl".<Marker variant="separator"> <MarkerContent>Today</MarkerContent> </Marker> <Marker variant="separator"> <MarkerContent>Worked for 42s</MarkerContent> </Marker> <Marker variant="separator"> <MarkerContent>Conversation compacted</MarkerContent> </Marker>
border adds a rule under the row and the padding to clear it. Stacked with a tight gap it turns a run of markers into a log — a list of steps that already happened.<Marker variant="border">
<MarkerIcon>
<GitBranchIcon />
</MarkerIcon>
<MarkerContent>Switched to release-candidate</MarkerContent>
</Marker>
<Marker variant="border">
<MarkerIcon>
<MagnifyingGlassIcon />
</MarkerIcon>
<MarkerContent>Reviewed 8 related files</MarkerContent>
</Marker>
<Marker variant="border">
<MarkerIcon>
<FileTextIcon />
</MarkerIcon>
<MarkerContent>Opened implementation notes</MarkerContent>
</Marker>asChild swaps the root div for whatever element you pass, which is how a marker becomes a real link or button. An a child picks up an underline and a foreground hover automatically; a button is unstyled, so add the hover treatment yourself.<Marker asChild>
<a href="#links-and-buttons">
<MarkerIcon>
<GitPullRequestIcon />
</MarkerIcon>
<MarkerContent>View the pull request</MarkerContent>
</a>
</Marker>
<Marker asChild>
<button
type="button"
className="transition-colors hover:text-foreground"
onClick={() => toast("You clicked the revert button")}
>
<MarkerIcon>
<ArrowCounterClockwiseIcon />
</MarkerIcon>
<MarkerContent>Revert this change</MarkerContent>
</button>
</Marker>Bubble rows: a separator opens the day, an inline status covers the work between two turns. Keep them at the same width as the bubbles so the thread keeps one left edge.<Marker variant="separator">
<MarkerContent>Today</MarkerContent>
</Marker>
<Bubble variant="muted">
<BubbleContent>Can you check why the install is failing?</BubbleContent>
</Bubble>
<Marker role="status">
<MarkerIcon>
<MagnifyingGlassIcon />
</MarkerIcon>
<MarkerContent>Explored 4 files</MarkerContent>
</Marker>
<Bubble align="end">
<BubbleContent>
The workspace package declares peers that Yarn 1 never installs.
</BubbleContent>
</Bubble>div with no implicit role, which is right for a note that is already in reading order. Add role="status" only when the text updates in place — a streaming status or a progress line — so the change is announced politely rather than re-read on every token. A separator carrying a label needs no role at all: the divider lines are decorative pseudo-elements and are never exposed. MarkerIcon is aria-hidden, so an icon-only marker has no accessible name — give the root an aria-label in that case. Finally, a marker that does something must be a real button or a through asChild; the muted text is small, so never rely on hover alone to signal that it is interactive.<Marker role="status">
<MarkerIcon>
<Spinner />
</MarkerIcon>
<MarkerContent>Compacting conversation</MarkerContent>
</Marker>
<Marker aria-label="Synced">
<MarkerIcon>
<CheckIcon />
</MarkerIcon>
</Marker>
<Marker asChild>
<a href="/files">
<MarkerIcon>
<FileTextIcon />
</MarkerIcon>
<MarkerContent>Explored 4 files</MarkerContent>
</a>
</Marker>div root and two span slots. Marker mirrors variant to data-variant, which MarkerContent reads to lay itself out. The package also exports markerVariants for composing the root styles into a custom component. All native props pass through.| Part | Prop | Default |
|---|---|---|
| Marker | variant?: "default" | "separator" | "border" | "default" |
| Marker | asChild?: boolean | false |
| Marker | className?: string | — |
| MarkerIcon | className?: string | — |
| MarkerContent | className?: string | — |
Tokens used
5 design tokens consumed by the Marker component.
--muted-foreground
Muted Foreground
--foreground
Foreground
--border
Border
--text-sm
Text Small (14px)
--spacing
Spacing unit (4px)