Drag Scroll Area
Makes an overflowing horizontal strip drag-scrollable, with alpha edge fades and keyboard-focus-into-view.
npx shadcn@latest add @redpanda/drag-scroll-areapnpm dlx shadcn@latest add @redpanda/drag-scroll-areayarn dlx shadcn@latest add @redpanda/drag-scroll-areabunx shadcn@latest add @redpanda/drag-scroll-areanubx shadcn@latest add @redpanda/drag-scroll-areaaube dlx shadcn@latest add @redpanda/drag-scroll-areaWhen to use
Wrap any single-row content that can overflow its container horizontally — a tab strip, a
filter toolbar, a chip row — to make it draggable instead of clipping or forcing a wrap onto
multiple lines. It is the primitive used to make Tabs strips scrollable.
Reach for it when:
- The number of items is variable and a fixed single-row height matters (no layout shift from wrapping).
- You want a pointer-draggable strip without a visible scrollbar.
For vertical scrolling or a styled cross-browser scrollbar, use ScrollArea instead.
Behavior
- Drag to scroll — click and drag the strip sideways. A real drag (more than 4px) swallows the trailing click, so dragging never accidentally activates the item under the pointer.
- Alpha edge fades — the side(s) with hidden content fade out via a CSS mask over the content itself. Because it masks rather than overlays a color, it blends on any background and needs no theming. The fade only appears on edges that actually overflow.
- Preserve the bottom edge — set
preserveBottomEdgeto keep the bottom N px fully opaque (e.g. an underline tab strip’s baseline border) while the labels above it still fade. It unions a second mask layer withmask-composite: add, so a pixel survives if either layer keeps it. - Keyboard focus — when any focusable descendant receives focus (roving tab focus, arrow keys,
programmatic focus), it is scrolled fully clear of the edge fade. This is generic: it reacts to
focusinand has no knowledge of its children. - Grab cursor — shown only while there is something to scroll.
Usage
import { DragScrollArea } from '@/components/drag-scroll-area';
<DragScrollArea>
<div className="flex w-max gap-2">{/* single-row content */}</div>
</DragScrollArea>;
The child should size to its content (e.g. w-max) so it can overflow rather than shrink to fit.
Props
DragScrollArea accepts all native div props, plus:
| Prop | Type | Default | Description |
|---|---|---|---|
fadeSize |
number |
32 |
Width of the edge fade in px, and the inset a focused child is scrolled clear of. |
preserveBottomEdge |
number |
0 |
Keep the bottom N px fully opaque, exempt from the edge fade (e.g. an underline baseline). |
testId |
string |
— | Sets data-testid on the scroll container for testing. |
Accessibility
- Focusing a descendant always scrolls it fully into view, so keyboard users never land on a clipped item.
- The edge fade snaps in and out (no transition), so it carries no motion for reduced-motion users.
- Horizontal only by design — do not use it for vertical overflow.