Actions

IconButton

A square button with an icon, labelled for screen readers and on hover; built-in icons or your own.

#forms#button#icon#no-js

Demo

Preview
Not starred (a toggle, with your own icon)

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

Example

The code of the preview above.

demos/icon_button.qwe
component IconButtonDemoLive() {    state starred = false    <div.demo-row>        <IconButton label="Star" pressed={starred} variant="outline" on_click={() => { starred = !starred }}>            <svg viewBox="0 0 24 24" fill={starred ? "currentColor" : "none"} stroke="currentColor" stroke-width="2"                 stroke-linejoin="round" aria-hidden="true"><path d="m12 2 3.1 6.3 6.9 1-5 4.9 1.2 6.8L12 17.8 5.8 21l1.2-6.8-5-4.9 6.9-1Z"/></svg>        </IconButton>        <span.demo-note>{starred ? "Starred" : "Not starred"} (a toggle, with your own icon)</span>    </div>}view IconButtonDemo() =    <div.demo-stack>        <div.demo-row>            {for i in ["plus", "x", "check", "edit", "trash", "search", "copy", "more", "menu", "settings", "chevron-down"] =>                <IconButton icon={i} label={i}/>}        </div>        <div.demo-row>            <IconButton icon="plus" label="Add" variant="solid" tone="accent"/>            <IconButton icon="edit" label="Edit" variant="soft" tone="accent"/>            <IconButton icon="settings" label="Settings" variant="outline" href="#icon_button"/>            <IconButton icon="trash" label="Delete" tone="danger"/>            <IconButton icon="x" label="Close" size="sm" round={true}/>            <IconButton icon="search" label="Search" size="lg" variant="outline" round={true}/>            <IconButton icon="copy" label="Copy" disabled={true}/>        </div>        <IconButtonDemoLive/>    </div>

Source

ui/icon_button.qwe
// IconButton: a square button that shows an icon instead of words. Its label is// what screen readers say and what shows when the pointer rests on it.////   <IconButton icon="trash" label="Delete" tone="danger"/>//   <IconButton icon="settings" label="Settings" href="/settings" variant="outline"/>//   <IconButton label="Star"><svg viewBox="0 0 24 24">...</svg></IconButton>//// icon: one of "plus", "x", "check", "edit", "trash", "search", "copy", "more",// "menu", "settings", "chevron-down"; or leave it out and give your own <svg> as// children (it is sized to 18px and drawn in the text colour).// label (required): its aria-label and title.// variant: "ghost" (default), "soft", "outline" or "solid".// tone: "neutral" (default), "accent" or "danger". size: "sm", "md" (default) or "lg".// round: a circle. pressed: true or false makes it a toggle (aria-pressed).// href makes it an <a>; otherwise type, disabled and on_click() go to the <button>.import ui/theme.qwe use ui_clslet icon_button_css = css`.ui-icon-btn {  display: inline-grid; place-items: center; flex: none; width: 38px; height: 38px; padding: 0;  border: 1px solid transparent; border-radius: var(--ui-radius-sm); text-decoration: none; cursor: pointer;  transition: background .15s, border-color .15s, color .15s, box-shadow .15s;  --b: var(--ui-fg); --bf: var(--ui-bg); --bs: var(--ui-subtle);}.ui-icon-btn svg { width: 18px; height: 18px; }.ui-icon-btn:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-icon-btn[disabled], .ui-icon-btn[aria-disabled="true"] { opacity: .55; cursor: not-allowed; pointer-events: none; }.ui-icon-btn-accent { --b: var(--ui-accent); --bf: var(--ui-accent-fg); --bs: var(--ui-accent-soft); }.ui-icon-btn-danger { --b: var(--ui-danger); --bf: #fff; --bs: var(--ui-danger-soft); }.ui-icon-btn-ghost { background: transparent; color: var(--b); }.ui-icon-btn-ghost.ui-icon-btn-neutral { color: var(--ui-muted); }.ui-icon-btn-ghost:hover { background: var(--bs); color: var(--b); }.ui-icon-btn-soft { background: var(--bs); color: var(--b); }.ui-icon-btn-soft:hover { background: color-mix(in srgb, var(--bs) 80%, var(--b)); }.ui-icon-btn-outline { background: transparent; color: var(--b); border-color: var(--ui-border); }.ui-icon-btn-outline:hover { border-color: var(--b); background: var(--bs); }.ui-icon-btn-solid { background: var(--b); color: var(--bf); }.ui-icon-btn-solid:hover { background: color-mix(in srgb, var(--b) 88%, var(--ui-fg)); }.ui-icon-btn[aria-pressed="true"] { background: var(--ui-accent-soft); color: var(--ui-accent); border-color: transparent; }.ui-icon-btn-sm { width: 30px; height: 30px; }.ui-icon-btn-sm svg { width: 16px; height: 16px; }.ui-icon-btn-lg { width: 46px; height: 46px; }.ui-icon-btn-lg svg { width: 22px; height: 22px; }.ui-icon-btn-round { border-radius: 50%; }@media (prefers-reduced-motion: reduce) { .ui-icon-btn { transition: 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 icon_button_path(name: str) =    match name {        "plus" => "M12 5v14M5 12h14"        "x" => "M18 6 6 18M6 6l12 12"        "check" => "M20 6 9 17l-5-5"        "edit" => "M12 20h9M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"        "trash" => "M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M10 11v6M14 11v6"        "search" => "M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM21 21l-4.3-4.3"        "copy" => "M10 8h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H10a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2ZM4 16a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2"        "more" => "M12 13a1 1 0 1 0 0-2 1 1 0 0 0 0 2ZM19 13a1 1 0 1 0 0-2 1 1 0 0 0 0 2ZM5 13a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"        "menu" => "M4 6h16M4 12h16M4 18h16"        "settings" => "M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z"        "chevron-down" => "m6 9 6 6 6-6"        _ => ""    }view IconButton(label: str, icon: str? = null, variant: str = "ghost", tone: str = "neutral", size: str = "md",                href: str? = null, type: str = "button", disabled: bool = false, round: bool = false,                pressed: bool? = null, on_click = null, class: str? = null, children) {    style global icon_button_css    let cls = ui_cls(["ui-icon-btn", `ui-icon-btn-${variant}`, `ui-icon-btn-${tone}`,                      size != "md" && `ui-icon-btn-${size}`, round && "ui-icon-btn-round", 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={icon_button_path(icon)}/></svg>        : children    href != null        ? <a class={cls} href={href} aria-label={label} title={label} aria-disabled={disabled ? "true" : null}>{body}</a>        : <button class={cls} type={type} disabled={disabled} aria-label={label} title={label}                  aria-pressed={pressed == null ? null : pressed ? "true" : "false"}                  @click={() => { on_click && on_click() }}>{body}</button>}
Thread
69Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.