---
title: Shimmer
description: Animated text-glyph shimmer for live and streaming status
visibility: guest
draft: false
---

# Shimmer

An animated highlight sweep that runs **across the letterforms** of live text —
the "Generating response…" / "Thinking…" affordance you see while an AI assistant
streams or a long task runs. Unlike [Skeleton](/components/skeleton), which
shimmers a gray placeholder block, Shimmer clips the gradient to the **real
copy** via `background-clip: text`, so the words themselves shimmer.

The text stays in the DOM and is read by screen readers — the shimmer is purely
decorative and is disabled under reduced-motion, forced-colors, and
reduced-transparency.

## Basic Usage

:::preview{title="A shimmering status line"}
<x-wirekit::shimmer>Generating response…</x-wirekit::shimmer>
:::

## Settled state (`active`)

The `active` prop is what makes this Livewire-native: bind it to a component
property so the shimmer runs **only** while work is in flight and settles to
plain text when done — no conditional markup, no JavaScript.

:::preview{title="Active vs. settled"}
<x-wirekit::stack gap="sm">
    <x-wirekit::shimmer :active="true">Streaming the answer…</x-wirekit::shimmer>
    <x-wirekit::shimmer :active="false">Here is the finished answer.</x-wirekit::shimmer>
</x-wirekit::stack>
:::

## Duration

`duration` accepts any CSS time value and overrides the `--shimmer-wk-duration`
token for that instance — slower reads as calmer, faster as more urgent.

:::preview{title="Custom shimmer speeds"}
<x-wirekit::stack gap="sm">
    <x-wirekit::shimmer duration="3s">Deliberating…</x-wirekit::shimmer>
    <x-wirekit::shimmer duration="1s">Working fast…</x-wirekit::shimmer>
</x-wirekit::stack>
:::

## In context

Because the wrapper defaults to an inline `<span>`, a shimmer sits inside a
heading, a row, or next to a [Spinner](/components/spinner).

:::preview{title="Shimmer alongside a spinner"}
<x-wirekit::row gap="sm" align="center">
    <x-wirekit::spinner size="sm" intent="accent" />
    <x-wirekit::shimmer>Assistant is thinking…</x-wirekit::shimmer>
</x-wirekit::row>
:::

## Livewire

Drive `active` straight from a component property so the effect mirrors the
real streaming state:

```blade
{{-- 1. `$streaming` is true only while the assistant streams tokens --}}
<x-wirekit::shimmer :active="$streaming">
    {{ $partialResponse ?: 'Generating response…' }}
</x-wirekit::shimmer>
```

```php
// 2. Flip $streaming around the streamed call — the shimmer follows it
public bool $streaming = false;

public function ask(): void
{
    $this->streaming = true;
    $this->stream(to: 'partialResponse', content: '…');  // wire:stream
    $this->streaming = false;
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `active` | bool | `true` | When `false` the slot renders as plain inherited-color text (no shimmer). Bind to a Livewire property to gate the effect on streaming state. |
| `as` | string | `'span'` | Wrapper tag: `span`, `div`, `p`, `strong`, `em`. |
| `duration` | string\|null | `null` | Any CSS `<time>`; overrides `--shimmer-wk-duration` for this instance. |
| `scope` | string\|null | `null` | Scoped personalization name. |

## Accessibility

- The shimmering text is **real text** — it stays in the DOM and is announced by
  screen readers. The shimmer is decorative only.
- Under `prefers-reduced-motion: reduce` the sweep animation is removed.
- Under `forced-colors: active` (Windows High Contrast) the transparent glyph
  fill is restored to a solid, system-colored glyph so the text never vanishes.
- Under `prefers-reduced-transparency: reduce` the transparent fill is likewise
  restored to solid inherited-color text.
- For a streaming region, pair the shimmer with an `aria-live="polite"` container
  and announce the settled text once — not every token.

## Keyboard Interaction

This component is purely presentational and does not respond to keyboard input.

## Pitfalls

- **Don't shimmer text the user must read carefully.** The effect is for
  transient status ("Generating…"), not for body copy that lingers.
- **Turn it off when done.** Bind `active` to the real loading state so settled
  content renders as ordinary, high-contrast text.
- **Keep runs short.** A shimmer on a long paragraph draws the eye away from the
  content — reserve it for a status line.

## Design Tokens

| Element | Token |
| --- | --- |
| Travel duration | `--shimmer-wk-duration` (default `2s`) |
| Highlight band half-width | `--shimmer-wk-band` (default `3ch`) |
| Band tilt | `--shimmer-wk-angle` (default `105deg`) |
| Base glyph color | `--color-wk-text-muted` |
| Highlight band color | `--color-wk-text` |

The gradient is clipped to the text via `background-clip: text`, which is in the
supported-browser baseline (Chrome/Edge 111, Safari 16.4, Firefox 128).

## Further Reading

- [MDN: `background-clip: text`](https://developer.mozilla.org/en-US/docs/Web/CSS/background-clip)
- [MDN: `prefers-reduced-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion)
