Product Roadmap Board — HTML CSS JS Snippet

Product Roadmap Board · Dashboards · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Three-column Planned / In Progress / Shipped layout
Colour-coded status dots matching legend (grey, indigo, green)
Four category tags: feature, bug, perf, ux with colour coding
Upvote toggle: vote is added/removed locally without mutating the source
Derived vote count (base + local vote) updates on every toggle
Quarter labels on upcoming items; ✓ Shipped badge on done items
Item count badge per column header
Data-driven rendering from a plain JavaScript columns array

About this UI Snippet

Product Roadmap Board — Planned, In Progress & Shipped with Upvotes

Screenshot of the Product Roadmap Board snippet rendered live

A public product roadmap board is a standard transparency feature for SaaS products and developer tools, and a frequently-searched UI component. Showing users what is coming, what is being built now, and what has shipped builds trust and reduces "when is X coming?" support requests. This snippet provides a complete three-column Kanban-style roadmap with category tags, target quarters, upvote buttons with live vote counts, and a shipped confirmation badge — the released items typically also appear in a changelog feed.

The three-column model

Planned / In Progress / Shipped is the canonical roadmap structure: planned items are committed but not yet started, in-progress items are actively being built, and shipped items are done and deployed. The columns use a CSS grid with three equal columns. Each column is a card with a coloured status dot in the header, an item count badge, and a scrollable list of feature cards.

Feature cards

Each item card shows a title, a category tag (feature, bug, perf, ux), a short description, an upvote button, and a quarter or shipped badge. The tag uses a colour-coded design: purple for features, red for bugs, blue for performance, and amber for UX — making it easy to scan by type across all three columns.

Upvote system

Each item has a vote button (△ hollow, ▲ filled when voted). Clicking toggles the voted state in a local votes map and re-renders. The displayed count is base votes + (voted ? 1 : 0), derived from the votes map rather than mutating the source array — a clean approach that makes the votes undoable. In production, votes would be persisted to a backend with user authentication to prevent duplicate voting.

Data-driven rendering

The entire board renders from a columns array of plain JavaScript objects. Adding a new feature card is adding an object to the correct column's items array; moving a card between columns is moving the object. In a real product this data would come from a backend, a headless CMS, or a product management tool like Linear, Productboard, or Canny via their public API.

Quarter and shipped labels

Planned and in-progress items show their target quarter (Q3 2026, Q4 2026). Shipped items show a green ✓ Shipped badge instead of a quarter, since the timeline is no longer relevant. This distinction is a small detail that makes the board feel genuinely useful rather than a cosmetic exercise.

Step by step

How to Use

  1. 1
    Browse the roadmapScan the three columns for planned, in-progress, and shipped items. Category tags and quarter labels give context for each item.
  2. 2
    Upvote featuresClick △ on any item to cast your vote. The count increases and the button fills. Click again to remove your vote.
  3. 3
    Add your own itemsAdd an object to any column's items array: { id, title, tag, desc, votes, quarter }. Shipped column items show a "✓ Shipped" badge.
  4. 4
    Move items between columnsCut and paste an item object from one column's items array to another — the column it is in determines its status dot and badge.
  5. 5
    Load from an APIReplace the columns array with a fetch() to your roadmap endpoint (Canny, Productboard, Linear, or your own API) and call render() in the then callback.
  6. 6
    Export for your frameworkClick "React" for a component with votes in useState and columns as props. Click "Vue" for a Vue 3 SFC with a reactive votes map.

Real-world uses

Common Use Cases

Public SaaS product roadmap page
Publish the roadmap at /roadmap on your product site. Load entries from a headless CMS or your own API so the product team can update it without a deploy. The upvote system gives users a voice in prioritisation and gives the product team quantified demand data — how many users want dark mode vs CSV import.
Embed in an in-app feedback hub
Mount the roadmap inside the product itself, visible from a "Roadmap" link in the sidebar or settings menu. Users who just hit a limitation can see if it is already planned or in progress, reducing "where is X?" support requests. The shipped column doubles as an in-app changelog.
Internal sprint board for product and engineering
Adapt the three columns to Now / Next / Later or This Sprint / Next Sprint / Backlog for an internal planning board. Add drag-and-drop to move cards between columns, and replace the vote button with an effort/impact score for a lightweight prioritisation matrix.
Sync with Linear, Canny, or Productboard
Fetch items from Linear's GraphQL API (filtering by project and status), Canny's REST API, or Productboard's features endpoint and map the response to the columns array shape. Persist upvotes to the same backend so votes aggregate across all visitors rather than resetting on refresh.
Stakeholder and investor transparency page
Use the roadmap board as a lightweight external-facing product brief for investors, enterprise buyers, and integration partners. The Shipped column demonstrates execution cadence, In Progress shows current focus, and Planned shows strategic direction — a persuasive combination in sales and due-diligence contexts.
Study Kanban-style data-driven UI rendering
The board renders entirely from a nested data array: columns containing items. The upvote system demonstrates a clean derived-state pattern (base + local vote) rather than mutating source data. These patterns — column-based rendering, toggle state without mutation, data-driven re-render — transfer directly to task managers, CRMs, and project tools.

Got questions?

Frequently Asked Questions

The items array stores a base vote count that never changes. A separate votes plain object acts as a toggle map: votes[id] = true when voted. In the renderer, count = item.votes + (votes[id] ? 1 : 0) derives the displayed value from both sources. This means undoing a vote (deleting votes[id]) automatically returns the displayed count to the original, and the source data stays clean — a pattern that makes syncing to a backend much simpler because you only send the toggle event, not a raw count.

On the client side, store voted item ids in localStorage so votes persist across page reloads. On the backend, record a vote as a row in a votes table with (userId, itemId, createdAt) and a unique constraint on (userId, itemId) to enforce one vote per user per item. The vote endpoint increments a denormalized vote_count on the item row on insert and decrements on delete, returning the new total to the client.

For Linear: query the GraphQL API for issues in a specific project, filtering by state. Map "Backlog" to planned, "In Progress" to progress, and "Done" to shipped. Include the title, description, priority/votes, and target date. For Canny: GET /v1/posts with board_id and category filters. Both APIs return JSON that maps cleanly to the columns[].items shape. Refresh on a server-side schedule and cache so the public page does not hammer the external API.

Accept columns as a prop (or fetch in useEffect). Keep a votes Set in useState for voted item ids. Toggle votes: setVotes(prev => { const next = new Set(prev); next.has(id) ? next.delete(id) : next.add(id); return next; }). Render each column and item from the data, deriving the vote count as item.votes + (votes.has(item.id) ? 1 : 0). The shipped check and quarter label render conditionally based on the column id. For the Tailwind version, click "Tailwind" to get the same markup with utility classes instead of a scoped stylesheet.