Marker
Show a line in a conversation that is not a message, such as a status, a note or a date.
Installation
pnpm dlx shadcn@4.18.0 add @siteplane/markerComplete the one-time setup first. Registry JSON lists source files and dependencies.
For this page’s examples, also install:
pnpm dlx shadcn@4.18.0 add @siteplane/bubble @siteplane/message @siteplane/motion @siteplane/spinnerUsage
import { ArrowRightLeftIcon } from "lucide-react";
import {
Marker,
MarkerContent,
MarkerIcon,
} from "@/siteplane/components/marker";
<Marker>
<MarkerIcon>
<ArrowRightLeftIcon />
</MarkerIcon>
<MarkerContent>Switched to the Thorough model</MarkerContent>
</Marker>;Composition
Marker
├── MarkerIcon (optional)
└── MarkerContentA marker is a row of quiet text, 14 px in the tertiary color, as wide as the thread. Between turns it is a row of its own in a Message Scroller; inside a turn it sits in the content of the Message, such as the status of a reply that is on its way. For the steps an agent took, use Trace instead.
Live status
Give a line that changes role="status", so screen readers say it when it appears. For a reply that is on its way, put a Spinner in MarkerIcon and the words in MotionShimmer, which sweeps a highlight across them and stands still under reduced motion. The example at the top of this page is the Thinking status of a reply that was sent and has no words yet.
System notes
A note records something that happened in the conversation, such as a change of model or a history that was compacted. Lead it with an icon in MarkerIcon, 16 px unless the icon has a size class of its own. The icon is hidden from assistive technology, so the words carry the meaning, and it keeps to the first line when a long note wraps.
variant="border" draws a line under the note, for a note that stands apart from the turns below it, such as the note at the top of a shared conversation.
<Marker variant="border">
<MarkerIcon>
<UsersIcon />
</MarkerIcon>
<MarkerContent>Shared with the Customer success team</MarkerContent>
</Marker>Separators
variant="separator" puts the content between two divider lines, for a date such as Today or for a break such as a summary of earlier messages. The lines are the kit's Separator without a role, so the label is read as plain text. An icon or a Button beside the content stays with it between the lines, for example Restore on a saved checkpoint.
Stopped response
When the person presses Stop, keep the words that arrived and add a marker after them that says the response was stopped, inside the assistant's turn. With role="status", screen readers say it once when it appears.
AI SDK
The lines of a marker follow from the chat's status and its events. The examples on this page show fixed lines; the code below derives them from useChat instead.
Submitted
While status is submitted, the reply has no message yet, and the Thinking status stands in for it until its first part arrives. The Message Scroller page shows it as a pending row of the thread.
// After the last message of the thread:
status === "submitted" ? (
<Marker role="status">
<MarkerIcon>
<Spinner />
</MarkerIcon>
<MarkerContent>
<MotionShimmer>Thinking</MotionShimmer>
</MarkerContent>
</Marker>
) : null;Stopped replies
stop ends the stream, and onFinish then reports isAbort. Keep the ids of the replies that were stopped, and show the marker after them.
const [stopped, setStopped] = useState<string[]>([]);
const { messages, status, stop } = useChat({
onFinish: ({ isAbort, message }) => {
if (isAbort) setStopped((ids) => [...ids, message.id]);
},
});
// In the turn of an assistant message, after its text:
stopped.includes(message.id) ? (
<Marker role="status">
<MarkerContent>You stopped the response.</MarkerContent>
</Marker>
) : null;Dates
A UIMessage carries no date. Keep one with each message, for example in its metadata, and put a separator marker before the first message of each day.
API reference
| Prop | Type | Default | Description |
|---|---|---|---|
Marker. | default | border | separator | default | default is a row of text; border draws a rule under the row; separator puts the content between two divider lines. |
Marker. | ARIA role | none | Pass status for a line that changes, such as a live status. A separator with a label needs no role. |
Marker | span props | — | A decorative icon before the content, hidden from assistive technology. The glyph is 16 px unless it has a size class, and stays on the first line of a note that wraps. |
Marker | span props | — | The words of the marker. Long words and URLs wrap instead of widening the thread. |
Marker renders a div and accepts its props, a ref and className;
MarkerIcon and MarkerContent render spans. A marker takes no render prop,
so an action is a Button inside it, beside the content. The type MarkerVariant
is exported.
Usage notes
A marker is not a message and has no avatar or actions of its own. Use Message for a turn and Trace for the steps an agent took. Its color is quiet on purpose, so put everything that matters into its words.