Marquee
<quiet-marquee>
Scrolls its content in a smooth, continuous loop like a news ticker. Use a marquee for logo walls, testimonials, headlines, and other streams of content that benefit from ambient motion.
Accessibility is built in. The marquee shows a pause button, pauses while the user hovers over it or focuses anything inside it, honors reduced motion preferences, and announces its content to screen readers exactly once. Slotted content is cloned automatically to fill the loop, so you only need to provide one copy, and the edges fade so items glide in and out of view.
<quiet-marquee id="marquee__overview" speed="40" label="Cat market update">
<span class="stock up">
<span class="symbol">MEOW</span>
<quiet-number number="42.87" type="currency" currency="USD"></quiet-number>
<span class="change">
<quiet-icon name="trending-up"></quiet-icon>
<quiet-number number="0.024" type="percent" maximum-fraction-digits="1"></quiet-number>
</span>
</span>
<span class="stock down">
<span class="symbol">PURR</span>
<quiet-number number="18.32" type="currency" currency="USD"></quiet-number>
<span class="change">
<quiet-icon name="trending-down"></quiet-icon>
<quiet-number number="0.008" type="percent" maximum-fraction-digits="1"></quiet-number>
</span>
</span>
<span class="stock up">
<span class="symbol">NAPS</span>
<quiet-number number="106.5" type="currency" currency="USD"></quiet-number>
<span class="change">
<quiet-icon name="trending-up"></quiet-icon>
<quiet-number number="0.051" type="percent" maximum-fraction-digits="1"></quiet-number>
</span>
</span>
<span class="stock down">
<span class="symbol">HISS</span>
<quiet-number number="3.09" type="currency" currency="USD"></quiet-number>
<span class="change">
<quiet-icon name="trending-down"></quiet-icon>
<quiet-number number="0.012" type="percent" maximum-fraction-digits="1"></quiet-number>
</span>
</span>
<span class="stock up">
<span class="symbol">TUNA</span>
<quiet-number number="67.4" type="currency" currency="USD"></quiet-number>
<span class="change">
<quiet-icon name="trending-up"></quiet-icon>
<quiet-number number="0.033" type="percent" maximum-fraction-digits="1"></quiet-number>
</span>
</span>
</quiet-marquee>
<style>
#marquee__overview {
font-variant-numeric: tabular-nums;
.stock {
display: flex;
align-items: center;
gap: 0.5rem;
.symbol {
font-weight: var(--quiet-font-weight-semibold);
}
.change {
display: flex;
align-items: center;
gap: 0.25rem;
}
&.up .change {
color: var(--quiet-constructive-text-colorful);
}
&.down .change {
color: var(--quiet-destructive-text-colorful);
}
}
}
</style>
Marquee honors the user's
setting. If you're not seeing animations, this might be why. The content remains visible, and users can
start the marquee themselves with its play button. To override this behavior, which is generally not
recommended, use the ignore-reduced-motion attribute.
Examples Jump to heading
Providing content Jump to heading
Slot in the items you want to scroll as direct children of the marquee. Any content works, and you can mix different types of elements freely. The marquee measures them and clones them as many times as needed to keep the loop seamless, even as the container resizes. Clones are hidden from assistive devices and can't receive focus, so screen readers hear each item only once.
You can style items with regular CSS since they live in your document, not the shadow root. Use descendant
selectors such as #my-marquee img, because the clones are nested one level deeper than the
originals and direct child selectors won't match them.
<quiet-marquee id="marquee__content" label="Cats on parade">
<img src="https://images.unsplash.com/photo-1514888286974-6c03e2ca1dba?q=80&w=400&auto=format&fit=crop" alt="A black and white cat resting on a ledge">
<img src="https://images.unsplash.com/photo-1472491235688-bdc81a63246e?q=80&w=400&auto=format&fit=crop" alt="A blue-eyed cat looking up">
<img src="https://images.unsplash.com/photo-1529778873920-4da4926a72c2?q=80&w=400&auto=format&fit=crop" alt="A wide-eyed kitten lying down">
<img src="https://images.unsplash.com/photo-1503844281047-cf42eade5ca5?q=80&w=400&auto=format&fit=crop" alt="A gray kitten peeking from behind a tree">
<img src="https://images.unsplash.com/photo-1498336179775-9836baef8fdf?q=80&w=400&auto=format&fit=crop" alt="A fluffy kitten sprawled on a red pillow">
</quiet-marquee>
<style>
#marquee__content img {
width: 10rem;
height: 7rem;
object-fit: cover;
border-radius: var(--quiet-border-radius-md);
}
</style>
Prefer non-interactive content. Links and buttons work, and focusing them pauses the marquee, but their clones are inert stand-ins that can't be clicked. Avoid form controls entirely, since cloned controls still submit with a containing form.
Setting the speed Jump to heading
Use the speed attribute to set the scrolling speed in pixels per second. The default is 50.
Because speed is a velocity, not a duration, marquees with different amounts of content move at the same
pace.
<div id="marquee__speed">
<quiet-marquee speed="150" label="Zoomies in progress">
<span>🐈</span>
<span>🐈⬛</span>
<span>🐈</span>
<span>🐈⬛</span>
<span>💨</span>
</quiet-marquee>
<quiet-slider label="Speed" min="10" max="400" value="150" step="10" with-tooltip></quiet-slider>
</div>
<script>
const container = document.getElementById('marquee__speed');
const marquee = container.querySelector('quiet-marquee');
const slider = container.querySelector('quiet-slider');
slider.addEventListener('quiet-input', () => {
marquee.speed = slider.value;
});
</script>
<style>
#marquee__speed {
quiet-marquee {
font-size: 2rem;
margin-block-end: 1.5rem;
}
quiet-slider {
max-width: 260px;
}
}
</style>
Reversing the direction Jump to heading
Add the reverse attribute to scroll the other way. Horizontal marquees are RTL-aware, so
content always enters from the natural reading end unless you reverse it.
<div id="marquee__reverse">
<quiet-marquee reverse label="Cats walking backwards">
<span>Backwards day at the cat café</span>
<quiet-icon name="coffee"></quiet-icon>
<span>All zoomies are now reverse zoomies</span>
<quiet-icon name="run"></quiet-icon>
<span>Tail-first entrances only</span>
<quiet-icon name="arrow-big-right"></quiet-icon>
</quiet-marquee>
<quiet-switch label="Reverse" checked></quiet-switch>
</div>
<script>
const container = document.getElementById('marquee__reverse');
const marquee = container.querySelector('quiet-marquee');
const reverseSwitch = container.querySelector('quiet-switch');
reverseSwitch.addEventListener('quiet-change', () => {
marquee.reverse = reverseSwitch.checked;
});
</script>
<style>
#marquee__reverse {
quiet-marquee {
margin-block-end: 1.5rem;
}
quiet-icon {
color: var(--quiet-primary-text-colorful);
}
}
</style>
Vertical marquees Jump to heading
Set orientation to vertical to scroll up instead of across. Give the marquee a
height, otherwise there's nothing to clip the content to. Combine with reverse to scroll down.
<div id="marquee__vertical">
<quiet-marquee orientation="vertical" reverse speed="30" label="Adoption updates">
<div>🎉 Mochi found a forever home</div>
<div>🏠 Biscuit is settling in nicely</div>
<div>😻 Clementine adopted by a lovely family</div>
<div>🧶 Ziggy now rules a house with three yarn baskets</div>
<div>🛋️ Pretzel claimed the good couch on day one</div>
</quiet-marquee>
<quiet-switch label="Reverse" checked></quiet-switch>
</div>
<script>
const container = document.getElementById('marquee__vertical');
const marquee = container.querySelector('quiet-marquee');
const reverseSwitch = container.querySelector('quiet-switch');
reverseSwitch.addEventListener('quiet-change', () => {
marquee.reverse = reverseSwitch.checked;
});
</script>
<style>
#marquee__vertical {
quiet-marquee {
height: 8rem;
max-width: 400px;
margin-inline: auto;
margin-block-end: 1.5rem;
&::part(content) {
justify-content: center;
}
}
}
</style>
Adjusting the gap Jump to heading
Use the --gap custom property to control the space between items, including the space between
the last item and the start of the next loop.
<quiet-marquee id="marquee__gap" style="--gap: 4rem;" label="Well-spaced cats">
<span>😸</span>
<span>😹</span>
<span>😻</span>
<span>😼</span>
<span>😽</span>
</quiet-marquee>
<style>
#marquee__gap {
font-size: 2rem;
}
</style>
Changing the fade Jump to heading
Content fades in and out at the marquee's edges by default. The fade is a mask, so it works over any
background, including images and gradients. Use the --fade-size custom property to adjust its
size.
<quiet-marquee style="--fade-size: 6rem;" label="Slow fading cat facts">
<span>Cats sleep for about 70% of their lives</span>
<quiet-icon name="moon"></quiet-icon>
<span>A group of kittens is called a kindle</span>
<quiet-icon name="zzz"></quiet-icon>
<span>Cats can rotate their ears 180 degrees</span>
<quiet-icon name="bed"></quiet-icon>
</quiet-marquee>
To remove the fade entirely, add the without-fade attribute.
<quiet-marquee without-fade label="Sharp-edged cat facts">
<span>A cat's nose print is as unique as a fingerprint</span>
<quiet-icon name="paw"></quiet-icon>
<span>Cats can jump up to six times their height</span>
<quiet-icon name="fish"></quiet-icon>
<span>Most cats have no eyelashes</span>
<quiet-icon name="cat"></quiet-icon>
</quiet-marquee>
Pausing and playing Jump to heading
The marquee pauses while the user hovers over it and while anything inside it has focus. Add
without-pause-on-hover to keep it moving under the pointer.
To pause it programmatically, set the paused attribute or property. The
paused custom state applies whenever the marquee isn't moving for any reason, so you can style
it with :state(paused). The example below uses it to draw a dashed outline while the marquee is
paused.
<div id="marquee__pausing">
<quiet-marquee label="Pausable cat parade" without-pause-on-hover>
<span>🐱</span>
<span>🐈</span>
<span>🐱</span>
<span>🐈⬛</span>
<span>🐱</span>
</quiet-marquee>
<quiet-button toggle="off">Pause</quiet-button>
</div>
<script>
const container = document.getElementById('marquee__pausing');
const marquee = container.querySelector('quiet-marquee');
const button = container.querySelector('quiet-button');
button.addEventListener('click', () => {
marquee.paused = button.toggle === 'on';
});
</script>
<style>
#marquee__pausing {
quiet-marquee {
font-size: 2rem;
margin-block-end: 1rem;
&:state(paused) {
outline: 2px dashed var(--quiet-neutral-stroke-soft);
outline-offset: 0.25rem;
}
}
}
</style>
A play/pause button is included in the marquee's corner so all users can stop the motion. On devices that
support hovering, it stays hidden until you hover over the marquee or focus the button with the keyboard. On
touch devices, it's always visible. You can restyle it with the pause-button part, or remove it
with the without-controls attribute. This is only recommended for presentational content, as
removing these options hinders accessibility for some users.
<quiet-marquee
label="A well-controlled cat parade"
without-pause-on-hover
without-controls
>
<span>Press the button to give the cats a break</span>
<quiet-icon name="paw"></quiet-icon>
<span>They will wait exactly where you left them</span>
<quiet-icon name="hourglass"></quiet-icon>
<span>Unlike real cats</span>
<quiet-icon name="heart"></quiet-icon>
</quiet-marquee>
Auto-playing content that lasts more than five seconds must have a visible way to pause it, per
WCAG
. If you use without-controls, provide your own pause control.
Looping a set number of times Jump to heading
Use the loop attribute to stop after a specific number of cycles. One cycle is one full pass of
the content. The marquee dispatches quiet-animation-complete when it finishes, and you can call
restart() to play it again. The default, 0, loops forever.
Marquees never animate while scrolled out of view, so the laps don't start counting until the marquee enters the viewport and won't tick down while it's offscreen.
<div id="marquee__loop">
<quiet-marquee loop="3" speed="120" label="Three lap sprint">
<span>Three laps around the living room, then a nap</span>
<span>🏁</span>
</quiet-marquee>
<quiet-button>Restart</quiet-button>
</div>
<script>
const container = document.getElementById('marquee__loop');
const marquee = container.querySelector('quiet-marquee');
const button = container.querySelector('quiet-button');
marquee.addEventListener('quiet-animation-complete', () => {
console.log('The marquee finished looping');
});
button.addEventListener('click', () => {
marquee.restart();
});
</script>
<style>
#marquee__loop {
quiet-marquee {
margin-block-end: 1rem;
}
}
</style>
More examples Jump to heading
Marquees show up in more places than you might think. The classic "trusted by" logo wall renders its logos desaturated until you hover, which pairs nicely with the marquee pausing while you point at it.
<quiet-marquee id="marquee__logos" speed="40" label="Tools we love">
<quiet-icon name="brand-github"></quiet-icon>
<quiet-icon name="brand-figma"></quiet-icon>
<quiet-icon name="brand-firefox"></quiet-icon>
<quiet-icon name="brand-docker"></quiet-icon>
<quiet-icon name="brand-chrome"></quiet-icon>
<quiet-icon name="brand-gitlab"></quiet-icon>
<quiet-icon name="brand-vscode"></quiet-icon>
<quiet-icon name="brand-slack"></quiet-icon>
</quiet-marquee>
<style>
#marquee__logos {
--gap: 3rem;
quiet-icon {
opacity: 0.6;
filter: grayscale(1);
font-size: 3rem;
transition:
200ms filter ease,
200ms opacity ease;
&:hover {
opacity: 1;
filter: none;
}
}
}
</style>
You can also style the marquee as a full-width promo bar. Links work inside it, and tabbing to one pauses the marquee so keyboard users can activate it. On a colored background, style links and their focus rings so they stay visible.
<quiet-marquee id="marquee__announcement" speed="20" label="Announcements">
<span>Complimentary shipping on orders over $50</span>
<span class="separator">·</span>
<span>The autumn collection has arrived <a href="#">Discover</a></span>
<span class="separator">·</span>
<span>Now open: our atelier in Whiskerville</span>
<span class="separator">·</span>
</quiet-marquee>
<style>
#marquee__announcement {
border-radius: var(--quiet-border-radius-md);
background-color: light-dark(#1a1a1a, #f5f2ec);
color: light-dark(#f5f2ec, #1a1a1a);
font-size: 0.8125rem;
letter-spacing: 0.08em;
text-transform: uppercase;
.separator {
opacity: 0.5;
}
a {
color: inherit;
text-decoration: underline;
text-underline-offset: 0.25em;
&:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
}
}
</style>
Keyboard support Jump to heading
Tab to the marquee's pause button, then use the keys below.
| Key | Action |
|---|---|
| Space / Enter | Pauses or resumes the marquee. |
API Jump to heading
Importing Jump to heading
The autoloader is the recommended way to import components but, if you prefer to do it manually, the following code snippets will be helpful.
To manually import <quiet-marquee> from the CDN, use the following code.
import 'https://cdn.quietui.org/v6.0.0/components/marquee/marquee.js';
To manually import <quiet-marquee> from a self-hosted distribution, use the following
code. Remember to replace /path/to/quiet with the appropriate local path.
import '/path/to/quiet/components/marquee/marquee.js';
Slots Jump to heading
Marquee supports the following slots. Learn more about using slots
| Name | Description |
|---|---|
| (default) | The content to scroll. The marquee clones it as needed to fill the loop, hiding the clones from assistive devices so the content is only announced once. |
Properties Jump to heading
Marquee has the following properties that can be set with corresponding attributes. In many cases, the attribute's name is the same as the property's name. If an attribute is different, it will be displayed after the property. Learn more about attributes and properties
| Property | Description | Reflects | Type | Default |
|---|---|---|---|---|
label
|
A custom label for the marquee. This won't be displayed on the screen, but it will be announced by assistive devices. |
|
string
|
''
|
orientation
|
The direction content scrolls. |
|
'horizontal' |
'vertical'
|
'horizontal'
|
reverse
|
Reverses the scroll direction. |
|
boolean
|
false
|
speed
|
The scrolling speed in pixels per second. |
|
number
|
50
|
paused
|
Pauses the marquee. |
|
boolean
|
false
|
loop
|
The number of times the marquee loops before stopping. Use 0, the default, to loop
forever.
|
|
number
|
0
|
withoutPauseOnHover
without-pause-on-hover
|
By default, the marquee pauses while the user hovers over it. Use this attribute to disable that behavior. |
|
boolean
|
false
|
withoutFade
without-fade
|
By default, content fades in and out at the marquee's edges. Use this attribute to remove the fade. |
|
boolean
|
false
|
withoutControls
without-controls
|
Removes the built-in pause button. If you do this, make sure to provide another visible way to pause the marquee so it remains accessible. |
|
boolean
|
false
|
ignoreReducedMotion
ignore-reduced-motion
|
By default, no animation will occur when the user indicates a preference for reduced motion. Use this attribute to override this behavior when necessary. |
|
boolean
|
false
|
Methods Jump to heading
Marquee supports the following methods. You can obtain a reference to the element and call them like functions in JavaScript. Learn more about methods
| Name | Description | Arguments |
|---|---|---|
restart() |
Restarts the marquee from the beginning, e.g. after a finite number of loops has finished. |
Events Jump to heading
Marquee dispatches the following custom events. You can listen to them the same way was native events. Learn more about custom events
| Name | Description |
|---|---|
quiet-animation-complete |
Emitted when a finite number of loops has finished playing. Only emitted when the
loop attribute is set to a positive number.
|
CSS custom properties Jump to heading
Marquee supports the following CSS custom properties. You can style them like any other CSS property. Learn more about CSS custom properties
| Name | Description | Default |
|---|---|---|
--gap |
The space between items and between each repetition of the content. |
1rem
|
--fade-size |
The size of the fade mask at the marquee's edges. Use the without-fade attribute to
remove the fade entirely.
|
2rem
|
CSS parts Jump to heading
Marquee exposes internal elements that can be styled with CSS using the selectors shown below. Learn more about CSS parts
| Name | Description | CSS selector |
|---|---|---|
content |
The container that clips the scrolling content. |
::part(content)
|
track |
The moving container that holds the content and its clones. |
::part(track)
|
pause-button |
The pause/play toggle button, a <button> element.
|
::part(pause-button)
|
Custom States Jump to heading
Marquee has the following custom states. You can target them with CSS using the selectors shown below. Learn more about custom states
| Name | Description | CSS selector |
|---|---|---|
paused |
Applied when the marquee isn't scrolling for any reason, including the
paused attribute, hovering, focus, reduced motion, and being scrolled out of view.
|
:state(paused)
|
Dependencies Jump to heading
Marquee automatically imports the following elements. Sub-dependencies are also included in this list.