Skip to content
Velocity X Docs
← All components

Horizontal News Ticker

Free · MIT

Breaking-news style ticker with a fixed live tag and a CSS-only infinite-scrolling headline marquee.

Category
Carousels & Tickers
Framework
astro
Version
1.0.0
Peer deps
none

Install

bash
velocityx add news-ticker-horizontal

Free and MIT licensed — no account needed. The file lands under src/components/velocity/news-ticker-horizontal/ by default (override with --dest), and it's yours to edit from the moment it's written.

Demo

View the live demo on the showcase site ↗

Source

Pulled live from the registry — this is the exact file velocityx add news-ticker-horizontal writes into your project right now.

components/velocity/news-ticker-horizontal/NewsTickerHorizontal.astro

astro
---
//    VELOCITY COMPONENT · theming
// 
//    Colours here are Tailwind `primary-*` / `accent-*`, which resolve through
//    the Velocity preset. Out of the box they are a neutral indigo — NOT the
//    Aidxn brand — so this drops into your project without dragging our look in.
// 
//    ⚡ NOT USING VELOCITY? Run this once, in your project root:
// 
//          velocityx theme
// 
//    It writes velocity-preset.cjs + velocity-theme.css. Add the preset to your
//    tailwind.config, import the css, then set your whole brand in ONE place:
// 
//          :root {
//            --vx-brand: custom;
//            --vx-primary-600: 220 38 38;   <- your action colour
//            --vx-primary-700: 185 28 28;
//          }
// 
//    ⚠️  Raw RGB channels, space separated — NOT hex. `#DC2626` here silently
//        breaks every /opacity variant in the library (bg-primary-600/20 etc).
// 
//    Prefer not to install anything? Find-and-replace `primary-` with your own
//    scale name. Nothing else in this file depends on Velocity.
/**
 * NewsTickerHorizontal — classic "breaking news" style horizontal
 * ticker: a fixed "LIVE"/category tag on the left, infinite-scrolling
 * headline list on the right. CSS-only marquee (same engine as
 * LogoTicker/TextMarquee), pauses on hover/focus, static wrapped list
 * under prefers-reduced-motion.
 *
 * Usage:
 *   <NewsTickerHorizontal
 *     tag="Updates"
 *     items={[{ text: "New feature shipped", href: "/changelog" }]}
 *   />
 */
export interface NewsItem {
  text: string;
  href?: string;
}

export interface Props {
  tag?: string;
  items: NewsItem[];
  speed?: number;
}

const { tag = 'Live', items, speed = 26 } = Astro.props;
const uid = `nt-${Math.random().toString(36).slice(2, 9)}`;
---

<div class="flex items-stretch rounded-xl border border-secondary-200 dark:border-secondary-800 overflow-hidden bg-white dark:bg-secondary-900" role="region" aria-label="News ticker">
  <span class="shrink-0 flex items-center gap-1.5 bg-primary-600 dark:bg-primary-500 px-4 text-xs font-bold uppercase tracking-wide text-white">
    <span class="relative flex h-1.5 w-1.5">
      <span class="motion-safe:animate-ping absolute inline-flex h-full w-full rounded-full bg-white opacity-75"></span>
      <span class="relative inline-flex h-1.5 w-1.5 rounded-full bg-white"></span>
    </span>
    {tag}
  </span>

  <div class="relative flex-1 overflow-hidden [mask-image:linear-gradient(to_right,black_92%,transparent)]">
    <div
      class={`${uid} flex w-max items-center gap-10 py-3 px-4 motion-reduce:animate-none`}
      style={`--nt-duration:${speed}s;`}
    >
      {[0, 1].map(() => (
        <div class="flex items-center gap-10 shrink-0">
          {items.map((item) => (
            item.href ? (
              <a href={item.href} class="text-sm text-secondary-700 dark:text-secondary-300 hover:text-primary-600 dark:hover:text-primary-300 whitespace-nowrap">{item.text}</a>
            ) : (
              <span class="text-sm text-secondary-700 dark:text-secondary-300 whitespace-nowrap">{item.text}</span>
            )
          ))}
        </div>
      ))}
    </div>
  </div>
</div>

<style is:global>
  @keyframes v-ticker-scroll {
    from { transform: translateX(0); }
    to { transform: translateX(-50%); }
  }
</style>

<style set:html={`
  .${uid} {
    animation: v-ticker-scroll var(--nt-duration) linear infinite;
  }
  .${uid}:hover, .${uid}:focus-within {
    animation-play-state: paused;
  }
  @media (prefers-reduced-motion: reduce) {
    .${uid} {
      animation: none !important;
      flex-wrap: wrap;
      width: 100%;
    }
    .${uid} > div:nth-child(2) {
      display: none;
    }
  }
`}></style>

Theming

Uses the shared primary-* / accent-* scale — see Theming if colours look unstyled after install.