Skip to main content
The Ticker component creates an animated horizontal scrolling display of word tags. It supports multiple rows, shuffle randomization, different visual variants, and smooth animations with hover interactions.

Import

import Ticker from "../components/Ticker.astro";

Usage

Basic Usage

src/pages/[lang]/index.astro
---
import Ticker from "../../components/Ticker.astro";
import { getCollection } from "astro:content";

const words = await getCollection("words-es");
const wordList = words.map((w) => w.data.word);
const shuffled = wordList.sort(() => Math.random() - 0.5).slice(0, 30);
---

<Ticker words={shuffled} lang="es" />

Vibrant Variant

<Ticker 
  words={featuredWords} 
  lang="en" 
  variant="vibrant" 
  rows={1} 
/>

Multiple Rows

<Ticker 
  words={allWords} 
  lang="es" 
  variant="default" 
  rows={3} 
/>

Props

words
string[]
default:"[]"
Array of words to display in the ticker. Words are automatically shuffled and distributed across rows.Example: ["Chapín", "Catracho", "Guanaco", "Tico"]
lang
string
default:"es"
The current language code. Used for generating correct URLs to word detail pages.Values: "es" or "en"
variant
'default' | 'vibrant'
default:"default"
Visual style variant:
  • "default" - Clean, minimal styling with subtle animations
  • "vibrant" - Enhanced gradients, glows, and animated effects
rows
number
default:"3"
Number of horizontal rows to display. Words are automatically distributed evenly across rows.

Features

Word Shuffling

Words are shuffled on both server and client side:
function shuffle<T>(arr: T[]): T[] {
  const a = [...arr];
  for (let i = a.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [a[i], a[j]] = [a[j], a[i]];
  }
  return a;
}

const shuffled = shuffle(words);

Row Distribution

Words are evenly distributed across the specified number of rows:
const perRow = Math.ceil(shuffled.length / numRows);
const baseRows = Array.from({ length: numRows }, (_, i) =>
  shuffled.slice(i * perRow, (i + 1) * perRow)
);

Alternating Animation Directions

Rows alternate between left-to-right and right-to-left scrolling:
<div
  class={`ticker-content ${
    idx % 2 === 1 
      ? "animate-[scroll_28s_linear_infinite_reverse]" 
      : "animate-[scroll_22s_linear_infinite]"
  }`}
>

Pause on Hover

Animations pause when hovering over a row:
.ticker-row:hover .ticker-content {
  animation-play-state: paused;
}

Edge Fade Gradients

Smooth fade effects on the left and right edges:
<div class="ticker-fade-left" />
<div class="ticker-fade-right" />
.ticker-fade-left {
  left: 0;
  background: linear-gradient(to right, var(--background) 0%, transparent 100%);
}

.ticker-fade-right {
  right: 0;
  background: linear-gradient(to left, var(--background) 0%, transparent 100%);
}

Variants

Default Variant

Clean, minimal styling with subtle hover effects:
  • Simple border and background
  • Minimal shadows
  • Clean text rendering
  • Subtle hover transforms

Vibrant Variant

Enhanced visual effects with gradients and glows:
.ticker--vibrant .ticker-row {
  border: 1px solid rgba(96, 165, 250, 0.18);
  background:
    radial-gradient(circle at 30% 20%, rgba(96, 165, 250, 0.2), transparent 60%),
    linear-gradient(135deg, rgba(30, 41, 59, 0.85), rgba(15, 23, 42, 0.88));
  border-radius: 20px;
  box-shadow:
    0 8px 28px -10px rgba(96, 165, 250, 0.25),
    0 2px 6px rgba(0, 0, 0, 0.5);
}

Vibrant Features

  • Gradient backgrounds with radial overlays
  • Animated sheen effect rotating across the row
  • Glowing dot indicators with pulse animation
  • Text with gradient fills and hue-shift animation
  • Enhanced hover effects with stronger transforms

Word Tags

Each word is rendered as a clickable tag:
<a
  class="ticker-word"
  href={`${prefix}/palabras/${slugify(w)}/`}
  tabindex="-1"
  aria-hidden="true"
>
  <span class="ticker-dot h-2 w-2 animate-pulse rounded-xl" />
  <span class="ticker-text">{w}</span>
</a>

Tag Features

  • Animated pulsing dot indicator
  • Hover scale and translation effects
  • Theme-aware styling
  • Automatic slug generation for URLs

Client-Side Rehydration

The component includes a script that runs on page load to:
  1. Reshuffle words for variation
  2. Redistribute across rows
  3. Clone elements for infinite scroll effect
  4. Set dynamic animation durations
rows.forEach((r, rowIdx) => {
  // ... shuffle and populate ...
  
  // Clone for infinite scroll
  const original = [...r.children];
  original.forEach((n) => r.appendChild(n.cloneNode(true)));
  original.forEach((n) => r.appendChild(n.cloneNode(true)));
  
  // Set dynamic speed
  const baseDuration = 22;
  const variance = 6;
  const speed = baseDuration + rowIdx * variance;
  r.style.animationDuration = speed + "s";
});

Animations

Scroll Animation

@keyframes scroll {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(-33.333%);
  }
}

Pulse Glow (Vibrant)

@keyframes pulseGlow {
  0%, 100% {
    transform: scale(1);
    box-shadow:
      0 0 10px 2px rgba(96, 165, 250, 0.75),
      0 0 24px -4px rgba(14, 165, 233, 0.4);
  }
  50% {
    transform: scale(1.25);
    box-shadow:
      0 0 16px 4px rgba(96, 165, 250, 0.95),
      0 0 36px -4px rgba(14, 165, 233, 0.6);
  }
}

Hue Shift (Vibrant)

@keyframes hueShift {
  0% {
    filter: hue-rotate(0deg);
  }
  100% {
    filter: hue-rotate(360deg);
  }
}

Accessibility

Reduced Motion

Animations are disabled for users who prefer reduced motion:
@media (prefers-reduced-motion: reduce) {
  .ticker-content {
    animation: none !important;
  }
  
  .ticker-dot {
    animation: none !important;
  }
}

ARIA Attributes

Ticker content is marked as decorative:
<div class="ticker-content" aria-hidden="true">
  <a tabindex="-1" aria-hidden="true">...</a>
</div>

Responsive Design

Mobile Adjustments

@media (max-width: 768px) {
  .ticker-container {
    gap: 0.625rem;
    margin-top: 1.25rem;
    margin-bottom: 1.25rem;
  }
  
  .ticker-word {
    padding: 0.5rem 0.875rem;
    font-size: 0.8125rem;
  }
}

@media (max-width: 480px) {
  .ticker-word {
    padding: 0.4rem 0.75rem;
    font-size: 0.75rem;
  }
  
  .ticker-dot {
    width: 0.375rem;
    height: 0.375rem;
  }
}

Theme Support

Both variants support light and dark themes:
html[data-theme="light"] .ticker--vibrant .ticker-row {
  background:
    radial-gradient(circle at 30% 20%, rgba(96, 165, 250, 0.4), rgba(248, 250, 252, 0.92) 60%),
    linear-gradient(135deg, rgba(255, 255, 255, 0.95), rgba(241, 245, 249, 0.9));
  border: 1px solid rgba(73, 151, 208, 0.35);
}

Performance Optimization

  • Uses will-change: transform for smooth animations
  • GPU-accelerated transforms
  • Efficient cloning strategy for infinite scroll
  • Conditional animation based on user preferences

Layout Examples

Hero Section

<section class="mx-auto max-w-[1100px] px-6">
  <h1>Discover Guatemalan Slang</h1>
  <Ticker words={featured} lang="es" variant="vibrant" rows={1} />
</section>

Multiple Rows

<section class="mx-auto max-w-[1100px] px-6">
  <Ticker words={allWords} lang="en" variant="default" rows={3} />
</section>
  • slugify() - Convert words to URL-safe slugs

Build docs developers (and LLMs) love