Skip to content

Marquee ​

A purely presentational component that loops long content seamlessly via a slot; supports pause on hover and static fallback under prefers-reduced-motion. Scroll speed is a true pixels-per-second value (the component measures content width to derive duration), and content shorter than the container is auto-filled; when max-loops finishes, oas-finish is emitted (detail { loops }).

Breaking change: speed semantics changed from "seconds" to "pixels per second"

In previous versions speed was the duration of a single loop (seconds): speed="8" meant one full loop in 8 seconds — the wider the content, the faster it visually moved, so the same value produced wildly different speeds across containers. Now speed is the true scroll speed (pixels per second, default 48): the component measures the content width with a ResizeObserver and derives the animation duration (duration = content width ÷ speed), so the visual speed stays constant regardless of content or container width.

Migration guide: small legacy values (e.g. speed="8") now mean 8px/s (extremely slow) — re-set them to a target speed (24–120 is common). To keep duration semantics, override the CSS variable --oas-marquee-duration directly (the measurement write will still overwrite it; not recommended).

Basic Usage ​

Default looping scroll (48px/s)

OAS-UI component library · Web Components with no framework dependencies · Complete TypeScript types · Accessible ·

Speed Control ​

speed=96 fast / speed=24 slow (pixels per second)

Fast scrolling: 96 pixels per second Slow scrolling: 24 pixels per second

With the same speed="48", a 200px short text and a 2000px long text scroll at exactly the same visual speed (under the old "duration" semantics they differed by 10x) — this is why the speed semantics changed.

Auto-fill ​

When content is shorter than the container, duplicating it only once leaves blank space on the right after half a loop (a broken seam). The component measures the container and content widths and auto-fills the clone group with enough copies, so the viewport always has content during a full one-group translation — seamless, no broken seam.

auto-fill: short content loops seamlessly in a wide container

✦ Short notice
  • The clone count is capped (50): with extremely short content in an extremely wide container, blank space is preferred over unbounded DOM growth; for heavy content like image walls, keep the content itself at least one viewport wide.
  • The fill count is re-derived automatically when the container size changes (ResizeObserver).

Vertical Scrolling ​

orientation="vertical" switches to vertical scrolling (announcement-style long text). The container must have a fixed height (overflow clipping only makes sense with one), given via host styles.

orientation=vertical: vertical scrolling (fixed 96px height container)

Notice 1: New version released
Notice 2: marquee supports true pixels-per-second speed
Notice 3: content shorter than the viewport is auto-filled
Notice 4: appending content does not jump the scroll

A vertical marquee is continuous-flow semantics; for "scroll one item at a time and pause", use oas-carousel with vertical direction.

Reverse Scrolling ​

reverse: opposite directions (two rows in counter-flow)

Forward: flows from left to right · OAS-UI · Web Components · Reverse: flows from right to left · OAS-UI · Web Components ·

Multiple Rows ​

The component keeps a single continuous scrolling row; a multi-row marquee is composed from several oas-marquee elements interleaved with reverse (no extra attribute — stacking them vertically yields multiple rows):

Multiple rows: forward / reverse / forward

Row 1 · forward · OAS-UI · Web Components · framework-agnostic UI library · Row 2 · reverse · OAS-UI · Web Components · framework-agnostic UI library · Row 3 · forward · OAS-UI · Web Components · framework-agnostic UI library ·

Edge Fading ​

fade-edges fades out both container edges (mask-image mask, off by default — fading softens edges and changes existing appearances, enable on demand). The fade width is controlled by the CSS variable --oas-marquee-fade-size (default 24px).

fade-edges: logo wall with faded edges

ABCDEF

Pause on Hover ​

pause-on-hover: pauses on mouse hover / focus

Hover over this line to pause the scroll; move away to resume.

Dynamic Content Updates ​

When the host appends to or modifies the slot, the component rebuilds the clone group while preserving the animation phase (it records the current phase and resumes with a negative animation-delay), so the scroll position users see does not jump. The instance below appends a live notice every 3 seconds:

Appending content: no scroll jump

All systems normal ·

Element Content ​

slot supports arbitrary element combinations

NewThe latest release is liveRecommendedBuilt on Web Components standards

Image / Logo Wall ​

Content is not limited to text — putting images or logos in the slot produces the classic seamless "brand wall" scrolling.

Logo wall (seamless loop + faded edges)

ABCDEF

Image scrolling (img)

Image 1Image 2Image 3Image 4

Limited Loop Count (max-loops) ​

max-loops limits the number of loop rounds (positive integer): after playing N rounds the animation freezes on the final frame (animation-fill-mode: forwards) and oas-finish is dispatched once (detail: { loops }). When unset or set to an invalid/non-integer value the marquee loops infinitely. A "round" equals the shift distance (one copy of the content), independent of content width.

Under prefers-reduced-motion the animation is disabled and content is shown statically — nothing "finishes playing", so oas-finish is not dispatched.

max-loops=3: freezes after 3 rounds and dispatches oas-finish

This notice plays 3 times and then stops ·

Playing…

RTL (Right-to-Left) ​

When the container is in an RTL writing direction (host or ancestor dir="rtl", or oas-config-provider direction injection), the translation direction mirrors automatically: content flows to the right and new content enters from the left edge (mirroring LTR's "flow left, enter from right"); seamless looping and auto-fill are unaffected. reverse composes orthogonally with the mirror — under RTL, reverse flows to the left. Vertical scrolling is independent of writing direction.

dir=rtl: mirrored translation (content flows rightward)

Flowing from left to right · RTL mirror · OAS-UI · Web Components ·

API ​

oas-marquee ​

Attributes ​

AttributeDescriptionTypeDefault
dirWriting direction for RTL detection; horizontal translation mirrors under RTL——
fade-edgesBoolean; when present, fades out both container edges with a mask-image (off by default); fade width via --oas-marquee-fade-size——
max-loopsLimit loop rounds (positive integer): freezes on the final frame after N rounds and dispatches oas-finish once; missing/invalid = infinitestring—
orientationScroll direction: horizontal (default) / vertical (vertical scrolling; the container needs a fixed height)——
pause-on-hoverBoolean; when present, pauses the animation on hover/focus (animation-play-state: paused)——
reverseBoolean; when present, scrolls in the opposite directionboolean—
speedScroll speed in pixels per second (default 48); the animation duration is derived from the measured content width (duration = distance / speed); invalid or non-positive values fall back to the defaultstring—

Events ​

EventDescription
oas-finishDispatched once when the max-loops run finishes, detail: { loops }

Slots ​

NameDescription
defaultScrolling content

CSS Variables ​

CSS VariableDefault
--oas-marquee-duration20s
--oas-marquee-fade-size24px
--oas-marquee-shift50%
  • When the system enables "reduce motion" (prefers-reduced-motion: reduce), the animation is disabled and content is shown statically.
  • The duplicated content group carries aria-hidden, so screen readers do not read it twice.
  • No other events; purely presentational, except a single oas-finish when max-loops finishes.

Released under the MIT OR Apache-2.0 License.