Roadmap
Columns (Now, Next, Later) of items with a status badge, tags, a target date and votes; side by side, stacked where narrow.
Demo
Now
2Being built
Dark mode everywhere
Every page and email follows the system.
Faster search
Waiting on the new index.
Later
2Plugins
Rules that run in the browser and the server.
Offline mode
Shipped
0Nothing here 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/roadmap.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/roadmap.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/roadmap.qwe
view RoadmapDemo() = <Roadmap id="roadmap-demo" label="Product roadmap" columns={[ { title: "Now", text: "Being built", items: [ { title: "Dark mode everywhere", text: "Every page and email follows the system.", status: "in-progress", tags: ["ui"], date: "Nov 2026", votes: 214 }, { title: "Faster search", status: "blocked", text: "Waiting on the new index.", tags: ["search"] }, ] }, { title: "Next", text: "Planned for the quarter", items: [ { title: "CSV export", status: "planned", date: "Q1 2027", href: "#roadmap", votes: 88 }, { title: "Two-factor sign-in", status: "planned", tags: ["security"], date: "Q1 2027" }, ] }, { title: "Later", items: [{ title: "Plugins", status: "exploring", text: "Rules that run in the browser and the server." }, { title: "Offline mode", status: "someday" }] }, { title: "Shipped", items: [] }, ]}/>Source
ui/roadmap.qwe
// Roadmap: what is being worked on now, next and later, as columns of items, each// with its status, a line about it, tags and a target date: a product's public// roadmap, a team's plan.//// <Roadmap columns={[// { title: "Now", items: [{ title: "Dark mode", status: "in-progress", text: "Every page", tags: ["ui"] }] },// { title: "Next", items: [{ title: "Exports", status: "planned", date: "Q1 2027" }] },// { title: "Later", items: [{ title: "Plugins", status: "exploring" }] },// ]}/>//// columns: a list of { title, text?, items }: text a line under the column's// title. items: maps of { title, text?, status?, tags?, date?, href?, votes? }:// status is "done", "in-progress", "planned", "exploring" or "blocked", a badge in// its colour (any other word shows as it is, grey); date when it is aimed for, as// it should read; href makes the title a link; votes a count of people who want it.// label: what screen readers call it ("Roadmap"). id: its id. The columns sit side// by side and stack where it is narrow; each column is a list.import ui/theme.qwe use ui_clslet roadmap_css = css`.ui-roadmap { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; align-items: start; min-width: 0; font: 14px/1.45 var(--ui-font); color: var(--ui-fg); }.ui-roadmap-col { display: grid; gap: 10px; min-width: 0; padding: 12px; border-radius: var(--ui-radius); background: var(--ui-subtle); }.ui-roadmap-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; padding: 0 4px; }.ui-roadmap-title { margin: 0; font-size: 14px; font-weight: 700; }.ui-roadmap-count { min-width: 22px; padding: 1px 7px; border-radius: 999px; background: var(--ui-surface); color: var(--ui-muted); font-size: 12px; text-align: center; font-variant-numeric: tabular-nums; }.ui-roadmap-sub { margin: -6px 4px 0; color: var(--ui-muted); font-size: 13px; }.ui-roadmap-list { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }.ui-roadmap-item { display: grid; gap: 6px; padding: 12px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); }.ui-roadmap-item-title { margin: 0; font-size: 14px; font-weight: 600; line-height: 1.35; }.ui-roadmap-item-title a { color: inherit; text-decoration: none; }.ui-roadmap-item-title a:hover { color: var(--ui-accent); text-decoration: underline; }.ui-roadmap-item-title a:focus-visible { outline: none; border-radius: 4px; box-shadow: var(--ui-focus); }.ui-roadmap-text { margin: 0; color: var(--ui-muted); font-size: 13px; }.ui-roadmap-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; font-size: 12px; }.ui-roadmap-status { --t: var(--ui-muted); display: inline-flex; align-items: center; gap: 5px; padding: 1px 8px 1px 6px; border-radius: 999px; background: color-mix(in srgb, var(--t) 14%, transparent); color: var(--t); font-weight: 600; }.ui-roadmap-status::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }.ui-roadmap-done { --t: var(--ui-success); }.ui-roadmap-in-progress { --t: var(--ui-accent); }.ui-roadmap-planned { --t: var(--ui-info); }.ui-roadmap-exploring { --t: var(--ui-warning); }.ui-roadmap-blocked { --t: var(--ui-danger); }.ui-roadmap-tag { padding: 1px 7px; border-radius: 4px; background: var(--ui-subtle); color: var(--ui-muted); }.ui-roadmap-date, .ui-roadmap-votes { margin-left: auto; color: var(--ui-muted); font-variant-numeric: tabular-nums; }.ui-roadmap-date + .ui-roadmap-votes { margin-left: 0; }.ui-roadmap-empty { padding: 14px; border: 1px dashed var(--ui-border); border-radius: var(--ui-radius-sm); color: var(--ui-muted); font-size: 13px; text-align: center; }`let roadmap_known = ["done", "in-progress", "planned", "exploring", "blocked"]// A status as it reads: "in-progress" is "In progress".fn roadmap_status(s: str) -> str { let t = replace(s, "-", " ") upper(slice(t, 0, 1)) + slice(t, 1, len(t))}view Roadmap(columns: list = [], label: str = "Roadmap", empty: str = "Nothing here yet.", id: str? = null, class: str? = null) { style global roadmap_css <section class={ui_cls(["ui-roadmap", class])} id={id} aria-label={label}> {for [ci, col] in enumerate(columns) => { let items = col.items ?? [] let hid = id != null ? `${id}-col-${ci}` : null <div.ui-roadmap-col key={col.title}> <div.ui-roadmap-head> <h3.ui-roadmap-title id={hid}>{col.title}</h3> <span.ui-roadmap-count aria-label={`${len(items)} ${len(items) == 1 ? "item" : "items"}`}>{len(items)}</span> </div> {col.text != null && <p.ui-roadmap-sub>{col.text}</p>} {len(items) == 0 ? <div.ui-roadmap-empty>{empty}</div> : <ul.ui-roadmap-list aria-labelledby={hid} aria-label={hid == null ? col.title : null}> {for it in items => <li.ui-roadmap-item key={it.title}> <h4.ui-roadmap-item-title>{it.href != null ? <a href={it.href}>{it.title}</a> : it.title}</h4> {it.text != null && <p.ui-roadmap-text>{it.text}</p>} {(it.status != null || len(it.tags ?? []) > 0 || it.date != null || it.votes != null) && <div.ui-roadmap-meta> {it.status != null && <span class={ui_cls(["ui-roadmap-status", contains(roadmap_known, it.status) && `ui-roadmap-${it.status}`])}> {roadmap_status(it.status)} </span>} {for t in it.tags ?? [] => <span.ui-roadmap-tag key={t}>{t}</span>} {it.date != null && <span.ui-roadmap-date>{it.date}</span>} {it.votes != null && <span.ui-roadmap-votes aria-label={`${it.votes} votes`}>▲ {it.votes}</span>} </div>} </li>} </ul>} </div> }} </section>}Related
FlowStepsA process as boxes joined by arrows, with words on the arrows, tones and done, current or coming steps; horizontal, or vertical where narrow.6 views · 7 downloadsMilestoneListMilestones down a line with dates, done, current, coming or late, a progress bar on those under way, and the whole project's progress.6 views · 7 downloadsProgressListItems each with a label, a bar and its value or percentage, marked in red when one goes over its limit.6 views · 4 downloadsChecklistInputCheckboxes under a progress line that says how many are done and fills as they are ticked; each posts with a form.5 views · 7 downloadsActivityFeedA stream of what people did, with their pictures, an icon for each kind of action, when, quotes and day headings.5 views · 7 downloadsArticleCardA post to click into: image, category, title, excerpt, author, date and reading time; image on top or to the side.5 views · 7 downloads
Thread
Thread 0 posts
On Roadmap: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.