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-buttonUsage
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