Theme
On this page (13)
Component Skill — components/marker/component-skill.md

Native basis

A <div class="marker"> - or an <a> / <button> when the row is an action - with a decorative .marker-icon and the .marker-content words; role="status" for progress.

Web Platform APIs

role="status"background-clip: text::before / ::afterprefers-reduced-motion

Classes

.marker.marker-icon.marker-content

Data attributes

data-variant (border, separator), data-orientation="vertical", data-tone (success, warning, info, destructive, primary), data-size (sm, lg); on the content: data-shimmer.

§Marker

An icon (decorative - aria-hidden) and the words; muted and sized to its content, so it sits between messages without competing with them.

§Variants

default - inline; border - a full-width row with a rule under it; separator - a centered label between two rules.

§Status with a spinner

A step in progress: the shipped spinner in the icon slot and role='status', so the change is announced politely.

§Shimmer

data-shimmer on the content sweeps a light band across the words - streaming text while a model thinks. Plain muted text under reduced motion.

§Separator

A date divider in a thread, with inline status markers under it.

§Border

Bordered rows separate the steps of an agent run - each one full width with a rule under it.

§Stacked

data-orientation='vertical' puts the icon above the words, centered - an empty state or the start of a thread.

§Tones

data-tone colors the icon only - success, warning, info, destructive, primary; the words stay quiet and still say what happened.

When the marker is an action, render it as a real <a> or <button class='marker'> - focusable, underlined on hover.

§In a conversation

Markers between bubbles: the date, what the assistant did, and the reply.

§Sizes

data-size sm · (default) · lg - the icon scales with the words.

§Right to left

Logical throughout - the icon leads on the right in dir='rtl'.

§CSS view file

/* -- Marker component ------------------------------------------------ */
/* An inline status, system note, bordered row or labeled separator in a
   conversation ("Explored 4 files", "Today", "Conversation compacted").
   CSS only; a spinner / icon goes in .marker-icon, the words in
   .marker-content. */
@layer components {
  .marker {
    --_ic: currentColor;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: fit-content;
    max-width: 100%;
    padding-block: 0.25rem;
    font-size: 0.8125rem;
    line-height: 1.4;
    color: var(--muted-foreground);
    text-align: start;
    /* -- Variants -------------------------------------------------- */
    /* border: a full-width row with a rule under it */
    &[data-variant="border"] {
      width: 100%;
      padding-block: 0.5rem;
      border-block-end: 1px solid var(--border);
    }
    /* separator: a centered label between two rules ("Today") */
    &[data-variant="separator"] {
      width: 100%;
      justify-content: center;
      gap: 0.75rem;
      font-size: 0.75rem;
      font-weight: 500;
      &::before,
      &::after {
        content: "";
        flex: 1 1 0;
        min-width: 1rem;
        height: 1px;
        background: var(--border);
      }
    }
    /* stacked: the icon above the words, centered */
    &[data-orientation="vertical"] {
      flex-direction: column;
      gap: 0.375rem;
      text-align: center;
      &:not([data-variant="separator"], [data-variant="border"]) { margin-inline: auto; }
    }
    /* -- Tones: color the icon (the words stay quiet) -------------- */
    &[data-tone="success"] { --_ic: oklch(0.6 0.15 150); }
    &[data-tone="warning"] { --_ic: oklch(0.72 0.16 70); }
    &[data-tone="info"] { --_ic: oklch(0.6 0.16 250); }
    &[data-tone="destructive"] { --_ic: var(--destructive); }
    &[data-tone="primary"] { --_ic: var(--primary); }
    /* -- Sizes ------------------------------------------------------ */
    &[data-size="sm"] { font-size: 0.75rem; gap: 0.375rem; & .marker-icon { font-size: 0.875rem; } }
    &[data-size="lg"] { font-size: 0.875rem; & .marker-icon { font-size: 1.125rem; } }
  }
  .marker-icon {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    font-size: 1rem;
    line-height: 1;
    color: var(--_ic);
    & svg { width: 1em; height: 1em; }
  }
  .marker-content {
    min-width: 0;
    text-wrap: pretty;
    overflow-wrap: anywhere;
    & strong, & b { font-weight: 600; color: var(--foreground); }
    & code { font-family: var(--font-mono); font-size: 0.92em; color: var(--foreground); }
  }
  /* -- Shimmer: streaming text ("Thinking…") - a light band sweeps
     across the words (background-clip: text) ------------------------ */
  .marker-content[data-shimmer] {
    color: transparent;
    background: linear-gradient(
      90deg,
      var(--muted-foreground) 0%,
      var(--muted-foreground) 35%,
      var(--foreground) 50%,
      var(--muted-foreground) 65%,
      var(--muted-foreground) 100%
    ) 0 0 / 250% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    animation: marker-shimmer 1.8s linear infinite;
  }
  @keyframes marker-shimmer {
    from { background-position: 100% 0; }
    to { background-position: 0% 0; }
  }
  /* -- Interactive: the whole marker as a link or a button ----------- */
  :is(a, button).marker {
    appearance: none;
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    font: inherit;
    font-size: 0.8125rem;
    color: var(--muted-foreground);
    text-decoration: none;
    cursor: pointer;
    transition: color 150ms ease;
    &:hover { color: var(--foreground); & .marker-content { text-decoration: underline; text-underline-offset: 2px; } }
    &:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
    &:disabled { opacity: 0.5; pointer-events: none; }
  }
  :is(a, button).marker[data-variant="border"] { border-radius: 0; border-block-end: 1px solid var(--border); }
}
@media (prefers-reduced-motion: reduce) {
  @layer components {
    .marker-content[data-shimmer] { animation: none; color: var(--muted-foreground); background: none; }
    :is(a, button).marker { transition: none; }
  }
}
@media (prefers-contrast: more) {
  @layer components {
    .marker { color: var(--foreground); }
    .marker[data-variant="separator"]::before,
    .marker[data-variant="separator"]::after { background: var(--foreground); }
  }
}
@media (forced-colors: active) {
  @layer components {
    .marker { color: CanvasText; }
    .marker-icon { color: CanvasText; }
    .marker[data-variant="separator"]::before,
    .marker[data-variant="separator"]::after { background: CanvasText; }
    .marker-content[data-shimmer] { color: CanvasText; background: none; }
    :is(a, button).marker { color: LinkText; }
  }
}

Comments, ideas or improvements? Edit this page's source on GitHub