Layout

Reel

A strip that scrolls sideways and snaps to each item, with a slim scrollbar or none, and the keyboard.

#layout#list#mobile#no-js

Demo

Preview

Episode 1

21 min

Episode 2

24 min

Episode 3

27 min

Episode 4

30 min

Episode 5

33 min

Episode 6

36 min

Episode 7

39 min

Episode 8

42 min

Episode 9

45 min

Episode 10

48 min

Episode 11

51 min

Episode 12

54 min

Swipe, scroll sideways, or focus a strip and use the arrow keys. The first snaps to each card's start, the second to each day's middle, with no scrollbar.

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/reel.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/reel.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/reel.qwe
view ReelDemoCard(i: int) =    <Card title={`Episode ${i}`} subtitle={`${18 + i * 3} min`} padding="sm">        <div style={`height: 70px; border-radius: var(--ui-radius-sm); background: linear-gradient(135deg, var(--ui-accent-soft), var(--ui-${i % 2 == 0 ? "info" : "success"}-soft))`}></div>    </Card>view ReelDemo() =    <div.demo-stack>        <Reel id="reel-demo-episodes" label="Episodes" item_width="200px">            {for i in range(1, 13) => <ReelDemoCard i={i}/>}        </Reel>        <Reel id="reel-demo-days" label="Days" gap="sm" snap="center" bar={false} pad={4}>            {for d in ["Mon 3", "Tue 4", "Wed 5", "Thu 6", "Fri 7", "Sat 8", "Sun 9", "Mon 10", "Tue 11", "Wed 12", "Thu 13", "Fri 14", "Sat 15", "Sun 16", "Mon 17", "Tue 18"] =>                <Button variant={d == "Wed 5" ? "soft" : "outline"} size="sm">{d}</Button>}        </Reel>        <p.demo-note>Swipe, scroll sideways, or focus a strip and use the arrow keys. The first snaps to each card's start, the second to each day's middle, with no scrollbar.</p>    </div>

Source

ui/reel.qwe
// Reel: a strip of things that scrolls sideways: cards, photos, chips, a row of// days. It snaps to each item, scrolls with a swipe, a wheel or the keyboard (it// takes focus), and shows a slim scrollbar, or none. No script runs.////   <Reel label="Recently viewed" item_width="220px"> {for p in products => <ProductCard .../>} </Reel>//   <Reel gap="sm" snap="center" bar={false}> {for d in days => <DayChip .../>} </Reel>//// item_width: every item's width, as CSS ("220px"; left out, each is as wide as it is).// gap: pixels (12, the default) or "none", "xs" (4), "sm" (8), "md" (12), "lg" (20), "xl" (32).// snap: where scrolling stops, each item's "start" (default) or "center", or "none".// bar: the scrollbar shows (true). pad: room inside, in pixels (0), kept by the snapping.// label: its name for screen readers; it then takes focus, so the arrow keys scroll it.// height: the strip's height, as CSS; then an <img> in it is that tall and as wide// as its shape says.import ui/theme.qwe use ui_clslet reel_css = css`.ui-reel {  display: flex; gap: var(--ui-reel-gap, 12px); min-width: 0; padding: var(--ui-reel-pad, 0); overflow: auto hidden;  overscroll-behavior-x: contain; scroll-snap-type: x proximity; scroll-padding-inline: var(--ui-reel-pad, 0);  scrollbar-width: thin; scrollbar-color: var(--ui-strong) transparent; outline: none; border-radius: var(--ui-radius-sm);}.ui-reel > *, .ui-reel > quillo-raw > * { flex: none; width: var(--ui-reel-item, auto); min-width: 0; scroll-snap-align: start; }.ui-reel-center > *, .ui-reel-center > quillo-raw > * { scroll-snap-align: center; }.ui-reel-nosnap { scroll-snap-type: none; }.ui-reel-tall > img, .ui-reel-tall > quillo-raw > img { width: auto; height: var(--ui-reel-h); }.ui-reel-nobar { scrollbar-width: none; }.ui-reel-nobar::-webkit-scrollbar { display: none; }.ui-reel:focus-visible { box-shadow: var(--ui-focus); }@media (prefers-reduced-motion: no-preference) { .ui-reel { scroll-behavior: smooth; } }`fn reel_gap(gap) -> str =    match gap {        int as n => `${n}px`        float as n => `${n}px`        "none" => "0"        "xs" => "4px"        "sm" => "8px"        "lg" => "20px"        "xl" => "32px"        _ => "12px"    }view Reel(item_width: str? = null, gap = 12, snap: str = "start", bar: bool = true, pad: int = 0, label: str? = null,          height: str? = null, id: str? = null, class: str? = null, children) {    style global reel_css    <div class={ui_cls(["ui-reel", snap == "center" && "ui-reel-center", snap == "none" && "ui-reel-nosnap",                        !bar && "ui-reel-nobar", height != null && "ui-reel-tall", class])}         id={id} tabindex={label != null ? "0" : null} role={label != null ? "region" : null} aria-label={label}         style={`--ui-reel-gap: ${reel_gap(gap)}; --ui-reel-item: ${item_width ?? "auto"}; --ui-reel-pad: ${pad}px${height != null ? `; --ui-reel-h: ${height}` : ""}`}>        {children}    </div>}
Thread
45Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.