Marquee
Headless, reduced-motion aware marquee driven by measured CSS custom properties.
|
|
| Package |
@vizejs/ui/marquee |
| Maturity |
stable |
| Own the source |
vize lib pull marquee |
| Requires |
context, controllable-state, id, measure |
| Aliases |
marquee, ticker, news ticker, logo wall, infinite scroller |
| Covers |
WAI-ARIA marquee role, WCAG 2.2.2 Pause, Stop, Hide, Magic UI Marquee |
Usage
import { MarqueeContent, MarqueePauseButton, Marquee, MarqueeRoot } from "@vizejs/ui/marquee";
Or copy the source into your project with vize lib pull marquee (see Source Distribution).
API
MarqueeContent
Source: src/families/media/marquee/marquee-content.vue
Slots
| Slot |
Slot props |
Description |
default |
MarqueeSlotState & { readonly copy: number } |
Content rendered once per copy. Only the first copy is exposed to assistive technology and sequential focus; the rest are aria-hidden and inert. Avoid ids inside the slot, since copies duplicate them. |
Exposed
| Member |
Type |
Description |
element |
HTMLDivElement | null |
Rendered track element that the consumer animates. |
measure |
() => void |
Re-measure geometry, e.g. after fonts load. |
Source: src/families/media/marquee/marquee-pause-button.vue
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before toggling. Call preventDefault() to keep the play intent. |
Slots
| Slot |
Slot props |
Description |
default |
MarqueeSlotState & { readonly label: string } |
Button content, e.g. an icon. The accessible name comes from the messages. |
Exposed
| Member |
Type |
Description |
element |
HTMLButtonElement | null |
Rendered native button. |
Marquee
Source: src/families/media/marquee/marquee-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned root id. null and undefined select a deterministic fallback. |
direction |
MarqueeDirection |
"left" |
Direction the content travels. |
speed |
number |
50 |
Travel speed in CSS pixels per second; the loop duration is derived from the measured copy length, so speed stays constant whatever the content size. |
repeat |
number | "auto" |
"auto" |
Number of rendered copies, or "auto" to fill the viewport seamlessly. |
playing |
boolean |
undefined |
Controlled play intent (v-model:playing). undefined selects defaultPlaying. |
defaultPlaying |
boolean |
true |
Initial play intent for uncontrolled use. |
pauseOnHover |
boolean |
true |
Pause while a mouse or pen pointer rests on the marquee. |
pauseOnFocus |
boolean |
true |
Pause while keyboard focus is inside the marquee. |
respectReducedMotion |
boolean |
true |
Stay paused while prefers-reduced-motion: reduce matches until the user presses play. |
messages |
MarqueeMessageOverrides | undefined |
undefined |
Localized strings for the pause button. |
ariaLabel |
string |
undefined |
Accessible name of the marquee region. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the marquee region. |
Events
| Event |
Payload |
Description |
update:playing |
[playing: boolean] |
Fired when the play intent requests a new controlled value. |
stateChange |
[state: MarqueeState, reason: MarqueePauseReason | null] |
Fired after every distinct animation-state transition. |
Slots
| Slot |
Slot props |
Description |
default |
MarqueeSlotState |
Content, pause button, and decorations. Receives the animation state. |
Exposed
| Member |
Type |
Description |
copies |
— |
|
direction |
— |
|
duration |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
pause |
— |
|
pauseReason |
— |
|
play |
— |
|
playing |
— |
|
state |
— |
|
toggle |
— |
|
MarqueeRoot
Source: src/families/media/marquee/marquee-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned root id. null and undefined select a deterministic fallback. |
direction |
MarqueeDirection |
"left" |
Direction the content travels. |
speed |
number |
50 |
Travel speed in CSS pixels per second; the loop duration is derived from the measured copy length, so speed stays constant whatever the content size. |
repeat |
number | "auto" |
"auto" |
Number of rendered copies, or "auto" to fill the viewport seamlessly. |
playing |
boolean |
undefined |
Controlled play intent (v-model:playing). undefined selects defaultPlaying. |
defaultPlaying |
boolean |
true |
Initial play intent for uncontrolled use. |
pauseOnHover |
boolean |
true |
Pause while a mouse or pen pointer rests on the marquee. |
pauseOnFocus |
boolean |
true |
Pause while keyboard focus is inside the marquee. |
respectReducedMotion |
boolean |
true |
Stay paused while prefers-reduced-motion: reduce matches until the user presses play. |
messages |
MarqueeMessageOverrides | undefined |
undefined |
Localized strings for the pause button. |
ariaLabel |
string |
undefined |
Accessible name of the marquee region. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the marquee region. |
Events
| Event |
Payload |
Description |
update:playing |
[playing: boolean] |
Fired when the play intent requests a new controlled value. |
stateChange |
[state: MarqueeState, reason: MarqueePauseReason | null] |
Fired after every distinct animation-state transition. |
Slots
| Slot |
Slot props |
Description |
default |
MarqueeSlotState |
Content, pause button, and decorations. Receives the animation state. |
Exposed
| Member |
Type |
Description |
state |
MarqueeState |
Animation state. |
pauseReason |
MarqueePauseReason | null |
Highest-priority pause reason, or null while running. |
playing |
boolean |
Whether the play intent is on (v-model:playing). |
direction |
MarqueeDirection |
Scroll direction. |
copies |
number |
Number of rendered copies (the first is the accessible one). |
duration |
number | null |
Seconds for one full loop, or null until measured. |
element |
HTMLDivElement | null |
Rendered root element. |
play |
() => boolean |
Turn the play intent on and opt in to motion under reduced-motion preferences. |
pause |
() => boolean |
Turn the play intent off. |
toggle |
() => boolean |
Toggle the play intent. |
Behavior
Normative state x input -> outcome table for marquee-root.vue,
marquee-content.vue, and marquee-pause-button.vue (@vizejs/ui/marquee).
Every row is proven by the named test.
Animation uses consumer CSS keyframes driven by measured custom properties rather
than the Web Animations API. The markup and the paused state stay declarative, so
they are identical on the server and after hydration. Reduced-motion media
queries also work in plain CSS, no JavaScript frame loop runs, and pausing is just
animation-play-state keyed on data-state. The root publishes:
--vize-ui-marquee-distance: the distance between two copies, gap included.
--vize-ui-marquee-duration: distance / speed.
--vize-ui-marquee-copies: the number of copies.
Recipe:
[data-vize-ui="marquee-root"] {
overflow: hidden;
}
[data-vize-ui="marquee-track"] {
display: flex;
width: max-content;
animation: vize-marquee var(--vize-ui-marquee-duration, 0s) linear infinite;
}
[data-vize-ui="marquee-root"][data-state="paused"] [data-vize-ui="marquee-track"] {
animation-play-state: paused;
}
[data-direction="right"] [data-vize-ui="marquee-track"],
[data-direction="down"] [data-vize-ui="marquee-track"] {
animation-direction: reverse;
}
@keyframes vize-marquee {
to {
translate: calc(-1 * var(--vize-ui-marquee-distance, 0px));
}
}
| ID |
State |
Input |
Outcome |
Evidence |
| M1 |
default |
render |
role="marquee" region with a deterministic id; the first copy is exposed, duplicates are aria-hidden and inert |
renders an accessible marquee region with one exposed copy and inert duplicates |
| M2 |
measured |
mount / resize |
distance, duration, and copy count derive from measured copies; repeat="auto" fills the viewport plus one copy |
measures copies to publish distance, duration, and a viewport-filling repeat count |
| M3 |
vertical |
direction="up" / resize |
measurement uses the block axis and re-runs on resize |
vertical directions measure along the block axis and resize re-measures |
| M4 |
running |
hover / keyboard focus |
mouse hover and focus inside pause with reasons hover/focus; touch never hovers; both are configurable |
hover and keyboard focus pause and resume the animation |
| M5 |
running |
pause button |
toggles the play intent (WCAG 2.2.2), swapping its accessible name; preventDefault() keeps the intent |
the pause button toggles the play intent per WCAG 2.2.2 |
| M6 |
reduced motion |
mount / play |
prefers-reduced-motion pauses with reason reduced-motion until the user presses play; opt out with respectReducedMotion=false |
reduced motion keeps the marquee paused until the user opts in |
| M7 |
controlled |
v-model:playing / API |
the parent owns the intent; messages localize the button; the instance exposes state and play/pause/toggle |
controlled playing, localized messages, and the exposed instance |
| M8 |
missing provider |
setup |
parts fail closed with the shared context diagnostic |
compound parts require a matching root provider |
| M9 |
pure helpers |
geometry |
copy counts clamp to 2–32, durations round to milliseconds, and styles omit unmeasured values |
derives orientation, copy counts, durations, and custom properties |
| M10 |
SSR |
isolated requests |
markup is byte-identical with two copies and no duration until measured |
renders byte-identical marquee markup across isolated SSR requests |
| M11 |
SSR / hydration |
hydrate |
server markup hydrates without warnings or node replacement |
hydrates marquee markup without warnings or node replacement |
| M12 |
types |
compile |
directions, states, reasons, messages, and exposes are closed and read-only |
marquee.types.test-d.ts |
Copies repeat the slot, so ids inside marquee content would be duplicated;
avoid them.