Layout

ScrollArea

A box with its own scrollbars and a height limit, fading at the edges where there is more to see.

#layout#utility#accessibility#no-js

Demo

Preview

0.18 — fixes and small improvements, release 1 of the list; the box fades at the edge where there is more.

0.17 — fixes and small improvements, release 2 of the list; the box fades at the edge where there is more.

0.16 — fixes and small improvements, release 3 of the list; the box fades at the edge where there is more.

0.15 — fixes and small improvements, release 4 of the list; the box fades at the edge where there is more.

0.14 — fixes and small improvements, release 5 of the list; the box fades at the edge where there is more.

0.13 — fixes and small improvements, release 6 of the list; the box fades at the edge where there is more.

0.12 — fixes and small improvements, release 7 of the list; the box fades at the edge where there is more.

0.11 — fixes and small improvements, release 8 of the list; the box fades at the edge where there is more.

0.10 — fixes and small improvements, release 9 of the list; the box fades at the edge where there is more.

0.9 — fixes and small improvements, release 10 of the list; the box fades at the edge where there is more.

0.8 — fixes and small improvements, release 11 of the list; the box fades at the edge where there is more.

0.7 — fixes and small improvements, release 12 of the list; the box fades at the edge where there is more.

0.6 — fixes and small improvements, release 13 of the list; the box fades at the edge where there is more.

0.5 — fixes and small improvements, release 14 of the list; the box fades at the edge where there is more.

0.4 — fixes and small improvements, release 15 of the list; the box fades at the edge where there is more.

0.3 — fixes and small improvements, release 16 of the list; the box fades at the edge where there is more.

0.2 — fixes and small improvements, release 17 of the list; the box fades at the edge where there is more.

0.1 — fixes and small improvements, release 18 of the list; the box fades at the edge where there is more.

Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
Step 8
Step 9
Step 10
Step 11
Step 12

Each box takes focus, so the arrow keys scroll it; screen readers hear its name.

Get it

It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.

$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/scroll_area.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/scroll_area.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/scroll_area.qwe
view ScrollAreaDemo() =    <div.demo-stack>        <ScrollArea id="scroll-area-demo-notes" height="220px" label="Release notes" pad={4}>            {for v in range(1, 19) => <p style="margin: 0 0 10px"><b>0.{19 - v}</b> — fixes and small improvements, release {v} of the list; the box fades at the edge where there is more.</p>}        </ScrollArea>        <ScrollArea id="scroll-area-demo-steps" axis="x" height={null} label="Steps">            <div style="display: flex; gap: 12px; width: max-content; padding: 4px 0">                {for i in range(1, 13) => <div style="width: 160px; padding: 14px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-subtle); font-size: 13px">Step {i}</div>}            </div>        </ScrollArea>        <p.demo-note>Each box takes focus, so the arrow keys scroll it; screen readers hear its name.</p>    </div>

Source

ui/scroll_area.qwe
// ScrollArea: a box with scrollbars of its own, no taller (or wider) than it is// told, whose content fades out at an edge where there is more to see. The fades// follow the scrolling in CSS alone (scroll-driven animations); browsers without// them show no fade. The box takes focus, so the keyboard scrolls it, and screen// readers hear it as a region with its name.////   <ScrollArea height="280px" label="Release notes"> {for r in releases => <p>...</p>} </ScrollArea>//   <ScrollArea axis="x" label="Steps"> <div style="display: flex; gap: 12px"> ... </div> </ScrollArea>//// height: how tall it may be, as CSS ("280px"); width: how wide (none).// axis: "y" (down, the default), "x" (across) or "both".// label: its name for screen readers; without one it is a plain box.// fade: the fades at the edges (true). pad: room inside, in pixels (0).// thin: a slim scrollbar (true).import ui/theme.qwe use ui_clslet scroll_area_css = css`.ui-scroll {  position: relative; max-height: var(--ui-scroll-h, none); max-width: var(--ui-scroll-w, none); min-width: 0;  overflow: hidden auto; overscroll-behavior: contain; padding: var(--ui-scroll-pad, 0); outline: none;  border-radius: var(--ui-radius-sm); color: var(--ui-fg); font: 15px/1.5 var(--ui-font);}.ui-scroll-x { overflow: auto hidden; }.ui-scroll-both { overflow: auto; }.ui-scroll-thin { scrollbar-width: thin; scrollbar-color: var(--ui-strong) transparent; }.ui-scroll:focus-visible { box-shadow: var(--ui-focus); }.ui-scroll-fade { position: sticky; z-index: 1; display: block; pointer-events: none; opacity: 0; }.ui-scroll-top, .ui-scroll-bottom { left: 0; right: 0; height: 0; }.ui-scroll-top { top: 0; }.ui-scroll-bottom { bottom: 0; }.ui-scroll-top::before, .ui-scroll-bottom::before { content: ""; position: absolute; left: 0; right: 0; height: 28px; }.ui-scroll-top::before { top: 0; background: linear-gradient(var(--ui-scroll-bg, var(--ui-bg)), transparent); }.ui-scroll-bottom::before { bottom: 0; background: linear-gradient(transparent, var(--ui-scroll-bg, var(--ui-bg))); }.ui-scroll-start, .ui-scroll-end { top: 0; bottom: 0; width: 0; height: 0; float: left; }.ui-scroll-start { left: 0; }.ui-scroll-end { right: 0; float: right; }.ui-scroll-start::before, .ui-scroll-end::before { content: ""; position: absolute; top: 0; bottom: 0; width: 28px; }.ui-scroll-start::before { left: 0; background: linear-gradient(to right, var(--ui-scroll-bg, var(--ui-bg)), transparent); }.ui-scroll-end::before { right: 0; background: linear-gradient(to left, var(--ui-scroll-bg, var(--ui-bg)), transparent); }@supports (animation-timeline: scroll()) {  .ui-scroll-top { animation: ui-scroll-show linear both; animation-timeline: scroll(nearest block); animation-range: 0 40px; }  .ui-scroll-bottom { animation: ui-scroll-hide linear both; animation-timeline: scroll(nearest block); animation-range: calc(100% - 40px) 100%; }  .ui-scroll-start { animation: ui-scroll-show linear both; animation-timeline: scroll(nearest inline); animation-range: 0 40px; }  .ui-scroll-end { animation: ui-scroll-hide linear both; animation-timeline: scroll(nearest inline); animation-range: calc(100% - 40px) 100%; }}@keyframes ui-scroll-show { from { opacity: 0; } to { opacity: 1; } }@keyframes ui-scroll-hide { from { opacity: 1; } to { opacity: 0; } }`view ScrollArea(height: str? = "280px", width: str? = null, axis: str = "y", label: str? = null, fade: bool = true,                pad: int = 0, thin: bool = true, id: str? = null, class: str? = null, children) {    style global scroll_area_css    let across = axis == "x" || axis == "both"    let down = axis == "y" || axis == "both"    <div class={ui_cls(["ui-scroll", axis == "x" && "ui-scroll-x", axis == "both" && "ui-scroll-both", thin && "ui-scroll-thin", class])}         id={id} tabindex={label != null ? "0" : null} role={label != null ? "region" : null} aria-label={label}         style={`--ui-scroll-h: ${height ?? "none"}; --ui-scroll-w: ${width ?? "none"}; --ui-scroll-pad: ${pad}px`}>        {fade && down && <span.ui-scroll-fade.ui-scroll-top aria-hidden="true"></span>}        {fade && across && <span.ui-scroll-fade.ui-scroll-start aria-hidden="true"></span>}        {children}        {fade && across && <span.ui-scroll-fade.ui-scroll-end aria-hidden="true"></span>}        {fade && down && <span.ui-scroll-fade.ui-scroll-bottom aria-hidden="true"></span>}    </div>}
Thread
67Useful? Like it, or add to its thread below.

Thread 0 posts

On ScrollArea: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.