useScrollEdges React hook in TypeScript
v1.0.0Added in 1.0.0
A React scroll shadow and fade hook: knows which edges of a scroll container hide more content and fades them with a mask.
Scroll the notification list: the top fades in as content slides under the header, a hairline appears below the title, and the bottom fade melts away as you reach the end. Swipe the category rail sideways: the edges fade the same way and the arrow buttons (on desktop) hide at each end.
useScrollEdges is a free React hook in the React hooks and utilities category, written in TypeScript for React 19. It installs as source with npx wingo-ui@latest add use-scroll-edges.
Add to a project
Requirements: React 19, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add use-scroll-edgesUsage
Import useScrollEdges from @/hooks/use-scroll-edges.
import { fadeMaskStyle, useScrollEdges } from "@/hooks/use-scroll-edges"
import { cn } from "@/lib/utils"
export function Notifications({ items }: { items: string[] }) {
const { ref, edges } = useScrollEdges<HTMLUListElement>()
return (
<section className="flex max-h-80 flex-col">
<h2 className={cn("px-4 py-3 text-[15px] font-semibold", edges.top && "border-b border-foreground/10")}>Notifications</h2>
<ul ref={ref} style={fadeMaskStyle("y")} className="min-h-0 flex-1 overflow-y-auto overscroll-contain px-4">
{items.map((item) => (
<li key={item} className="py-3 text-sm">{item}</li>
))}
</ul>
</section>
)
}API
fadedefault24numberuseScrollEdges(options?): the largest fade in px. The hook writes --fade-top, --fade-bottom, --fade-left and --fade-right on the element, each growing with the distance to its edge, so a fade eases out as you reach the end instead of popping.
thresholddefault1numberDistance in px from an edge that still counts as being at it (absorbs sub-pixel scroll positions).
disableddefaultfalsebooleanStops measuring; the fades stay at their last size.
returns{ ref: RefCallback<T>; edges: ScrollEdges; overflow: { x: boolean; y: boolean } }ref goes on the scrolling element (a plain overflow div or a Radix ScrollArea viewport). edges says where more content lies ({ top, bottom, left, right }) and overflow whether it scrolls at all; both re-render only when a value flips, never per scroll frame. Handles RTL.
Parts
ScrollEdges
type{ top: boolean; bottom: boolean; left: boolean; right: boolean }: where more content lies.
ScrollAxis
type"x" | "y" | "both", the axis fadeMaskStyle fades.
UseScrollEdgesOptions
typeThe options object: fade, threshold and disabled.
UseScrollEdgesResult
typeThe return value: { ref, edges, overflow }.
fadeMaskStyle
functionA mask-image style driven by the vars above. Spread it into the same element's style. It needs no background color, so it works on any surface in both themes. Signature: (axis?: "x" | "y" | "both") => CSSProperties.
Examples
Read to the end to accept
edges.bottom keeps the Accept button locked until the reader scrolls to the last line.
Dividers only when needed
The header and footer hairlines appear only while content scrolls under them.
Dependencies
No dependencies beyond React.
Files
- hooks/use-scroll-edges.ts
FAQ
Is the useScrollEdges hook free?
Yes. The source is free and needs no account: copy it from the Code tab or install it with the CLI.
How do I install useScrollEdges?
Run npx wingo-ui@latest add use-scroll-edges. With shadcn, run npx shadcn@latest add https://wingo-ui.com/r/use-scroll-edges.json.