Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Layout

Scroll Area

Layout

Scroll Area React component for Tailwind CSS v4

Page HeaderSeparator
Live props
Layout
orientation
maxHeight
Behavior
type
scrollHideDelay600ms
snap
controls
scrollStep0.8
focusable
Appearance
fade
fadeSize24px
scrollbarSize
Content
label
Code
import { ScrollArea } from "@/components/ui/scroll-area"

<ScrollArea />
  1. Home
  2. Components
  3. Layout
  4. Scroll Area
Pro

Scroll Area React component for Tailwind CSS v4

v1.0.2·Updated in 1.3.1

A React scroll area with slim custom scrollbars, edge fades that ease out at the ends, snap scrolling and prev / next buttons for rails.

Scroll with a wheel, a trackpad or a finger: momentum stays native, the content fades out toward every edge that has more to see, and each fade melts away as you reach the end. With a mouse the slim bars appear on hover and the thumb can be dragged or the track clicked to jump. On a phone the bars show while you scroll and are only indicators, like on iOS and Android, so a swipe near the edge always moves the content. Tab to the area to scroll it with the arrows, Page Up / Down, Home / End and Space; a focus ring and the bars show while it has focus, and a focused rail takes Home, End, Page keys and Space sideways too. Rails can snap card by card and, with a mouse, show round prev / next buttons on the sides with more content; they fade out at the ends, and one that hides under keyboard focus hands focus to the rail.

Scroll Area is a Pro React component in the React layout components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add scroll-area, together with Button, useMediaQuery, useScrollEdges, Motion presets and UIProvider and the npm packages @radix-ui/react-scroll-area and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add scroll-area

Scroll Area is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import ScrollArea from @/components/ui/scroll-area.

import { ScrollArea } from "@/components/ui/scroll-area"

const listings = [
  { id: "1", title: "Cube Aim bike", city: "Brooklyn", price: "$450" },
  { id: "2", title: "iPhone 13, 128 GB", city: "Boston", price: "$460" },
  { id: "3", title: "Sofa bed", city: "Portland", price: "$280" },
]

export function Notifications({ items }: { items: { id: string; title: string; time: string }[] }) {
  return (
    <ScrollArea label="Notifications" maxHeight={320} className="rounded-2xl bg-surface shadow-raised">
      <ul className="divide-y divide-foreground/10">
        {items.map((item) => (
          <li key={item.id} className="flex min-h-14 flex-col justify-center px-4 py-2.5">
            <span className="text-[15px] font-medium">{item.title}</span>
            <span className="text-[13px] text-muted-foreground">{item.time}</span>
          </li>
        ))}
      </ul>
    </ScrollArea>
  )
}

export function Recommended() {
  return (
    <ScrollArea
      orientation="horizontal"
      snap="mandatory"
      controls
      label="Recommended"
      classNames={{ viewport: "scroll-px-4" }}
    >
      <div className="flex w-max gap-3 px-4 pb-3">
        {listings.map((listing) => (
          <article key={listing.id} className="w-56 shrink-0 snap-start rounded-2xl bg-surface p-4 shadow-raised">
            <h3 className="truncate text-sm font-medium">{listing.title}</h3>
            <p className="text-xs text-muted-foreground">{listing.city}</p>
            <p className="mt-2 text-[15px] font-semibold tabular-nums">{listing.price}</p>
          </article>
        ))}
      </div>
    </ScrollArea>
  )
}

Props

  • orientationdefault "vertical"
    "vertical" | "horizontal" | "both"

    Which scrollbars render and which edges fade. horizontal turns the area into a rail (the demo swaps the chat for listing cards), both scrolls a wide table either way.

  • typedefault "hover"
    "hover" | "scroll" | "auto" | "always"

    When the bars show: while the pointer is over the area, while it scrolls, whenever the content overflows, or always. On touch screens "hover" behaves like "scroll", since a finger never hovers. Content that fits shows no bars in any mode.

  • scrollHideDelaydefault 600
    number

    Milliseconds before the bars fade out after the pointer leaves ("hover") or scrolling stops ("scroll").

  • fadedefault true
    boolean

    Fades the content out toward every edge with more to scroll. It is a mask, not an overlay, so it works on any surface in both themes and never covers the bars.

  • fadeSizedefault 24
    number

    The largest fade in px. Each fade grows with the distance to its edge, so it eases out as you reach the end instead of popping.

  • scrollbarSizedefault "md"
    "sm" | "md"

    Bar thickness, 8 or 10px. The thumb fills it inside a 2px inset (4 or 6px). On touch screens the bars are indicators only, so a swipe that lands on the thumb still scrolls the content.

  • snapdefault "none"
    "none" | "mandatory" | "proximity"

    CSS scroll snap along the orientation. mandatory always rests on an item (card rails), proximity only near one (chips, long lists). The children set snap-start or snap-center.

  • controlsdefault false
    boolean

    Horizontal and both: floating prev / next buttons on the sides with more content. Mouse and trackpad only (a finger swipes); they fade out at each end and leave the tab order while hidden.

  • scrollStepdefault 0.8
    number

    Share of the viewport width one control click, or Page Up / Down or Space on a focused rail, scrolls. Smooth, and instant under reduced motion.

  • labeldefault ""
    string

    Accessible name of the scrollable region, like "Messages". With a label the viewport is announced as a region. The demo uses a sample while this is empty.

  • focusabledefault true
    boolean

    Makes the viewport a tab stop while it overflows, so keyboard users can scroll it; the focus ring sits just inside the edge and the bars show while it has focus. Turn it off when the content is itself focusable (a list of links).

  • maxHeightdefault ""
    number | string

    Convenience max height of the viewport: a number (or numeric string) in px, or any CSS length like "50dvh". The area grows with its content up to it. Otherwise size the root with a class (h-72, flex-1 min-h-0). The demo caps the chat with it.

  • viewportRef
    Ref<HTMLDivElement> (a useRef object or an (el) => void callback)

    The scrolling element, for scrollTo, scrollIntoView or measuring. The demo uses it to start the chat at the latest message.

  • controlLabelsdefault "{ previous: "Scroll back", next: "Scroll forward" }"
    { previous?: string; next?: string }

    aria-labels of the prev / next buttons.

  • onEdgesChange
    (edges: { top: boolean; bottom: boolean; left: boolean; right: boolean }) => void

    Which edges have more content, called when one flips (never per scroll frame). Handy for a header hairline that appears once the list scrolls under it.

  • classNames
    Partial<Record<"viewport" | "scrollbar" | "thumb" | "controls", string>>

    Extra classes for the inner parts. viewport takes scroll padding (scroll-px-4) for rails, thumb a custom color (bg-brand/50), controls a different vertical position.

  • className
    string

    Extra classes for the root, merged last: its size (h-72, flex-1 min-h-0) and radius, which the viewport inherits. Other props (style, aria-*, dir) go to the Radix root too; dir="rtl" puts the vertical bar on the left and mirrors the fades, the controls and the Home / End keys of a rail.

  • children
    ReactNode

    The content. Rails lay their items out in a w-max flex row of snap-start items. The file also exports ScrollBar (orientation, size "sm" | "md", classNames.thumb), the same bar as a part for compositions on the Radix root.

Parts

  • ScrollBar

    component

    One slim scrollbar with its thumb, for compositions built on the Radix scroll area root. ScrollArea renders these for you.

    • orientationdefault "vertical"
      "vertical" | "horizontal"

      Which axis the bar scrolls.

    • sizedefault "md"
      "sm" | "md"

      8px (sm) or 10px (md) thick.

    • classNames
      Partial<Record<"thumb", string>>

      Extra classes for the thumb, e.g. a custom color.

  • ScrollAreaProps

    type

    The props of ScrollArea, for wrappers like a horizontal rail or a chat log.

Examples

Vertical list with fades

maxHeight caps it; onEdgesChange draws the header hairline once the list scrolls under it.

Listing rail

orientation="horizontal", snap="mandatory" and controls: cards settle one by one, the arrows hide at each end.

Both axes

A 12-column price table in a small box; the fades follow all four edges.

Always vs on hover

type="always" keeps the bar; "hover" shows it while you point at the list, while it has keyboard focus, and while scrolling on touch.

Chips on a phone

snap="proximity" rests near a chip without forcing it; no controls on touch, a swipe is enough.

Inside a popover

maxHeight, label="Results", a slim bar, and focusable={false} since every row is a button already.

Slim bar, brand thumb

scrollbarSize="sm", a longer fade and classNames.thumb in the brand color.

Dependencies

@radix-ui/react-scroll-arealucide-reactbuttonuse-media-queryuse-scroll-edgesmotionui-config

Files

  • components/ui/scroll-area.tsx

FAQ

Is the React Scroll Area component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Scroll Area?

Run npx wingo-ui@latest add scroll-area. It also adds the Wingo UI items it builds on (Button, useMediaQuery, useScrollEdges, Motion presets and UIProvider) and the npm packages @radix-ui/react-scroll-area and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/scroll-area.

How does Scroll Area behave on phones?

On a phone the bars show while you scroll and are only indicators, like on iOS and Android, so a swipe near the edge always moves the content.

See also

useScrollEdgeshookScroll ProgressButtonMotion presetslib

More React layout components

Settings LayoutSplit ViewToolbarSeparatorCardApp Shell
All 12 React layout components
PreviousPage HeaderNext Separator
Emma CarterCube Aim bike · $450
Today

Hi! Is the Cube bike still available?

10:02

Hi Emma! Yes, it is still available.

10:04

Great. What size is the frame?

10:05

M, it fits riders from 5'7" to 6'1".

10:05

Perfect, I am 5'9". Has it been serviced?

10:07

Yes, I replaced the chain and the brake pads last month.

10:08

Is the price a little negotiable?

10:11

I can do $420 if you pick it up this week.

10:12

Sounds good. Can I come see it tomorrow?

10:14

Sure. I am in Park Slope, near Prospect Park.

10:15

Is around 6:30 PM okay?

10:15

Perfect, see you then.

10:16

Thanks! I will text you when I leave home.

10:18

See you tomorrow!

10:18

Scroll the conversation or send a message: the fades ease out at both ends

type="hover" · hides after 600ms · step 0.8