caliper uidocs

Scroll aware fab

Free

The main action kept in reach: it shows its label at the top and when scrolling up, and shrinks to its icon while you scroll down.

Installs components/ui/scroll-aware-fab.tsxDepends on none beyond ReactMotion none in component source
pnpm dlx shadcn@latest add https://caliperui.site/r/scroll-aware-fab.json
  • Amara Okaforapproved INV-2041
  • Tomás Ferreiracommented on the Ostra deal
  • Priya Ramanmatched 12 payments
  • Kiara Murayamainvited a teammate
  • Rafael Abreuclosed September
  • Amara Okaforexported the ledger
  • Tomás Ferreirachanged a rule
  • Priya Ramanapproved INV-2041
  • Kiara Murayamacommented on the Ostra deal
  • Rafael Abreumatched 12 payments
  • Amara Okaforinvited a teammate
  • Tomás Ferreiraclosed September
  • Priya Ramanexported the ledger
  • Kiara Murayamachanged a rule
  • Rafael Abreuapproved INV-2041
  • Amara Okaforcommented on the Ostra deal
  • Tomás Ferreiramatched 12 payments
  • Priya Ramaninvited a teammate
  • Kiara Murayamaclosed September
  • Rafael Abreuexported the ledger
  • Amara Okaforchanged a rule
  • Tomás Ferreiraapproved INV-2041
  • Priya Ramancommented on the Ostra deal
  • Kiara Murayamamatched 12 payments

When to use #

Use it for

  • Phone screens with one primary action, like compose.
  • Long lists where the action should not cover content.

Skip it when

  • Desktop layouts; put the action in the header.

Installation #

terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/scroll-aware-fab.json

Adds its 2 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.

Usage #

This is the code behind the preview above.

example.tsx
import { PenLine } from "lucide-react"
import { useRef } from "react"
import { Avatar } from "@/components/ui/avatar"
import { ScrollAwareFab } from "@/components/ui/scroll-aware-fab"

const actions = ["approved INV-2041", "commented on the Ostra deal", "matched 12 payments", "invited a teammate", "closed September", "exported the ledger", "changed a rule"];

const names = ["Amara Okafor", "Tomás Ferreira", "Priya Raman", "Kiara Murayama", "Rafael Abreu"];

export function ScrollAwareFabDemo() {
  const scroller = useRef<HTMLDivElement>(null);
  return (
    <div className="relative h-[440px] w-full max-w-[360px] overflow-hidden rounded-[22px] shadow-[0_0_0_1px_var(--border)]">
      <div ref={scroller} className="h-full overflow-y-auto" tabIndex={0} role="region" aria-label="Messages">
        <ul className="grid">
          {Array.from({ length: 24 }, (_, index) => (
            <li key={index} className="flex items-center gap-3 border-b border-border px-4 py-3">
              <Avatar name={names[index % names.length]!} className="size-8 rounded-[10px] text-[11px]" />
              <span className="grid min-w-0">
                <span className="truncate text-sm font-medium">{names[index % names.length]}</span>
                <span className="truncate text-xs text-muted-foreground">{actions[index % actions.length]}</span>
              </span>
            </li>
          ))}
        </ul>
      </div>
      <ScrollAwareFab icon={<PenLine />} label="New message" scrollRoot={scroller} position="absolute" />
    </div>
  );
}

API reference #

ScrollAwareFab

component

Also accepts the native props of its root element and a ref.

PropTypeDefaultDescription
icon *ReactNode—
label *string—What it does, e.g. "New message". Always its accessible name.
scrollRootRefObject<HTMLElement | null>—The element that scrolls, if not the window.
position"fixed" | "absolute""fixed""absolute" places it in its nearest positioned parent, e.g. a phone frame.

Accessibility #

It is built on native elements and keeps aria-label and aria-hidden in step with state.

No custom key handling; Tab and the native activation keys do the work.

Motion #

It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.