# Echo Reel: Footer Wordmark

> A studio's footer whose wordmark turns to whichever link you point at or tab to, and back to the name when you leave.

A shadcn/ui block from the [@keyframeui](https://keyframeui.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/keyframeui/echo-reel-footer-demo).

- Page, with a live preview: https://shadcn.exchange/r/keyframeui/echo-reel-footer-demo
- Screenshot: https://shadcn.exchange/shot/keyframeui/echo-reel-footer-demo/light.png
- Registry item: https://keyframeui.com/r/echo-reel-footer-demo.json
- Categories: text, hero, motion
- Listed: 2026-11-02, source last changed 2026-11-02

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/keyframeui/echo-reel-footer-demo.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `https://keyframeui.com/r/echo-reel.json`

## Notes from the registry

Live preview, examples and props: https://keyframeui.com/components/echo-reel#echo-reel-footer-demo

## Files

### `registry/demos/echo-reel-footer-demo.tsx`

```tsx
"use client";

import * as React from "react";

import { EchoReel } from "@/components/ui/echo-reel";

const BRAND = "Fieldwork";
const LINKS = [
  { label: "Work", href: "#work" },
  { label: "Studio", href: "#studio" },
  { label: "Journal", href: "#journal" },
  { label: "Contact", href: "#contact" },
];
const WORDS = [BRAND, ...LINKS.map((link) => link.label)];

export function EchoReelFooterDemo() {
  const [word, setWord] = React.useState(0);
  const back = React.useRef<number | undefined>(undefined);

  const show = (index: number) => {
    window.clearTimeout(back.current);
    setWord(index);
  };
  // Wait a beat before going back to the name, so moving between links
  // doesn't turn the reel twice.
  const release = () => {
    window.clearTimeout(back.current);
    back.current = window.setTimeout(() => setWord(0), 250);
  };

  React.useEffect(() => () => window.clearTimeout(back.current), []);

  return (
    <footer className="w-full max-w-4xl overflow-hidden rounded-3xl border border-[#0b0b0d]/10 bg-white px-5 pt-8 pb-5 text-[#0b0b0d] sm:px-10 dark:border-white/10 dark:bg-[#07080c] dark:text-[#f4f4f6]">
      <div className="flex flex-wrap items-start justify-between gap-x-10 gap-y-6">
        <div className="max-w-[17rem]">
          <p className="flex items-center gap-2 text-sm font-semibold tracking-tight">
            <span aria-hidden className="size-2.5 rounded-full bg-[#c6ff4f] ring-1 ring-[#0b0b0d]/15 dark:ring-0" />
            Fieldwork Studio
          </p>
          <p className="mt-2 text-sm leading-relaxed text-[#0b0b0d]/60 dark:text-[#f4f4f6]/60">
            Brand, product and type for companies that ship. Lisbon and remote.
          </p>
          <a
            href="#contact"
            className="mt-3 inline-block rounded text-sm font-medium underline decoration-[#0b0b0d]/25 underline-offset-4 hover:decoration-current focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current dark:decoration-[#f4f4f6]/30"
          >
            hello@fieldwork.studio
          </a>
        </div>

        <nav aria-label="Footer" onPointerLeave={release} onBlur={release}>
          <ul className="grid grid-cols-2 gap-x-10 gap-y-1 text-sm font-medium">
            {LINKS.map((link, i) => (
              <li key={link.href}>
                <a
                  href={link.href}
                  onPointerEnter={() => show(i + 1)}
                  onFocus={() => show(i + 1)}
                  className="-mx-1.5 inline-flex rounded-md px-1.5 py-1 text-[#0b0b0d]/70 transition-colors duration-150 hover:text-[#0b0b0d] focus-visible:outline-2 focus-visible:outline-current dark:text-[#f4f4f6]/70 dark:hover:text-[#c6ff4f]"
                >
                  {link.label}
                </a>
              </li>
            ))}
          </ul>
        </nav>
      </div>

      <EchoReel
        words={WORDS}
        value={word}
        onValueChange={(index) => setWord(index)}
        interval={false}
        className="mt-8 text-[#0b0b0d] dark:text-[#c6ff4f]"
      />

      <div className="mt-3 flex flex-wrap items-center justify-between gap-2 border-t border-[#0b0b0d]/10 pt-4 text-xs text-[#0b0b0d]/55 dark:border-white/10 dark:text-[#f4f4f6]/50">
        <span>© 2026 Fieldwork Studio Lda.</span>
        <a href="#top" className="rounded hover:text-current focus-visible:outline-2 focus-visible:outline-current">
          Back to top ↑
        </a>
      </div>
    </footer>
  );
}
```
