---
title: "WorkingIndicator"
description: "An inline indicator for long-running work, with live elapsed time."
category: AiAgent
status: beta
since: 0.3.9
tags: [ai, loading, status, timer]
syncStatus: reviewed
verified: true
---

## Usage

### Basic
`label` names the work; `startedAt` sets the clock's origin.
:::TuffDemoWrapper{demo="WorkingIndicatorWorkingIndicatorDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'

  const startedAt = ref(Date.now())
  </script>

  <template>
    <TxWorkingIndicator label="Churning" :started-at="startedAt" />
  </template>
---
:::

### Pixel Patterns
`variant` sets the pixel-grid pattern.
:::TuffDemoWrapper{demo="WorkingIndicatorVariantsDemo" code-lang="vue"}
---
code: |
  <template>
    <TxWorkingIndicator variant="drive" label="Churning" />
    <TxWorkingIndicator variant="dots" label="Sampling" />
    <TxWorkingIndicator variant="orbit" label="Counting" />
  </template>
---
:::

### Best Practices

- Use it for "something is running, and for how long"; use `TxTypingIndicator` for someone composing, `TxSpinner` for a wait with no meaning, and `TxLoadingState` for page-level states.
- Name the work in `label` ("Indexing the repository"), not just "Working": the readout says how long, the label says what.
- Turn `showElapsed` off for instant operations; a readout only matters past a few seconds.
- Always pass `startedAt` on streaming surfaces, or the clock resets to zero whenever the component is rebuilt.
- When elapsed time matters to a screen-reader user, have the host announce the total once the task finishes.

## API Reference

### Props

| Prop | Type | Default | Description |
|------|------|---------|------|
| `label` | `string` | `'Working'` | The shimmering status text. |
| `variant` | `'drive' \| 'dots' \| 'orbit'` | `'drive'` | Pixel-grid pattern. |
| `startedAt` | `number` | — | Clock origin in epoch milliseconds; omit to count from mount. |
| `showElapsed` | `boolean` | `true` | When off, drops the readout and stops the interval. |
| `elapsedFormatter` | `(ms: number) => string` | — | Overrides the default format (`12.3s` / `2m 3.0s`). |
| `ariaLabel` | `string` | — | Accessible name of the status region; omit to announce the visible label. |

### Slots

| Name | Scope | Description |
|------|------|------|
| `label` | — | Replaces the shimmering label, for rich content. |

## Overview

- The root is a `role="status"`; the readout updates every 100ms and carries `aria-hidden="true"`, so only the label is announced.
- The reading is always `Date.now() - startedAt`, so it doesn't fall behind when a background tab throttles timers.
- Under reduced motion the grid rests in its dim state while the clock keeps running.
- The directory also exports `useElapsed` and `formatElapsed` for laying out the reading yourself.

## Technologies

- The nine cells' animation delays live in SCSS `:nth-child()` rules, so one `animation: none` switches them all off under reduced motion.
- Adapted from [Beautiful UI](https://www.beautifului.dev) (© 2026 Shane Levine, MIT).
- Source: `packages/tuffex/packages/components/src/working-indicator/`.

<TuffDocSourceLink />
