Scroll Area
Augments native scroll with a styled overlay scrollbar on bits-ui ScrollArea. Content keeps native momentum — only the scrollbar chrome is replaced, and it fades on idle.
Usage
Tags
v1.2.0-beta
v1.1.0
v1.0.0
v0.9.4
v0.9.3
v0.9.2
v0.9.1
v0.9.0
v0.8.2
v0.8.1
v0.8.0
v0.7.5
v0.7.4
v0.7.3
v0.7.2
v0.7.1
v0.7.0
v0.6.1
v0.6.0
v0.5.0
scroll-area.svelte
<script lang="ts">
import { ScrollArea as ScrollAreaPrimitive } from 'bits-ui';
import { Scrollbar } from './index.js';
import { cn, type WithoutChild } from '$lib/utils.js';
let {
ref = $bindable(null),
viewportRef = $bindable(null),
class: className,
orientation = 'vertical',
scrollbarXClasses = '',
scrollbarYClasses = '',
children,
...restProps
}: WithoutChild<ScrollAreaPrimitive.RootProps> & {
orientation?: 'vertical' | 'horizontal' | 'both' | undefined;
scrollbarXClasses?: string | undefined;
scrollbarYClasses?: string | undefined;
viewportRef?: HTMLElement | null;
} = $props();
</script>
<ScrollAreaPrimitive.Root
bind:ref
data-slot="scroll-area"
class={cn('relative', className)}
{...restProps}
>
<ScrollAreaPrimitive.Viewport
bind:ref={viewportRef}
data-slot="scroll-area-viewport"
tabindex={0}
class="size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-(length:--ring-width) focus-visible:ring-ring/50 focus-visible:outline-1"
>
{@render children?.()}
</ScrollAreaPrimitive.Viewport>
{#if orientation === 'vertical' || orientation === 'both'}
<Scrollbar orientation="vertical" class={scrollbarYClasses} />
{/if}
{#if orientation === 'horizontal' || orientation === 'both'}
<Scrollbar orientation="horizontal" class={scrollbarXClasses} />
{/if}
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
Horizontal
Set orientation="horizontal" to show a horizontal scrollbar.
tag-01tag-02tag-03tag-04tag-05tag-06tag-07tag-08tag-09tag-10tag-11tag-12tag-13tag-14tag-15tag-16tag-17tag-18tag-19tag-20tag-21tag-22tag-23tag-24
scroll-area.svelte
<script lang="ts">
import { ScrollArea as ScrollAreaPrimitive } from 'bits-ui';
import { Scrollbar } from './index.js';
import { cn, type WithoutChild } from '$lib/utils.js';
let {
ref = $bindable(null),
viewportRef = $bindable(null),
class: className,
orientation = 'vertical',
scrollbarXClasses = '',
scrollbarYClasses = '',
children,
...restProps
}: WithoutChild<ScrollAreaPrimitive.RootProps> & {
orientation?: 'vertical' | 'horizontal' | 'both' | undefined;
scrollbarXClasses?: string | undefined;
scrollbarYClasses?: string | undefined;
viewportRef?: HTMLElement | null;
} = $props();
</script>
<ScrollAreaPrimitive.Root
bind:ref
data-slot="scroll-area"
class={cn('relative', className)}
{...restProps}
>
<ScrollAreaPrimitive.Viewport
bind:ref={viewportRef}
data-slot="scroll-area-viewport"
tabindex={0}
class="size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-(length:--ring-width) focus-visible:ring-ring/50 focus-visible:outline-1"
>
{@render children?.()}
</ScrollAreaPrimitive.Viewport>
{#if orientation === 'vertical' || orientation === 'both'}
<Scrollbar orientation="vertical" class={scrollbarYClasses} />
{/if}
{#if orientation === 'horizontal' || orientation === 'both'}
<Scrollbar orientation="horizontal" class={scrollbarXClasses} />
{/if}
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
With separator
Recent commits
v1.2.0-beta — handoff polish
v1.1.0 — handoff polish
v1.0.0 — handoff polish
v0.9.4 — handoff polish
v0.9.3 — handoff polish
v0.9.2 — handoff polish
v0.9.1 — handoff polish
v0.9.0 — handoff polish
v0.8.2 — handoff polish
v0.8.1 — handoff polish
scroll-area.svelte
<script lang="ts">
import { ScrollArea as ScrollAreaPrimitive } from 'bits-ui';
import { Scrollbar } from './index.js';
import { cn, type WithoutChild } from '$lib/utils.js';
let {
ref = $bindable(null),
viewportRef = $bindable(null),
class: className,
orientation = 'vertical',
scrollbarXClasses = '',
scrollbarYClasses = '',
children,
...restProps
}: WithoutChild<ScrollAreaPrimitive.RootProps> & {
orientation?: 'vertical' | 'horizontal' | 'both' | undefined;
scrollbarXClasses?: string | undefined;
scrollbarYClasses?: string | undefined;
viewportRef?: HTMLElement | null;
} = $props();
</script>
<ScrollAreaPrimitive.Root
bind:ref
data-slot="scroll-area"
class={cn('relative', className)}
{...restProps}
>
<ScrollAreaPrimitive.Viewport
bind:ref={viewportRef}
data-slot="scroll-area-viewport"
tabindex={0}
class="size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-(length:--ring-width) focus-visible:ring-ring/50 focus-visible:outline-1"
>
{@render children?.()}
</ScrollAreaPrimitive.Viewport>
{#if orientation === 'vertical' || orientation === 'both'}
<Scrollbar orientation="vertical" class={scrollbarYClasses} />
{/if}
{#if orientation === 'horizontal' || orientation === 'both'}
<Scrollbar orientation="horizontal" class={scrollbarXClasses} />
{/if}
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
API reference
Inherits bits-ui ScrollArea.Root props. The wrapper also renders a styled Scrollbar + Thumb pair internally.
| Prop | Type | Default | Description |
|---|---|---|---|
'vertical' | 'horizontal' | 'both' | 'vertical' | Which scrollbar axis to render. Both shows vertical and horizontal. | |
'auto' | 'always' | 'scroll' | 'hover' | 'hover' | When the scrollbar is visible. Hover shows it on pointer-over; scroll shows it only while scrolling. | |
number | 600 | Milliseconds to wait before hiding the scrollbar in scroll/hover mode. | |
'ltr' | 'rtl' | 'ltr' | Reading direction. Flips horizontal scrollbar placement. | |
HTMLElement | null | null | Two-way-bindable reference to the scroll viewport element. | |
string | — | Merged onto the viewport container via tailwind-merge. |