Data

Roadmap

Columns (Now, Next, Later) of items with a status badge, tags, a target date and votes; side by side, stacked where narrow.

#list#card#progress#no-js

Demo

Preview

Now

2

Being built

  • Dark mode everywhere

    Every page and email follows the system.

    In progressuiNov 2026▲ 214
  • Faster search

    Waiting on the new index.

    Blockedsearch

Next

2

Planned for the quarter

  • CSV export

    PlannedQ1 2027▲ 88
  • Two-factor sign-in

    PlannedsecurityQ1 2027

Later

2
  • Plugins

    Rules that run in the browser and the server.

    Exploring
  • Offline mode

    Someday

Shipped

0
Nothing 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.qwe
main.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>}
Thread
86Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.