Browse long or wide content inside a region.
Basic usage
Scrollbars fade in when the pointer enters the region and fade out when it leaves, without taking up content space. Wheel, touch and keyboard scrolling retain native behavior.
import { Scrollbar } from '@ifhover/leaf-ui';
export function ScrollbarBasic() {
return (
<Scrollbar height={200} aria-label={'Project activity'}>
{Array.from({ length: 12 }, (_, index) => (
<div key={String(index)} style={{ padding: '12px 20px' }}>
{'Project activity'} {index + 1}
</div>
))}
</Scrollbar>
);
}
Horizontal and always visible
orientation controls the scrolling axes. Use scrollbar="always" to keep bars visible, or hidden to hide them without disabling scrolling. Drag a thumb or click its track to move through the content.
import { Scrollbar } from '@ifhover/leaf-ui';
export function ScrollbarHorizontal() {
return (
<Scrollbar
orientation="horizontal"
scrollbar="always"
aria-label={'Project board'}
>
<div style={{ display: 'flex', width: 'max-content', gap: 16, padding: '12px 12px 24px' }}>
{Array.from({ length: 6 }, (_, index) => (
<div
key={String(index)}
style={{
width: 180,
padding: 24,
borderRadius: 'var(--leaf-radius)',
background: 'var(--leaf-color-surface-muted)',
}}
>
{'Project'} {index + 1}
</div>
))}
</div>
</Scrollbar>
);
}
Dynamic content and scroll control
Thumbs update when content or container sizes change. The forwarded ref points to the scrolling viewport: call scrollTo, read scrollTop, or use it with BackTop. At a scroll boundary, or when content fits, the wheel continues scrolling the page.
import { Button, Flex, Scrollbar } from '@ifhover/leaf-ui';
import { useRef, useState } from 'react';
export function ScrollbarDynamic() {
const viewport = useRef<HTMLDivElement>(null);
const [count, setCount] = useState(3);
return (
<Flex direction="column" gap="md">
<Flex gap="sm" wrap>
<Button variant="outline" onClick={() => setCount((previous) => previous + 3)}>
{'Add items'}
</Button>
<Button
variant="soft"
onClick={() => viewport.current?.scrollTo({ top: 0, behavior: 'smooth' })}
>
{'Back to top'}
</Button>
</Flex>
<Scrollbar ref={viewport} maxHeight={200} aria-label={'Dynamic list'}>
{Array.from({ length: count }, (_, index) => (
<div key={String(index)} style={{ padding: '12px 20px' }}>
{'Item'} {index + 1}
</div>
))}
</Scrollbar>
</Flex>
);
}
import type { ScrollbarProps } from '@ifhover/leaf-ui';
style and className apply to the outer wrapper. id, role, tabIndex, region names and onScroll apply to the scrolling viewport. Provide aria-label or aria-labelledby to name it. Focus a thumb with Tab and use arrow keys, PageUp / PageDown or Home / End.
ScrollArea uses the same scrollbar implementation and defaults to vertical scrolling. Existing usage remains supported.