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.
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.
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.
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.
Both directions
A spreadsheet scrolls both ways. Make the header row and the first column sticky so every number keeps its labels.
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
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
Terms of service
Updated 1 March 2025. Read them before you continue.
Guidelines
The title and its action stay in view while the list moves.
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.
A row cut in half and a visible scrollbar tell people the list goes on.
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
ReactElement or a function that returns the element to render.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
Tokens it reads
Color
Shape & size
Type & motion
This component only
/* 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
- Scrolling stays native: the wheel, touch, the keyboard and the browser's own find-in-page all keep working.
- The viewport is focusable only while something overflows, so keyboard users can always reach hidden content (axe: scrollable-region-focusable).
- Name it when it is a region: role="region" with aria-label or aria-labelledby on the root.
- The scrollbars are for the pointer and are hidden from screen readers (aria-hidden). They only fade, never move, so reduced motion needs nothing extra.