Navigation

BackToTop

A button in the corner that shows once the page is scrolled down and takes it back to the top.

#navigation#button#utility#interactive

Demo

Preview

Version 0.24: fixes and small improvements, item 1 of a long list.

Version 0.23: fixes and small improvements, item 2 of a long list.

Version 0.22: fixes and small improvements, item 3 of a long list.

Version 0.21: fixes and small improvements, item 4 of a long list.

Version 0.20: fixes and small improvements, item 5 of a long list.

Version 0.19: fixes and small improvements, item 6 of a long list.

Version 0.18: fixes and small improvements, item 7 of a long list.

Version 0.17: fixes and small improvements, item 8 of a long list.

Version 0.16: fixes and small improvements, item 9 of a long list.

Version 0.15: fixes and small improvements, item 10 of a long list.

Version 0.14: fixes and small improvements, item 11 of a long list.

Version 0.13: fixes and small improvements, item 12 of a long list.

Version 0.12: fixes and small improvements, item 13 of a long list.

Version 0.11: fixes and small improvements, item 14 of a long list.

Version 0.10: fixes and small improvements, item 15 of a long list.

Version 0.9: fixes and small improvements, item 16 of a long list.

Version 0.8: fixes and small improvements, item 17 of a long list.

Version 0.7: fixes and small improvements, item 18 of a long list.

Version 0.6: fixes and small improvements, item 19 of a long list.

Version 0.5: fixes and small improvements, item 20 of a long list.

Version 0.4: fixes and small improvements, item 21 of a long list.

Version 0.3: fixes and small improvements, item 22 of a long list.

Version 0.2: fixes and small improvements, item 23 of a long list.

Version 0.1: fixes and small improvements, item 24 of a long list.

Scroll the list above: the button shows in its corner. This page has one too, at the bottom right of the window, once it is scrolled far down.

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

Example

The code of the preview above.

demos/back_to_top.qwe
view BackToTopDemo() =    <div.demo-stack>        <div id="btt-demo-log" tabindex="0" aria-label="Release notes"             style="height: 260px; overflow: auto; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); padding: 4px 18px">            {for v in range(1, 25) => <p>Version 0.{25 - v}: fixes and small improvements, item {v} of a long list.</p>}            <BackToTop scroller="btt-demo-log" after={200} label="Back to the newest" target="btt-demo-log"/>        </div>        <p.demo-note>Scroll the list above: the button shows in its corner. This page has one too, at the bottom right            of the window, once it is scrolled far down.</p>        <BackToTop after={1600} show_label={true}/>    </div>

Source

ui/back_to_top.qwe
// BackToTop: a round button in the corner that shows once the page is scrolled// down, and takes it back to the top: smoothly, or at once for people who ask// their system for less motion. Hidden, it cannot be reached or heard.////   <BackToTop target="main"/>//   <div id="log" style="height: 400px; overflow: auto"> ... <BackToTop scroller="log"/></div>//// after: how far down (in pixels) the page is scrolled before it shows (400).// label: its name and tooltip ("Back to top"); show_label: the label shows beside// the arrow too. target: the id of an element to move the focus to once at the// top (the page's <main> or first heading), so the keyboard carries on from// there. scroller: the id of a scrolling element to follow instead of the window;// then put the BackToTop last inside it, and it stays at its bottom right corner.import ui/theme.qwe use ui_clslet back_to_top_css = css`.ui-btt {  position: fixed; right: max(20px, env(safe-area-inset-right)); bottom: max(20px, env(safe-area-inset-bottom)); z-index: 40;}.ui-btt-inline { position: sticky; right: auto; bottom: 12px; z-index: 2; height: 0; }.ui-btt-inline .ui-btt-btn { position: absolute; right: 12px; bottom: 0; }.ui-btt-btn {  display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-width: 44px; height: 44px; padding: 0 12px;  border: 1px solid var(--ui-border); border-radius: 999px; background: var(--ui-surface); color: var(--ui-fg);  box-shadow: var(--ui-shadow); font: 500 14px/1 var(--ui-font); cursor: pointer;  opacity: 0; visibility: hidden; transform: translateY(8px);  transition: opacity .2s, transform .2s, visibility .2s, background .15s, border-color .15s, box-shadow .15s;}.ui-btt-btn svg { width: 18px; height: 18px; flex: none; }.ui-btt-btn:not(.ui-btt-labelled) { width: 44px; padding: 0; }.ui-btt-shown .ui-btt-btn { opacity: 1; visibility: visible; transform: none; }.ui-btt-btn:hover { border-color: var(--ui-strong); background: var(--ui-subtle); }.ui-btt-btn:focus-visible { outline: none; box-shadow: var(--ui-shadow), var(--ui-focus); }@media (prefers-reduced-motion: reduce) { .ui-btt-btn { transition: none; } }`component BackToTop(after: int = 400, label: str = "Back to top", show_label: bool = false, target: str? = null,                    scroller: str? = null, class: str? = null) {    style global back_to_top_css    state shown = false    state box = null    <div class={ui_cls(["ui-btt", scroller != null && "ui-btt-inline", shown && "ui-btt-shown", class])}         @mount={(el) => {             box = scroller != null ? js("document.getElementById", scroller) : null             let from = box ?? js("window")             let check = (_e = null) => { shown = (box != null ? box.scrollTop : js("scrollY")) > after }             check()             from.addEventListener("scroll", check, { passive: true })             () => from.removeEventListener("scroll", check)         }}>        <button type="button" class={ui_cls(["ui-btt-btn", show_label && "ui-btt-labelled"])}                aria-label={show_label ? null : label} title={label}                @click={(e) => {                    let still = js("matchMedia", "(prefers-reduced-motion: reduce)").matches                    (box ?? js("window")).scrollTo({ top: 0, behavior: still ? "instant" : "smooth" })                    let to = target != null ? js("document.getElementById", target) : null                    if to != null {                        if !to.hasAttribute("tabindex") && to.tabIndex < 0 { to.tabIndex = -1 }                        to.focus({ preventScroll: true })                    } else {                        e.target.closest("button").blur()                    }                }}>            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"                 stroke-linejoin="round" aria-hidden="true"><path d="M12 19V5M5 12l7-7 7 7"/></svg>            {show_label && <span>{label}</span>}        </button>    </div>}
Thread
68Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.