# Loader (/ui/components/react/loader)



<Playground component="loader" />

## Usage [#usage]

```tsx
import { Loader } from '@appica/ui-react/loader'
```

```tsx
<Loader />
```

`Loader` is an indeterminate progress indicator — it conveys "working on it" without a percentage. It comes in two shapes via `variant`: `bar` (a track with a sweeping fill, the default) and `dots` (three dots flowing left to right). Both read a little more like *progress* than a [`Spinner`](/ui/components/react/spinner), so they suit section- and status-level waits.

Like the spinner, it's **sized by font-size** — set a `text-*` class (or inherit `1em`) to scale it — and `currentColor` makes it match the surrounding text instead of the primary accent. It renders a `role="status"` element labelled "Loading".

Use `Loader` for "this area is loading", inline status text, and "load more" buttons. For a compact spinner inside a small button or icon button, reach for [`Spinner`](/ui/components/react/spinner) instead.

## Examples [#examples]

### Variants [#variants]

Two shapes: `bar` (a sweeping track) and `dots` (a flowing trio). Both loop indefinitely and honour `prefers-reduced-motion` by holding a static frame.

```tsx
import { Loader } from '@appica/ui-react/loader'

const VARIANTS = ['bar', 'dots'] as const

export default function LoaderVariants() {
  return (
    <div className="flex items-center gap-12">
      {VARIANTS.map((variant) => (
        <div key={variant} className="flex flex-col items-center gap-3">
          <Loader variant={variant} />
          <span className="text-foreground-muted text-xs">{variant}</span>
        </div>
      ))}
    </div>
  )
}
```

### Sizing [#sizing]

The loader scales with `font-size`, so a `text-*` utility resizes it — there's no `size` prop. Its width/height ratio is fixed, so it grows proportionally.

```tsx
import { Loader } from '@appica/ui-react/loader'

export default function LoaderSizing() {
  return (
    <div className="flex flex-wrap items-center gap-8">
      <Loader className="text-xl" />
      <Loader className="text-3xl" />
      <Loader className="text-5xl" />
      <Loader className="text-7xl" />
    </div>
  )
}
```

### Color [#color]

By default the loader uses the primary accent (with a soft track on the `bar`). Add `currentColor` and it adopts the surrounding text color instead.

```tsx
import { Loader } from '@appica/ui-react/loader'

export default function LoaderColor() {
  return (
    <div className="flex items-center gap-12 text-4xl">
      <Loader />
      <span className="text-violet-500">
        <Loader currentColor />
      </span>
      <span className="text-success-emphasis">
        <Loader currentColor />
      </span>
    </div>
  )
}
```

### Load more [#load-more]

A "load more" button that shows the `dots` loader while the next page fetches. `currentColor` matches the button text.

```tsx
'use client'

import * as React from 'react'
import { Button } from '@appica/ui-react/button'
import { Loader } from '@appica/ui-react/loader'

const ALL = ['Aurora', 'Borealis', 'Cosmos', 'Drift', 'Ember', 'Flux', 'Glow', 'Halo', 'Ion']

export default function LoaderButton() {
  const [count, setCount] = React.useState(3)
  const [loading, setLoading] = React.useState(false)

  const loadMore = () => {
    setLoading(true)
    setTimeout(() => {
      setCount((c) => Math.min(c + 3, ALL.length))
      setLoading(false)
    }, 1400)
  }

  return (
    <div className="flex w-56 flex-col gap-2">
      <ul className="flex flex-col gap-2">
        {ALL.slice(0, count).map((item) => (
          <li
            key={item}
            className="bg-background-subtle border-border-muted text-foreground-emphasis rounded-lg border px-4 py-3 text-sm"
          >
            {item}
          </li>
        ))}
      </ul>
      {count < ALL.length && (
        <Button onClick={loadMore} disabled={loading} className="self-center">
          {loading ? <Loader variant="dots" currentColor className="text-3xl" /> : 'Load more'}
        </Button>
      )}
    </div>
  )
}
```

### Section loading [#section-loading]

Drive a panel from your own loading state — show a centred loader while data is in flight, then swap in the content. The Refresh button re-runs it.

```tsx
'use client'

import * as React from 'react'
import { Button } from '@appica/ui-react/button'
import { Loader } from '@appica/ui-react/loader'

export default function LoaderSection() {
  const [loading, setLoading] = React.useState(true)

  const load = React.useCallback(() => {
    setLoading(true)
    const timer = setTimeout(() => setLoading(false), 1800)
    return () => clearTimeout(timer)
  }, [])

  React.useEffect(() => load(), [load])

  return (
    <div className="border-border bg-background w-80 overflow-hidden rounded-xl border">
      <div className="border-border-muted bg-background-subtle flex items-center justify-between border-b px-4 py-2.5">
        <span className="text-foreground-intense text-sm font-semibold">Revenue</span>
        <Button variant="outline" size="sm" onClick={load} disabled={loading}>
          Refresh
        </Button>
      </div>
      <div className="flex h-32 items-center justify-center p-4">
        {loading ? (
          <div className="flex flex-col items-center gap-3">
            <Loader variant="bar" className="text-4xl" />
            <span className="text-foreground-muted text-xs">Crunching the numbers…</span>
          </div>
        ) : (
          <div className="text-center">
            <p className="text-foreground-intense mb-0.5 text-3xl font-semibold">$48,250</p>
            <p className="text-success-emphasis text-sm">+12.5% this month</p>
          </div>
        )}
      </div>
    </div>
  )
}
```

### Inline status [#inline-status]

Set `currentColor` and drop a loader straight into a line of text — sized in `em`, it sits on the baseline next to a "syncing…" or "uploading…" message and takes the text's color.

```tsx
import { Loader } from '@appica/ui-react/loader'

export default function LoaderInline() {
  return (
    <div className="flex flex-col gap-4 text-sm">
      <span className="text-foreground-muted inline-flex items-center gap-2">
        Syncing your changes
        <Loader variant="dots" currentColor className="text-[1.6em]" />
      </span>
      <span className="text-secondary-emphasis inline-flex items-center gap-2">
        Uploading 3 files
        <Loader variant="bar" currentColor className="text-[1.6em]" />
      </span>
    </div>
  )
}
```

## RTL [#rtl]

Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your tree in `DirectionProvider` so direction-aware behavior follows the same direction.

```tsx
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

Beside text, the loader sits on the correct side under RTL since it follows the inline flow. For setup details and caveats, see the [RTL guide](/ui/docs/react/rtl).

<RtlPreview component="loader" />

## API reference [#api-reference]

`Loader` renders a single `<span role="status">` and forwards `ref`, `className`, and any other span attributes.

| Prop           | <ColMinWidth width="210">Type</ColMinWidth> | Default     | <ColMinWidth width="240">Description</ColMinWidth>                                                         |
| -------------- | ------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------- |
| `variant`      | <Code>'bar' \| 'dots'</Code>                | `'bar'`     | The animated shape.                                                                                        |
| `currentColor` | `boolean`                                   | `false`     | Inherit the surrounding text color (`currentColor`) instead of the primary accent.                         |
| `aria-label`   | `string`                                    | `'Loading'` | Accessible name for the `role="status"` region.                                                            |
| `className`    | `string`                                    | —           | Extra classes, merged via `tailwind-merge`. Set a `text-*` size here — the loader scales with `font-size`. |

## Accessibility [#accessibility]

* The loader is a `role="status"` live region with a default `aria-label="Loading"`; pass a more specific label (e.g. "Uploading files") when it helps.
* When a loader replaces a section's content, keep one labelled status in the region rather than several, so screen readers announce a single "loading" state.
* A loader sitting beside text that already states the status can be marked `aria-hidden` to avoid a duplicate announcement.
* The animation is replaced by a static frame when the user prefers reduced motion.
