> ## Documentation Index
> Fetch the complete documentation index at: https://docs.capsule.new/llms.txt
> Use this file to discover all available pages before exploring further.

# UI And Pages

> Page registration, UI widgets, and React page patterns.

## Page registration

Capsule supports two page models, and most real apps use both.

The usual split is simple. Use the Python DSL for dashboards, metrics, settings, tables, and task views. Use React when the page starts to feel like a richer application surface, such as a mailbox, approval queue, or list-detail workflow.

Both page types can be public or gated to signed-in users of the deployed app with `access="authenticated"`.

## Worked example

A strong Capsule app often uses both page models at once:

```python theme={null}
@app.page("Overview", icon="layout-dashboard", access="authenticated")
def overview():
    return cpsl.ui.Page(
        [
            cpsl.ui.Row(
                [
                    cpsl.ui.Metric("Threads", data="mailbox_metrics", field="total_threads"),
                    cpsl.ui.Metric("Needs Review", data="mailbox_metrics", field="needs_review"),
                ]
            ),
            cpsl.ui.Card(
                "Mailbox Settings",
                [
                    cpsl.ui.Toggle("Show closed threads", setting="show_closed_threads"),
                    cpsl.ui.Select("Default mailbox tab", setting="default_tab"),
                ],
            ),
        ]
    )


app.add_page(
    "Mailbox",
    icon="mail",
    component="pages/mailbox.tsx",
    access="authenticated",
)
```

That is the usual split:

* use the DSL for dashboards, metrics, settings, and task views
* use React for richer list/detail or workflow-heavy surfaces

### Python DSL pages

```python theme={null}
@app.page("Overview", icon="layout-dashboard", access="authenticated")
def overview():
    return cpsl.ui.Page([...])
```

Arguments:

| Argument | Meaning                                                                         |
| -------- | ------------------------------------------------------------------------------- |
| `name`   | Sidebar label                                                                   |
| `icon`   | Icon name                                                                       |
| `order`  | Explicit page ordering                                                          |
| `access` | `"public"` for open pages, or `"authenticated"` for signed-in users of this app |

Example:

```python theme={null}
@app.page("Overview", icon="layout-dashboard")
def overview():
    return cpsl.ui.Page(
        [
            cpsl.ui.Row(
                [
                    cpsl.ui.Metric("Threads", data="mailbox_metrics", field="total_threads"),
                    cpsl.ui.Metric("Needs Review", data="mailbox_metrics", field="needs_review"),
                ]
            ),
            cpsl.ui.Card(
                "Mailbox Settings",
                [
                    cpsl.ui.Toggle("Show closed threads", setting="show_closed_threads"),
                    cpsl.ui.Select("Default mailbox tab", setting="default_tab"),
                ],
            ),
        ]
    )
```

### React pages

```python theme={null}
app.add_page(
    "Dashboard",
    icon="chart-column",
    component="pages/dashboard.tsx",
    packages=["lightweight-charts@4.2.0"],
    access="authenticated",
)
```

Arguments:

| Argument    | Meaning                                                                         |
| ----------- | ------------------------------------------------------------------------------- |
| `name`      | Sidebar label                                                                   |
| `icon`      | Icon name                                                                       |
| `component` | Path to the TSX component                                                       |
| `packages`  | Extra npm packages for the page bundle                                          |
| `order`     | Explicit page ordering                                                          |
| `access`    | `"public"` for open pages, or `"authenticated"` for signed-in users of this app |

Example:

```python theme={null}
app.add_page(
    "Mailbox",
    icon="mail",
    component="pages/mailbox.tsx",
    access="authenticated",
)
```

## DSL widgets

The Python DSL is deliberately small. You compose a page from a few primitives rather than learning a big UI framework.

### Layout

| Widget                                    | Purpose             |
| ----------------------------------------- | ------------------- |
| `cpsl.ui.Page(children)`                  | Root page container |
| `cpsl.ui.Row(children)`                   | Horizontal grouping |
| `cpsl.ui.Column(children)`                | Vertical grouping   |
| `cpsl.ui.Card(title=None, children=None)` | Card wrapper        |
| `cpsl.ui.Divider()`                       | Visual separator    |
| `cpsl.ui.Text(content, style=None)`       | Text element        |

### Data display

| Widget        | Purpose                                            |
| ------------- | -------------------------------------------------- |
| `Metric(...)` | KPI or summary stat                                |
| `Table(...)`  | Table from collection, data source, or inline rows |
| `Chart(...)`  | Chart from a data source                           |

### Settings widgets

| Widget                                                             | Purpose                         |
| ------------------------------------------------------------------ | ------------------------------- |
| `Toggle(label, setting=...)`                                       | Boolean setting                 |
| `TextInput(label, setting=..., multiline=False, placeholder=None)` | String setting                  |
| `NumberInput(label, setting=..., min=None, max=None, step=None)`   | Numeric setting                 |
| `Select(label, setting=...)`                                       | Setting with predefined options |

### Task UI

| Widget                             | Purpose                        |
| ---------------------------------- | ------------------------------ |
| `TaskBoard(...)`                   | Kanban-style task board        |
| `TaskBoardColumn(label, statuses)` | Custom task-board column group |

Here is a compact DSL page that uses several of these widgets together:

```python theme={null}
@app.page("Dashboard", icon="layout-dashboard")
def dashboard():
    return cpsl.ui.Page(
        [
            cpsl.ui.Row(
                [
                    cpsl.ui.Metric("Threads", data="mailbox_metrics", field="total_threads"),
                    cpsl.ui.Metric("Needs Review", data="mailbox_metrics", field="needs_review"),
                ]
            ),
            cpsl.ui.Divider(),
            cpsl.ui.Toggle("Show closed threads", setting="show_closed_threads"),
        ]
    )
```

## Widget notes

* `Metric` can display a literal `value` or bind to `data` plus `field`.
* `Table` can bind to a collection, a data handler, or inline `rows`.
* `Chart` binds to a named data source and supports `line`, `bar`, `pie`, `scatter`, and `area`.
* Passing a `CollectionRef` to `Table(...)` lets Capsule inherit collection metadata automatically.

## React page runtime

React pages are built with the `@capsule/page` runtime. The most common helpers are:

* `useData(...)`
* `useCollection(...)`
* `useTheme()`
* `useCapsule()` for the current app user plus `login()` / `logout()`

Common import pattern:

```tsx theme={null}
import { useCapsule, useCollection, useData, useTheme } from "@capsule/page"
```

Example:

```tsx theme={null}
import { EmptyState, useCapsule, useData, useTheme } from "@capsule/page"

export default function Mailbox() {
  const theme = useTheme()
  const { user } = useCapsule()
  const threads = useData("threads", { params: { status: "needs-review" } })

  if (threads.data?.gmail_connected === false) {
    return (
      <EmptyState
        title="Gmail not connected"
        description="This signed-in app user still needs a Gmail integration before the mailbox can load."
      />
    )
  }

  return (
    <div style={{ color: theme.color.foreground }}>
      <p>Signed in as {user?.email}</p>
      <h2>Threads: {threads.data?.threads?.length ?? 0}</h2>
    </div>
  )
}
```

For `access="authenticated"` pages, Capsule handles the sign-in gate before the React page loads. Inside the page, `useCapsule().user` is the signed-in user for this app. If you want a public page to prompt for sign-in, `useCapsule().login()` opens the app sign-in flow.

React pages are the right choice when you need custom interactivity or external packages. The Python DSL is usually faster when metrics, tables, charts, and settings widgets are enough.

## Naming pitfall

Do not confuse:

* `cpsl.Column` for collection schema
* `cpsl.ui.Column` for page layout

## See also

* [Add Pages](/build/add-pages)
* [Custom React Pages](/features/custom-react-pages)
* [Workflows Reference](/reference/workflows)
* [App API Reference](/reference/app-api)
