Marker
The inline transcript annotation — a status line, a system note, and the labeled divider between one day's messages and the next.
import { Marker, MarkerVariant } from "@glint/components/marker.slint";import { Tokens } from "@glint/theme/tokens.slint";import { IconSet } from "@lucide";
export component Demo inherits Window { width: 460px; height: 220px; background: Tokens.color-background;
VerticalLayout { padding: 24px; spacing: 16px; alignment: center;
Marker { text: "Today"; variant: MarkerVariant.separator; }
Marker { icon: IconSet.Info; text: "Context cleared"; }
Marker { text: "Searching the codebase"; busy: true; } }}Usage
import { Marker, MarkerVariant } from "@glint/components/marker.slint";
export component AppWindow inherits Window { in property <bool> working;
VerticalLayout { spacing: 12px;
// The divider between two days of a transcript. Marker { text: "Yesterday"; variant: MarkerVariant.separator; }
// A system note between two turns. Marker { text: "Ada joined the conversation"; }
// A status line while a tool call is in flight. `busy` is what makes // it a live region, so it is announced as it arrives. if root.working: Marker { text: "Running the test suite"; busy: true; shimmer: true; } }}A marker is the row a transcript says something in its own voice: a divider, a
system note, a status line. It is one label, optionally with a leading glyph,
and it is announced as one node — a piece of text while it is inert, a button
once interactive makes it one.
It is the annotation with a divider variant, not a divider with a label.
Separator already draws a captioned rule, and
MarkerVariant.separator reuses it for the two halves it centers the label
between. What a rule cannot carry is a leading glyph, a Spinner, a live
region or an activation — and a transcript’s dividers need all four, which is
why this component exists at all.
Everything on the row is the consumer’s: Glint formats no dates, counts
nothing, and picks no icon. text is what the row says and what assistive
technology reads.
Examples
Variants
default is the bare row. border draws a rule under it, which separates a
note from what follows without spending a line on a divider. separator
centers the label between two rules that take the rest of the width — the
“Today” divider.
import { Marker, MarkerVariant } from "@glint/components/marker.slint";import { Tokens } from "@glint/theme/tokens.slint";
export component Demo inherits Window { width: 460px; height: 220px; background: Tokens.color-background;
VerticalLayout { padding: 24px; spacing: 20px; alignment: center;
Marker { text: "A plain note"; variant: MarkerVariant.default; }
Marker { text: "A note with a rule under it"; variant: MarkerVariant.border; }
Marker { text: "Today"; variant: MarkerVariant.separator; } }}Busy, and the shimmer
busy swaps the glyph for a Spinner and turns the
row into a polite live region, so a status arriving mid-stream is announced
without interrupting whatever the reader is doing. shimmer is the streaming
treatment on the label: the pulse Skeleton
breathes with, on the same 800ms rhythm. It is not shadcn’s moving gradient —
a gradient sweeping across glyphs needs a shader Slint 1.17 does not expose.
The pulse only ticks while there is one to draw, so a still marker keeps nothing awake.
import { Button, ButtonVariant } from "@glint/components/button.slint";import { Marker } from "@glint/components/marker.slint";import { Tokens } from "@glint/theme/tokens.slint";import { IconSet } from "@lucide";
export component Demo inherits Window { width: 460px; height: 240px; background: Tokens.color-background;
in-out property <bool> working: true;
VerticalLayout { padding: 24px; spacing: 20px; alignment: center;
Marker { text: root.working ? "Reading src/main.rs" : "Read src/main.rs"; icon: IconSet.FileText; busy: root.working; shimmer: root.working; }
HorizontalLayout { alignment: center; Button { variant: ButtonVariant.outline; text: root.working ? "Finish" : "Start again"; clicked => { root.working = !root.working; } } } }}An interactive marker
interactive makes the whole row a control — “3 messages hidden, show them”,
“retry”. The pointer, Enter, Space and the accessible
default action all land on the same clicked, and action-label names it
where the note is not the whole story. A marker that is not interactive is not
a control: nothing announces it as one, and Tab has nothing to land on.
import { Marker, MarkerVariant } from "@glint/components/marker.slint";import { Tokens } from "@glint/theme/tokens.slint";import { IconSet } from "@lucide";
export component Demo inherits Window { width: 460px; height: 200px; background: Tokens.color-background;
in-out property <bool> expanded: false;
VerticalLayout { padding: 24px; spacing: 16px; alignment: center;
Marker { variant: MarkerVariant.border; icon: root.expanded ? IconSet.ChevronUp : IconSet.ChevronDown; text: root.expanded ? "Hide 12 earlier messages" : "Show 12 earlier messages"; interactive: true; action-label: root.expanded ? "Hide 12 earlier messages" : "Show 12 earlier messages"; clicked => { root.expanded = !root.expanded; } }
Text { text: root.expanded ? "The earlier messages are in the transcript." : "The earlier messages are folded away."; color: Tokens.color-muted-foreground; font-size: Tokens.typography-body-sm-size; horizontal-alignment: center; } }}API Reference
Properties
| Property | Type | Default | Description |
|---|---|---|---|
variant | in MarkerVariant | MarkerVariant.default | Which of the three shapes to draw. |
text | in string | no default | The note itself. |
icon | in LucideIcon | no default | Leading glyph, normally selected from IconSet in lucide-slint. Left unset the row is the label alone; busy replaces it with a Spinner. |
busy | in bool | false | Something is happening: the glyph becomes a Spinner and the row starts announcing itself politely. |
shimmer | in bool | false | The streaming treatment on the label — see the note in the header for what it is and what it is not. |
interactive | in bool | false | Makes the whole row a control. A marker that is not interactive has nothing for Tab to land on. |
action-label | in string | root.text | What assistive technology announces the control as. The note says it for the common case; override it where the note is not the whole story. |
Callbacks
| Callback | Description |
|---|---|
clicked() | Fired when an interactive row is activated by pointer, by Enter or Space, or through the accessible default action. |
Enums
| Enum | Values |
|---|---|
MarkerVariant | default, border, separator |
Accessibility
- The row speaks once. A marker is a single node in the tree: a piece of
text while it is inert, and a
buttononceinteractivemakes it one. Neither is layered over the other — Slint requiresaccessible-roleto be a constant, so the two areif-guarded siblings and the row picks one (ADR-0026). busyis a polite live region, which is how a status that arrives mid-stream is announced without interrupting the reader — the same mechanism MessageScroller uses for an arriving turn.- The rules are decorative. A
separatormarker announces its label and never the hairlines around it, so a divider does not read as content. - The glyph is decorative too. The icon and the busy spinner carry no role: the label is what is announced, and a second node would say it twice.
- Keyboard. An interactive marker is one tab stop that Enter and Space fire; a marker that is not interactive takes no tab stop at all, because an affordance that does nothing must not collect one.
- Keyboard-only focus ring, drawn around the row when the keyboard put the focus there and not when the pointer did.
action-labelnames the control for assistive technology, defaulting totext. Override it where the note reads as a statement rather than as what activating the row will do.