Scroll area
One themed treatment for every scrollable region - styled scrollbars, edge fades, and zero JavaScript.
A vertical list
The default. Size the viewport with classes - here max-h-56 - and everything past it scrolls with a thin themed scrollbar instead of the browser's default slab. tabindex="0" comes for free, so the arrow keys and Page Up/Down work the moment you tab into it.
- Release note 1 v4.1.0
- Release note 2 v4.2.0
- Release note 3 v4.3.0
- Release note 4 v4.4.0
- Release note 5 v4.5.0
- Release note 6 v4.6.0
- Release note 7 v4.7.0
- Release note 8 v4.8.0
- Release note 9 v4.9.0
- Release note 10 v4.10.0
- Release note 11 v4.11.0
- Release note 12 v4.12.0
- Release note 13 v4.13.0
- Release note 14 v4.14.0
<.scroll_area
aria-label="Recent releases"
class="max-h-56 w-full max-w-sm rounded-lg border border-gray-200 p-3 dark:border-gray-800"
>
<ul class="space-y-2 text-sm text-gray-700 dark:text-gray-300">
<li :for={n <- 1..14} class="flex items-center justify-between gap-4">
<span>Release note {n}</span>
<span class="text-xs text-gray-400">v4.{n}.0</span>
</li>
</ul>
</.scroll_area>
Horizontal, with faded edges
A tag rail that runs off the side of a card. fade_edges masks the scrolling axis so the row dissolves at the clip rather than being guillotined - the visual cue that says there is more this way.
Both axes, gutter reserved
A deploy log with orientation="both": one container, two themed scrollbars and a themed corner where they meet. gutter_stable reserves the scrollbar's space up front, so the lines do not shuffle sideways the first time a scrollbar appears. On overlay scrollbars (the macOS default) there is no gutter to reserve and the flag quietly does nothing.
09:14:01.118 [info] release v4.15.0 building on builder-04 (elixir 1.19.1 / otp 28) 09:14:04.902 [info] ==> deps compiled in 3.7s, 214 modules 09:14:11.470 [info] ==> assets: tailwind 4.1.3 wrote priv/static/assets/app.css in 812ms 09:14:12.006 [info] ==> digest: 46 files, 1.9MB total, gzip 412KB 09:14:19.331 [info] image pushed: registry.fly.io/acme-prod:deployment-01K2 (sha256:9f21c4ab) 09:14:26.884 [info] machine 5683d9 updating in syd, waiting for health checks 09:14:38.019 [info] machine 5683d9 healthy after 11.1s, 1 of 2 machines updated 09:14:51.744 [info] machine 90e8ff healthy after 9.4s, 2 of 2 machines updated 09:14:52.100 [info] deployment complete, 0 failed, rollback not required
<.scroll_area
orientation="both"
gutter_stable
aria-label="Deploy log"
class="max-h-48 w-full max-w-md rounded-lg bg-gray-900 p-4 dark:border dark:border-gray-800"
>
<pre class="w-max font-mono text-xs leading-6 text-gray-100">{PetalComponents.Showcase.ScrollArea.deploy_log()}</pre>
</.scroll_area>
Asking for a permanent scrollbar
visibility="always" asks the engine to keep the scrollbar drawn instead of fading it in on scroll. It is a request, not a guarantee: WebKit honours it, Firefox has no mechanism for it, and no browser overrides an OS set to hide scrollbars. Reach for it when a region would otherwise look like static text.
Scrollbars belong to the operating system. This component themes what the platform hands it - the thumb colour, the track, the width where that is ours to set - and leaves the behaviour alone.
That means native momentum scrolling, native keyboard handling and native assistive-tech behaviour, none of which a JavaScript scrollbar reimplementation gets for free.
The trade is that a Mac still looks like a Mac. We think that is the right way round.
<.scroll_area
visibility="always"
class="max-h-40 w-full max-w-sm rounded-lg border border-gray-200 p-3 text-sm text-gray-700 dark:border-gray-800 dark:text-gray-300"
>
<p class="mb-3">
Scrollbars belong to the operating system. This component themes what the platform hands it - the thumb colour, the track, the width where that is ours to set - and leaves the behaviour alone.
</p>
<p class="mb-3">
That means native momentum scrolling, native keyboard handling and native assistive-tech behaviour, none of which a JavaScript scrollbar reimplementation gets for free.
</p>
<p>
The trade is that a Mac still looks like a Mac. We think that is the right way round.
</p>
</.scroll_area>
Properties
| Attribute | Type | Default | Description |
|---|---|---|---|
class
|
any |
nil
|
size the viewport here, e.g. class="max-h-72" or class="max-w-full" - sizing is deliberately class-driven, not attr-driven |
fade_edges
|
boolean |
false
|
fade content out at the scroll edges with a mask-image gradient, hinting that more content exists past the clip. Masks apply on the scrolling axis only, and are static - they do not track scroll position |
gutter_stable
|
boolean |
false
|
reserve scrollbar space with scrollbar-gutter: stable so content does not shift when the scrollbar appears or disappears. Classic scrollbars only - overlay scrollbars have no gutter to reserve |
orientation
|
string |
"vertical"
|
which axis scrolls: vertical (overflow-y), horizontal (overflow-x), or both
one of: "vertical", "horizontal", "both"
|
rest
|
global |
||
visibility
|
string |
"auto"
|
auto follows the platform (overlay scrollbars appear on scroll on macOS); always requests a persistently visible scrollbar where the engine allows it - see the platform-truth note in the module docs
one of: "auto", "always"
|
:inner_block
slot
|
slot |