Projects Page
betaA whole projects page for an app: the left rail beside one set of projects shown as a board, a list, or a timeline. On narrow screens the rail moves into a menu.
Preview
Projects
10 projects across 4 stages
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. Add the template. It also adds every component it uses. Its own files go in your
componentsfolder, and they are yours to edit.npx shadcn@latest add @txbrendan/projects-page
Use it for
- The projects screen of an app. Render it as the whole page, from your app's page file.
- Pass the rail's places in `rail` and the projects in `projectViews`. Each takes the props of its own part.
- Mark the place you're in with the rail's `active` or `defaultActive`. The rail beside the page and the one in the menu stay in step.
- Render one Toaster on the page, so moving or removing a project can offer Undo.
- Set `embedded` when you show the page inside another page, like a gallery, so it has no second main.
Avoid it for
- The rail's `onSearch` here. The rail draws twice, beside the page and in the menu, so its shortcut would listen twice. Put search in a Command Menu instead.
- A hero, a tagline, or a feature grid on top. An app screen leads with the work.
Props
- rail
Required. The Rail's props: the app's name, the places, and Settings. Below 40rem the rail moves into a menu, and its header sits beside the menu button.
RailProps
- projectViews
Required. The Project Views block's props: the projects, the stages, today, and the view to start on.
ProjectViewsProps
- menuLabel
Names the menu button and the menu on narrow screens.
string · default "Menu"
- embedded
Renders the content in a div, not a main, for a preview inside another page.
boolean · default false
- className
Extra classes on the page. Later classes win.
string
| Prop | Type | Default | What it does |
|---|---|---|---|
| rail | RailProps | - | Required. The Rail's props: the app's name, the places, and Settings. Below 40rem the rail moves into a menu, and its header sits beside the menu button. |
| projectViews | ProjectViewsProps | - | Required. The Project Views block's props: the projects, the stages, today, and the view to start on. |
| menuLabel | string | "Menu" | Names the menu button and the menu on narrow screens. |
| embedded | boolean | false | Renders the content in a div, not a main, for a preview inside another page. |
| className | string | - | Extra classes on the page. Later classes win. |
Accessibility
- The content sits in one main, unless embedded is set.
- On narrow screens the menu button has its label as its name and its tooltip, and the menu opens as a dialog named by the same label. Choosing a place closes it.
- The rail and the projects keep their own keyboard and screen reader behavior.
Examples
Projects beside the app's rail
<ProjectsPage
rail={{
header: <span className="px-2 font-(--type-weight-strong)">Workspace</span>,
groups: [{ items: [{ id: "home", label: "Overview", icon: "house" }, { id: "projects", label: "Projects", icon: "file-text" }] }],
defaultActive: "projects",
footer: { id: "settings", label: "Settings", icon: "settings" },
}}
projectViews={{
today: "2026-10-30",
defaultProjects: [
{ id: "help", name: "Help center", stage: "review", start: "2026-10-12", due: "2026-10-31" },
{ id: "payments", name: "Payments page", stage: "doing", start: "2026-10-19", due: "2026-11-06" },
],
}}
/>A narrow-screen menu with its own name
<ProjectsPage
menuLabel="Places"
rail={{
groups: [{ items: [{ id: "home", label: "Overview", icon: "house" }, { id: "projects", label: "Projects", icon: "file-text" }] }],
defaultActive: "projects",
}}
projectViews={{ today: "2026-10-30" }}
/>