Widget
A dashboard panel: a title row with a menu slot, the body, and a footer line or link, as a named region.
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/widget.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/widget.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/widget.qwe
view WidgetDemo() = <div.demo-stack> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; align-items: stretch"> <Widget id="widget-demo-revenue" title="Revenue" subtitle="This month" footer="Updated 2 minutes ago" menu={<DropdownMenu id="widget-demo-menu" label="Period" size="sm" variant="ghost" items={[{ label: "This month" }, { label: "Last month" }, { label: "This year" }]}/>}> <Stat label="Gross" value="$48,210" delta={12.5} delta_suffix="%" card={false} trend={[31, 34, 33, 38, 41, 40, 45, 48]}/> </Widget> <Widget title="Recent orders" href="#widget" link="All orders" padding="none"> <DataTable columns={[{ key: "id", label: "Order" }, { key: "who", label: "Customer" }, { key: "total", label: "Total", align: "right" }]} rows={[{ id: "#1041", who: "Ada L.", total: "$120.00" }, { id: "#1040", who: "Grace H.", total: "$64.50" }, { id: "#1039", who: "Linus B.", total: "$980.00" }]}/> </Widget> <Widget title="Sync" subtitle="Stripe" loading={true} footer={<span>Started <time datetime="2026-10-28T09:12:00Z">09:12</time></span>}> <Progress value={62} label="Importing customers" show_value={true}/> </Widget> </div> </div>Source
ui/widget.qwe
// Widget: one panel of a dashboard: a title row with room for a menu or a// button, the body, and a line along the bottom. For a plain surface, use Card;// a Widget is a named region a screen reader can jump to.//// <Widget title="Revenue" subtitle="This month"><BarChart .../></Widget>// <Widget title="Recent orders" menu={<DropdownMenu id="orders-menu" items={...}/>}// href="/orders" link="All orders"><DataTable .../></Widget>// <Widget title="Sync" loading={true} footer="Updated 2 minutes ago">...</Widget>//// title: the panel's name (an <h3>); subtitle: a quieter line beside it. menu:// HTML at the right end of the title row (a DropdownMenu, an IconButton, a// Badge). children: the body. footer: text, or HTML, along the bottom; href and// link: a link there instead, or as well ("See all" by default). loading: a thin// moving stripe under the title row, and aria-busy, while the body is stale.// padding: "md" (default) or "none" (for a table or a chart that fills it).// id: the panel's id. Inside a DashboardGrid, span says how wide it is.import ui/theme.qwe use ui_clslet widget_css = css`.ui-widget { display: flex; flex-direction: column; min-width: 0; min-height: 100%; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); color: var(--ui-fg); font: 14px/1.5 var(--ui-font); }.ui-widget-head { position: relative; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 18px 12px; }.ui-widget-titles { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; min-width: 0; }.ui-widget-title { margin: 0; font-size: 15px; font-weight: 600; line-height: 1.3; }.ui-widget-sub { margin: 0; color: var(--ui-muted); font-size: 13px; }.ui-widget-menu { display: flex; flex: none; align-items: center; gap: 6px; margin: -6px -8px -6px 0; }.ui-widget-stripe { position: absolute; left: 0; right: 0; bottom: 0; height: 2px; overflow: hidden; }.ui-widget-stripe::after { content: ""; position: absolute; width: 40%; height: 100%; background: var(--ui-accent); animation: ui-widget-slide 1.3s ease-in-out infinite; }@keyframes ui-widget-slide { from { left: -40%; } to { left: 100%; } }.ui-widget-body { flex: 1; padding: 4px 18px 18px; }.ui-widget-head + .ui-widget-body:empty { display: none; }.ui-widget-body > :first-child { margin-top: 0; }.ui-widget-body > :last-child { margin-bottom: 0; }.ui-widget-bare .ui-widget-body { padding: 0; }.ui-widget-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 18px; border-top: 1px solid var(--ui-border); color: var(--ui-muted); font-size: 13px; }.ui-widget-foot > * { margin: 0; }.ui-widget-link { margin-left: auto; color: var(--ui-accent); font-weight: 500; text-decoration: none; white-space: nowrap; border-radius: 3px; }.ui-widget-link:hover { text-decoration: underline; }.ui-widget-link:focus-visible { outline: none; box-shadow: var(--ui-focus); }@media (prefers-reduced-motion: reduce) { .ui-widget-stripe::after { left: 0; width: 100%; opacity: .45; animation: none; } }`view Widget(title: str, subtitle: str? = null, menu = null, footer = null, href: str? = null, link: str = "See all", loading: bool = false, padding: str = "md", id: str? = null, class: str? = null, children) { style global widget_css let tid = id != null ? `${id}-title` : null <section id={id} class={ui_cls(["ui-widget", padding == "none" && "ui-widget-bare", loading && "ui-widget-loading", class])} aria-labelledby={tid} aria-label={tid == null ? title : null} aria-busy={loading ? "true" : null}> <div.ui-widget-head> <div.ui-widget-titles> <h3.ui-widget-title id={tid}>{title}</h3> {subtitle != null && <p.ui-widget-sub>{subtitle}</p>} </div> {menu != null && <div.ui-widget-menu>{menu}</div>} {loading && <div.ui-widget-stripe aria-hidden="true"></div>} </div> <div.ui-widget-body>{children}</div> {(footer != null || href != null) && <div.ui-widget-foot> {footer != null && (type_of(footer) == "str" ? <p>{footer}</p> : footer)} {href != null && <a.ui-widget-link href={href}>{link} →</a>} </div>} </section>}Related
PanelA bordered box in three parts: a header bar with a title and controls, a body, and a footer bar for actions.5 views · 7 downloadsDashboardGridWidgets on a twelve-column grid, each spanning columns and rows (DashboardCell), stacking on a phone.7 views · 9 downloadsSettingsSectionA titled block of settings rows, each a label and hint with its control at the right (SettingsRow).5 views · 5 downloadsBentoGridA grid of tiles spanning columns and rows, with titles, tints and links, packed to leave no holes.9 views · 6 downloadsCardA surface for one thing, with a title, a header and a footer, or a link as a whole.8 views · 6 downloadsMasonryCards of different heights packed into columns, each filled from the top, on CSS columns alone.6 views · 7 downloads
Thread
Thread 0 posts
On Widget: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.