> ## Documentation Index
> Fetch the complete documentation index at: /llms.txt
> Use this file to discover all available pages before exploring further.

# Text Shimmer

> A static headline receives one clean specular gradient sweep through its glyphs, then returns to its ordinary text color.

# Text Shimmer

A static headline receives one clean specular gradient sweep through its glyphs, then returns to its ordinary text color.

**Text Shimmer**

```html text-shimmer.html
<!doctype html>
<!--
  text-shimmer: HyperFrames video primitive (text effects / drift / emphasize)

  Concept: a headline remains fixed while one narrow specular band crosses
  its glyphs. The text is clean before and after the pass. There is no
  entrance, exit, pulse, scale, or supporting decoration.

  Evidence: design-inspiration/INDEX.md and viral-micro-interactions.md,
  animated text shimmer by jhey Tan, adapted from an infinite CSS loop into
  one deterministic, seek-driven video beat.

  Variables:
    - text (string, default "Effortless"): headline copy.
    - accent (green | blue | violet, default green): shimmer hue.
    - sweep_at (number, 0.2 to 2 seconds, default 1.2): requested sweep start.

  Envelope: IN includes clean stillness until sweep_at, then one fixed 0.9s
  sweep. If the mounted duration is too short, the sweep starts earlier and
  compresses only when it cannot fit. HOLD is elastic and keeps the clean end
  state alive with an off-glyph drift. OUT is intentionally zero because the
  treatment returns to ordinary text rather than dismissing it.

  Mount contract: everything renderable lives inside the template. #root is
  elastic, fills the host box, establishes the container query basis, and has
  no data-width or data-height. The paused GSAP timeline registers under the
  hardcoded text-shimmer key.
-->
<html
  lang="en"
  data-composition-id="text-shimmer"
  data-composition-duration="3"
  data-composition-variables='[
    { "id": "text", "type": "string", "role": "content", "label": "Headline", "description": "Text receiving the single shimmer pass.", "default": "Effortless" },
    { "id": "accent", "type": "enum", "role": "style", "label": "Accent", "description": "Hue of the specular shimmer band.", "default": "green", "options": [{ "value": "green", "label": "Green" }, { "value": "blue", "label": "Blue" }, { "value": "violet", "label": "Violet" }] },
    { "id": "sweep_at", "type": "number", "role": "timing", "label": "Sweep start", "description": "Requested start time for the shimmer sweep.", "default": 1.2, "min": 0.2, "max": 2, "step": 0.1, "unit": "s" }
  ]'
>
  <head>
    <meta charset="UTF-8" />
    <title>Text Shimmer</title>
  </head>
  <body>
    <template>
      <div id="root" data-composition-id="text-shimmer" data-duration="3" data-fps="30">
        <style>
          *,
          *::before,
          *::after {
            box-sizing: border-box;
          }

          #root {
            position: absolute;
            inset: 0;
            container-type: size;
            isolation: isolate;
            overflow: hidden;
            background: var(--bg, transparent);
            color: var(--fg, #f8fafc);
            font-family: var(--font-display, Inter, system-ui, sans-serif);
            pointer-events: none;
          }

          .ts-clip {
            position: absolute;
            inset: 0;
            display: grid;
            place-items: center;
            overflow: hidden;
            padding: var(--space-3, 4cqmin);
            background: var(--surface, transparent);
          }

          .ts-headline {
            --ts-base: var(--fg, #f8fafc);
            --ts-accent: var(--brand, #22c55e);
            display: block;
            max-width: 90cqw;
            color: transparent;
            font-size: clamp(28px, 16cqmin, 176px);
            font-weight: 780;
            line-height: 0.96;
            letter-spacing: -0.055em;
            overflow-wrap: anywhere;
            text-align: center;
            background-image: linear-gradient(
              110deg,
              var(--ts-base) 0%,
              var(--ts-base) 40%,
              color-mix(in srgb, var(--ts-accent) 58%, var(--fg, white)) 47%,
              var(--fg, white) 50%,
              color-mix(in srgb, var(--ts-accent) 72%, var(--fg, white)) 53%,
              var(--ts-base) 60%,
              var(--ts-base) 100%
            );
            background-position: 100% 50%;
            background-repeat: no-repeat;
            background-size: 300% 100%;
            background-clip: text;
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            will-change: background-position;
          }
        </style>

        <div
          id="text-shimmer-clip"
          class="ts-clip clip"
          data-start="0"
          data-duration="3"
          data-track-index="0"
        >
          <span class="ts-headline"></span>
        </div>

        <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
        <script>
          (function () {
            "use strict";

            var root = document.getElementById("root");
            var headline = root.querySelector(".ts-headline");
            var vars =
              window.__hyperframes && window.__hyperframes.getVariables
                ? window.__hyperframes.getVariables()
                : {};

            var text = vars.text == null ? "Effortless" : String(vars.text);
            var accent = vars.accent === "blue" || vars.accent === "violet" ? vars.accent : "green";
            var accentTokens = {
              green: "var(--brand, #22c55e)",
              blue: "var(--accent, #38bdf8)",
              violet: "var(--accent-2, #a78bfa)",
            };
            var accentColor = accentTokens[accent];
            var sweepAtValue = vars.sweep_at;
            var sweepAt =
              typeof sweepAtValue === "number" && Number.isFinite(sweepAtValue)
                ? Math.max(0.2, Math.min(2, sweepAtValue))
                : 1.2;

            headline.textContent = text;
            headline.style.setProperty("--ts-accent", accentColor);

            var SWEEP_BASE = 0.9;
            var durationValue = parseFloat(root.dataset.duration || "3");
            var duration = Number.isFinite(durationValue) ? Math.max(0.001, durationValue) : 3;
            var SWEEP = Math.min(SWEEP_BASE, duration);
            var SWEEP_START = Math.min(sweepAt, Math.max(0, duration - SWEEP));
            var IN = SWEEP_START + SWEEP;
            var HOLD = Math.max(0, duration - IN);

            var tl = gsap.timeline({ paused: true });
            tl.fromTo(
              headline,
              { backgroundPosition: "100% 50%" },
              { backgroundPosition: "0% 50%", duration: SWEEP, ease: "sine.inOut" },
              SWEEP_START,
            );

            if (HOLD > 0) {
              tl.to(
                headline,
                { backgroundPosition: "4% 50%", duration: HOLD, ease: "sine.inOut" },
                IN,
              );
            }

            tl.seek(0);
            window.__timelines = window.__timelines || {};
            window.__timelines["text-shimmer"] = tl;
          })();
        </script>
      </div>
    </template>
  </body>
</html>
```

Copy npx hyperframes add text-shimmer to the clipboard

That writes one file: `compositions/components/text-shimmer.html`.

## Paste it into your composition

Open `compositions/components/text-shimmer.html` and copy what is inside into your own composition.

A component has no size or duration of its own. It takes both from the composition
you paste it into.

## Variables

Every one of these has a default, so the piece works untouched. Set the ones you
want to change on the element:

| Variable | Default | Accepts | What it does |
| - | - | - | - |
| `text` | `Effortless` | string | Text receiving the single shimmer pass. |
| `accent` | `green` | `green`, `blue`, `violet` | Hue of the specular shimmer band. |
| `sweep_at` | `1.2` | 0.2s to 2s, step 0.1s | Requested start time for the shimmer sweep. |

Set them with `data-variable-values` on the element that mounts it. These are the
defaults, so this behaves exactly like the preview above until you change one:

```html wrap
<div
  data-composition-id="text-shimmer"
  data-composition-src="compositions/components/text-shimmer.html"
  data-variable-values='{"text":"Effortless","accent":"green","sweep_at":1.2}'
></div>
```

{/* hf:generated-footer */}

Tagged `motion-primitive` `text-effects` `shimmer` `specular` `emphasis`.

## Related topics

* [Browse the complete Catalog](/catalog.md)
* [Add assets and Catalog items in the browser editor](/studio/assets-and-blocks.md)
* [Put blocks together into one video](/concepts/compositions.md)
