# Roadmap 1

> A three-column Now, Next and Later board that publishes order rather than dates

A shadcn/ui block from the [@shadcnstore](https://www.shadcnstore.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/shadcnstore/roadmap-1).

- Page, with a live preview: https://shadcn.exchange/r/shadcnstore/roadmap-1
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/shadcnstore/roadmap-1-light-8c17fc954d1f1449.png
- Registry item: https://shadcnstore.com/r/roadmap-1.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://shadcnstore.com/r/roadmap-1.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `badge`, `card`

## Files

### `storage/app/registry-sources/marketing/product-updates/roadmap-1/page.tsx`

```tsx
import { Roadmap1 } from "@/components/blocks/marketing/product-updates/roadmap-1/components/roadmap-1"

export default function Page() {
  return (
    <div className="flex min-h-svh w-full items-start justify-center">
      <div className="w-full">
        <Roadmap1 />
      </div>
    </div>
  )
}
```

### `storage/app/registry-sources/marketing/product-updates/roadmap-1/roadmap-1.tsx`

```tsx
import { Badge } from '@/components/ui/badge'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { cn } from '@/lib/utils'

import {
  roadmapColumnLabels,
  roadmapColumnOrder,
  roadmapEntries,
  simpleRoadmapCopy,
} from "@/components/data/roadmap-1-data"

const dateFormat = new Intl.DateTimeFormat('en-US', {
  year: 'numeric',
  month: 'long',
  day: 'numeric',
})

/**
 * A three-column roadmap that publishes order rather than dates.
 *
 * The columns are Now / Next / Later, not quarters. A quarter is a promise a roadmap cannot keep,
 * and the day one slips the whole board reads as stale — which is why so many public roadmaps are
 * eighteen months out of date. Order is a promise a team can actually keep, so the board stays
 * true for longer and needs editing less often.
 *
 * Only the first column is marked. Every column tinted is no emphasis at all, and the one thing a
 * reader wants from a roadmap on first glance is which of these is happening now.
 *
 * No hooks and no handlers, so it renders as a server component straight from your own board.
 */
export function Roadmap1() {
  return (
    <section aria-labelledby="roadmap-1-heading" className="w-full py-16 sm:py-24">
      <div className="mx-auto w-full max-w-6xl px-4 sm:px-6 lg:px-8">
        <div className="max-w-2xl">
          <p className="text-muted-foreground text-sm font-medium">{simpleRoadmapCopy.eyebrow}</p>
          <h2 id="roadmap-1-heading" className="mt-2 text-3xl font-semibold tracking-tight sm:text-4xl">
            {simpleRoadmapCopy.heading}
          </h2>
          <p className="text-muted-foreground mt-3 text-lg text-pretty">{simpleRoadmapCopy.lede}</p>
          <p className="text-muted-foreground mt-3 text-sm">
            {'Last updated '}
            <time dateTime={simpleRoadmapCopy.updatedAt}>
              {dateFormat.format(new Date(simpleRoadmapCopy.updatedAt))}
            </time>
          </p>
        </div>

        <div className="mt-12 grid gap-6 md:grid-cols-3">
          {roadmapColumnOrder.map((column, columnIndex) => {
            const entries = roadmapEntries.filter(entry => entry.column === column)
            const label = roadmapColumnLabels[column]

            return (
              /*
                A section per column, each labelled, so a screen reader can move between them and
                hear which is which rather than meeting one long undifferentiated list.
              */
              <section key={column} aria-labelledby={`roadmap-1-${column}`} className="flex flex-col gap-4">
                <div
                  className={cn(
                    'border-t pt-4',
                    // Only "Now" is marked. Emphasis on every column is emphasis on none.
                    columnIndex === 0 && 'border-t-primary border-t-2',
                  )}
                >
                  <div className="flex items-baseline justify-between gap-2">
                    <h3 id={`roadmap-1-${column}`} className="font-medium">
                      {label.title}
                    </h3>
                    {/*
                      The count is decorative next to a list the reader can see; the heading above
                      already names the column, so this is hidden rather than read out as a stray
                      number after it.
                    */}
                    <span aria-hidden="true" className="text-muted-foreground text-sm tabular-nums">
                      {entries.length}
                    </span>
                  </div>
                  <p className="text-muted-foreground mt-1 text-sm">{label.hint}</p>
                </div>

                <ul className="flex flex-col gap-3">
                  {entries.map(entry => (
                    <li key={entry.id}>
                      {/*
                        The full Card composition rather than everything dumped in CardContent:
                        the title and the sentence under it are a header and its description, and
                        saying so is what lets a buyer restyle one card in one place.
                      */}
                      <Card className="h-full gap-3 py-4">
                        <CardHeader className="px-4">
                          <CardTitle className="text-base leading-6 text-balance">
                            {entry.title}
                          </CardTitle>
                          <CardDescription className="text-pretty">{entry.description}</CardDescription>
                        </CardHeader>
                        <CardContent className="px-4">
                          <Badge variant="secondary" className="rounded-full font-normal">
                            {entry.area}
                          </Badge>
                        </CardContent>
                      </Card>
                    </li>
                  ))}
                </ul>
              </section>
            )
          })}
        </div>
      </div>
    </section>
  )
}

export default Roadmap1
```

### `storage/app/registry-sources/marketing/product-updates/roadmap-1/roadmap-1-data.ts`

```ts
/**
 * A roadmap that commits to order, not to dates.
 *
 * Same workflow-automation product as the changelog blocks in this section, so a buyer can put
 * the two on one page and have them agree. The columns are Now / Next / Later rather than
 * quarters on purpose: a quarter is a promise a roadmap cannot keep, and the moment one slips the
 * whole board reads as stale. Order is a promise a team can keep.
 */
export type RoadmapColumn = 'now' | 'next' | 'later'

export interface RoadmapEntry {
  id: string
  title: string
  description: string
  column: RoadmapColumn
  /** The area of the product this lands in, for readers scanning for their own. */
  area: string
}

export const roadmapColumnOrder: RoadmapColumn[] = ['now', 'next', 'later']

export const roadmapColumnLabels: Record<RoadmapColumn, { title: string; hint: string }> = {
  now: { title: 'Now', hint: 'In build, shipping this quarter' },
  next: { title: 'Next', hint: 'Specced and queued behind Now' },
  later: { title: 'Later', hint: 'Agreed in principle, not yet specced' },
}

export const simpleRoadmapCopy = {
  eyebrow: 'Roadmap',
  heading: 'What we are building next',
  lede: 'Order we are confident about. Dates we are not, so we do not publish them.',
  updatedAt: '2026-07-24',
}

export const roadmapEntries: RoadmapEntry[] = [
  {
    id: 'branch-conditions',
    title: 'Branching on step output',
    description: 'Route a run down a different path based on what the previous step returned.',
    column: 'now',
    area: 'Editor',
  },
  {
    id: 'run-search',
    title: 'Search across runs',
    description: 'Find a run by input, error text or the step it failed on.',
    column: 'now',
    area: 'Runs',
  },
  {
    id: 'secrets-scoping',
    title: 'Per-environment secrets',
    description: 'Scope a secret to staging without duplicating the workflow that reads it.',
    column: 'now',
    area: 'Security',
  },
  {
    id: 'workflow-versions',
    title: 'Workflow version history',
    description: 'See what a workflow looked like on the day a run went through it, and restore it.',
    column: 'next',
    area: 'Editor',
  },
  {
    id: 'usage-alerts',
    title: 'Usage alerts',
    description: 'Warn a workspace before it reaches its plan ceiling rather than at it.',
    column: 'next',
    area: 'Billing',
  },
  {
    id: 'sdk-python',
    title: 'Python SDK',
    description: 'Trigger and inspect workflows from Python, matching the TypeScript client.',
    column: 'next',
    area: 'Developers',
  },
  {
    id: 'self-hosted-runners',
    title: 'Self-hosted runners',
    description: 'Run steps inside your own network for workloads that cannot leave it.',
    column: 'later',
    area: 'Platform',
  },
  {
    id: 'workflow-templates',
    title: 'Shared templates',
    description: 'Publish a workflow as a starting point for other teams in the workspace.',
    column: 'later',
    area: 'Collaboration',
  },
]
```
