# Table Views

The CMS provides two view modes for every table: **Table** mode for browsing and navigating records, and **Data Table** mode for spreadsheet-style inline editing. Switch between them with the toolbar toggle.

## Table Mode

Table mode is the default browse experience:

- **Click rows** to navigate to the record detail view
- **Row action menus** (three-dot icon) with view, edit, delete, and custom actions
- **Column headers** are sortable — click to toggle ascending/descending
- **Search bar** for full-text search across all fields
- **Responsive columns** that adapt to available space

Each row shows the most relevant columns for the table. Foreign key columns display human-readable labels (resolved via `_label` fields) instead of raw UUIDs. Boolean columns render as colored Yes/No badges. Enum columns use color-coded pills.

### Row Actions

The three-dot menu on each row provides:

| Action | Visibility | Description |
|--------|-----------|-------------|
| View details | Always | Navigate to the record detail page |
| Edit | When role has `update` access | Navigate to the edit form |
| Custom actions | Filtered by role + record state | Run actions like approve, void, post |
| Delete | When role has `delete` access | Delete with confirmation |

Actions are filtered by the current role's write permissions and the action's access conditions. For example, an "approve" action that requires `status === "pending"` only appears on pending records.

## Data Table Mode

Data Table mode provides an Excel/Google Sheets-like editing experience:

- **Cell selection** with a blue focus ring
- **Keyboard navigation** using arrow keys, Tab, Enter, and Escape
- **Type-to-edit** — start typing to enter edit mode on the selected cell
- **Row numbers** displayed in the leftmost column
- **Hint bar** at the bottom showing keyboard shortcuts

### Editable Fields

Which cells are editable is determined by the table's guards:

- Fields in `guards.updatable` are editable
- Fields in `guards.immutable` are read-only (shown with a disabled style)
- Fields in `guards.protected` are read-only (marked "Updated via actions only")
- Audit fields (`createdAt`, `createdBy`, `modifiedAt`, `modifiedBy`) are always read-only

Non-editable cells can still be selected and copied, but they don't enter edit mode.

### Cell Types

| Column Type | Edit Control | Behavior |
|-------------|-------------|----------|
| Text | Text input | Free-text entry |
| Number | Number input | Numeric entry with step controls |
| Boolean | Yes/No dropdown | Toggle between Yes and No |
| FK reference | Typeahead dropdown | Server-side search with debounced queries |
| Enum | Select dropdown | Choose from allowed values |

### Saving

Changes are auto-saved when you:

- Press **Enter** to confirm and move down
- Press **Tab** to confirm and move to the next editable cell
- **Click away** from the editing cell (blur)

Press **Escape** to cancel an edit and revert to the previous value.

## Views

Views are named column projections defined in your table's resource config. They control which columns appear in the table based on the current role.

### Toolbar

The toolbar shows a view dropdown when the table has views defined. Options include:

- **All Fields** — Shows all columns (default)
- Custom views — Only show the columns specified in the view config

Views are filtered by role access. If a view's access rules require `admin` and the current user is a `member`, that view won't appear in the dropdown.

### Example

Given this definition:

```typescript
read: {
  views: {
    summary: {
      fields: ['id', 'name', 'status'],
      access: { roles: ['member', 'admin'] },
    },
    full: {
      fields: ['id', 'name', 'status', 'ssn', 'internalNotes'],
      access: { roles: ['admin'] },
    },
  },
}
```

- Members see: "All Fields" and "summary"
- Admins see: "All Fields", "summary", and "full"
- Owners see: "All Fields", "summary", and "full"

When a view is selected, the CMS calls the `listView` API endpoint instead of the standard `list`, fetching only the projected columns.

## Default Sort

You can set a default sort order for a table by adding `defaultSort` to your resource config:

{/* doc-compile: skip — one-key excerpt; the point is the `defaultSort` line, and a full host table would bury it. Whole-file table examples live in /compiler/definitions/schema. */}

```typescript
export default defineTable(podcastEpisodes, {
  defaultSort: { field: "createdAt", order: "desc" },
  // ...
});
```

When the CMS loads the table, it applies this sort automatically. Users can still click column headers to change the sort — `defaultSort` only sets the initial state.

## Pagination

The bottom of every table view shows pagination controls:

- **Page range indicator** — "Showing 1-25 of 142"
- **Page buttons** — Navigate directly to a page, with ellipsis for large page counts
- **Previous/Next** arrows

The default page size is 25 records. The CMS resets to page 1 when you change the search query or switch views.

## Search

The search bar performs full-text search across all columns. Type a query and the CMS debounces the request, then fetches matching records from the API.

Search works in both Table and Data Table modes, and works with views (searching within the projected columns).

## Next Steps

- **[Inline Editing](/ui/admin/inline-editing)** — Deep dive into spreadsheet editing, FK typeahead, and keyboard shortcuts
- **[Security](/ui/admin/security)** — How roles and guards affect the table UI
- **[Actions](/ui/admin/actions)** — Custom actions in the row menu and detail view
