{"slug":"marquee","title":"Marquee","description":"Using the marquee machine in your project.","contentType":"component","framework":"react","content":"An accessible auto-scrolling marquee component for displaying scrolling content\nlike logos, announcements, or featured items.\n\n## Resources\n\n\n[Latest version: v1.31.0](https://www.npmjs.com/package/@zag-js/marquee)\n[Logic Visualizer](https://zag-visualizer.vercel.app/marquee)\n[Source Code](https://github.com/chakra-ui/zag/tree/main/packages/machines/marquee)\n\n\n\n**Features**\n\n- Smooth GPU-accelerated animations with seamless looping\n- Horizontal and vertical scrolling with RTL support\n- Pause on hover and keyboard focus\n- Customizable speed and spacing\n- Accessible and respects `prefers-reduced-motion`\n\n## Installation\n\nTo use the marquee machine in your project, run the following command in your\ncommand line:\n\n```bash\nnpm install @zag-js/marquee @zag-js/react\n# or\nyarn add @zag-js/marquee @zag-js/react\n```\n\n## Anatomy\n\nTo set up the marquee correctly, you'll need to understand its anatomy and how\nwe name its parts.\n\n> Each part includes a `data-part` attribute to help identify them in the DOM.\n\n\n\n## Usage\n\nFirst, import the marquee package into your project\n\n```jsx\nimport * as marquee from \"@zag-js/marquee\"\n```\n\nThe marquee package exports two key functions:\n\n- `machine` — The state machine logic for the marquee widget.\n- `connect` — The function that translates the machine's state to JSX attributes\n  and event handlers.\n\n> You'll also need to provide a unique `id` to the `useMachine` hook. This is\n> used to ensure that every part has a unique identifier.\n\nNext, import the required hooks and functions for your framework and use the\nmarquee machine in your project 🔥\n\n```tsx\nimport * as marquee from \"@zag-js/marquee\"\nimport { useMachine, normalizeProps } from \"@zag-js/react\"\nimport { useId } from \"react\"\n\nconst logos = [\n  { name: \"Microsoft\", logo: \"🏢\" },\n  { name: \"Apple\", logo: \"🍎\" },\n  { name: \"Google\", logo: \"🔍\" },\n  { name: \"Amazon\", logo: \"📦\" },\n]\n\nfunction Marquee() {\n  const service = useMachine(marquee.machine, {\n    id: useId(),\n    autoFill: true,\n  })\n\n  const api = marquee.connect(service, normalizeProps)\n\n  return (\n    <div {...api.getRootProps()}>\n      {/* Optional: Add fade gradient at start */}\n      <div {...api.getEdgeProps({ side: \"start\" })} />\n\n      <div {...api.getViewportProps()}>\n        {/* Render content (original + clones) */}\n        {Array.from({ length: api.contentCount }).map((_, index) => (\n          <div key={index} {...api.getContentProps({ index })}>\n            {logos.map((item, i) => (\n              <div key={i} {...api.getItemProps()}>\n                <span className=\"logo\">{item.logo}</span>\n                <span className=\"name\">{item.name}</span>\n              </div>\n            ))}\n          </div>\n        ))}\n      </div>\n\n      {/* Optional: Add fade gradient at end */}\n      <div {...api.getEdgeProps({ side: \"end\" })} />\n    </div>\n  )\n}\n```\n\n### Auto-filling content\n\nTo automatically duplicate content to fill the container and prevent gaps during\nanimation, set the `autoFill` property in the machine's context to `true`.\n\n```jsx {2}\nconst service = useMachine(marquee.machine, {\n  autoFill: true,\n})\n```\n\nThe `api.contentCount` property tells you the total number of content elements\nto render (original + clones). Use this value in your loop:\n\n```jsx\n{\n  Array.from({ length: api.contentCount }).map((_, index) => (\n    <div key={index} {...api.getContentProps({ index })}>\n      {/* Your content */}\n    </div>\n  ))\n}\n```\n\n> **Note:** The `api.multiplier` property is also available if you need to know\n> the duplication factor specifically (number of clones excluding the original).\n\n### Changing the scroll direction\n\nTo change the scroll direction, set the `side` property in the machine's context\nto one of: `\"start\"`, `\"end\"`, `\"top\"`, or `\"bottom\"`.\n\n```jsx {2}\nconst service = useMachine(marquee.machine, {\n  side: \"end\", // scrolls from right to left in LTR\n})\n```\n\n**Directional behavior:**\n\n- `\"start\"` — Scrolls from inline-start to inline-end (respects RTL)\n- `\"end\"` — Scrolls from inline-end to inline-start (respects RTL)\n- `\"top\"` — Scrolls from bottom to top (vertical)\n- `\"bottom\"` — Scrolls from top to bottom (vertical)\n\n### Adjusting animation speed\n\nTo control how fast the marquee scrolls, set the `speed` property in the\nmachine's context. The value is in pixels per second.\n\n```jsx {2}\nconst service = useMachine(marquee.machine, {\n  speed: 100, // 100 pixels per second\n})\n```\n\n**Considerations:**\n\n- Higher values create faster scrolling\n- Lower values create slower, more readable scrolling\n- Speed is automatically adjusted based on content and container size\n\n### Setting spacing between items\n\nTo customize the gap between marquee items, set the `spacing` property in the\nmachine's context to a valid CSS unit.\n\n```jsx {2}\nconst service = useMachine(marquee.machine, {\n  spacing: \"2rem\",\n})\n```\n\n### Reversing the animation direction\n\nTo reverse the animation direction without changing the scroll side, set the\n`reverse` property in the machine's context to `true`.\n\n```jsx {2}\nconst service = useMachine(marquee.machine, {\n  reverse: true,\n})\n```\n\n### Pausing on user interaction\n\nTo pause the marquee when the user hovers or focuses any element inside it, set\nthe `pauseOnInteraction` property in the machine's context to `true`.\n\n```jsx {2}\nconst service = useMachine(marquee.machine, {\n  pauseOnInteraction: true,\n})\n```\n\nThis is especially important for accessibility when your marquee contains\ninteractive elements like links or buttons.\n\n### Setting initial paused state\n\nTo start the marquee in a paused state, set the `defaultPaused` property in the\nmachine's context to `true`.\n\n```jsx {2}\nconst service = useMachine(marquee.machine, {\n  defaultPaused: true,\n})\n```\n\n### Delaying the animation start\n\nTo add a delay before the animation starts, set the `delay` property in the\nmachine's context to a value in seconds.\n\n```jsx {2}\nconst service = useMachine(marquee.machine, {\n  delay: 2, // 2 second delay\n})\n```\n\n### Limiting loop iterations\n\nBy default, the marquee loops infinitely. To limit the number of loops, set the\n`loopCount` property in the machine's context.\n\n```jsx {2}\nconst service = useMachine(marquee.machine, {\n  loopCount: 3, // stops after 3 complete loops\n})\n```\n\n> Setting `loopCount` to `0` (default) creates an infinite loop.\n\n### Listening for loop completion\n\nWhen the marquee completes a single loop iteration, the `onLoopComplete`\ncallback is invoked.\n\n```jsx {2-4}\nconst service = useMachine(marquee.machine, {\n  onLoopComplete() {\n    console.log(\"Completed one loop\")\n  },\n})\n```\n\n### Listening for animation completion\n\nWhen the marquee completes all loops and stops (only for finite loops), the\n`onComplete` callback is invoked.\n\n```jsx {2-4}\nconst service = useMachine(marquee.machine, {\n  loopCount: 3,\n  onComplete() {\n    console.log(\"Marquee finished all loops\")\n  },\n})\n```\n\n### Controlling the marquee programmatically\n\nThe marquee API provides methods to control playback:\n\n```jsx\n// Pause the marquee\napi.pause()\n\n// Resume the marquee\napi.resume()\n\n// Toggle pause state\napi.togglePause()\n\n// Restart the animation from the beginning\napi.restart()\n```\n\n### Monitoring pause state changes\n\nWhen the marquee pause state changes, the `onPauseChange` callback is invoked.\n\n```jsx {2-5}\nconst service = useMachine(marquee.machine, {\n  onPauseChange(details) {\n    // details => { paused: boolean }\n    console.log(\"Marquee is now:\", details.paused ? \"paused\" : \"playing\")\n  },\n})\n```\n\n### Adding fade gradients at edges\n\nTo add fade gradients at the edges of the marquee, use the `getEdgeProps`\nmethod:\n\n```jsx\n<div {...api.getRootProps()}>\n  {/* Fade gradient at start */}\n  <div {...api.getEdgeProps({ side: \"start\" })} />\n\n  <div {...api.getViewportProps()}>{/* Content */}</div>\n\n  {/* Fade gradient at end */}\n  <div {...api.getEdgeProps({ side: \"end\" })} />\n</div>\n```\n\nStyle the edge gradients using CSS:\n\n```css\n[data-part=\"edge\"][data-side=\"start\"] {\n  width: 100px;\n  background: linear-gradient(to right, white, transparent);\n}\n\n[data-part=\"edge\"][data-side=\"end\"] {\n  width: 100px;\n  background: linear-gradient(to left, white, transparent);\n}\n```\n\n## Styling guide\n\n### Required keyframe animations\n\nFor the marquee to work, you **must** include the required keyframe animations\nin your CSS. These animations control the scrolling behavior:\n\n```css\n@keyframes marqueeX {\n  0% {\n    transform: translateX(0%);\n  }\n  100% {\n    transform: translateX(var(--marquee-translate));\n  }\n}\n\n@keyframes marqueeY {\n  0% {\n    transform: translateY(0%);\n  }\n  100% {\n    transform: translateY(var(--marquee-translate));\n  }\n}\n```\n\n**Important:** The animations use the `--marquee-translate` CSS variable which\nis automatically set by the machine based on the `side` and `dir` props. This\nenables seamless looping when combined with the cloned content.\n\n### Base content styles\n\nTo apply the animations, add these base styles to your content elements:\n\n```css\n[data-scope=\"marquee\"][data-part=\"content\"] {\n  animation-timing-function: linear;\n  animation-duration: var(--marquee-duration);\n  animation-delay: var(--marquee-delay);\n  animation-iteration-count: var(--marquee-loop-count);\n}\n\n[data-part=\"content\"][data-side=\"start\"],\n[data-part=\"content\"][data-side=\"end\"] {\n  animation-name: marqueeX;\n}\n\n[data-part=\"content\"][data-side=\"top\"],\n[data-part=\"content\"][data-side=\"bottom\"] {\n  animation-name: marqueeY;\n}\n\n[data-part=\"content\"][data-reverse] {\n  animation-direction: reverse;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  [data-part=\"content\"] {\n    animation: none !important;\n  }\n}\n```\n\n**Note:** The machine automatically handles layout styles (`display`,\n`flex-direction`, `flex-shrink`) and performance optimizations\n(`backface-visibility`, `will-change`, `transform: translateZ(0)`), so you only\nneed to add the animation properties.\n\n### CSS variables\n\nThe machine automatically sets these CSS variables:\n\n- `--marquee-duration` — Animation duration in seconds\n- `--marquee-spacing` — Spacing between items\n- `--marquee-delay` — Delay before animation starts\n- `--marquee-loop-count` — Number of iterations (or \"infinite\")\n- `--marquee-translate` — Transform value for animations\n\n### Styling parts\n\nEarlier, we mentioned that each marquee part has a `data-part` attribute added\nto them to select and style them in the DOM.\n\n```css\n[data-scope=\"marquee\"][data-part=\"root\"] {\n  /* styles for the root container */\n}\n\n[data-scope=\"marquee\"][data-part=\"viewport\"] {\n  /* styles for the viewport */\n}\n\n[data-scope=\"marquee\"][data-part=\"content\"] {\n  /* styles for each content container */\n}\n\n[data-scope=\"marquee\"][data-part=\"item\"] {\n  /* styles for individual marquee items */\n}\n\n[data-scope=\"marquee\"][data-part=\"edge\"] {\n  /* styles for fade edge gradients */\n}\n```\n\n### Orientation-specific styles\n\nThe marquee adds a `data-orientation` attribute for orientation-specific\nstyling:\n\n```css\n[data-part=\"root\"][data-orientation=\"horizontal\"] {\n  /* styles for horizontal marquee */\n}\n\n[data-part=\"root\"][data-orientation=\"vertical\"] {\n  /* styles for vertical marquee */\n}\n```\n\n### Paused state\n\nWhen the marquee is paused, a `data-paused` attribute is set on the root:\n\n```css\n[data-part=\"root\"][data-paused] {\n  /* styles for paused state */\n}\n```\n\n### Clone identification\n\nCloned content elements have a `data-clone` attribute for styling duplicates\ndifferently:\n\n```css\n[data-part=\"content\"][data-clone] {\n  /* styles for cloned content */\n}\n```\n\n### Side-specific styles\n\nEach content element has a `data-side` attribute indicating the scroll\ndirection:\n\n```css\n[data-part=\"content\"][data-side=\"start\"] {\n  /* styles for content scrolling to inline-end */\n}\n\n[data-part=\"content\"][data-side=\"end\"] {\n  /* styles for content scrolling to inline-start */\n}\n\n[data-part=\"content\"][data-side=\"top\"] {\n  /* styles for content scrolling up */\n}\n\n[data-part=\"content\"][data-side=\"bottom\"] {\n  /* styles for content scrolling down */\n}\n```\n\n## Accessibility\n\n### ARIA attributes\n\nThe marquee component includes proper ARIA attributes:\n\n- `role=\"region\"` with `aria-roledescription=\"marquee\"` for proper semantics\n- `aria-label` describing the marquee content\n- `aria-hidden=\"true\"` on cloned content to prevent duplicate announcements\n\n### Keyboard interaction\n\nWhen `pauseOnInteraction` is enabled:\n\n- **Focus** — Pauses the marquee when any child element receives focus\n- **Blur** — Resumes the marquee when focus leaves the component\n\n### Motion preferences\n\nThe marquee automatically respects the user's motion preferences via the\n`prefers-reduced-motion` media query, disabling animations when requested.\n\n### Best practices\n\n1. **Use descriptive labels** — Set a meaningful `aria-label` via the\n   `translations.root` property:\n\n   ```jsx\n   const service = useMachine(marquee.machine, {\n     translations: {\n       root: \"Featured partner logos\", // instead of generic \"Marquee content\"\n     },\n   })\n   ```\n\n2. **Enable pause on interaction** — Essential for accessibility when content\n   contains links or important information:\n\n   ```jsx\n   const service = useMachine(marquee.machine, {\n     pauseOnInteraction: true,\n   })\n   ```\n\n3. **Consider infinite loops carefully** — Infinite animations can cause\n   discomfort for users with vestibular disorders. Consider providing pause\n   controls or limiting loop iterations for critical content.\n\n4. **Decorative vs. informational** — Marquees work best for decorative content\n   (logos, testimonials). For important information, consider static displays or\n   user-controlled carousels instead.\n\n## Methods and Properties\n\n### Machine Context\n\nThe marquee machine exposes the following context properties:\n\n**`ids`**\nType: `Partial<{ root: string; viewport: string; content: (index: number) => string; }>`\nDescription: The ids of the elements in the marquee. Useful for composition.\n\n**`translations`**\nType: `IntlTranslations`\nDescription: The localized messages to use.\n\n**`side`**\nType: `Side`\nDescription: The side/direction the marquee scrolls towards.\n\n**`speed`**\nType: `number`\nDescription: The speed of the marquee animation in pixels per second.\n\n**`delay`**\nType: `number`\nDescription: The delay before the animation starts (in seconds).\n\n**`loopCount`**\nType: `number`\nDescription: The number of times to loop the animation (0 = infinite).\n\n**`spacing`**\nType: `string`\nDescription: The spacing between marquee items.\n\n**`autoFill`**\nType: `boolean`\nDescription: Whether to automatically duplicate content to fill the container.\n\n**`pauseOnInteraction`**\nType: `boolean`\nDescription: Whether to pause the marquee on user interaction (hover, focus).\n\n**`reverse`**\nType: `boolean`\nDescription: Whether to reverse the animation direction.\n\n**`paused`**\nType: `boolean`\nDescription: Whether the marquee is paused.\n\n**`defaultPaused`**\nType: `boolean`\nDescription: Whether the marquee is paused by default.\n\n**`onPauseChange`**\nType: `(details: PauseStatusDetails) => void`\nDescription: Function called when the pause status changes.\n\n**`onLoopComplete`**\nType: `() => void`\nDescription: Function called when the marquee completes one loop iteration.\n\n**`onComplete`**\nType: `() => void`\nDescription: Function called when the marquee completes all loops and stops.\nOnly fires for finite loops (loopCount > 0).\n\n**`dir`**\nType: `\"ltr\" | \"rtl\"`\nDescription: The document's text/writing direction.\n\n**`id`**\nType: `string`\nDescription: The unique identifier of the machine.\n\n**`getRootNode`**\nType: `() => ShadowRoot | Node | Document`\nDescription: A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.\n\n### Machine API\n\nThe marquee `api` exposes the following methods:\n\n**`paused`**\nType: `boolean`\nDescription: Whether the marquee is currently paused.\n\n**`orientation`**\nType: `\"horizontal\" | \"vertical\"`\nDescription: The current orientation of the marquee.\n\n**`side`**\nType: `Side`\nDescription: The current side/direction of the marquee.\n\n**`multiplier`**\nType: `number`\nDescription: The multiplier for auto-fill. Indicates how many times to duplicate content.\nWhen autoFill is enabled and content is smaller than container, this returns\nthe number of additional copies needed. Otherwise returns 1.\n\n**`contentCount`**\nType: `number`\nDescription: The total number of content elements to render (original + clones).\nUse this value when rendering your content in a loop.\n\n**`pause`**\nType: `VoidFunction`\nDescription: Pause the marquee animation.\n\n**`resume`**\nType: `VoidFunction`\nDescription: Resume the marquee animation.\n\n**`togglePause`**\nType: `VoidFunction`\nDescription: Toggle the pause state.\n\n**`restart`**\nType: `VoidFunction`\nDescription: Restart the marquee animation from the beginning.\n\n### Data Attributes\n\n**`Root`**\n\n**`data-scope`**: marquee\n**`data-part`**: root\n**`data-state`**: \"paused\" | \"idle\"\n**`data-orientation`**: The orientation of the marquee\n**`data-paused`**: Present when paused\n\n**`Viewport`**\n\n**`data-scope`**: marquee\n**`data-part`**: \n**`data-orientation`**: The orientation of the viewport\n**`data-side`**: \n\n**`Content`**\n\n**`data-scope`**: marquee\n**`data-part`**: \n**`data-index`**: The index of the item\n**`data-orientation`**: The orientation of the content\n**`data-side`**: \n**`data-reverse`**: \n**`data-clone`**: \n\n**`Edge`**\n\n**`data-scope`**: marquee\n**`data-part`**: \n**`data-side`**: \n**`data-orientation`**: The orientation of the edge\n\n### CSS Variables\n\n<CssVarTable name=\"marquee\" />","package":"@zag-js/marquee","editUrl":"https://github.com/chakra-ui/zag/edit/main/website/data/components/marquee.mdx"}