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
Usage
Installation
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
| Prop | Type | Default | Description |
|---|---|---|---|
maxStretch | number | 280 | Peak height in pixels that the aurora can stretch from the floor. |
colors | string[] | DEFAULT_COLORS | Array of CSS color hex/rgb strings forming the vertical gradient strip from top to bottom. |
columns | number | 9 | Number of vertical gradient columns across the aurora width. |
stiffness | number | 380 | Spring stiffness determining snap-back acceleration. |
damping | number | 32 | Spring damping factor controlling wobble oscillation decay. |
blur | number | 16 | Gaussian blur radius in pixels applied to the canvas. |
glow | number | 0 | White floor bloom opacity between 0 and 1. |
flip | boolean | false | Inverts column pyramid so edges stand tallest and the center dips. |
rotate | 0 | 180 | 0 | Set to 180 to invert the aurora so columns hang from the lifting page edge. |
windowScroll | boolean | false | Binds overscroll to the window and renders a fixed bottom viewport portal. |
scrollRef | RefObject<HTMLElement | null> | undefined | Ref to an external scroll container when painting overlay-only. |
contentSelector | string | '[data-stretchy-page]' | CSS selector for the page element that lifts during stretch. |
demoId | string | undefined | Unique identifier to scope programmatic play triggers. |
className | string | undefined | Additional Tailwind CSS classes applied to the root container. |