Marquee
loomui/marqueeFreeComponent
A seamless scrolling row or column, in either direction, that pauses on hover.
Live preview
live · rendered by the registry
Rendered by loomui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://loomui.design/r/marquee.jsonSource
1"use client"23import * as React from "react"45import { cn } from "@/lib/utils"6import { useInViewport } from "@/registry/lib/use-in-viewport"78export interface MarqueeProps extends React.ComponentProps<"div"> {9 /** Travel bottom to top instead of right to left. */10 vertical?: boolean11 /** Flip the direction of travel. */12 reverse?: boolean13 /** Stop while the pointer is over the track. */14 pauseOnHover?: boolean15 /** Copies of the children. Two is the minimum for a seamless loop. */16 repeat?: number17 /** Seconds for one full pass of a single copy. */18 duration?: number19 /** Gap between items, as a CSS length. */20 gap?: string21 /** Render the row static. */22 paused?: boolean23 /** Fade both edges out. Pass a CSS length to size the fade. */24 fade?: boolean | string25}2627/**28 * Alpha sampled off a smoothstep curve. A two-stop gradient ramps linearly, and29 * the eye reads where that ramp meets full opacity as a hard line. Sampling the30 * curve rounds both ends off, so the fade runs out instead of stopping.31 */32const FADE_STOPS = [0, 0.0608, 0.216, 0.5, 0.784, 0.939, 1]3334function fadeMask(axis: "to right" | "to bottom", size: string) {35 const last = FADE_STOPS.length - 136 const ratio = (index: number) => (index / last).toFixed(4)3738 const leading = FADE_STOPS.map(39 (alpha, index) => `rgb(0 0 0 / ${alpha}) calc(${size} * ${ratio(index)})`40 )41 const trailing = FADE_STOPS.map(42 (_, index) =>43 `rgb(0 0 0 / ${FADE_STOPS[last - index]}) calc(100% - ${size} * ${ratio(44 last - index45 )})`46 )4748 return `linear-gradient(${axis}, ${[...leading, ...trailing].join(", ")})`49}5051export function Marquee({52 children,53 className,54 vertical = false,55 reverse = false,56 pauseOnHover = false,57 repeat = 2,58 duration = 40,59 gap = "1rem",60 paused = false,61 fade = false,62 style,63 ...props64}: MarqueeProps) {65 const root = React.useRef<HTMLDivElement>(null)66 // A marquee nobody can see is 40 seconds of work per loop for nothing.67 const onScreen = useInViewport(root)68 const mask = fade69 ? fadeMask(70 vertical ? "to bottom" : "to right",71 fade === true ? "12%" : fade72 )73 : undefined7475 return (76 <div77 ref={root}78 data-slot="marquee"79 className={cn(80 "group flex overflow-hidden",81 vertical ? "flex-col" : "flex-row",82 className83 )}84 style={85 {86 gap,87 // The keyframes travel exactly one copy plus one gap, which is what88// … truncated
What it pulls in
Other registry items
Files it writes
More from loomui
All 91 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Aurora Backdropaurora-backdrop | loomui | Components | Free | no deps | |
| Bento Gridbento-grid | loomui | Components | Free | no deps | |
| Card Stackcard-stack | loomui | Components | Free | no deps | |
| Compare Slidercompare-slider | loomui | Components | Free | no deps | |
| Confetti Buttonconfetti-button | loomui | Components | Free | no deps | |
| Count Upcount-up | loomui | Components | Free | no deps | |
| Credit Cardcredit-card | loomui | Components | Free | no deps | |
| Drawerdrawer | loomui | Components | Free | 1 dep |
