Stretch refreshPro
Pull a feed down and a line stretches to tell you when to let go.
Live · keyboard ready
Brightline activity
Updated 14 min ago
- Timelines, inboxes, and activity feeds where new entries arrive at the top.
- Panels that should support pull to refresh by touch, mouse drag, and trackpad scroll.
- Use a plain list for static content that never refreshes.
- Use pagination for paging through older items.
- Use timeline for a static history of events.
Installation
Pro source and install commands unlock with a Pro plan.
example.tsx
import { StretchRefresh } from "@/registry/components/stretch-refresh/stretch-refresh"; export function Inbox() { const [messages, setMessages] = useState(initial); return ( <StretchRefresh title="Inbox" items={messages} getKey={m => m.id} renderItem={m => <MessageRow message={m} />} onRefresh={async () => { const fresh = await fetchNew(); setMessages(prev => [...fresh, ...prev]); return `${fresh.length} new messages`; }} /> );}Fill the height of a sidebar
example.tsx
<aside style={{ display: "flex", flexDirection: "column", height: "100dvh" }}> <StretchRefresh title="Activity" items={events} getKey={e => e.id} renderItem={e => <EventRow event={e} />} onRefresh={reload} className="grow" /></aside>StretchRefresh
A feed that refreshes when pulled down past its top edge. The sheet rubber-bands while rows spread apart, a line in the gap stretches to show when to let go, then runs while the work happens.
PropTypeDefaultDescription
titleRequiredstring–Heading above the feed.subtitlestring–Short status under the heading, such as "Updated 14 min ago". A change rises in.itemsRequiredreadonly T[]–Rows in the feed.getKeyRequired(item: T) => string–Stable key for each row.renderItemRequired(item: T) => ReactNode–Renders one row.onRefreshRequired() => Promise<string | void>–Runs after a pull past the threshold or a press of the refresh button. Update items before it resolves; resolve with a short result such as "3 new updates".thresholdnumber72Pull distance in px, after resistance, that arms a refresh.doneLabelstring"Up to date"Result shown when onRefresh resolves without one.errorLabelstring"Couldn’t refresh"Result shown when onRefresh rejects.refreshLabelstring"Refresh"Accessible name of the refresh button.classNamestring–Extra class on the root.- EnterorSpace
- On the refresh button, runs a refresh without pulling.
- A refresh button gives keyboard and switch users the same path as the pull gesture, with aria-disabled while busy.
- The feed is a labelled, focusable region and the list sets aria-busy while refreshing.
- "Refreshing" and the result are announced in a polite status region.
- The sheet rubber-bands with growing resistance while rows spread apart; scrolling up again at the top also pulls.
- A line in the gap reaches full length exactly at the threshold, runs while refreshing, then gives way to the result as new rows settle in from the top.
- Reduced motion keeps the same states and only fades.
- The root is a flex column with min-height 0, so give it a sized parent; the feed scrolls inside it.
- Touch pulls use native listeners and only take over once the feed is at the top, so normal scrolling is untouched.
- Rows are not virtualized and use layout position animation; keep the feed to what one panel shows plus a page or so.
- Wheel and touchmove listeners are non-passive so the pull can prevent scrolling, but they return early unless pulling.
Notes for AI
Give your coding assistant the Markdown reference instead of screenshots.
- Choose it for timelines, inboxes, and activity feeds where new entries arrive at the top. For static lists use a plain list, and for paging use pagination.
- Update items inside onRefresh before it resolves, and keep the call at least half a second so the running line reads as work.
The full library index for assistants is at /llms.txt.
