# Shimmer

> Label with a moving light sweep that shows the agent is working.

- **Slug:** `shimmer`
- **Kind:** atom · **Category:** Feedback
- **Source:** ported from [Beautiful UI](https://github.com/slev12397/beautiful-ui/blob/main/components/atoms/Shimmer.tsx) (MIT)
- **Docs page:** https://viewership.ai/ui/components/shimmer
- **All files in one bundle:** https://viewership.ai/ui/components/shimmer.bundle.txt
- **Structured JSON:** https://viewership.ai/ui/components/shimmer.json

## Install

1. Import the foundation tokens once (download: https://viewership.ai/ui/raw/foundation.css.txt):

```astro
---
// src/layouts/Layout.astro
import "../styles/foundation.css";
---
```

2. Copy this file into `src/components/viewership/shimmer/`:

   - `Shimmer.astro` (raw: https://viewership.ai/ui/raw/shimmer/Shimmer.astro.txt)

npm packages: none.

## Usage

```astro
---
import Shimmer from "../components/viewership/shimmer/Shimmer.astro";
---

<Shimmer />
```

## Example

The gallery demo, verbatim. Use the import path from Usage instead of `./`.

```astro
---
import Shimmer from "./Shimmer.astro";

const steps = [
  { label: "Read GA4 data for week 41", state: "done" },
  { label: "Compare against Google Ads spend", state: "done" },
  { label: "Drafting report summary…", state: "active" },
  { label: "Suggest budget moves for next week", state: "todo" },
];
---

<div class="stack">
  <div class="bar" role="status">
    <span class="spinner" aria-hidden="true"></span>
    <Shimmer>Summarizing weekly report…</Shimmer>
  </div>

  <ol class="steps" aria-label="Agent progress">
    {steps.map((s) => (
      <li class:list={["step", `step--${s.state}`]}>
        <span class="dot" aria-hidden="true"></span>
        {s.state === "active" ? <Shimmer>{s.label}</Shimmer> : <span>{s.label}</span>}
      </li>
    ))}
  </ol>
</div>

<style>
  .stack {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 20px;
  }
  .bar {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 36px;
    padding: 0 14px 0 12px;
    border-radius: var(--vs-radius-pill);
    background: var(--vs-surface);
    box-shadow: var(--vs-shadow-overlay);
    font-size: 12.5px;
  }
  .spinner {
    width: 12px;
    height: 12px;
    flex-shrink: 0;
    box-sizing: border-box;
    border-radius: 50%;
    border: 1.5px solid var(--vs-line-strong);
    border-top-color: var(--vs-ink-2);
    animation: vs-spin 700ms linear infinite;
  }
  .steps {
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: 300px;
    margin: 0;
    padding: 14px 16px;
    list-style: none;
    border-radius: var(--vs-radius-card);
    background: var(--vs-surface);
    box-shadow: var(--vs-shadow-card);
    font-size: 13px;
  }
  .step {
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--vs-ink-2);
  }
  .dot {
    width: 6px;
    height: 6px;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--vs-line-strong);
  }
  .step--done {
    color: var(--vs-ink-3);
  }
  .step--done .dot {
    background: var(--vs-green);
  }
  .step--active .dot {
    background: var(--vs-accent);
    animation: vs-pulse 1.4s ease-in-out infinite;
  }
  .step--todo {
    color: var(--vs-ink-3);
  }
  @media (prefers-reduced-motion: reduce) {
    .spinner,
    .step--active .dot {
      animation: none;
    }
  }
</style>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `(default slot)` | `text` |  | The label. Inherits font. Static under reduced motion. |

## Used by

[Selection Actions](https://viewership.ai/ui/components/selection-actions.md)

## Source

### `src/components/viewership/shimmer/Shimmer.astro`

```astro
---
/**
 * Shimmer: a label with a light sweep, to show the agent is working.
 *
 * Ported from Beautiful UI `components/atoms/Shimmer.tsx`
 * (MIT, © 2026 Shane Levine). Inherits font size and weight. The text stays
 * real text; put it in an `aria-live` region when it appears dynamically.
 * Under reduced motion it renders as plain secondary ink.
 *
 * @example
 * <Shimmer>Drafting the weekly report…</Shimmer>
 */
import type { HTMLAttributes } from "astro/types";

type Props = HTMLAttributes<"span">;

const { class: className, ...rest } = Astro.props;
---

<span class:list={["vs-shimmer", className]} {...rest}><slot /></span>

<style>
  .vs-shimmer {
    display: inline-block;
    background-image: linear-gradient(90deg, var(--vs-ink-3) 35%, var(--vs-ink) 50%, var(--vs-ink-3) 65%);
    background-size: 200% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: vs-shimmer-text 1.8s linear infinite;
  }
  @media (prefers-reduced-motion: reduce) {
    .vs-shimmer {
      animation: none;
      background: none;
      color: var(--vs-ink-2);
    }
  }
  @media (forced-colors: active) {
    .vs-shimmer {
      background: none;
      color: CanvasText;
    }
  }
</style>
```
