bottega

primitives

Marquee Fill Button

A bold, pill-shaped button whose uppercase label fades away on hover as a marquee-scrolling duplicate slides through. The infinite-scroll illusion is achieved with CSS text-shadow copies at ±5em offsets — no extra DOM nodes. The scrolling span is aria-hidden so assistive technology reads only the static label; the native button is keyboard operable with a focus-visible ring. Continuous marquee animation stops under prefers-reduced-motion, keeping the static label visible.

Ported from doniaskima (MIT)


Install

1. Register the namespace (once per project):

json
// components.json — register the @bottega namespace once
{
  "registries": {
    "@bottega": { "url": "https://bottega.ariacode.ca/r/{name}.json" }
  }
}

2. Add the component:

bash
npx shadcn add @bottega/marquee-fill-button

Usage

Usagetsx
<MarqueeFillButton />

Props

Standard props — see source.


Source

marquee-fill-button.tsxtsx
"use client";

import type React from "react";
import styles from "./marquee-fill-button.module.css";

export type MarqueeFillButtonProps = React.ComponentProps<"button">;

/**
 * A pill-shaped button whose label fades out on hover as a marquee-scrolling
 * duplicate takes its place. The text-shadow trick clones the label at ±5em
 * to create a seamless infinite-scroll illusion without extra DOM nodes.
 * The scrolling copy is aria-hidden; the native button is keyboard operable
 * with a focus-visible ring.
 */
export function MarqueeFillButton({
  children = "Button",
  className,
  ...props
}: MarqueeFillButtonProps) {
  return (
    <button
      type="button"
      className={[styles.btn, className].filter(Boolean).join(" ")}
      {...props}
    >
      {/* static visible label — fades out on hover */}
      <span className={styles.text}>{children}</span>
      {/* marquee duplicate — hidden from assistive technology */}
      <span aria-hidden="true" className={styles.marquee}>
        {children}
      </span>
    </button>
  );
}

Dependencies

  • @bottega/tokens