# Now Next Later Phases

> Directional roadmap grouped into Now, Next, and Later phases with colored markers, a target month each, progress bars, and vote counts on upcoming items.

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

- Page, with a live preview: https://shadcn.exchange/r/7ovr/roadmap-2
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/7ovr/roadmap-2-light-ad0f7d80c60bc676.png
- Registry item: https://7ovr.com/r/roadmap-2.json
- Categories: roadmap
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://7ovr.com/r/roadmap-2.json
```

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

## Dependencies

- npm packages: `@base-ui/react`
- Registry items: `avatar`, `badge`, `progress`

## Files

### `registry/blocks/roadmap/2/roadmap-block.tsx`

```tsx
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import { cn } from "@/lib/utils"

type Contributor = { name: string; src: string }

type Item = {
  title: string
  description: string
  progress?: number
  votes?: number
  team?: Contributor[]
}

type Phase = {
  id: string
  label: string
  caption: string
  target: string
  tone: "active" | "upcoming" | "future"
  items: Item[]
}

const team: Record<string, Contributor> = {
  ada: { name: "Ada", src: "https://i.pravatar.cc/64?img=47" },
  leo: { name: "Leo", src: "https://i.pravatar.cc/64?img=12" },
  mara: { name: "Mara", src: "https://i.pravatar.cc/64?img=32" },
  owen: { name: "Owen", src: "https://i.pravatar.cc/64?img=53" },
}

const phases: Phase[] = [
  {
    id: "now",
    label: "Now",
    caption: "In active development",
    target: "June 2026",
    tone: "active",
    items: [
      {
        title: "Native mobile apps",
        description:
          "Offline-first iOS and Android with real-time sync and push.",
        progress: 72,
        team: [team.ada, team.leo, team.mara],
      },
      {
        title: "Audit log export",
        description: "Stream workspace activity to your SIEM in real time.",
        progress: 41,
        team: [team.owen, team.ada],
      },
    ],
  },
  {
    id: "next",
    label: "Next",
    caption: "Up next",
    target: "September 2026",
    tone: "upcoming",
    items: [
      {
        title: "Custom dashboards",
        description: "Compose the metric views your team cares about.",
        votes: 218,
      },
      {
        title: "Public API v2",
        description: "A faster, fully typed API with per-key rate limits.",
        votes: 173,
      },
    ],
  },
  {
    id: "later",
    label: "Later",
    caption: "On the horizon",
    target: "April 2027",
    tone: "future",
    items: [
      {
        title: "Workflow automation",
        description: "Trigger actions from events without writing code.",
        votes: 94,
      },
    ],
  },
]

const dotTone: Record<Phase["tone"], string> = {
  active: "bg-primary",
  upcoming: "bg-muted-foreground/60",
  future: "border border-muted-foreground/50",
}

export default function RoadmapBlock() {
  return (
    <section className="flex w-full items-center justify-center bg-muted/30 px-6 py-16 text-foreground">
      <div className="mx-auto w-full max-w-2xl">
        <div className="mb-10">
          <Badge variant="outline" className="mb-4">
            Roadmap
          </Badge>
          <h2 className="font-heading text-3xl font-bold tracking-tight">
            Where we are headed
          </h2>
          <p className="mt-3 text-muted-foreground">
            What the team is building now and what comes next. Priorities shift
            as we learn from your feedback.
          </p>
        </div>

        <div className="flex flex-col gap-8">
          {phases.map((phase) => (
            <div key={phase.id} className="flex flex-col gap-4">
              <div className="flex items-center gap-3">
                <span
                  className={cn("size-2.5 rounded-full", dotTone[phase.tone])}
                  aria-hidden="true"
                />
                <h3 className="font-heading text-base font-semibold tracking-tight">
                  {phase.label}
                </h3>
                <span className="text-sm text-muted-foreground">
                  {phase.caption}
                </span>
                <span className="ml-auto text-xs font-medium text-muted-foreground">
                  {phase.target}
                </span>
              </div>

              <ul className="flex flex-col gap-3">
                {phase.items.map((item) => (
                  <li
                    key={item.title}
                    className="flex flex-col gap-2 rounded-lg border border-border bg-card p-4 transition-colors hover:border-muted-foreground/40"
                  >
                    <div>
                      <h4 className="font-heading text-sm font-semibold">
                        {item.title}
                      </h4>
                      <p className="mt-0.5 text-xs/relaxed text-muted-foreground">
                        {item.description}
                      </p>
                    </div>

                    {item.progress != null ? (
                      <div className="mt-0.5 flex flex-col gap-1.5">
                        <div className="flex items-center justify-between gap-3">
                          <div className="flex -space-x-2">
                            {item.team?.map((member) => (
                              <Avatar
                                key={member.name}
                                className="size-6 border-2 border-card"
                              >
                                <AvatarImage
                                  src={member.src}
                                  alt={member.name}
                                  className="grayscale"
                                />
                                <AvatarFallback className="text-[10px]">
                                  {member.name[0]}
                                </AvatarFallback>
                              </Avatar>
                            ))}
                          </div>
                          <span className="text-xs font-medium text-muted-foreground tabular-nums">
                            {item.progress}%
                          </span>
                        </div>
                        <Progress
                          value={item.progress}
                          aria-label={`${item.title} progress`}
                        />
                      </div>
                    ) : item.votes != null ? (
                      <Badge variant="outline" className="w-fit">
                        {item.votes} Votes
                      </Badge>
                    ) : null}
                  </li>
                ))}
              </ul>
            </div>
          ))}
        </div>
      </div>
    </section>
  )
}
```
