# Pattern: CRUD List

## Intent

The standard data list page for any entity in the system. Combines a filterable table with row-level actions. Editing and creation are handled by modals (`Dialog`) or side panels (`Sheet`), not by navigating to a separate route.

---

## When to Use

- Team management, user lists, product catalogs, record management
- Any page where the primary task is viewing, filtering, and acting on a list of records
- When create/edit flows are short enough to fit in a modal or side sheet

---

## Anatomy

```
┌─────────────────────────────────────────────────────────────┐
│  Page Header: Entity Title + Description + [New Entity] Btn  │
├─────────────────────────────────────────────────────────────┤
│  Card Container:                                             │
│  ┌──────────┬───────────────────────────────────────────┐  │
│  │ [Search] │ [Filter Dropdown] [Status Filter] ...     │  │
│  ├──────────┴───────────────────────────────────────────┤  │
│  │ Table: thead > th columns                            │  │
│  │   tbody > tr per record, last col = action menu      │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
```

---

## Full Pattern Code

```tsx
import {
  Button,
  Input,
  Table,
  TableHeader,
  TableRow,
  TableHead,
  TableBody,
  TableCell,
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuItem,
  Badge,
  Card,
} from 'xertica-ui/ui';
import { Search, Plus, MoreHorizontal } from 'lucide-react';

export function TeamMembersCRUD() {
  return (
    <div className="flex flex-col gap-6 p-6 w-full max-w-[1400px] mx-auto">
      {/* 1. Page Header */}
      <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
        <div>
          <h1 className="text-2xl font-bold tracking-tight">Team Members</h1>
          <p className="text-muted-foreground">Manage member access to the system.</p>
        </div>
        <Button className="gap-2">
          <Plus className="size-4" />
          New Member
        </Button>
      </div>

      {/* 2. Table Card */}
      <Card>
        {/* Filter Row */}
        <div className="flex p-4 border-b gap-2 flex-wrap">
          <div className="relative w-full max-w-sm">
            <Search className="absolute left-2.5 top-2.5 size-4 text-muted-foreground" />
            <Input placeholder="Search by email..." className="w-full h-9 pl-8" />
          </div>
          {/* Add more filter dropdowns here if needed */}
        </div>

        {/* Data Table */}
        <Table>
          <TableHeader>
            <TableRow>
              <TableHead>Name</TableHead>
              <TableHead>Email</TableHead>
              <TableHead className="hidden md:table-cell">Role</TableHead>
              <TableHead>Status</TableHead>
              <TableHead className="text-right">Actions</TableHead>
            </TableRow>
          </TableHeader>
          <TableBody>
            <TableRow>
              <TableCell className="font-medium">Admin Dev</TableCell>
              <TableCell>dev@example.com</TableCell>
              <TableCell className="hidden md:table-cell">Administrator</TableCell>
              <TableCell>
                <Badge variant="outline">Active</Badge>
              </TableCell>
              <TableCell className="text-right">
                <DropdownMenu>
                  <DropdownMenuTrigger asChild>
                    <Button variant="ghost" size="icon" className="size-8">
                      <MoreHorizontal className="size-4" />
                    </Button>
                  </DropdownMenuTrigger>
                  <DropdownMenuContent align="end">
                    <DropdownMenuItem>Edit Profile</DropdownMenuItem>
                    <DropdownMenuItem className="text-destructive">Revoke Access</DropdownMenuItem>
                  </DropdownMenuContent>
                </DropdownMenu>
              </TableCell>
            </TableRow>
          </TableBody>
        </Table>
      </Card>
    </div>
  );
}
```

---

## Composition Rules

1. **Filter search input** always uses the `relative` + `absolute icon` + `pl-8` pattern — never put the icon outside the input.
2. **Row action menus** always use `<DropdownMenu>` with `<DropdownMenuTrigger asChild>` wrapping a ghost icon button.
3. **Status cells** always use `<Badge>` — never plain text.
4. **Destructive row actions** (delete, revoke) use `className="text-destructive"` on the `<DropdownMenuItem>`.
5. **The create button** is always in the top-right of the page header, uses `<Plus className="size-4" />` icon.
6. **Never open a new route for create/edit** — use a `<Dialog>` or `<Sheet>` instead.
7. Wrap the entire table (filter row + `<Table>`) inside a **single `<Card>`** — do not create separate cards for the filter and the table.

---

## Related Patterns

- [Dashboard Pattern](./dashboard.md) — Overview page that links to CRUD pages
- [Form Pattern](./form.md) — The create/edit form used inside the Dialog or Sheet

## Related Components

- [`Table`](../components/table.md)
- [`DropdownMenu`](../components/dropdown-menu.md)
- [`Badge`](../components/badge.md)
- [`Input`](../components/input.md)
- [`Dialog`](../components/dialog.md) — For create/edit modals
- [`Sheet`](../components/sheet.md) — For create/edit side panels
