Cloud|

@k2b/ui

PullToRefresh

Touch pull, mouse drag, or wheel overscroll at the top of a list that runs one refresh.

PullToRefresh@k2b/ui

With the list at its top, pull down on a touch screen, drag the free space with a mouse, or overscroll with a trackpad. The refresh icon follows the pull and spins while the simulated reload runs.

  1. Ada LovelaceInvoice 1048 approved
  2. Grace HopperCompiler notes for Monday
  3. Katherine JohnsonTrajectory review
  4. Margaret HamiltonOnboard software checklist
  5. Radia PerlmanSpanning tree follow-up
  6. Barbara LiskovSubstitution principle draft

TSX

Copy
<PullToRefresh onRefresh={() => reloadConversations()} label="Refreshing conversations">
<ScrollArea role="region" aria-label="Needs action">
<ConversationRows />
</ScrollArea>
</PullToRefresh>

PullToRefresh wraps one scroll container and turns a downward pull on a list that is already at its top into a single refresh. It owns the gesture and the indicator; the application owns the refresh action.

Three inputs share one model: a touch pull, a mouse or pen drag that starts on non-interactive space, and trackpad or wheel overscroll. A round indicator with the refresh icon slides in from above by the damped pull distance and turns with the progress. Past 64 px of damped travel (the same distance Android's swipe-to-refresh uses) the indicator switches to the accent color; releasing there, or overscrolling that far with a wheel, calls onRefresh once and shows a spinner until the returned promise settles. A shorter pull, an upward move, or a cancelled pointer resets without a call.

Import

tsx
import { PullToRefresh } from "@k2b/ui";

Use PullToRefresh

Give PullToRefresh exactly one scroll container as its child, usually a ScrollArea, and pass the same refresh the view already exposes elsewhere:

tsx
<PullToRefresh class="flex-1" onRefresh={() => reloadConversations()} label="Refreshing conversations">
  <ScrollArea scrollPreserveKey="conversations">
    <ConversationRows />
  </ScrollArea>
</PullToRefresh>

The wrapper is a flex column with min-height: 0 and leaves height and flex growth to the surrounding layout. Only the indicator moves; the wrapped content never shifts and nothing is clipped.

The gesture starts only while every element between the input and the wrapper is scrolled to the top, so a scrolled list keeps scrolling normally. Mouse and pen drags that start on links, buttons, inputs, or editable content are left to those controls; wheel overscroll and touch pulls work anywhere in the list. A trackpad has no release event, so overscroll triggers as soon as the distance is reached and resets after 300 ms without wheel input or when the user scrolls back down.

onRefresh must return a promise. The indicator stays busy and ignores new pulls until it settles, so a slow refresh cannot be triggered twice. Pass disabled while the view is already loading for another reason.

Accessibility

The indicator is a polite live region that announces label (or the shared "Refreshing" message) only while the refresh runs; it is otherwise silent and takes no pointer events. The gesture is an enhancement: keep a keyboard-reachable refresh next to it, such as an existing refresh or retry button or the page reload. Under prefers-reduced-motion, the indicator neither eases nor turns; it still appears at the pull distance and shows the busy spinner state.

Runtime

The wrapper is server-renderable in its idle state. The gesture, the indicator movement, and the busy state need hydration; before that, the child scrolls natively and nothing else happens.

Example

tsx
<PullToRefresh
  onRefresh={async () => {
    await conversations.refresh();
  }}
  label="Refreshing conversations"
>
  <ScrollArea role="region" aria-label="Needs action" style={{ height: "18rem" }}>
    <ConversationRows />
  </ScrollArea>
</PullToRefresh>

Connect a coding agent

The Cloud skill provides compact working instructions. MCP supplies exact current documentation when details matter.

CLI command
bunx skills add https://docs.example.com

This command installs the working instructions published by this website in the selected coding agent.

For exact, current details, also connect the MCP server through one of the agent tabs.

Installation uses the open-source Vercel Skills CLI.

The skill and MCP complement each other: the skill describes workflows, while MCP supplies current documentation.