# Shimmer

> Loading text with a slanted band of light that sweeps across it, like "Thinking" while an answer streams in. It can also cycle through messages.

- Name: `shimmer`
- Status: beta
- Categories: motion, feedback
- File: the install writes `shimmer.tsx` to your `ui` alias, `@/components/ui` by default.
- Gallery: https://arbokui.com/components/shimmer
- Registry item: https://arbokui.com/r/shimmer.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/shimmer
   ```

## Use it for

- A short line of text that says work is happening, like "Thinking" or "Writing the answer" while a reply streams in.
- Real progress steps. Pass the current step as children and change it as the work moves on.
- Put it inside a role="status" element that stays on the page, so screen readers hear each step once.
- shine picks the light: gloss, a soft sheen with a bright streak, glint, two streaks like light on glass, or color, a band that runs through the theme's tone colors.
- ShimmerStack for a few lines at once, like loading steps. It starts each line a little after the one above, so the light runs down them in order, even when the lines differ in length or size.
- ShimmerMessages for filler text that does not report real progress, like "Thinking", "Still thinking", "Almost there". It shows each message for interval milliseconds.
- It takes the font size and weight around it. Set them with className, from the theme's type sizes, like text-(length:--type-body) font-(--type-weight-strong).

## Avoid it for

- Content that is loading in a known shape, like a card or a list. Use Skeleton.
- An action in progress, like saving. Use loading on the Button.
- Solid tone fills, like a brand button. The band reads the card color, so it has no room to show there.
- ShimmerMessages for real steps. Screen readers hear only its first message. Use Shimmer and change its children instead.
- Long paragraphs. Keep it to one short line. If it wraps, each line gets its own band.
- Emoji. The band fills the letters with the text color, so an emoji turns into a flat shape.

## Props

- `children`: `ReactNode`. Shimmer only. The text that shimmers. Required. An icon inside keeps the text's color.
- `shine`: `"gloss" | "glint" | "color"`. Default `"gloss"`. How the light looks. gloss is a soft sheen with a bright streak. glint is two streaks, a small one and a bright one. color runs through the theme's tone colors.
- `ShimmerStack`: `component`. A column for stacked Shimmer lines. Each child starts its sweep 120ms after the one above, up to the seventh, and every line's band moves at the same speed. A ShimmerMessages line keeps its place in the ripple as its message changes. Set \[--shimmer-stagger:200ms\] to change the step.
- `messages`: `readonly string[]`. ShimmerMessages only. The messages to show in turn. Screen readers hear only the first one.
- `interval`: `number`. Default `2000`. ShimmerMessages only. How long each message shows, in milliseconds.
- `loop`: `boolean`. Default `true`. ShimmerMessages only. Start over after the last message. Set false to stop on the last one.
- `className`: `string`. Merged last. 'animate-none' stops the sweep. '\[animation-delay:120ms\]' starts it later. '\[--shimmer-band:32ch\]' makes the band wider.

## Accessibility

- Shimmer is real text, so screen readers read it and people can select it. Selected text shows solid, with no band. The sweep is only a look.
- It is not a live region. To have screen readers hear it, keep one role="status" element on the page and put the Shimmer inside it while work runs.
- Keep that status element on the page before the text arrives. Some screen readers miss a status region that arrives together with its text.
- ShimmerMessages gives screen readers its first message only, and hides the rest from them. A status region around it then speaks once, not on every change.
- At rest the text is the foreground color in a light theme, and a little dimmer in a dark one, so the band can brighten it. The band passes over a few letters at a time and fades them for a moment. That fade has no contrast check, so never put the only copy of something people must read in a Shimmer.
- The sweep runs over 1.3 times --motion-pulse-duration. The theme sets that to 0s for motion off and for reduced motion, so the text then shows plain, with no band.
- In forced colors the band is dropped and the text takes the system text color.
- In ShimmerMessages, each new message fades in from --motion-distance below while the last one fades out. Reduced motion keeps the fade and drops the movement. Motion off swaps the text at once.
- ShimmerMessages holds its width at the longest message, so content beside it does not shift when the message changes.
- In right-to-left text, the band sweeps from right to left.

## Examples

### Thinking

```tsx
<Shimmer>Thinking</Shimmer>
```

### Each shine

```tsx
<Shimmer shine="gloss">Thinking</Shimmer>
<Shimmer shine="glint">Searching the web</Shimmer>
<Shimmer shine="color">Writing your report</Shimmer>
```

### Stacked lines with the light rippling down

```tsx
<ShimmerStack>
  <Shimmer>Thinking</Shimmer>
  <Shimmer>Searching the web</Shimmer>
  <Shimmer className="text-(length:--type-section)">Writing your report</Shimmer>
</ShimmerStack>
```

### Real steps in a status region that stays on the page

```tsx
function Reply({ step }: { step: string | null }) {
  return (
    <p role="status">
      {step === null ? null : <Shimmer>{step}</Shimmer>}
    </p>
  );
}
```

### Cycling filler messages

```tsx
<ShimmerMessages messages={["Thinking", "Still thinking", "Almost there"]} interval={2500} loop={false} />
```

### With an icon (needs lucide-react)

```tsx
import { Sparkles } from "lucide-react";

<Shimmer className="inline-flex items-center gap-1.5">
  <Sparkles className="size-4" aria-hidden />
  Thinking
</Shimmer>
```
