# Marker

Show a line in a conversation that is not a message, such as a status, a note or a date.

Live status while the assistant thinks

[Complete TSX file](/examples/marker/default.tsx)

```tsx
"use client";

import {
  Marker,
  MarkerContent,
  MarkerIcon,
} from "@/siteplane/components/marker";
import { MotionShimmer } from "@/siteplane/components/motion";
import { Spinner } from "@/siteplane/components/spinner";

export default function Example() {
  return (
    <div className="w-full max-w-md">
      <Marker role="status">
        <MarkerIcon>
          <Spinner />
        </MarkerIcon>
        <MarkerContent>
          <MotionShimmer>Thinking</MotionShimmer>
        </MarkerContent>
      </Marker>
    </div>
  );
}
```

## Installation

Complete [one-time setup](/docs/installation/) first.

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/marker
```

With npm: `npx shadcn@4.18.0 add @siteplane/marker`. [Registry JSON](/registry/marker.json) lists source files and dependencies.

For this page’s examples, also install:

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/bubble @siteplane/message @siteplane/motion @siteplane/spinner
```

## Usage

```tsx
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

```text
Marker
├── MarkerIcon (optional)
└── MarkerContent
```

A 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](/docs/components/message-scroller/); inside a turn it sits in the content of the [Message](/docs/components/message/), such as the status of a reply that is on its way. For the steps an agent took, use [Trace](/docs/components/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](/docs/components/spinner/) in `MarkerIcon` and the words in [MotionShimmer](/docs/components/motion/), 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.

Notes about the model and the history

[Complete TSX file](/examples/marker/note.tsx)

```tsx
"use client";

import { ArrowRightLeftIcon, FoldVerticalIcon } from "lucide-react";

import {
  Marker,
  MarkerContent,
  MarkerIcon,
} from "@/siteplane/components/marker";

export default function Example() {
  return (
    <div className="flex w-full max-w-md flex-col gap-4">
      <Marker>
        <MarkerIcon>
          <ArrowRightLeftIcon />
        </MarkerIcon>
        <MarkerContent>Switched to the Thorough model</MarkerContent>
      </Marker>
      <Marker>
        <MarkerIcon>
          <FoldVerticalIcon />
        </MarkerIcon>
        <MarkerContent>Conversation compacted</MarkerContent>
      </Marker>
    </div>
  );
}
```

`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.

```tsx
<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](/docs/components/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.

A date and a summary between divider lines

[Complete TSX file](/examples/marker/separator.tsx)

```tsx
"use client";

import { ArchiveIcon } from "lucide-react";

import {
  Marker,
  MarkerContent,
  MarkerIcon,
} from "@/siteplane/components/marker";

export default function Example() {
  return (
    <div className="flex w-full max-w-md flex-col gap-8">
      <Marker variant="separator">
        <MarkerContent>Today</MarkerContent>
      </Marker>
      <Marker variant="separator">
        <MarkerIcon>
          <ArchiveIcon />
        </MarkerIcon>
        <MarkerContent>Earlier messages were summarized</MarkerContent>
      </Marker>
    </div>
  );
}
```

## 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.

A reply that the person stopped

[Complete TSX file](/examples/marker/stopped.tsx)

```tsx
"use client";

import { Bubble, BubbleContent } from "@/siteplane/components/bubble";
import { Marker, MarkerContent } from "@/siteplane/components/marker";
import { Message, MessageContent } from "@/siteplane/components/message";

export default function Example() {
  return (
    <div className="flex w-full max-w-md flex-col gap-8">
      <Message align="end">
        <MessageContent>
          <Bubble align="end" variant="muted">
            <BubbleContent>
              Summarize this week's support tickets.
            </BubbleContent>
          </Bubble>
        </MessageContent>
      </Message>
      <Message>
        <MessageContent>
          <Bubble variant="ghost">
            <BubbleContent>
              Support received 128 tickets this week, up 9 percent on last week.
              Billing questions made up 31 percent of them, mostly about the
              invoice
            </BubbleContent>
          </Bubble>
          <Marker role="status">
            <MarkerContent>You stopped the response.</MarkerContent>
          </Marker>
        </MessageContent>
      </Message>
    </div>
  );
}
```

## 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](/docs/components/message-scroller/#ai-sdk) page shows it as a pending row of the thread.

```tsx
// 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.

```tsx
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.variant | 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.role | ARIA role | none | Pass status for a line that changes, such as a live status. A separator with a label needs no role. |
| MarkerIcon | 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. |
| MarkerContent | 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.

[Read the installed source](/source/marker).

## Usage notes

A marker is not a message and has no avatar or actions of its own. Use [Message](/docs/components/message/) for a turn and [Trace](/docs/components/trace/) for the steps an agent took. Its color is quiet on purpose, so put everything that matters into its words.
