Actions

Fab

A round floating action button fixed to a corner of the screen, with an icon and an optional label.

#button#forms#mobile#no-js

Demo

Preview

These are inline, in the corners of the box above; without inline they sit in the corners of the screen.

Not called yet.

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

Example

The code of the preview above.

demos/fab.qwe
component FabDemoLive() {    state calls = 0    <div.demo-stack>        <div style="position: relative; height: 120px; border: 1px dashed var(--ui-border); border-radius: var(--ui-radius)">            <Fab id="fab-demo-call" label="Call us" icon="phone" tone="neutral" inline={true} on_click={() => { calls += 1 }}/>        </div>        <p.demo-note>{calls == 0 ? "Not called yet." : `Called ${calls} ${calls == 1 ? "time" : "times"}.`}</p>    </div>}view FabDemo() =    <div.demo-stack>        <div style="position: relative; height: 150px; border: 1px dashed var(--ui-border); border-radius: var(--ui-radius)">            <Fab id="fab-demo-new" label="New message" icon="edit" href="#fab" inline={true}/>            <Fab id="fab-demo-add" label="Add" extended={true} corner="bottom-left" inline={true}/>            <Fab label="Search" icon="search" size="sm" corner="top-right" inline={true}/>            <Fab label="Delete" icon="x" tone="danger" size="lg" corner="top-left" inline={true}/>        </div>        <p.demo-note>These are inline, in the corners of the box above; without inline they sit in the corners of the screen.</p>        <FabDemoLive/>    </div>

Source

ui/fab.qwe
// Fab: a floating action button, round, fixed to a corner of the screen, for the one// thing a page is mostly for: write, add, call. An icon, and a label beside it when// extended; the label is always its name for screen readers and its tooltip.////   <Fab label="New message" icon="edit" href="/messages/new"/>//   <Fab label="Add" extended={true} corner="bottom-left"/>//   <Fab label="Call us" icon="phone" tone="neutral" on_click={() => { calling = true }}/>//// label (required): its name; shown beside the icon when extended.// icon: "plus" (default), "edit", "message", "search", "phone", "mail", "share", "heart",// "up", "check" or "x"; or icon={null} and your own <svg> as children.// href makes it a link; else on_click(), inside one of your components.// corner: "bottom-right" (default), "bottom-left", "top-right" or "top-left".// inline: not fixed to the screen but to the corner of its nearest positioned ancestor// (a panel with `position: relative`), as in the gallery.// tone: "accent" (default), "neutral" or "danger". size: "sm", "md" (default) or "lg".// id: the element's id, for a test or a link to it.import ui/theme.qwe use ui_clslet fab_css = css`.ui-fab {  position: fixed; z-index: 40; display: inline-flex; align-items: center; justify-content: center; gap: 8px;  width: 56px; height: 56px; padding: 0; border: 0; border-radius: 999px; background: var(--b); color: var(--bf);  box-shadow: var(--ui-shadow-lg); font: 600 15px/1 var(--ui-font); text-decoration: none; white-space: nowrap; cursor: pointer;  transition: transform .15s, box-shadow .15s, background .15s;  --b: var(--ui-accent); --bf: var(--ui-accent-fg);}.ui-fab-bottom-right { right: max(20px, env(safe-area-inset-right)); bottom: max(20px, env(safe-area-inset-bottom)); }.ui-fab-bottom-left { left: max(20px, env(safe-area-inset-left)); bottom: max(20px, env(safe-area-inset-bottom)); }.ui-fab-top-right { right: max(20px, env(safe-area-inset-right)); top: max(20px, env(safe-area-inset-top)); }.ui-fab-top-left { left: max(20px, env(safe-area-inset-left)); top: max(20px, env(safe-area-inset-top)); }.ui-fab-inline { position: absolute; z-index: 2; }.ui-fab-inline.ui-fab-bottom-right { right: 16px; bottom: 16px; }.ui-fab-inline.ui-fab-bottom-left { left: 16px; bottom: 16px; }.ui-fab-inline.ui-fab-top-right { right: 16px; top: 16px; }.ui-fab-inline.ui-fab-top-left { left: 16px; top: 16px; }.ui-fab-neutral { --b: var(--ui-fg); --bf: var(--ui-bg); }.ui-fab-danger { --b: var(--ui-danger); --bf: #fff; }.ui-fab:hover { background: color-mix(in srgb, var(--b) 88%, var(--ui-fg)); transform: translateY(-1px); }.ui-fab:active { transform: none; }.ui-fab:focus-visible { outline: none; box-shadow: var(--ui-shadow-lg), var(--ui-focus); }.ui-fab[aria-disabled="true"] { opacity: .55; cursor: not-allowed; pointer-events: none; }.ui-fab svg { flex: none; width: 24px; height: 24px; }.ui-fab-extended { width: auto; padding: 0 20px 0 16px; }.ui-fab-sm { width: 44px; height: 44px; font-size: 14px; }.ui-fab-sm svg { width: 20px; height: 20px; }.ui-fab-sm.ui-fab-extended { padding: 0 16px 0 12px; }.ui-fab-lg { width: 68px; height: 68px; font-size: 16px; }.ui-fab-lg svg { width: 28px; height: 28px; }.ui-fab-lg.ui-fab-extended { padding: 0 26px 0 20px; }@media (prefers-reduced-motion: reduce) { .ui-fab { transition: none; } .ui-fab:hover { transform: none; } }`// The path of a built-in icon (24 by 24, drawn with a 2px line), or "" for a name it does not know.fn fab_path(name: str) =    match name {        "plus" => "M12 5v14M5 12h14"        "edit" => "M12 20h9M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"        "message" => "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2Z"        "search" => "M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM21 21l-4.3-4.3"        "phone" => "M22 16.9v3a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3.1 19.5 19.5 0 0 1-6-6A19.8 19.8 0 0 1 2.1 4.2 2 2 0 0 1 4.1 2h3a2 2 0 0 1 2 1.7c.1.9.4 1.9.7 2.8a2 2 0 0 1-.5 2.1L8.1 9.9a16 16 0 0 0 6 6l1.3-1.3a2 2 0 0 1 2.1-.4c.9.3 1.8.6 2.8.7a2 2 0 0 1 1.7 2Z"        "mail" => "M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2ZM22 7l-10 7L2 7"        "share" => "M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8M16 6l-4-4-4 4M12 2v13"        "heart" => "M20.8 4.6a5.5 5.5 0 0 0-7.8 0L12 5.7l-1-1.1a5.5 5.5 0 0 0-7.8 7.8l1 1L12 21l7.8-7.6 1-1a5.5 5.5 0 0 0 0-7.8Z"        "up" => "M12 19V5M5 12l7-7 7 7"        "check" => "M20 6 9 17l-5-5"        "x" => "M18 6 6 18M6 6l12 12"        _ => ""    }view Fab(label: str, icon: str? = "plus", href: str? = null, extended: bool = false, corner: str = "bottom-right",         inline: bool = false, tone: str = "accent", size: str = "md", disabled: bool = false, id: str? = null,         on_click = null, class: str? = null, children) {    style global fab_css    let cls = ui_cls(["ui-fab", `ui-fab-${corner}`, inline && "ui-fab-inline", extended && "ui-fab-extended",                      tone != "accent" && `ui-fab-${tone}`, size != "md" && `ui-fab-${size}`, class])    let body = [        icon != null            ? <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"                   stroke-linejoin="round" aria-hidden="true"><path d={fab_path(icon)}/></svg>            : children,        extended && <span>{label}</span>,    ]    href != null        ? <a class={cls} id={id} href={href} aria-label={extended ? null : label} title={label}             aria-disabled={disabled ? "true" : null}>{body}</a>        : <button class={cls} id={id} type="button" aria-label={extended ? null : label} title={label} disabled={disabled}                  @click={() => { on_click && on_click() }}>{body}</button>}
Thread
57Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.