// preview in
saved for every page

ScrollArea

A box with a fixed size whose content scrolls natively — wheel, touch and keyboard — with scrollbars drawn by the skin. Use it for a chat history, a long list in a card or a table wider than the screen.

Built on Base UIPattern Scrollable region (WCAG 2.1.1)Client componentSource ↗
minimal · light · #3fa34d
Jan 2025
Feb 2025
Mar 2025
Apr 2025
May 2025
Jun 2025
Jul 2025
Aug 2025
Sep 2025
Oct 2025
Nov 2025
Dec 2025
Jan 2026
Feb 2026
Mar 2026
Apr 2026
May 2026
Jun 2026
Jul 2026
Aug 2026
Sep 2026
Oct 2026
Nov 2026
Dec 2026
orientation

Installation

Set up Camaleo once (Installation), then import it. It renders on the client.

import { ScrollArea } from "@camaleo/react";

Anatomy

The root clips, the viewport scrolls. Give the root a height, or a max-height so it grows with its content up to that and then scrolls (a width, for horizontal scrolling): without one, the box just grows with its content. The scrollbars only appear when there is something to scroll.

Examples

Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above. Click inside one and use the arrow keys, or press Tab until the box gets a focus ring.

minimal · light · #3fa34d
  1. Hi! My order #4821 still says “preparing”. Is it on its way?Ana Ribeiro · 09:12
  2. Hi Ana, let me check that for you.You · 09:13
  3. It left our warehouse this morning. The carrier just hasn’t scanned it yet.You · 09:14
  4. Oh, good. When should it arrive?Ana Ribeiro · 09:15
  5. By Thursday, 14 March. You’ll get the tracking link by email in the next hour.You · 09:15
  6. Perfect. Can I still change the delivery address?Ana Ribeiro · 09:17
  7. Not anymore, sorry. But you can pick it up at the depot if you won’t be home.You · 09:18
  8. That works. Thanks for the quick answer!Ana Ribeiro · 09:19

One row, sideways

On a phone, a row of filters that scrolls sideways takes less room than one that wraps into four lines. The cut-off chip at the edge shows there is more.

$ pnpm build
> @atlas/web@4.2.0 build /home/ci/workspace/atlas/apps/web
> next build --experimental-build-mode=compile && node scripts/upload-sourcemaps.mjs --release 4.2.0
✓ Compiled successfully in 41.8s (1,284 modules, 3 warnings: see /home/ci/workspace/atlas/apps/web/.next/trace)
⚠ ./src/features/billing/invoice-table.tsx: "useMemo" has a missing dependency: "currency" (react-hooks/exhaustive-deps)
✓ Uploaded 212 source maps to https://sourcemaps.atlas.example/releases/4.2.0 in 6.3s

Long lines

Logs, diffs and commands lose their shape when they wrap. Keep the lines whole and let the box scroll sideways.

A list between a title and a button

Only the part that grows scrolls. The title and the "New project" button stay where people expect them.

Monthly budget 2025
CategoryJanFebMarAprMayJunJulAugSepOctNovDec
Rent$8,200$8,400$8,600$8,350$8,550$8,300$8,500$8,250$8,450$8,200$8,400$8,600
Payroll$46,850$46,600$46,800$46,550$46,750$46,500$46,700$46,900$46,650$46,850$46,600$46,800
Software$3,350$3,100$3,300$3,500$3,250$3,450$3,200$3,400$3,150$3,350$3,100$3,300
Hardware$1,950$2,150$1,900$2,100$1,850$2,050$1,800$2,000$2,200$1,950$2,150$1,900
Travel$2,450$2,650$2,400$2,600$2,800$2,550$2,750$2,500$2,700$2,450$2,650$2,400
Marketing$5,600$5,350$5,550$5,300$5,500$5,250$5,450$5,200$5,400$5,600$5,350$5,550
Training$1,800$1,550$1,750$1,500$1,700$1,900$1,650$1,850$1,600$1,800$1,550$1,750
Insurance$1,100$1,300$1,050$1,250$1,000$1,200$950$1,150$900$1,100$1,300$1,050
Utilities$1,200$1,400$1,150$1,350$1,100$1,300$1,500$1,250$1,450$1,200$1,400$1,150
Office supplies$400$600$800$550$750$500$700$450$650$400$600$800
Legal$1,650$1,400$1,600$1,350$1,550$1,300$1,500$1,700$1,450$1,650$1,400$1,600
Taxes$8,050$7,800$8,000$8,200$7,950$8,150$7,900$8,100$7,850$8,050$7,800$8,000

Both directions

A spreadsheet scrolls both ways. Make the header row and the first column sticky so every number keeps its labels.

  1. Oh, good. When should it arrive?
  2. By Thursday, 14 March. You’ll get the tracking link by email in the next hour.
  3. Perfect. Can I still change the delivery address?
  4. Not anymore, sorry. But you can pick it up at the depot if you won’t be home.
  5. That works. Thanks for the quick answer!

New messages

Send a reply. The newest message scrolls into view, and role="log" on the list lets screen readers announce it without moving focus.

Activity log

  • Paula Lima approved invoice INV-2025-11812 Mar, 16:40
  • Rafael Souza changed the due date to 28 March12 Mar, 11:05
  • Bruno Costa uploaded contract_v3.pdf11 Mar, 17:22
  • Paula Lima added Julia Alves as a reviewer11 Mar, 09:30
  • Julia Alves commented: “Numbers match the PO.”10 Mar, 15:12
  • Rafael Souza created invoice INV-2025-11810 Mar, 14:58

Named by its heading

When the content overflows, the viewport joins the Tab order. Give the root role="region" and a name with aria-labelledby, so people hear what they landed on.

In context

These are whole pieces of UI you can copy, in the skin you picked.

Recent activity

Team Finance · last 7 days

  • Marina approved the March payroll2 h ago
  • Tiago paid invoice INV-2025-0975 h ago
  • Lucas exported the Q1 reportYesterday
  • Marina invited Carla to FinanceYesterday
  • Tiago flagged an expense from Uber2 days ago
  • Lucas closed February’s books4 days ago
  • Carla joined the team6 days ago
Activity feedCard · ScrollArea · Avatar · Button

Terms of service

Updated 1 March 2025. Read them before you continue.

1. Your account

You are responsible for the activity on your account and for keeping your password safe. Tell us right away if you notice any access you don’t recognise.

2. Payments

Plans are billed monthly in advance. Prices include taxes where required. You can cancel at any time and keep access until the end of the paid period.

3. Your data

You own the content you upload. We only process it to provide the service, and you can export or delete it whenever you like.

4. Acceptable use

Don’t use the service to send spam, break the law or interfere with other customers. We may suspend accounts that do.

5. Changes

We’ll email you 30 days before any change that affects you. If you keep using the service after that, the new terms apply.

Terms to acceptCard · ScrollArea · Checkbox · Button
Brand
16 brands
Brand filterFieldset · SearchInput · ScrollArea · Checkbox

Guidelines

Notifications
  • Your export is ready to download
  • Paula mentioned you in “Q1 budget”
  • 3 new comments on Payments v2
  • Invoice INV-2025-118 was paid
  • Bruno shared “Brand refresh” with you
  • Your trial ends in 5 days
  • Weekly report: 12 tasks done
DoScroll only the part that grows.

The title and its action stay in view while the list moves.

Choose how dates and times appear for your team.

Changes apply to everyone in the workspace.

Don’tA scroll area inside another.

The wheel gets stuck in the inner box, and people lose track of which one they are scrolling. Make the inner list shorter or let the page scroll.

  • Your export is ready to download
  • Paula mentioned you in “Q1 budget”
  • 3 new comments on Payments v2
  • Invoice INV-2025-118 was paid
  • Bruno shared “Brand refresh” with you
  • Your trial ends in 5 days
  • Weekly report: 12 tasks done
DoShow that there is more.

A row cut in half and a visible scrollbar tell people the list goes on.

  • Your export is ready to download
  • Paula mentioned you in “Q1 budget”
  • 3 new comments on Payments v2
  • Invoice INV-2025-118 was paid
  • Bruno shared “Brand refresh” with you
  • Your trial ends in 5 days
  • Weekly report: 12 tasks done
Don’tHide the scrollbar.

With no bar and a height that ends exactly on a row, the list looks complete. Most people will never find the last items.

Props

PropTypeDefaultDescription
orientation"both" | "horizontal" | "vertical""vertical"Which scrollbars to show. The content scrolls natively either way; this only picks the styled bars.
renderReactElement | (props, state) => ReactElement—Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render.
overflowEdgeThresholdnumber | { xStart, xEnd, yStart, yEnd }0How many pixels from an edge before the data-overflow-* attributes appear. Useful for fades.

The other props (style, className, role, aria-label…) go to the root. Set its size there.

Styling hooks

Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the box itself.

Attributes

data-orientationvertical · horizontal, on each scrollbar
data-has-overflow-x · data-has-overflow-ypresent when the content is wider or taller
data-overflow-y-start · data-overflow-y-endpresent while there is more above or below (also -x-)
data-hovering · data-scrollingon the scrollbar, while the pointer is over the box or it scrolls

Tokens it reads

Color

--cml-fg--cml-focus-ring--cml-focus-width

Shape & size

--cml-radius-pill

Type & motion

--cml-duration-fast--cml-ease-standard

This component only

--cml-scrollbar-idle-opacity--cml-scrollbar-size--cml-scrollbar-thumb-shadow--cml-scrollbar-track--cml-scrollbar-track-shadow
/* fade the bottom edge while there is more below */
.feed [data-slot="scroll-area"][data-overflow-y-end] [data-slot="scroll-area-viewport"] {
  mask-image: linear-gradient(to bottom, black calc(100% - 2rem), transparent);
}
/* always show the scrollbars at full strength */
.feed { --cml-scrollbar-idle-opacity: 1; }

Accessibility

TabMoves focus to the viewport when its content overflows. Without overflow it is skipped.
↓ / ↑Scroll down or up a little.
→ / ←Scroll sideways when the content is wider.
Page Down / Page UpScroll by almost a whole box.
Space / Shift + SpaceScroll down or up by a box, like Page Down and Page Up.
Home / EndJump to the start or the end.
Was this page helpful? · Edit this page on GitHub