# Marker

The inline transcript annotation — a status line, a system note, and the labeled divider between one day's messages and the next.

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

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

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

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

```slint
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 `button` once `interactive` makes it one. Neither is layered over the other — Slint requires `accessible-role` to be a constant, so the two are `if`-guarded siblings and the row picks one (ADR-0026).
- **`busy` is a polite live region**, which is how a status that arrives mid-stream is announced without interrupting the reader — the same mechanism [MessageScroller](/docs/components/message-scroller) uses for an arriving turn.
- **The rules are decorative.** A `separator` marker 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-label`** names the control for assistive technology, defaulting to `text`. Override it where the note reads as a statement rather than as what activating the row will do.
