# Custom table layouts

> Replace the whole table with a list or cards, add search, filters, sorting, and pagination to it, or customize its problem message.

Source: https://docs.inertiax.dev/table/guide/custom-layouts/

Pass children to `<AppTable>` to render a different layout. Use [regions](https://docs.inertiax.dev/table/guide/custom-renderers/) when you only need to replace part of the table.

## Render a name list

This example expects a `name` column. It uses one snapshot for both the result and request status:

**React**

```tsx
import { AppTable } from '@/components/app-table';
import { useTableController, useTableSnapshot } from '@inertiax/react-table';

function NameList() {
  const snapshot = useTableSnapshot();
  const controller = useTableController();
  const busy = snapshot.status.type === 'pending' || snapshot.status.type === 'scheduled';

  if (snapshot.outcome === 'problem') {
    return (
      <div role="alert" aria-busy={busy}>
        <p>This table link needs updating.</p>
        {snapshot.status.type === 'error' ? (
          <button type="button" onClick={() => controller.retry()}>Retry</button>
        ) : snapshot.canReset ? (
          <button type="button" disabled={busy} onClick={() => controller.resetProblem()}>
            Reset table settings
          </button>
        ) : <p>Open a new table link to continue.</p>}
      </div>
    );
  }

  const { component } = snapshot.envelope;
  return (
    <div aria-busy={busy}>
      {busy && <p role="status">Loading names…</p>}
      {(snapshot.status.type === 'error' || snapshot.status.type === 'externalError') && (
        <p role="alert">
          Could not update the list.{' '}
          {snapshot.status.type === 'error' && (
            <button type="button" onClick={() => controller.retry()}>Retry</button>
          )}
        </p>
      )}
      {component.data.rows.length === 0 ? <p>No matching names.</p> : (
        <ul>
          {component.data.rows.map((row) => (
            <li key={String(row[component.data.rowKey]?.value)}>
              {String(row.name?.value ?? '')}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

<AppTable prop="users"><NameList /></AppTable>;
```

Replacement children own the whole UI. This list displays the current page; add the [table controls](https://docs.inertiax.dev/table/guide/custom-layouts/#add-search-filters-sorting-and-pagination) for search, filters, sorting, and pagination, or build your own with the [controller](https://docs.inertiax.dev/table/guide/controller-events/), for example for selection. Previous rows remain visible during a reload or a failed request. An invalid-link problem has no successful result, so that branch provides [reset or retry](https://docs.inertiax.dev/table/guide/url-recovery/) instead.

`useTableSnapshot()` works for both outcomes. Hooks that require a result, such as `useTableEnvelope()` and `useTableState()`, must only mount after a successful outcome.

## Add search, filters, sorting, and pagination

Place `TableSearch`, `TableFilters`, `TableSort`, and `TablePagination` around your own layout to add search, filters, sorting, and pagination. This example shows orders as cards and expects `reference` and `customer_name` columns:

**React**

```tsx
import { AppTable } from '@/components/app-table';
import {
  TableFilters,
  TablePagination,
  TableSearch,
  TableSort,
  useTableSnapshot,
} from '@inertiax/react-table';

function OrderCards() {
  const snapshot = useTableSnapshot();
  if (snapshot.outcome === 'problem') return <p role="alert">This table link needs updating.</p>;

  const { data } = snapshot.envelope.component;
  return (
    <ul className="grid gap-4 sm:grid-cols-2">
      {data.rows.map((row) => (
        <li key={String(row[data.rowKey]?.value)}>
          <strong>{String(row.reference?.value ?? '')}</strong>
          <p>{String(row.customer_name?.value ?? '')}</p>
        </li>
      ))}
    </ul>
  );
}

<AppTable prop="orders">
  <div className="flex flex-wrap gap-2">
    <TableSearch />
    <TableSort />
    <TableFilters />
  </div>
  <OrderCards />
  <TablePagination />
</AppTable>;
```

Each control renders nothing when the table doesn’t offer its feature, so the same controls work with any table. They also render nothing for a problem outcome, so they can stay outside your layout’s success branch. They use the table’s texts and theme, and `TableSort` sets one sort key at a time. This example leaves out the loading, empty, error, and reset handling a custom layout owns; add it as in the [name list](https://docs.inertiax.dev/table/guide/custom-layouts/#render-a-name-list). See [Layout controls](https://docs.inertiax.dev/table/reference/react/#layout-controls) for each component.

## Customize the problem region

Use `customization.texts.problem` to change the wording. For a custom layout, use `problemState`:

**React**

```tsx
<AppTable
  prop="orders"
  customization={{
    regions: {
      problemState: ({ canReset, controller, status }) => (
        <div role="alert">
          <p>This table link needs updating.</p>
          {status.type === 'error' ? (
            <button type="button" onClick={() => controller.retry()}>Retry</button>
          ) : canReset ? (
            <button
              type="button"
              disabled={status.type === 'pending' || status.type === 'scheduled'}
              onClick={() => controller.resetProblem()}
            >
              Reset table settings
            </button>
          ) : <p>Open a new table link to continue.</p>}
        </div>
      ),
    },
  }}
/>
```

The region receives `componentId`, `problem`, `controller`, `status`, and `canReset`. Unlike the loading, empty and request-error regions, it replaces the whole table and returns ordinary markup, not table rows. See [Regions](https://docs.inertiax.dev/table/reference/react/#regions) for the full contract.
