Shimmer Text — .mv-shimmer-text

Pure-CSS light sheen that sweeps across text (background-clip): a “New” badge, AI “thinking…” states with animated dots, and a pulse variant.

CategoryAnimated text
TypeCSS only (.mv-shimmer-text)
Statusstable
Keywordsshimmer, shine, shiny, loading, thinking, ai, badge

When to use

Avoid when

Install

node scripts/add.mjs shimmer-text --out ./src/marvelous

AI agent with the Marvelous UI MCP server: install_components({ slugs: ["shimmer-text"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Files copied (dependencies included): tokens/tokens.css, core/base.css, components/shimmer-text/shimmer-text.css.

Usage

Canonical markup — start from it and customize with attributes, data-* and CSS variables:

<div style="display:grid;gap:1.75rem;justify-items:center;text-align:center">
  <a href="#" style="display:inline-flex;align-items:center;gap:.5rem;padding:.35rem .9rem;border:1px solid var(--mv-border);border-radius:999px;background:var(--mv-surface);font-size:.875rem;text-decoration:none;box-shadow:var(--mv-shadow-xs)">
    <span class="mv-shimmer-text">✨ New: one-click Figma export</span>
    <span aria-hidden="true" style="color:var(--mv-fg-muted)">→</span>
  </a>

  <p style="margin:0;font:700 clamp(1.8rem,4vw,2.6rem)/1.1 var(--mv-font-sans);letter-spacing:-.03em">
    <span class="mv-shimmer-text" style="--mv-shimmer-text-color:var(--mv-fg);--mv-shimmer-text-highlight:var(--mv-accent);--mv-shimmer-text-width:3em;--mv-shimmer-text-duration:3s">Ship faster.</span>
  </p>

  <div style="display:flex;align-items:center;gap:.65rem;padding:.7rem 1rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-lg);background:var(--mv-surface);min-width:17rem;text-align:start">
    <span aria-hidden="true" style="width:1.6rem;height:1.6rem;border-radius:50%;background:conic-gradient(from 90deg,var(--mv-accent),var(--mv-info),var(--mv-accent));flex-shrink:0"></span>
    <span id="shimmer-thinking" class="mv-shimmer-text" data-dots role="status" style="font-size:.925rem">Analyzing your documents</span>
  </div>

  <div style="display:flex;gap:1.5rem;flex-wrap:wrap;justify-content:center;font-size:.875rem">
    <span class="mv-shimmer-text" data-variant="pulse" role="status">Connecting to server…</span>
    <button class="mv-button" data-variant="outline"><span class="mv-shimmer-text" data-trigger="hover">Hover me</span></button>
  </div>
</div>
<script type="module">
  // Thinking states: cycle the label with a short blur swap.
  const el = document.getElementById("shimmer-thinking");
  const steps = ["Analyzing your documents", "Searching sources", "Writing the answer"];
  let i = 0;
  const reduce = matchMedia("(prefers-reduced-motion: reduce)").matches;
  const timer = setInterval(async () => {
    if (!el.isConnected) return clearInterval(timer);
    i = (i + 1) % steps.length;
    if (!reduce) await el.animate([{ opacity: 1 }, { opacity: 0, filter: "blur(2px)", translate: "0 -4px" }], { duration: 150, easing: "ease-in" }).finished;
    el.textContent = steps[i];
    if (!reduce) el.animate([{ opacity: 0, filter: "blur(2px)", translate: "0 6px" }, { opacity: 1 }], { duration: 220, easing: "ease-out" });
  }, 2600);
</script>

API

Attributes

NameTypeDefaultDescription
data-variantshine | pulseshineshine: a light band sweeps across; pulse: the text breathes between the two colors.
data-dotsbooleanAdds three dots that appear one by one (“thinking…” state).
data-triggerhoverThe sheen only runs on hover / focus (of the element or its parent).
data-directionreverseSweeps right to left.

CSS classes

NameDescription
mv-shimmer-textOn an inline element containing text (span, a, p…).

CSS variables

NameDefaultDescription
--mv-shimmer-text-colorvar(--mv-fg-muted)Base text color.
--mv-shimmer-text-highlightvar(--mv-fg)Sheen color.
--mv-shimmer-text-width5emWidth of the light band.
--mv-shimmer-text-angle100degSheen angle.
--mv-shimmer-text-duration2.4sDuration of one cycle (sweep + pause).
--mv-shimmer-text-delay0sDelay before the first sweep.

Accessibility

The text stays real text. For a loading state, add role="status" so label changes are announced. Reduced motion: static, readable text with all dots visible.