Skip to content
txbrendan/ui

Project Views

beta

Shows one set of projects as a Board, a List, or a Timeline, switched by tabs, with search, filters, and a sheet for each project. The name column stays still between List and Timeline, and only the dates side changes.

Built from Avatar Stack, Badge, Board, Button, Gantt Chart, Glide Highlight, Input, Menu, Motion Timing, Notice, Progress, Rolling Number, Search Field, Sheet, Skeleton, Tabs, Toast, and Tooltip.

Preview

Projects

14 projects across 4 stages

Wireframes
Design

Rough layouts for the new settings pages.

x
0/6 tasks0%
2comments4filesDueNov 13
Invoice templates
Web

Three layouts people can pick for their invoices.

x
1/8 tasks13%
1commentsDueNov 20
Push alerts
MobileUrgent

Alerts for replies and due dates on phones.

x
0/5 tasks0%
4commentsDueNov 9
Team permissions
Platform

Roles for owners, editors, and viewers.

x
2/12 tasks17%
2filesDueDec 4
Payments page
WebUrgent

One page for cards, bank details, and receipts.

x
9/20 tasks45%
12comments6filesDueNov 6
Onboarding emails
Marketing

A short series for the first week after sign up.

x
4/7 tasks57%
3comments1filesDueNov 5
Mobile app
Mobile

The first release for phones, with sign in and lists.

x
14/40 tasks35%
21comments9filesDueDec 11
Card issuing
Platform

Order and freeze cards from the account page.

x
2/10 tasks20%
5commentsDueNov 24
Help center
Web

Search and short guides for the top questions.

x
18/20 tasks90%
7comments3filesDueOct 31
Brand refresh
Design

New type, colors, and icons across the app.

x
11/12 tasks92%
9comments14filesDueNov 3
Chat alerts
Mobile

A badge and a sound for new messages.

x
6/6 tasks100%
2commentsDueNov 2
Billing update
Web

Plans and seats on one page.

x
10/10 tasks100%
6comments2filesDueOct 21
Reports hub
Platform

Every report in one place, with exports.

x
15/15 tasks100%
3comments5filesDueOct 24
Onboarding flow
Design

Three steps from sign up to the first project.

x
8/8 tasks100%
4commentsDueOct 27

starts on the timeline, in another locale

Projects

14 projects across 4 stages

Project
28 Sept – 14 Dec, today 30 Oct
To do4
Design
Web
Mobile
Urgent · 0%
Platform
In progress4
Web
Urgent · 45%
Marketing
Mobile
Platform
In review3
Web
Design
Mobile
Done3
Web
Platform
Design

loading

Projects

Project
Priority
Progress
Team
Due
Actions
To do
In progress
In review
Done

empty

Projects

0 projects

No projects yet.

error, then retry

Projects

Not loaded

no matches for a search

Projects

0 projects

No projects match “Roadmap”.

No projects match “Roadmap”.

narrow container

Projects

7 projects across 2 stages

Project
Priority
Progress
Team
Due
Actions
To do0
In progress4
Web
High
x
45%
Nov 6
Marketing
Medium
x
57%
Nov 5
Mobile
High
x
35%
Dec 11
Platform
Medium
x
20%
Nov 24
In review3
Web
Medium
x
90%
Oct 31
Design
Low
x
92%
Nov 3
Mobile
Medium
x
100%
Nov 2
Done0

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the block. It also adds every component it uses. Its own files go in your components folder, and they are yours to edit.

    npx shadcn@latest add @txbrendan/project-views

Use it for

  • Work that moves through stages, like projects, when people want a board, a list, and dates on one screen.
  • Pass defaultProjects for the block to own the list, or projects with onProjectsChange to own it yourself.
  • Give each project a stage id from stages. The default stages are To do, In progress, In review, and Done.
  • Pass today as an ISO day from the server. It marks the timeline and dates new projects, and keeps the first render the same on the server and in the browser.
  • Dates are ISO days, like "2026-11-06". start and due draw the timeline bar.
  • Move and Remove apply at once and offer Undo in a toast for 8 seconds, so place one Toaster on the page.
  • Set loading while the projects load, and error with onRetry when they fail.

Avoid it for

  • A single list with no stages. Use Table or List Row.
  • Only a timeline, with no board or list. Use Gantt Chart.
  • Dragging cards between lanes. Cards do not drag. Each card's menu moves it.

Props

  • projects

    The projects, in order inside each stage. Use with onProjectsChange to own them. Each id is unique, and stage is a stage's id.

    { id: string; name: string; kind?: string; stage: string; description?: string; priority?: "high" | "medium" | "low"; urgent?: boolean; tasks?: { done: number; total: number }; people?: string[]; comments?: number; files?: number; start?: string; due?: string }[]

  • defaultProjects

    The projects to start with, when the block owns them.

    Project[] · default []

  • onProjectsChange

    Called with the whole new list after an add, a move, a remove, or an undo.

    (projects: Project[]) => void

  • view

    Which view shows. Use with onViewChange.

    "board" | "list" | "timeline"

  • defaultView

    The view to start on.

    "board" | "list" | "timeline" · default "board"

  • onViewChange

    Called when a tab picks another view.

    (view: ProjectView) => void

  • defaultSearch

    The search to start with.

    string · default ""

  • stages

    The stages, in order. Each is a lane on the board and a group in the list.

    { id: string; title: string; tone?: "neutral" | "brand" | "danger" | "success" | "warning" }[] · default To do, In progress, In review, Done

  • today

    Today as an ISO day, passed from the server.

    string

  • locale

    The locale for dates.

    string · default "en-US"

  • timeZone

    The time zone the timeline reads days in.

    string · default "UTC"

  • loading

    Each view holds its space with placeholders, and the count waits.

    boolean · default false

  • error

    Says what went wrong in place of the views.

    string

  • onRetry

    Shows Retry beside the error.

    () => void

  • className

    Extra classes on the outer section, which holds the container query.

    string

Accessibility

  • The block is a section named by its heading. On a page with more than one, give each an aria-label, which then names it instead. The views are tabs, and the arrow keys move between them.
  • Each card and each row has one button named by the project, which opens its sheet. The rest of a row opens it with a pointer.
  • Each project's "…" button is named "More for" and the project, and opens Open, Move to, and Remove.
  • After a move or a remove, focus goes to the project in its new place, or to the one beside where it was.
  • List and Timeline are one table, with column headers, a row header for each project, and a row group for each stage.
  • Each lane is a group named by its stage and count. Each "+" button is named for its stage, and its tooltip says Add project.
  • The view that leaves is hidden from screen readers and from Tab while it fades.
  • The count line and each stage's count roll to new numbers. Reduced motion takes out the slide, and motion off shows each view at once.

Examples

Projects the block keeps

<ProjectViews
  today="2026-10-30"
  defaultProjects={[
    { id: "payments", name: "Payments page", kind: "Web", stage: "doing", priority: "high", urgent: true, tasks: { done: 9, total: 20 }, people: ["AK", "BL"], start: "2026-10-19", due: "2026-11-06" },
    { id: "help", name: "Help center", kind: "Web", stage: "review", tasks: { done: 18, total: 20 }, start: "2026-10-12", due: "2026-10-31" },
  ]}
/>

Your own state owns the projects and the view

"use client";

function TeamProjects({ initial, today }: { initial: Project[]; today: string }) {
  const [projects, setProjects] = useState(initial);
  const [view, setView] = useState<ProjectView>("list");
  return (
    <ProjectViews
      projects={projects}
      onProjectsChange={(next) => {
        setProjects(next);
        saveProjects(next);
      }}
      view={view}
      onViewChange={setView}
      today={today}
    />
  );
}

Your own stages

<ProjectViews
  today="2026-10-30"
  stages={[
    { id: "ideas", title: "Ideas" },
    { id: "building", title: "Building", tone: "brand" },
    { id: "shipped", title: "Shipped", tone: "success" },
  ]}
  defaultProjects={[{ id: "search", name: "Search", stage: "ideas" }]}
/>

While loading, and when loading fails

<ProjectViews
  projects={data ?? []}
  loading={isLoading}
  error={failed ? "Projects could not load." : undefined}
  onRetry={refetch}
/>