# Marquee

> An endless scrolling strip for logos, tags, or testimonials, with a play/pause toggle.

URL: https://apogee.avaui.dev/docs/marquee
Last updated: 2026-10-16

An endless scrolling strip for logos, tags, or testimonials, with a play/pause toggle.

Last updated October 16, 2026

Uses JavaScript

Adds a play/pause toggle that pauses the CSS scroll animation. The loop itself is pure CSS — always animating — JS only adds the pause control.

**Without JS:** The play/pause button is hidden (no-js:hidden) — the strip keeps looping and can't be paused via the button. It still pauses on :focus-within and holds still for prefers-reduced-motion.

```astro
<Marquee label="Client logos">
<span>Larkspur</span>
<span>Fernhill</span>
<span>Brightwell</span>
<span>Halcyon</span>
</Marquee>
```

## Add

```sh
npx @ava-ui/apogee add marquee
```

No Apogee dependencies. Bring `@lucide/astro` for the play/pause toggle icons. Ships one scoped JS island (progressive enhancement).

## Usage

```astro
---
import Marquee from '@/components/apogee/Marquee/Marquee.astro'
---
<Marquee label="Client logos">
  <img src="/logo-1.svg" alt="Larkspur" />
  <img src="/logo-2.svg" alt="Fernhill" />
  <img src="/logo-3.svg" alt="Brightwell" />
</Marquee>
```

The default slot is rendered once and duplicated into two looping panels internally — don’t wrap your items in extra markup expecting a single copy.

## API

`@composable` — exposed to Content Editors in AVA. Any other attribute passes through to the root (`[key: string]: any`).

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string` | `'Marquee'` | Accessible label for the strip (`aria-label` on the `role="group"` root). |
| `direction` | `'forwards' \| 'backwards'` | `'forwards'` | Scroll direction. `backwards` reverses the animation. |
| `speed` | `number` | `4` | Seconds per item. Total loop duration scales with item count, so pace stays constant regardless of how many items you add. |
| `class` | `string` | — | Extra classes on the root, merged last. |

## Variants

Both directions:

```astro
<Marquee label="Forwards" direction="forwards">
<span>One</span>
<span>Two</span>
<span>Three</span>
</Marquee>
<Marquee label="Backwards" direction="backwards">
<span>One</span>
<span>Two</span>
<span>Three</span>
</Marquee>
```

## States

-   **Playing** (default) — the track animates on a CSS loop; duration is `speed × item count`.
-   **Paused** — click the toggle, or focus anything inside the strip (`:focus-within` auto-pauses, so a keyboard user can’t lose a focused link off-screen).
-   **Reduced motion** — starts paused; still startable via the toggle.
-   **No JS** — toggle button hidden; the strip keeps looping. See the JsNote above.

## Examples

A slower strip of longer content:

```astro
<Marquee label="Headlines" speed={6}>
<span>Ship the release notes</span>
<span>Design review at 3pm</span>
<span>New docs are live</span>
</Marquee>
```

## Accessibility

-   `role="group"` with `aria-label={label}` on the root.
-   The duplicated panel (the seamless-loop copy) is `aria-hidden` and `inert` — never read or tabbed to twice.
-   The strip pauses on `:focus-within`, so a keyboard user can read a focused link without it scrolling away.
-   Holds still for `prefers-reduced-motion` by default; the toggle can still start it.
-   **Keyboard** — `Tab` reaches items inside the strip (links, buttons) in document order, then the play/pause toggle; `Enter` / `Space` on the toggle switches play state.
-   **No JS** — the toggle is hidden (`no-js:hidden`); the strip still loops via CSS and still pauses on focus and for reduced motion — only the manual pause control is unavailable.

## Tokens

| Token | Used for |
| --- | --- |
| `--border-neutral` | Toggle button border |
| `--background-page` | Toggle button background |
| `--foreground-neutral` | Toggle button icon colour |
| `--background-neutral-faded-highlighted` | Toggle button hover background |
| `--outline-color-focus` | Toggle button focus ring |

## Do / Don’t

-   **Do** keep the item count reasonable — very short lists loop awkwardly even with pace-scaling.
-   **Do** give images real `alt` text, or `alt=""` if purely decorative.
-   **Don’t** put content someone must read carefully inside a Marquee — motion undermines reading; reserve it for decorative or ambient content.
-   **Don’t** rely on the marquee alone for critical information — a no-JS or fast-scrolling user may not catch it.

## Content

-   Pass a `label` describing the strip’s purpose (“Client logos”, “Latest headlines”) — it’s announced once via the root’s `aria-label`.
-   Keep items short — long strings force a longer loop and awkward wrapping.

## Related

[

CarouselA horizontal scroll-snap track for browsing slides. Native scroll and swipe work with zero JS; arrows, dots, and autoplay are progressive enhancement.

](https://apogee.avaui.dev/docs/carousel)[

ScrollAreaA scroll container with a thin, token-coloured scrollbar.

](https://apogee.avaui.dev/docs/scroll-area)
