# Projects

> A projects page with board, list, and timeline layouts that share one shell per project.

- Name: `projects`
- Status: beta
- Categories: dashboard, projects
- Files: the install writes `projects.tsx`, and any part files named `projects-*.tsx`, to your `components` alias, `@/components` by default. Edit them freely. They are yours.
- Other registry items: `@arbokui/badge`, `@arbokui/card`, `@arbokui/progress`, `@arbokui/ring-progress`, `@arbokui/search-field`, `@arbokui/select`, `@arbokui/skeleton`, `@arbokui/tooltip`, `@arbokui/view-morph`, `@arbokui/view-switch`. The install adds them.
- Gallery: https://arbokui.com/templates/projects
- Registry item: https://arbokui.com/r/projects.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the template. It also adds every component it uses:

   ```bash
   npx shadcn@latest add @arbokui/projects
   ```

## Use it for

- A page of projects someone switches between a board, a list, and a timeline.
- Pass projects, and a today date, when the records are yours. The defaults are a worked example, and the page says so.
- Set embedded when the page sits inside another page, so it does not add a second main.
- Set loading while the records are on the way. The skeleton matches the layout. An empty projects array is the empty page. A search or priority that matches nothing says so, and a filter that matches some says how many of the total.
- Set error to a sentence that says what to do next, when the records could not be loaded.

## Avoid it for

- A table you sort and page. The list is one of three layouts of the same shells, not a data table.
- A second accent color. Orange is only the high pill. Red is only the date line. Timeline bars share one neutral fill.

## Props

- `projects`: `Project[]`. The records. Default is a worked example.
- `today`: `string`. The date the page treats as today, YYYY-MM-DD. Omit it and the page uses 2026-10-27 and says that date is a fixed example. A project due before the date, and not done, is overdue.
- `loading`: `boolean`. Default `false`. Shows a skeleton in the current layout's shape instead of the records.
- `error`: `string`. Replaces the records with this message. Say what to do next.
- `embedded`: `boolean`. Default `false`. Renders a div instead of a main, for a preview inside another page.
- `titleLevel`: `"h1" | "h2"`. Heading level of the title. Default h1, or h2 when embedded.
- `defaultLayout`: `"board" | "list" | "timeline"`. Default `"board"`. The layout on first paint, when layout is not set.
- `layout`: `"board" | "list" | "timeline"`. The layout that is showing. Use with onLayoutChange.
- `onLayoutChange`: `(layout: "board" | "list" | "timeline") => void`. Called with the layout that was just chosen.
- `defaultQuery`: `string`. Default `""`. The search box on first paint.
- `defaultPriority`: `"all" | "urgent" | "high" | "medium" | "low"`. Default `"all"`. The priority filter on first paint.
- `interactive`: `boolean`. Default `true`. When false, the project controls leave the tab order. The layout switch stays.
- `className`: `string`. Classes on the root, which holds the container query.
- `Part. Project`: `{ id: string; name: string; client: string; summary: string; stage: "todo" | "progress" | "review" | "done"; priority: "urgent" | "high" | "medium" | "low"; done: number; total: number; start: string; due: string; roles: string[] }`. One project. id is stable across layouts. start and due are YYYY-MM-DD. roles are job names, such as Lead, not people.

## Accessibility

- The page is a main, unless embedded. The title is an h1, or an h2 when embedded, unless titleLevel says otherwise.
- The layout control is a tab list. The morph is the tab panel, named by the active tab.
- The board is a list of lists: one list named Board, and a list named for each stage. The list is a table. The timeline groups its rows, and each group is a list named for its stage.
- Each project has one button, named with the project, the client, and the priority. The card, the row, and the timeline name are that button. The priority pill is named with its level. A tooltip carries the summary, the priority, the roles, and the task count. Enter or Space selects it. A layout change that drops focus puts it back on that button.
- Set interactive to false to take those buttons out of the tab order. The layout switch stays in it.
- A timeline bar's text equivalent is the name, the priority, the start, the due date, and the percent. The row shows that priority once, on the pill.
- A status outside the busy region says when the page is loading, empty, or filtered to nothing.
- The move follows the motion tokens. Motion off and reduced motion do not translate the shells.

## Examples

### The worked example

```tsx
<Projects />
```

### Inside another page

```tsx
<Projects embedded interactive={false} titleLevel="h1" />
```
