> For the complete documentation index, see [llms.txt](https://analog.groupthought.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://analog.groupthought.com/reference/alpine/marquee.md).

# x-marquee

A continuously scrolling row of content: a source row, a cloned target row, and one shared offset so the loop never jumps.

A continuously scrolling row of content: a source row, a cloned target row, and one shared offset so the loop never jumps.

| Directive / magic     | What it does                                                                                |
| --------------------- | ------------------------------------------------------------------------------------------- |
| `x-marquee`           | The root. Clones the source into the target and updates both rows from one offset           |
| `x-marquee.speed.120` | Scroll pace in pixels per second (default 60). A negative value scrolls the other way       |
| `x-marquee.fill`      | Repeats the source content until the row is wider than the viewport                         |
| `x-marquee.pause`     | Starts paused; call `$marquee.play()` to start it                                           |
| `x-marquee.scroll`    | Nudges the marquee forward and back as the page scrolls                                     |
| `x-marquee:source`    | The row with the real content                                                               |
| `x-marquee:target`    | An empty row for the cloned content. Gets `aria-hidden`                                     |
| `$marquee`            | `isPlaying`, `play()`, `pause()`, `toggle()`, `scrollTo(element)`, `isReady`, `offsetValue` |

Keyboard focus inside the marquee pauses the loop and moves the focused element to the visible left edge. Leaving resumes playback only when focus paused it. Clones get `aria-hidden`, are removed from the tab order, and have their `id` attributes removed. With `prefers-reduced-motion`, the rows don't animate. The `scroll` modifier is also disabled on low-power devices. Cloning and measurement only run while the marquee is visible.

## Examples

### A basic marquee

Use `x-marquee:source` for the content and an empty `x-marquee:target` after it:

```html
<div x-data x-marquee.speed.60.fill class="flex overflow-hidden">
  <div x-marquee:source class="flex w-max shrink-0 gap-x-[--layout-gutter] pr-[--layout-gutter]">
    <span>First item</span>
    <span>Second item</span>
  </div>
  <div x-marquee:target class="flex w-max shrink-0 gap-x-[--layout-gutter] pr-[--layout-gutter]"></div>
</div>
```

Give both rows the same classes, `w-max` so the row can be measured, and a trailing gap so the target starts at the right distance. The `marquee` snippet renders this structure for the Marquee section and maps the merchant's "Speed" setting onto `.speed`.

### Pausing on hover

Use `$marquee.pause()` and `$marquee.play()` on pointer events:

```html
<div @mouseenter="$marquee.pause()" @mouseleave="$marquee.play()" x-marquee.speed.60.fill>
  …
</div>
```

The `_marquee` block pauses this way, and also pauses while the block is selected in the theme editor (`@shopify:block:select`).

### Advancing with page scroll

Use the `scroll` modifier to add a scroll-driven nudge on top of the loop:

```html
<div x-marquee.speed.60.fill.scroll>…</div>
```

Scrolling down nudges the row along its own direction and scrolling up nudges it back, through a spring, so the loop's own clock is never disturbed.

### Waiting for the first render

Use `$marquee.isReady` to keep the rows invisible until cloning and measuring finish:

```html
<div x-marquee:source :class="$marquee.isReady ? 'opacity-100' : 'opacity-0'">…</div>
```

Without this the source row is visible in its unfilled state for the first frames.

## Options

All settings are modifiers on the root; there is no options object. If the source row is not wider than the viewport, the target stays empty and nothing animates. Use `fill` whenever the content can be short.
