Marker
Displays an inline status, system note, bordered row, or labeled separator in a conversation.
- Status
- Since
0.1.0- Accessibility pattern
- decorative icon + text
Last updated
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 markerInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/markerRun 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.
Anatomy
Examples
Composition
Marker
├── MarkerIcon
└── MarkerContentFeatures
- 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
shimmerutility for streaming status text - Customizable styling through the
classNameprop on every part
Variants
Use variant to switch between an inline marker, a bordered row and a labelled separator.
| Variant | Description |
|---|---|
default | An inline marker for status, notes and actions. |
border | A default marker with a bottom border under the row. |
separator | A centred label with divider lines on each side. |
Status
Set role="status" and include a Spinner for streaming or
in-progress markers, so updates are announced.
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.
Separator
Use the separator variant for labelled dividers — a date, a section break — in a conversation.
Border
Use the border variant for status rows that keep the default marker alignment while separating
the next row.
With Icon
Use MarkerIcon to render an icon alongside the content. Add flex-col to stack the icon above
the content.
Links and Buttons
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>Playground
Every Marker prop, auto-generated from its TypeScript types.
API Reference
Marker
The root marker element. The file also exports markerVariants for composing the marker styles
into your own components.
| Prop | Type | Default | Description |
|---|---|---|---|
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.Markerforwardsroleto 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 fromaria-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.
MarkerIconisaria-hidden, soMarkerContentcarries the meaning. An icon-only marker needs a name of its own — put it on an element that can hold one (role="img"plusaria-label), becausearia-labelon a bare<div>is prohibited. - Interactive markers. When a marker links or triggers an action, render it as a real
<a>or<button>throughrender, 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.
| Contract | States tested |
|---|---|
| Behaviour | default |
| Accessibility | semantic-html |
| Visual | default, hover |
Do / Don't
Deviations
Upstream's file plus packages/ui/upstream/patches/marker.patch. Every hunk:
- A11Y-2 —
markerVariants' base string gains an invisible::afterhit 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>throughrender; the row is then one line of 14px text overmin-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::afterwith no background paints nothing.marker.test.tsxmeasures both shapes. - Nothing else. Upstream's marker paints no focus glow, sets no cursor, disables nothing and
opens no portal;
MarkerIconis alreadyaria-hidden. - API-16 —
"use client"is added. Upstream ships none, and this file callsuseRender, which callsReact.useRefinternally; under thereact-servercondition that hook isundefined, 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 ownscn. - DOC-1 — prettier reflow plus the
registry-stamp.mjsprovenance header the three-copy model needs; no decision of its own.