IconButton
A square button with an icon, labelled for screen readers and on hover; built-in icons or your own.
Demo
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.qwemain.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>}Related
BookmarkButtonA save-for-later toggle whose bookmark fills when pressed, with or without its label.8 views · 8 downloadsButtonA button, or a link that looks like one: four variants, three tones, three sizes, loading.7 views · 7 downloadsButtonGroupButtons joined edge to edge into one control, or spaced in a row; across or upright.6 views · 6 downloadsFabA round floating action button fixed to a corner of the screen, with an icon and an optional label.5 views · 7 downloadsFollowButtonFollow becomes Following once pressed, and says Unfollow on hover, as the social sites do.5 views · 7 downloadsLikeButtonA heart that toggles with a count that follows it, a real toggle for screen readers and forms.5 views · 4 downloads
Thread
Thread 0 posts
On IconButton: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.