# Shimmer

Utilities for adding a shimmer effect to text elements.

Page: https://sui.draco.dev/docs/utils/shimmer

### Example: shimmer-demo

```tsx
import type { ExampleProps } from "../types";
export default function ShimmerDemo({ locale }: ExampleProps = {}) {
  const chinese = locale === "zh-CN";
  return (
    <p className="shimmer text-muted-foreground text-sm">
      {chinese ? "正在生成回复…" : "Generating response…"}
    </p>
  );
}
```

## Installation

Follow the [installation guide](/docs/installation). Both utilities are included when you import SUI’s global styles:

```css
@import "@workspace/ui/globals.css";
```

The utilities use the existing `shadcn/tailwind.css` import in `globals.css`; no additional stylesheet or React component is needed.

## Usage

| Class                         | Styles                                                                                               |
| ----------------------------- | ---------------------------------------------------------------------------------------------------- |
| `shimmer`                     | `background-clip: text;`  `animation: tw-shimmer var(--shimmer-duration, 2s) linear infinite;` |
| `shimmer-once`                | `animation-iteration-count: 1;`                                                                      |
| `shimmer-reverse`             | `animation-direction: reverse;`                                                                      |
| `shimmer-none`                | `--shimmer-image: none;`  `--shimmer-text-fill: currentColor;`                                 |
| `shimmer-color-<color>`       | `--shimmer-color: <color>;`                                                                          |
| `shimmer-color-[<value>]`     | `--shimmer-color: <value>;`                                                                          |
| `shimmer-color-<color>/<pct>` | `--shimmer-color: color-mix(in oklch, <color> <pct>, transparent);`                                  |
| `shimmer-duration-<number>`   | `--shimmer-duration: calc(<number> * 1ms);`                                                          |
| `shimmer-spread-<number>`     | `--shimmer-spread: calc(var(--spacing) * <number>);`                                                 |
| `shimmer-spread-[<value>]`    | `--shimmer-spread: <value>;`                                                                         |
| `shimmer-angle-<number>`      | `--shimmer-angle: calc(<number> * 1deg);`                                                            |

Add `shimmer` to a text element.

```tsx
<p className="shimmer text-muted-foreground">Generating response&hellip;</p>
```

The shimmer is built on `currentColor`, so it adapts to the element:

- The highlight is derived from the text color, with no configuration needed.
- It works on any color, from `text-muted-foreground` to brand colors.
- In dark mode, the highlight automatically brightens to stay visible.

The effect is pure CSS. The text is painted with `background-clip: text`, and the highlight sweeps across it in a seamless loop.

## With Marker

The shimmer composes with any component that renders text. A common pattern is a [Marker](/docs/components/marker) showing a live status while the assistant is working:

### Example: shimmer-marker

```tsx
import { Loader } from "@workspace/ui/components/loader";
import {
  Marker,
  MarkerContent,
  MarkerIcon,
} from "@workspace/ui/components/marker";
import type { ExampleProps } from "../types";

export default function ShimmerMarker({ locale }: ExampleProps = {}) {
  const chinese = locale === "zh-CN";
  return (
    <div className="flex w-full max-w-sm flex-col gap-4">
      <Marker role="status">
        <MarkerIcon>
          <Loader size="sm" label={chinese ? "正在思考" : "Thinking"} />
        </MarkerIcon>
        <MarkerContent className="shimmer">
          {chinese ? "正在思考…" : "Thinking…"}
        </MarkerContent>
      </Marker>
      <Marker variant="separator" role="status">
        <MarkerContent className="shimmer">
          {chinese ? "正在读取 4 个文件" : "Reading 4 files"}
        </MarkerContent>
      </Marker>
    </div>
  );
}
```

```tsx
<Marker role="status">
  <MarkerIcon>
    <Loader size="sm" label="Thinking" />
  </MarkerIcon>
  <MarkerContent className="shimmer">Thinking&hellip;</MarkerContent>
</Marker>
```

## Color

Use `shimmer-color-<color>` to set the highlight color explicitly. It accepts theme colors with an optional opacity modifier, or any arbitrary color value.

### Example: shimmer-color

```tsx
import type { ExampleProps } from "../types";
export default function ShimmerColor({ locale }: ExampleProps = {}) {
  const chinese = locale === "zh-CN";
  return (
    <div className="flex flex-col items-center gap-2 text-muted-foreground text-sm">
      <p className="shimmer shimmer-color-blue-500/60">
        {chinese ? "正在生成回复…" : "Generating response…"}
      </p>
      <p className="shimmer shimmer-color-[#378ADD]">
        {chinese ? "正在生成回复…" : "Generating response…"}
      </p>
    </div>
  );
}
```

```tsx
<p className="shimmer shimmer-color-blue-500/60">Generating response&hellip;</p>
<p className="shimmer shimmer-color-[#378ADD]">Generating response&hellip;</p>
```

## Duration

Use `shimmer-duration-<number>` to set the duration of one sweep in milliseconds. The default is `2000`, i.e. `2s`.

### Example: shimmer-duration

```tsx
import type { ExampleProps } from "../types";
export default function ShimmerDuration({ locale }: ExampleProps = {}) {
  const chinese = locale === "zh-CN";
  return (
    <div className="mx-auto grid w-full max-w-lg gap-6 text-center text-muted-foreground text-sm sm:grid-cols-2">
      <div className="flex flex-col gap-3">
        <p className="shimmer">
          {chinese ? "正在生成回复…" : "Generating response…"}
        </p>
        <p className="font-mono text-[0.9em]">shimmer</p>
      </div>
      <div className="flex flex-col gap-3">
        <p className="shimmer shimmer-duration-1000">
          {chinese ? "正在生成回复…" : "Generating response…"}
        </p>
        <p className="font-mono text-[0.9em]">shimmer-duration-1000</p>
      </div>
    </div>
  );
}
```

```tsx
<p className="shimmer shimmer-duration-1000">Generating response&hellip;</p>
```

## Spread

Use `shimmer-spread-<number>` to set the width of the highlight band using the spacing scale. The default is `calc(3ch + 40px)`: a fixed base plus a `3ch` term that scales with the font size.

### Example: shimmer-spread

```tsx
import type { ExampleProps } from "../types";
export default function ShimmerSpread({ locale }: ExampleProps = {}) {
  const chinese = locale === "zh-CN";
  return (
    <div className="mx-auto grid w-full max-w-lg gap-6 text-center text-muted-foreground text-sm sm:grid-cols-2">
      <div className="flex flex-col gap-3">
        <p className="shimmer shimmer-spread-4">
          {chinese ? "正在生成回复…" : "Generating response…"}
        </p>
        <p className="font-mono text-[0.9em]">shimmer-spread-4</p>
      </div>
      <div className="flex flex-col gap-3">
        <p className="shimmer shimmer-spread-24">
          {chinese ? "正在生成回复…" : "Generating response…"}
        </p>
        <p className="font-mono text-[0.9em]">shimmer-spread-24</p>
      </div>
    </div>
  );
}
```

```tsx
<p className="shimmer shimmer-spread-24">Generating response&hellip;</p>
```

For one-off values, use an arbitrary length or percentage:

```tsx
<p className="shimmer shimmer-spread-[5rem]">Generating response&hellip;</p>
```

## Angle

Use `shimmer-angle-<number>` to set the tilt of the highlight band in degrees. The default is `20`.

### Example: shimmer-angle

```tsx
import type { ExampleProps } from "../types";
export default function ShimmerAngle({ locale }: ExampleProps = {}) {
  const chinese = locale === "zh-CN";
  return (
    <div className="mx-auto grid w-full max-w-lg gap-6 text-center text-muted-foreground text-sm sm:grid-cols-2">
      <div className="flex flex-col gap-3">
        <p className="shimmer">
          {chinese ? "正在生成回复…" : "Generating response…"}
        </p>
        <p className="font-mono text-[0.9em]">shimmer</p>
      </div>
      <div className="flex flex-col gap-3">
        <p className="shimmer shimmer-angle-45">
          {chinese ? "正在生成回复…" : "Generating response…"}
        </p>
        <p className="font-mono text-[0.9em]">shimmer-angle-45</p>
      </div>
    </div>
  );
}
```

```tsx
<p className="shimmer shimmer-angle-45">Generating response&hellip;</p>
```

## Reverse

Use `shimmer-reverse` to sweep the highlight in the opposite direction. In RTL layouts the sweep already follows the reading direction. See [RTL](#rtl).

```tsx
<p className="shimmer shimmer-reverse">Generating response&hellip;</p>
```

## Play once

Use `shimmer-once` to play a single sweep instead of looping, useful as a reveal when streaming completes. Pair it with `shimmer-duration-<number>` to control how long the sweep takes.

### Example: shimmer-once

```tsx
import { Button } from "@workspace/ui/components/button";
import * as React from "react";
import type { ExampleProps } from "../types";

export default function ShimmerOnce({ locale }: ExampleProps = {}) {
  const chinese = locale === "zh-CN";
  const [key, setKey] = React.useState(0);

  return (
    <div className="flex flex-col items-center gap-4">
      <p
        key={key}
        className="shimmer shimmer-duration-1100 shimmer-once text-muted-foreground text-sm"
      >
        {chinese ? "正在生成回复…" : "Generating response…"}
      </p>
      <Button
        variant="outline"
        size="sm"
        onClick={() => setKey((value) => value + 1)}
      >
        {chinese ? "重播" : "Replay"}
      </Button>
    </div>
  );
}
```

```tsx
<p className="shimmer shimmer-duration-1100 shimmer-once">
  Response generated.
</p>
```

## Disabling the shimmer

Use `shimmer-none` to turn the effect off and render the text normally. It works in any class order, so the typical use is responsive or stateful:

### Example: shimmer-none

```tsx
import type { ExampleProps } from "../types";
export default function ShimmerNone({ locale }: ExampleProps = {}) {
  const chinese = locale === "zh-CN";
  return (
    <div className="flex flex-col items-center gap-3 text-muted-foreground text-sm">
      <p className="shimmer md:shimmer-none">
        {chinese ? "正在生成回复…" : "Generating response…"}
      </p>
      <p className="font-mono text-[0.9em]">shimmer md:shimmer-none</p>
    </div>
  );
}
```

```tsx
<p className="shimmer md:shimmer-none">Generating response&hellip;</p>
```

## Fallback

The shimmer is built on modern color features, [relative color syntax](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_colors/Relative_colors) and `color-mix()`, which are available in all current browsers. In older browsers without support, the highlight gradient is dropped and the text can render transparent. If you target older browsers, apply `shimmer` conditionally with a `supports-*` variant:

```tsx
<p className="supports-[color:oklch(from_white_l_c_h)]:shimmer">
  Generating response&hellip;
</p>
```

## Reduced motion

When the user prefers reduced motion, the animation is disabled automatically and the text renders normally. There is nothing to configure.

## RTL

Set `dir="rtl"` on the container or use SUI’s [DirectionProvider](/docs/components/direction).

The sweep follows the reading direction, left to right in LTR and right to left in RTL, with no extra classes. Use `shimmer-reverse` to flip the direction manually.

### Example: shimmer-rtl

```tsx
import type { ExampleProps } from "../types";
export default function ShimmerRtl({ locale }: ExampleProps = {}) {
  const chinese = locale === "zh-CN";
  return (
    <div className="mx-auto grid w-full max-w-lg gap-6 text-center text-muted-foreground text-sm sm:grid-cols-2">
      <div className="flex flex-col gap-3">
        <p dir="ltr" className="shimmer">
          {chinese ? "正在生成回复…" : "Generating response…"}
        </p>
        <p className="font-mono text-[0.9em]">dir=&quot;ltr&quot;</p>
      </div>
      <div className="flex flex-col gap-3">
        <p dir="rtl" className="shimmer">
          جارٍ إنشاء الرد&hellip;
        </p>
        <p className="font-mono text-[0.9em]">dir=&quot;rtl&quot;</p>
      </div>
    </div>
  );
}
```

## Accessibility

Shimmer is a visual effect, not a status announcement. Use a suitable `role="status"` for loading text and avoid repeatedly replacing the accessible label. Keep the underlying text readable; use `shimmer-none` after the operation completes. For a loading indicator with a label, see [Loader](/docs/components/loader).
