---
name: shimmer-text
description: Use when you want "Premium, commercial, polished" - Moves a subtle shine over text.
---

# Shimmer Text

> **Category:** Text  -  **Personality:** Premium, commercial, polished
>
> **Best use:** CTAs, badges, hero words
>
> **Ratings:** Professional 5/5 - Casual 3/5 - Exotic 3/5 - Visual impact 4/5 - Difficulty 2/5 - Performance cost 2/5

## What it is

A single bright "catch-light" band that sweeps once across otherwise calm metallic text, then pauses — like studio light raking over brushed gold or chrome. The trick is a wide gradient painted *only inside the glyphs* (`background-clip: text`) whose `background-position` is animated, so the highlight travels through the letters. Use it to make one important word feel expensive and considered: a hero product name, a price on a CTA, a "limited" badge. It is deliberately restrained — distinct from a fully colourful Animated Gradient Text, where the *whole* fill cycles through hues.

## Dependencies / CDN

None — pure CSS (one optional ~12-line vanilla-JS flourish for a hover replay). The demo only loads display/body fonts, which are optional:

```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600&family=Playfair+Display:wght@800&display=swap" rel="stylesheet">
```

## HTML

Wrap just the word(s) you want to shine in a `.shine` span. It is real text, so it stays selectable and accessible in every context:

```html
<!-- hero word -->
<h1 class="shine-host">Meet <span class="shine">Aurum&nbsp;One</span></h1>

<!-- CTA label -->
<button class="btn shine-host"><span class="shine">Pre-order &mdash; $549</span></button>

<!-- badge -->
<span class="badge"><span class="shine">Flagship &middot; 500 made</span></span>
```

## CSS

```css
.shine{
  /* a champagne-gold "metal" carrying ONE bright catch-light band at 50% */
  background-image: linear-gradient(100deg,
    #c9a55f 0%,  #c9a55f 38%,                 /* flat base metal           */
    #ecd49a 45%, #fff7e4 50%, #ecd49a 55%,    /* the moving highlight band  */
    #c9a55f 62%, #c9a55f 100%);               /* flat base metal           */
  background-size: 220% 100%;                 /* gradient wider than the text, so the band can travel */
  background-position: 110% 0;                /* park the glint off-screen at rest */

  -webkit-background-clip: text;              /* paint the gradient ONLY inside the glyphs */
          background-clip: text;
  -webkit-text-fill-color: transparent;       /* let the gradient show through (WebKit/Blink) */
          color: transparent;

  animation: shimmer 5s ease-in-out 1s infinite;
}

@keyframes shimmer{
  0%   { background-position: 110% 0; }   /* calm                              */
  42%  { background-position: -10% 0; }   /* light sweeps left → right and exits */
  100% { background-position: -10% 0; }   /* hold on plain metal, then loop     */
}

/* big hero word: a soft halo (drop-shadow respects the clipped glyph alpha) */
h1 .shine{ filter: drop-shadow(0 2px 16px rgba(214,170,96,.28)); }

/* --- graceful degradation --- */
@supports not ((-webkit-background-clip:text) or (background-clip:text)){
  .shine{ -webkit-text-fill-color: initial; color: #ecd49a; background: none; }  /* solid gold */
}
@media (prefers-reduced-motion: reduce){
  .shine{ animation: none; background-position: 34% 0; }   /* one fixed, tasteful catch of light */
}
```

## JavaScript

Not required — the shimmer is pure CSS. Optional nicety: replay an instant glint when the user hovers a host element. It is skipped entirely under reduced-motion.

```css
@keyframes flash{ from{ background-position:110% 0 } to{ background-position:-10% 0 } }
.flash{ animation: flash 1s ease-out 1 !important; }
```

```js
if (!matchMedia('(prefers-reduced-motion: reduce)').matches){
  document.querySelectorAll('.shine-host').forEach(host=>{
    host.addEventListener('pointerenter', ()=>{
      const el = host.querySelector('.shine'); if(!el) return;
      el.classList.remove('flash'); void el.offsetWidth; el.classList.add('flash'); // restart the animation
    });
  });
  addEventListener('animationend', e=>{                  // hand control back to the ambient loop
    if (e.animationName === 'flash') e.target.classList.remove('flash');
  });
}
```

## How it works

- **`background-clip: text` + transparent fill** makes the element's *background* visible only through the shape of the letters; everything outside the glyphs is clipped away. This is the whole effect.
- **An over-wide gradient** (`background-size: 220%`) holds a flat base colour for most of its width and one narrow bright band around the 50% mark. Because it is wider than the text, there is room to slide it.
- **Animating `background-position`** drags that band across the glyphs. Outside the band the text reads as uniform metal; the moving highlight *is* the shimmer.
- **The keyframes pause** at the end (42% → 100% holds off-screen) so the glint passes once, then rests — that hold is what makes it feel premium rather than busy.
- `background-repeat` is left at its default (`repeat`), so the gradient tiles seamlessly (base-to-base at the edges) and the text is always fully painted, even while the band is parked off-screen.

## Customization

- **Metal:** swap the three colours. Platinum/chrome = `#8e9aa6 / #cfd6dd / #ffffff`; rose-gold = `#caa0a0 / #f0c9c0 / #fff`; "ink" = `#5a6473 / #aeb7c4 / #fff`.
- **Glint width / softness:** widen or tighten the `45% / 50% / 55%` stops — closer together = a sharp specular streak, further apart = a soft sheen.
- **Pace & rhythm:** the `5s` duration sets how often it fires; the `42%` keyframe vs `100%` sets the sweep-to-rest ratio (smaller % = quicker flick, longer pause). Drop the hold (animate 0→100% only) for a continuous sweep.
- **Angle:** the gradient's `100deg` tilts the band; use `90deg` for a vertical wipe, `115deg` for a more diagonal rake.

## Accessibility & performance

- The shimmering text is **real DOM text** — selectable, translatable, and read normally by screen readers. Keep it that way; never bake it into an image.
- **Honour `prefers-reduced-motion`**: the effect drops to a single fixed catch of light (no looping animation). The optional JS bails out under the same query.
- Animating `background-position` triggers **paint** (not GPU compositing), so confine the effect to a few small headings/labels — not body copy or dozens of nodes. The built-in pause keeps it idle most of the time.
- Check contrast on the *base* colour (the glyphs are that tone most of the time), not the highlight — light gold on near-black passes comfortably.

## Gotchas

- **The `-webkit-` prefix is mandatory.** `-webkit-background-clip:text` + `-webkit-text-fill-color:transparent` is what actually drives Chrome/Safari; ship the unprefixed pair too.
- **Transparent fill with no gradient = invisible text.** Always provide the `@supports not (...)` fallback (solid colour) so unsupported engines still show the word.
- **It needs a gradient, not a flat colour** — a single solid background can't shimmer because there is no band to move. And the gradient must be wider than the text (`background-size > 100%`) or there is nowhere for the band to travel.
- **Don't set `background-repeat: no-repeat`** here: when the band is parked beyond the box, the un-tiled gradient would leave a strip of the text unpainted (transparent). The default `repeat` keeps every glyph filled.
- **Avoid an inherited `color` transition** on these elements — animating to/from `transparent` can produce a grey flash; style the fill via `-webkit-text-fill-color` instead.
