Skip to content
Component installs need the registry setup
VegaStack Design

Marker

Displays an inline status, system note, bordered row, or labeled separator in a conversation.

Status
stable
Since
0.1.0
Accessibility pattern
decorative icon + text

Last updated

Switched to a new branch
Thinking...
Conversation compacted
Explored 4 files

Install

Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Marker before installation. Retain the digest printed by this command.

pnpm exec vegastack-design verify marker

Install with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.

pnpm dlx shadcn@4.21.0 add @vegastack/marker

Run the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.

Usage

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

<Marker>
  <MarkerIcon>
    <CheckIcon />
  </MarkerIcon>
  <MarkerContent>Explored 4 files</MarkerContent>
</Marker>;

Marker is the inline annotation in a conversation — a status update, a system note, a bordered row, or a labelled divider. Compose it with Message in a thread.

Switched to a new branch
Thinking...
Conversation compacted
Explored 4 files

Anatomy

Marker
MarkerContent — data-slot="marker-content"
MarkerIcon — data-slot="marker-icon"

Examples

Composition

Marker
├── MarkerIcon
└── MarkerContent
Explored 4 files

Features

  • Inline marker, bordered row and labelled separator variants
  • A decorative icon slot that is hidden from assistive tech
  • A polymorphic root through render, for link and button markers
  • Pairs with the shimmer utility for streaming status text
  • Customizable styling through the className prop on every part
Switched to release-candidate
Opened implementation notes
Conversation compacted
Reading 4 files
View the pull request

Variants

Use variant to switch between an inline marker, a bordered row and a labelled separator.

VariantDescription
defaultAn inline marker for status, notes and actions.
borderA default marker with a bottom border under the row.
separatorA centred label with divider lines on each side.
A default marker for inline notes.
A separator marker
A border marker for row boundaries.

Status

Set role="status" and include a Spinner for streaming or in-progress markers, so updates are announced.

Compacting conversation
Running tests

Shimmer

Add the shimmer utility class to MarkerContent for an animated streaming-text effect. The utility ships in @vegastack/design-tokens, so it is already available wherever the token stylesheet is imported.

Thinking...
Reading 4 files

Separator

Use the separator variant for labelled dividers — a date, a section break — in a conversation.

Today
Worked for 42s
Conversation compacted

Border

Use the border variant for status rows that keep the default marker alignment while separating the next row.

Switched to release-candidate
Reviewed 8 related files
Opened implementation notes

With Icon

Use MarkerIcon to render an icon alongside the content. Add flex-col to stack the icon above the content.

Switched to a new branch
Explored 4 files
Syncing completed

Turn a marker into a link or a button with the render prop on Marker.

<Marker render={<a href="/pull/4189" />}>
  <MarkerContent>View the pull request</MarkerContent>
</Marker>
View the pull request

Playground

Every Marker prop, auto-generated from its TypeScript types.

Deployed to production
string

API Reference

Marker

The root marker element. The file also exports markerVariants for composing the marker styles into your own components.

PropTypeDefaultDescription
variant"border" | "default" | "separator" | null—

MarkerIcon

A decorative icon slot, hidden from assistive tech with aria-hidden.

MarkerIcon adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).

MarkerContent

MarkerContent adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).

Accessibility

Marker is presentational by default, and the correct semantics depend on how you use it. Choose the role from intent rather than relying on one default.

  • Status and progress. For a streaming or progress marker — "Thinking…", a running tool — set role="status" so assistive tech announces the update as it appears. Marker forwards role to the underlying element.
  • Labelled separators. A separator carrying text needs no role: the divider lines are decorative CSS pseudo-elements and the text is announced as ordinary content. Do not add role="separator" — a separator takes its name from aria-label, its contents are treated as presentational, and the visible label would stop being announced.
  • Bordered markers keep the default marker's semantics. The bottom border is decorative, so pick role="status", render, or no role from the marker's purpose.
  • Decorative icons. MarkerIcon is aria-hidden, so MarkerContent carries the meaning. An icon-only marker needs a name of its own — put it on an element that can hold one (role="img" plus aria-label), because aria-label on a bare <div> is prohibited.
  • Interactive markers. When a marker links or triggers an action, render it as a real <a> or <button> through render, so it is focusable and exposes the right role. The accessible name comes from the marker text — and the pointer target is grown to 24px by the hit area described under Deviations.
Compacting conversation
Today
Opened implementation notes
Explored 4 files
ContractStates tested
Behaviourdefault
Accessibilitysemantic-html
Visualdefault, hover

Do / Don't

Do
Render an interactive marker as a real anchor or button through the render prop, and give a streaming one role=status so the update is announced.
Don't
Put role=separator on a labelled divider — a separator takes its name from aria-label, so the visible label stops being announced.

Deviations

Upstream's file plus packages/ui/upstream/patches/marker.patch. Every hunk:

  • A11Y-2 — markerVariants' base string gains an invisible ::after hit area (after:absolute after:inset-x-0 after:-inset-y-1 after:content-['']), scoped to [a,button]:. Upstream's own Links and Buttons section renders the marker root as an <a> or a <button> through render; the row is then one line of 14px text over min-h-4, which measures 20px — 4px under SC 2.5.8 — and a marker row is a control rather than prose, so the inline-text exception does not reach it. The area grows the block axis only, so two stacked markers never fight for a pixel, and it is scoped to the interactive spellings, so a presentational marker is byte-identical to upstream. An ::after with no background paints nothing. marker.test.tsx measures both shapes.
  • Nothing else. Upstream's marker paints no focus glow, sets no cursor, disables nothing and opens no portal; MarkerIcon is already aria-hidden.
  • API-16 — "use client" is added. Upstream ships none, and this file calls useRender, which calls React.useRef internally; under the react-server condition that hook is undefined, so a server component rendering it throws on import. The boundary goes at the lowest interactive leaf, and this file is it.
  • DOC-2 — import { cn } from "cn" becomes @vegastack/design; our npm layer owns cn.
  • DOC-1 — prettier reflow plus the registry-stamp.mjs provenance header the three-copy model needs; no decision of its own.

On this page