Skip to contentAsheeUIAsheeUI

Marquee

Content that moves in a loop, a logo wall, a ticker, a column of live events.

A marquee is content that repeats without end: the customers on a logo wall, the rates on a ticker, the payments settling in a column beside a dashboard. It is the one component here whose purpose is motion, and it says so in its props: the axis to travel along, the direction, the speed, the distance between items, and whether the loop runs at all.

What makes it a component rather than a div with an animation is the seam. The children are rendered twice and the track travels by half its own length, so the loop returns to content rather than to empty space; the second copy is hidden from assistive technology, so the content is read once.

Usage

import { Marquee } from "@asheeui/web";

<Marquee speed="slow" gap="3rem" pauseOnHover fadeEdges>
  {customers.map((customer) => (
    <Typography key={customer} role="heading-sm" as="span">
      {customer}
    </Typography>
  ))}
</Marquee>

children is an array, and it is the array that is repeated, so the list a page passes is the list a reader sees, drawn twice rather than generated once. Each child needs its own key for the same reason any list does.

axis is x, the registered value, or y for a column; a vertical marquee has no width to claim and states h-full, so the frame it sits in supplies the height. direction is forward, the registered one, which moves from right to left on the horizontal axis and from bottom to top on the vertical, or reverse.

gap is a CSS length, registered as 1.5rem, and it is applied both between the items of one copy and between the two copies, so the loop's seam is spaced like everything else.

Speed, and what the reader can read

speed is a preset, slow at 50 seconds a cycle, normal at 30, fast at 15, or a number of seconds for one cycle, which is the same scale stated exactly. normal is the registered value.

Speed is the one prop worth thinking about in seconds rather than in tokens, because the duration is fixed and the distance is not: a cycle covers one copy of the content, so the same preset moves faster when the copy is wide, a wall of long customer names, and more gently when it is short. A ticker of short strings reads at fast, and a logo wall reads at slow, because a reader has to recognise a word they were not expecting.

pauseOnHover, registered false, stops the track while a pointer rests on it or while anything inside it has keyboard focus, so a reader who cannot use a pointer can still stop the loop, the class covers group-hover and group-focus-within, so it needs no state and no rerender. A marquee whose content holds nothing focusable therefore has only the reduced-motion opt-out below. fadeEdges, registered false, paints a gradient over each end so the content dissolves into the background instead of being cut off at the frame's edge.

Motion a reader can decline

Two things stop the loop: the isAnimated prop, registered true, and the document's prefers-reduced-motion preference. The component reads the preference through usePrefersReducedMotion and renders the content once, at rest, when either says stop.

A stopped marquee is a different layout rather than a paused one: one copy is drawn in a frame that scrolls along the axis and wraps on the cross axis, so nothing is clipped and nothing is duplicated. That is also why the duplicate is inert while the track moves, a link inside a moving marquee is one tab stop, announced once. The demo's own switch shows the difference between the moving and the resting form.

Props

Every option is registered, so a product's marquee is stated once under components.marquee: axis x, direction forward, speed normal, gap 1.5rem, pauseOnHover false, fadeEdges false and isAnimated true. There is no colour, size or radius to resolve, because a marquee draws no surface of its own, what it draws is the consumer's nodes, in a consumer's frame.

children, itemClassName, className and style are instance-only. What moves and how it is dressed in one place is a statement about that place; how fast and which way is a statement about the product.

Accessibility

  • The content is drawn twice for the seam, and the second copy is aria-hidden, so a reader hears each item once rather than twice in a row.
  • The loop yields to prefers-reduced-motion: reduce and to isAnimated, so a reader who has asked their system for less motion gets a still frame without the page having to know.
  • pauseOnHover is a pointer's gesture. There is no control in the page that stops the loop, so a keyboard or touch reader cannot pause it, and a screen that has to offer that states its own control over isAnimated, the demo in the previous section is the smallest version of it.
  • aria-hidden hides a subtree from assistive technology without removing it from the tab order, so a link or button inside a marquee is a tab stop in both copies. A marquee is for content that is read, not pressed.

Native

@asheeui/native renders a Marquee with the same props, driving an animated scroll view rather than a stylesheet keyframe. It yields to the platform's own motion setting the way the web yields to the media query, pauseOnHover has nothing to pause on because a touch screen has no pointer, and fadeEdges is not painted: a gradient needs a drawing library the package does not depend on, so the option resolves and changes nothing. gap is converted from a CSS length, rem at 16 units, px at one, and falls back to 24 when it names a unit the platform cannot state. See React Native.