Stretchy Footer

A Dia-style rubber overscroll component with a multi-column rainbow aurora that stretches up past the viewport floor and springs back with physics-based wobble.

The StretchyFooter component implements a rubber-band overscroll experience inspired by Dia. When the user scrolls past the bottom boundary, a stepped pyramid of gradient columns stretches up from the floor while lifting the page content, snapping back with customizable spring damping and wobbling harmonics.

Preview

Rubber-band overscroll

Overscroll past the bottom boundary to reveal the aurora. Springs back with customizable harmonic wobble.

Scroll down↓

Pull past bottom

Usage

Installation

$ pnpm dlx shadcn add https://klarden.vercel.app/r/stretchy-footer.json

Basic Scroller Example

Wrap your content directly inside StretchyFooter. The component provides an internal scroll container with overscroll physics:

import { StretchyFooter } from "@/components/ui/stretchy-footer";

export default function Page() {
  return (
    <StretchyFooter className="h-[500px]">
      <div className="p-8">
        <h2 className="text-xl font-bold">Scroll down</h2>
        <p className="mt-2 text-muted-foreground">
          Continue scrolling past the end to reveal the stretchy aurora.
        </p>
      </div>
    </StretchyFooter>
  );
}

Full-Page Window Scroll

To bind overscroll to the entire browser window instead of a nested container, set windowScroll={true} and designate the lifted element using contentSelector:

import { StretchyFooter } from "@/components/ui/stretchy-footer";

export default function RootPage() {
  return (
    <>
      <main data-stretchy-page className="min-h-screen">
        <section className="py-24">
          <h1>Full Page Content</h1>
        </section>
      </main>

      {/* Renders a fixed portal on document.body */}
      <StretchyFooter
        windowScroll
        contentSelector="[data-stretchy-page]"
        maxStretch={320}
      />
    </>
  );
}

External Scroll Container

If you have a custom scroller (like a modal or drawer), pass its ref to scrollRef:

import { useRef } from "react";
import { StretchyFooter } from "@/components/ui/stretchy-footer";

export function CustomModal() {
  const scrollerRef = useRef<HTMLDivElement>(null);

  return (
    <div className="relative h-96 overflow-hidden">
      <StretchyFooter
        scrollRef={scrollerRef}
        contentSelector="[data-modal-body]"
      />
      <div ref={scrollerRef} className="h-full overflow-y-auto">
        <div data-modal-body className="p-6">
          Modal content...
        </div>
      </div>
    </div>
  );
}

Programmatic Triggers

You can trigger the stretch and release animation programmatically without user touch or wheel input using playStretchyFooterDemo:

import { playStretchyFooterDemo } from "@/components/ui/stretchy-footer";

async function triggerRubberEffect() {
  await playStretchyFooterDemo({
    target: "my-footer-demo-id",
    amount: 0.9,
    holdMs: 800,
  });
}

Properties

PropTypeDefaultDescription
maxStretchnumber280Peak height in pixels that the aurora can stretch from the floor.
colorsstring[]DEFAULT_COLORSArray of CSS color hex/rgb strings forming the vertical gradient strip from top to bottom.
columnsnumber9Number of vertical gradient columns across the aurora width.
stiffnessnumber380Spring stiffness determining snap-back acceleration.
dampingnumber32Spring damping factor controlling wobble oscillation decay.
blurnumber16Gaussian blur radius in pixels applied to the canvas.
glownumber0White floor bloom opacity between 0 and 1.
flipbooleanfalseInverts column pyramid so edges stand tallest and the center dips.
rotate0 | 1800Set to 180 to invert the aurora so columns hang from the lifting page edge.
windowScrollbooleanfalseBinds overscroll to the window and renders a fixed bottom viewport portal.
scrollRefRefObject<HTMLElement | null>undefinedRef to an external scroll container when painting overlay-only.
contentSelectorstring'[data-stretchy-page]'CSS selector for the page element that lifts during stretch.
demoIdstringundefinedUnique identifier to scope programmatic play triggers.
classNamestringundefinedAdditional Tailwind CSS classes applied to the root container.