---
title: Marker
description: Status, notes, and separators for conversation threads.
group: Components
order: 45
---

Add status updates, system notes, bordered rows, and labeled dividers to a conversation. Pair with [Message](/docs/components/message) to build a thread.

```tsx
import { Marker, MarkerContent, MarkerIcon } from "@/components/ui/marker"

function GitBranchIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      className={className}
    >
      <path d="M6 3v12" />
      <circle cx="18" cy="6" r="3" />
      <circle cx="6" cy="18" r="3" />
      <path d="M18 9a9 9 0 0 1-9 9" />
    </svg>
  )
}

function SearchIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      className={className}
    >
      <circle cx="11" cy="11" r="8" />
      <path d="m21 21-4.3-4.3" />
    </svg>
  )
}

export function Example() {
  return (
    <div className="flex flex-col gap-3">
      <Marker>
        <MarkerIcon>
          <GitBranchIcon />
        </MarkerIcon>
        <MarkerContent>Switched to a new branch</MarkerContent>
      </Marker>
      <Marker>
        <MarkerContent className="shimmer">Thinking…</MarkerContent>
      </Marker>
      <Marker variant="separator">
        <MarkerContent>Conversation compacted</MarkerContent>
      </Marker>
      <Marker>
        <MarkerIcon>
          <SearchIcon />
        </MarkerIcon>
        <MarkerContent>Explored 4 files</MarkerContent>
      </Marker>
    </div>
  )
}
```

## Installation

```bash
npx @arctis-sh/@arctis-sh/ui@latest add marker
```

## Usage

```tsx
import { Marker, MarkerContent, MarkerIcon } from "@/components/ui/marker"
```

```tsx
<Marker>
  <MarkerIcon>
    <CheckIcon />
  </MarkerIcon>
  <MarkerContent>Explored 4 files</MarkerContent>
</Marker>
```

## Composition

```tree
Marker
├── MarkerIcon
└── MarkerContent
```

## Variants

Use `variant` to choose an inline, bordered, or separator layout.

```tsx
import { Marker, MarkerContent } from "@/components/ui/marker"

export function Example() {
  return (
    <div className="flex flex-col gap-6">
      <Marker>
        <MarkerContent>Inline note</MarkerContent>
      </Marker>
      <Marker variant="separator">
        <MarkerContent>Section</MarkerContent>
      </Marker>
      <Marker variant="border">
        <MarkerContent>Row boundary</MarkerContent>
      </Marker>
    </div>
  )
}
```

| Variant | Description |
| --- | --- |
| `default` | Inline marker for status, notes, and actions. |
| `border` | Default alignment with a bottom border under the row. |
| `separator` | Centered label with divider lines on each side. |

## Status

For in-progress markers, set `role="status"` and pair with `Spinner` so assistive technology announces updates.

```tsx
import { Marker, MarkerContent, MarkerIcon } from "@/components/ui/marker"
import { Spinner } from "@/components/ui/spinner"

export function Example() {
  return (
    <div className="flex flex-col gap-3">
      <Marker role="status">
        <MarkerIcon>
          <Spinner aria-hidden="true" role="presentation" aria-label="" />
        </MarkerIcon>
        <MarkerContent>Compacting conversation</MarkerContent>
      </Marker>
      <Marker role="status">
        <MarkerIcon>
          <Spinner aria-hidden="true" role="presentation" aria-label="" />
        </MarkerIcon>
        <MarkerContent>Running tests</MarkerContent>
      </Marker>
    </div>
  )
}
```

## Shimmer

Add the `shimmer` class to `MarkerContent` for a streaming text effect. Pair it with a spinner or icon in `MarkerIcon` while work is in progress.

```tsx
import { Marker, MarkerContent, MarkerIcon } from "@/components/ui/marker"
import { Spinner } from "@/components/ui/spinner"

function SearchIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      className={className}
    >
      <circle cx="11" cy="11" r="8" />
      <path d="m21 21-4.3-4.3" />
    </svg>
  )
}

export function Example() {
  return (
    <div className="flex flex-col gap-3">
      <Marker>
        <MarkerContent className="shimmer">Thinking…</MarkerContent>
      </Marker>
      <Marker role="status">
        <MarkerIcon>
          <Spinner aria-hidden="true" role="presentation" aria-label="" />
        </MarkerIcon>
        <MarkerContent className="shimmer">Thinking…</MarkerContent>
      </Marker>
      <Marker role="status">
        <MarkerIcon>
          <SearchIcon />
        </MarkerIcon>
        <MarkerContent className="shimmer">Reading 4 files</MarkerContent>
      </Marker>
    </div>
  )
}
```

## Separator

Use `variant="separator"` for dates or section dividers in a thread.

```tsx
import { Marker, MarkerContent } from "@/components/ui/marker"

export function Example() {
  return (
    <div className="flex flex-col gap-3">
      <Marker variant="separator">
        <MarkerContent>Today</MarkerContent>
      </Marker>
      <Marker variant="separator">
        <MarkerContent>Worked for 42s</MarkerContent>
      </Marker>
      <Marker variant="separator">
        <MarkerContent>Conversation compacted</MarkerContent>
      </Marker>
    </div>
  )
}
```

## Border

Use `variant="border"` to keep the default alignment and separate the next row.

```tsx
import { Marker, MarkerContent, MarkerIcon } from "@/components/ui/marker"

function GitBranchIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      className={className}
    >
      <path d="M6 3v12" />
      <circle cx="18" cy="6" r="3" />
      <circle cx="6" cy="18" r="3" />
      <path d="M18 9a9 9 0 0 1-9 9" />
    </svg>
  )
}

function SearchIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      className={className}
    >
      <circle cx="11" cy="11" r="8" />
      <path d="m21 21-4.3-4.3" />
    </svg>
  )
}

function FileTextIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      className={className}
    >
      <path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
      <path d="M14 2v4a2 2 0 0 0 2 2h4" />
      <path d="M10 9H8" />
      <path d="M16 13H8" />
      <path d="M16 17H8" />
    </svg>
  )
}

export function Example() {
  return (
    <div className="flex flex-col gap-3">
      <Marker variant="border">
        <MarkerIcon>
          <GitBranchIcon />
        </MarkerIcon>
        <MarkerContent>Switched to release-candidate</MarkerContent>
      </Marker>
      <Marker variant="border">
        <MarkerIcon>
          <SearchIcon />
        </MarkerIcon>
        <MarkerContent>Reviewed 8 related files</MarkerContent>
      </Marker>
      <Marker variant="border">
        <MarkerIcon>
          <FileTextIcon />
        </MarkerIcon>
        <MarkerContent>Opened implementation notes</MarkerContent>
      </Marker>
    </div>
  )
}
```

## With icon

`MarkerIcon` sits beside the content on one row.

```tsx
import { Marker, MarkerContent, MarkerIcon } from "@/components/ui/marker"

function GitBranchIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      className={className}
    >
      <path d="M6 3v12" />
      <circle cx="18" cy="6" r="3" />
      <circle cx="6" cy="18" r="3" />
      <path d="M18 9a9 9 0 0 1-9 9" />
    </svg>
  )
}

function SearchIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      className={className}
    >
      <circle cx="11" cy="11" r="8" />
      <path d="m21 21-4.3-4.3" />
    </svg>
  )
}

function CheckIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      className={className}
    >
      <path d="M5 13l4 4L19 7" />
    </svg>
  )
}

export function Example() {
  return (
    <div className="flex flex-col gap-3">
      <Marker>
        <MarkerIcon>
          <GitBranchIcon />
        </MarkerIcon>
        <MarkerContent>Switched to a new branch</MarkerContent>
      </Marker>
      <Marker>
        <MarkerIcon>
          <SearchIcon />
        </MarkerIcon>
        <MarkerContent>Explored 4 files</MarkerContent>
      </Marker>
      <Marker>
        <MarkerIcon>
          <CheckIcon />
        </MarkerIcon>
        <MarkerContent>Syncing completed</MarkerContent>
      </Marker>
    </div>
  )
}
```

## Links and buttons

Pass `render` to turn the root into a real `<a>` or `<button>`.

```tsx
"use client"

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

function GitBranchIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      className={className}
    >
      <path d="M6 3v12" />
      <circle cx="18" cy="6" r="3" />
      <circle cx="6" cy="18" r="3" />
      <path d="M18 9a9 9 0 0 1-9 9" />
    </svg>
  )
}

function RotateCcwIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      className={className}
    >
      <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
      <path d="M3 3v5h5" />
    </svg>
  )
}

export function Example() {
  return (
    <div className="flex flex-col gap-3">
      <Marker render={<a href="#" />}>
        <MarkerIcon>
          <GitBranchIcon />
        </MarkerIcon>
        <MarkerContent>View the pull request</MarkerContent>
      </Marker>
      <Marker render={<button type="button" />} onClick={() => undefined}>
        <MarkerIcon>
          <RotateCcwIcon />
        </MarkerIcon>
        <MarkerContent>Revert this change</MarkerContent>
      </Marker>
    </div>
  )
}
```

```tsx
<Marker render={<a href="/pr/42" />}>
  <MarkerContent>View the pull request</MarkerContent>
</Marker>
```

## Accessibility

Markers are presentational by default. Choose semantics based on their purpose.

**Status** — for streaming or progress, set `role="status"` so assistive tech announces the update. Hide decorative spinners inside `MarkerIcon`.

**Separators** — labeled dividers need no role. Divider lines are decorative CSS. Do not put `role="separator"` on a labeled marker; that role ignores text content and takes its name from `aria-label`.

**Icons** — `MarkerIcon` is `aria-hidden`. For icon-only markers, add `aria-label` (or visible text).

**Interactive** — use `render` with a real link or button so the control is reachable and correctly named by its content.

## API Reference

### Marker

Root element. `markerVariants` is also exported for composing styles. Standard HTML attributes such as `role`, `onClick`, and `aria-label` pass through to the root or the element provided through `render`.

| Prop | Type | Default |
| --- | --- | --- |
| variant | "default" \| "border" \| "separator" | "default" |
| render | ReactElement | — |
| className | string | — |

```tsx
<Marker>
  <MarkerContent>Explored 4 files</MarkerContent>
</Marker>
```

### MarkerIcon

Decorative icon slot. Always `aria-hidden`. Accepts native `span` props.

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<MarkerIcon>
  <CheckIcon />
</MarkerIcon>
```

### MarkerContent

Text content for the marker. Accepts native `span` props. Use `className="shimmer"` for the streaming text effect.

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<MarkerContent>Explored 4 files</MarkerContent>
```

## Source

```tsx
import {
  cloneElement,
  isValidElement,
  type ComponentProps,
  type HTMLAttributes,
  type ReactElement,
  type ReactNode,
} from "react";
import { cn } from "@/lib/utils";

type MarkerVariant = "default" | "border" | "separator";

const markerVariantClass: Record<MarkerVariant, string> = {
  default: "",
  separator:
    "before:mr-2 before:h-px before:min-w-4 before:flex-1 before:bg-border before:content-[''] after:ml-2 after:h-px after:min-w-4 after:flex-1 after:bg-border after:content-['']",
  border: "border-b border-border pb-2",
};

function markerVariants({
  variant = "default",
  className,
}: {
  variant?: MarkerVariant;
  className?: string;
} = {}) {
  return cn(
    "group/marker relative flex min-h-4 w-full items-center gap-2 text-left text-sm tracking-wide text-muted-foreground [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:underline-offset-3 [a]:hover:text-foreground",
    markerVariantClass[variant],
    className,
  );
}

type MarkerProps = HTMLAttributes<HTMLElement> & {
  variant?: MarkerVariant;
  render?: ReactElement;
  children?: ReactNode;
};

function Marker({
  className,
  variant = "default",
  render,
  children,
  ...props
}: MarkerProps) {
  const classes = markerVariants({ variant, className });

  if (isValidElement(render)) {
    const element = render as ReactElement<{
      className?: string;
      children?: ReactNode;
    }>;

    return cloneElement(element, {
      ...element.props,
      ...props,
      "data-slot": "marker",
      "data-variant": variant,
      className: cn(classes, element.props.className),
      children: children ?? element.props.children,
    } as never);
  }

  return (
    <div
      data-slot="marker"
      data-variant={variant}
      className={classes}
      {...props}
    >
      {children}
    </div>
  );
}

function MarkerIcon({ className, ...props }: ComponentProps<"span">) {
  return (
    <span
      data-slot="marker-icon"
      aria-hidden="true"
      className={cn(
        "size-4 shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className,
      )}
      {...props}
    />
  );
}

function MarkerContent({ className, ...props }: ComponentProps<"span">) {
  return (
    <span
      data-slot="marker-content"
      className={cn(
        "min-w-0 break-words group-data-[variant=separator]/marker:flex-none group-data-[variant=separator]/marker:text-center *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
        className,
      )}
      {...props}
    />
  );
}

export { Marker, MarkerIcon, MarkerContent, markerVariants };
export type { MarkerVariant };
```
