sveltelean Primitives
Versionv0.2.0 GitHub

Example

Release notes

2.4

Offline sync for notes; conflicts are shown side by side.

2.3

Shared folders with read-only members.

2.2

Search across attachments, including scanned pages.

2.1

Keyboard shortcuts for every command in the editor.

2.0

A new editor with tables, checklists and code blocks.

1.9

Exports to Markdown and HTML.

1.8

Reminders on notes, with a daily digest.

<div
	role="region"
	aria-label="Release notes"
	tabindex="0"
	data-slean="scroll-area"
	data-variant="outline"
	class="notes"
>
	<h3>2.4</h3>
	<p>Offline sync for notes; conflicts are shown side by side.</p>
	<h3>2.3</h3>
	<p>Shared folders with read-only members.</p>
	<h3>2.2</h3>
	<p>Search across attachments, including scanned pages.</p>
	<h3>2.1</h3>
	<p>Keyboard shortcuts for every command in the editor.</p>
	<h3>2.0</h3>
	<p>A new editor with tables, checklists and code blocks.</p>
	<h3>1.9</h3>
	<p>Exports to Markdown and HTML.</p>
	<h3>1.8</h3>
	<p>Reminders on notes, with a daily digest.</p>
</div>

<style>
	/* The page gives the area its size. */
	.notes {
		max-block-size: 14rem;
		padding: 1rem;
	}
</style>
Tier 0: the two sources differ only by the stylesheet import. Tab to the area and use the arrow keys, PageDown or End. The edge shadows appear where scroll-driven animations are supported. Everything here works with page JavaScript disabled.

Why this implementation exists

Every scrolling region is already a native component: overflow: auto scrolls, the browser draws the scrollbars, and the arrow keys, PageDown, Space, Home and End scroll a focused region. What a region with only text inside lacks is a way to receive focus in every browser, and a name for the screen reader that lands on it. tabindex="0" and role="region" with aria-label give it both.

Libraries replace the scrollbar with elements of their own to style it; the platform now styles the real one. scroll-area.css sets scrollbar-width: thin, scrollbar-color from the tokens and scrollbar-gutter: stable, contains overscroll on the scrolling axis, and, where scroll-driven animations exist, drives inset shadows from the region’s own scroll timeline.

The browser owns

  • scrolling, the scrollbars and their dragging
  • the scrolling keys: arrows, PageUp, PageDown, Space, Home, End
  • the tab stop from tabindex="0" and the announcement of the named region
  • scroll chaining and its containment, the scroll timeline behind the shadows

Svelte Lean owns

  • scroll-area.css: thin token-colored scrollbars, a stable gutter, overscroll contained on one axis
  • the edge shadows under @supports, mirrored under RTL
  • the contract: the name, the role and the tab stop a scrolling region needs
  • documentation

Usage

The markup needs no package. Install @svelte-lean/styles for the stylesheet; @svelte-lean/primitives adds the typed contract and nothing at runtime.

npm install @svelte-lean/styles
stylesheets
import '@svelte-lean/styles/scroll-area.css';

Give the region its size from the page: a max-block-size for a vertical area, content wider than the region for a horizontal one. Name it with aria-label or aria-labelledby pointing at the heading above it.

Use role="group" instead of region when a page has many small scroll areas: each named region is a landmark. Leave tabindex="0" in place either way.

data-variant="outline" adds a border and the surface color. The shadows are drawn under the content, so content with its own opaque background covers them.

Anatomy

PartElementdata-slean-partRequiredNotes
root<div role="region" aria-label="…" tabindex="0" data-slean="scroll-area">–yesdata-slean-orientation="vertical|horizontal"; data-variant="outline". The page sets its block size or max.

Runtime profile

Tier 0: the scroll area has no behavior module, the Vite plugin maps scroll-area to no module, and the page ships no Svelte Lean JavaScript for it.

Accessibility contract

  • tabindex="0" makes the region a tab stop in every browser, so a keyboard user can scroll it when it holds nothing focusable.
  • role="region" (or group) with a name: a focusable element needs one, and it is announced when focus lands on the region.
  • The scrolling keys are the browser’s: arrows, PageUp, PageDown, Space, Home and End.
  • The scrollbars are the platform’s, so their pointer behavior, their size in the system settings and forced colors are unchanged.

Keyboard

KeyWhenResult
TabanywhereReaches the region (tabindex="0")
ArrowDown/ArrowUpfocus on the regionScrolls by a line (native)
ArrowRight/ArrowLeftfocus on the regionScrolls sideways by a line (native)
PageDown/PageUp/Spacefocus on the regionScrolls by a page (native)
Home/Endfocus on the regionScrolls to the start or the end (native)

Platform features

FeatureBaselineOutside the target
scrollbar-widthBaseline 2024 (Safari 18.2)Safari before 18.2 draws its default scrollbar
scrollbar-colorBaseline 2025 (Safari 26.2)Safari before 26.2 keeps the system colors
scrollbar-gutterBaseline 2024The content shifts when the scrollbar appears
overscroll-behavior: containNot Baseline (Chrome 63, Firefox 59, Safari 16 on scrolling containers)The page scrolls on when the area reaches its end
Scroll-driven animationsNot Baseline (Chrome 115, Safari 26)No edge shadows; the area scrolls the same

Without JavaScript

Fully functional: scrolling, the keyboard and the scrollbars are native.

Server rendering

Static markup. The region starts at the beginning of its content.

Before hydration

Nothing is attached; the area scrolls before and after hydration alike.

Styling

scroll-area.css sets the scrollbar width, colors and gutter, the overscroll containment per axis, the outline variant and the scroll-driven edge shadows. The tokens it reads:

TokenDefault (light)Applies to
--slean-border-strongvar(--slean-neutral-8)scrollbar-color
--slean-radius-md0.625remborder-radius
--slean-bordervar(--slean-neutral-6)border
--slean-surfaceoklch(100% 0 0)background
--slean-fgvar(--slean-neutral-12)color
--slean-overlayoklch(0% 0 0 / 0.4)box-shadow

State selectors the stylesheet targets, all from the platform or ARIA: :dir(rtl).

Variant attributes: data-slean-orientation (horizontal); data-variant (outline).

Compatibility notes

scrollbar-width and scrollbar-gutter are Baseline 2024 (Safari 18.2) and scrollbar-color Baseline 2025 (Safari 26.2); older Safari keeps its default scrollbar. overscroll-behavior works on scrolling containers in every current engine but is not listed as Baseline, because engines differ on containers without overflow. Scroll-driven animations are not Baseline (Chrome 115, Safari 26, not Firefox): without them there are no edge shadows.

Examples

Horizontal

data-slean-orientation="horizontal" contains overscroll along the inline axis only, so a vertical wheel over the row still scrolls the page, and moves the edge shadows to the start and end sides. Focus the row and use ArrowRight, or scroll it sideways.

  • Harbor
  • Lighthouse
  • Breakwater
  • Tidewater
  • Mooring
  • Quayside
horizontal
<div
	role="region"
	aria-label="Recent projects"
	tabindex="0"
	data-slean="scroll-area"
	data-slean-orientation="horizontal"
>
	<ul class="row">
		<li>Harbor</li>
		<li>Lighthouse</li>
		<li>Breakwater</li>
		<li>Tidewater</li>
		<li>Mooring</li>
		<li>Quayside</li>
	</ul>
</div>

Testing

Source