List Layout

Arrange the list header, actions, and table, or replace the DataTable entirely.

<List> renders a thin shell: a title, a header actions slot, and its children. It has no default child. Whatever you put inside is all the user sees, so a list needs a <DataTable> (or your own component) and usually a <ListPagination>.

The header

<List> shows a title (the resource label, pluralized) and an actions slot on the right. Pass actions to fill it, typically the create / export / filter buttons, or actions={false} to omit it. Pass title to override the heading, or title={false} to drop it:

import {
  CreateButton,
  DataTable,
  ExportButton,
  List,
  ListPagination,
} from "@chelone/react";
 
export function PostList() {
  return (
    <List
      title="All posts"
      actions={
        <>
          <CreateButton />
          <ExportButton />
        </>
      }
    >
      <DataTable>
        <DataTable.Col source="title" />
      </DataTable>
      <ListPagination />
    </List>
  );
}

The table body

Inside <List>, a <DataTable> renders one <DataTable.Col> per column. A column either points at a source (and renders that value), wraps a field component, or supplies a render function. render receives the row record with every value typed unknown, so narrow a value before you use it:

import {
  DataTable,
  ReferenceField,
  TextField,
} from "@chelone/react";
 
export function PostTable() {
  return (
    <DataTable>
      {/* value straight from the record */}
      <DataTable.Col source="title" />
      {/* right-aligned numeric column */}
      <DataTable.NumberCol source="views" />
      {/* a field component reading the row record */}
      <DataTable.Col label="Author">
        <ReferenceField source="author_id" reference="authors">
          <TextField source="name" />
        </ReferenceField>
      </DataTable.Col>
      {/* an inline render for a computed cell */}
      <DataTable.Col
        label="Excerpt"
        render={(record) => String(record.body ?? "").slice(0, 40)}
      />
    </DataTable>
  );
}

Bulk actions

A <DataTable> shows no checkboxes and no bulk toolbar by default. bulkActionButtons defaults to false. Pass the buttons you want and the table adds a selection column; the toolbar appears once at least one row is selected:

import {
  BulkDeleteButton,
  BulkExportButton,
  DataTable,
  List,
  ListPagination,
} from "@chelone/react";
 
export function PostList() {
  return (
    <List>
      <DataTable
        bulkActionButtons={
          <>
            <BulkExportButton />
            <BulkDeleteButton />
          </>
        }
      >
        <DataTable.Col source="title" />
        <DataTable.NumberCol source="views" />
      </DataTable>
      <ListPagination />
    </List>
  );
}

For a toolbar of your own, pass bulkActionsToolbar instead. It replaces bulkActionButtons, and a <BulkActionsToolbar> is the stock shell to put your buttons in.

Let users choose columns

Put a <ColumnsButton> in the list actions and the user can show, hide and reorder the columns of the <DataTable> below it:

import {
  ColumnsButton,
  CreateButton,
  DataTable,
  List,
  ListPagination,
} from "@chelone/react";
 
export function PostList() {
  return (
    <List
      actions={
        <>
          <ColumnsButton />
          <CreateButton />
        </>
      }
    >
      <DataTable defaultHiddenColumns={["body"]}>
        <DataTable.Col source="title" />
        <DataTable.Col source="body" />
        <DataTable.NumberCol source="views" />
      </DataTable>
      <ListPagination />
    </List>
  );
}

The button needs a store on <Admin> and throws without one. The table publishes its columns into that store and the button reads them back, so the button stays disabled until a <DataTable> has mounted.

  • The hidden list is kept under <resource>.datatable and the order under <resource>.datatable_columnOrder. A localStorageAdapter store keeps both across reloads.
  • defaultHiddenColumns on <DataTable> lists the column ids hidden until the user stores a choice. A column's id is its source, or its label when it has a string one.
  • storeKey on <DataTable> replaces the <resource>.datatable prefix, for a second table on one resource. Give <ColumnsButton> the same storeKey.
  • A column with no source and no string label has no id. It is always shown and the panel does not list it.

A list that is a tree rather than a table can use <TreeList>, which nests the records by a parent field.

Replace the table

<List> only requires that its child reads the list context. To build a custom layout (cards, a gallery, a grid) read the records with useListContext directly and render them however you like. The context is not generic, so its records are typed { id } only. Cast the result to the shape of your resource:

import {
  List,
  ListPagination,
  type UseListControllerResult,
  useListContext,
} from "@chelone/react";
 
type Post = { id: number; title: string };
 
function PostCards() {
  const { data, isPending } = useListContext() as UseListControllerResult<Post>;
  if (isPending) return <p>Loading…</p>;
  return (
    <div className="grid grid-cols-3 gap-4">
      {data?.map((post) => (
        <article key={post.id} className="rounded border p-4">
          <h3>{post.title}</h3>
        </article>
      ))}
    </div>
  );
}
 
export function PostList() {
  return (
    <List>
      <PostCards />
      <ListPagination />
    </List>
  );
}

Props

PropTypeDefaultDescription
actionsfalse | ReactElement<unknown, string | JSXElementConstructor<any>>—Header actions (create/export/filter buttons); nothing by default.
childrenReactNode—
classNamestring—
componentElementType<any, keyof IntrinsicElements>—Element/component wrapping the children (defaults to `"div"`).
perPagenumber—Rows-per-page for the initial query.
resourcestring—Override the ambient resource (defaults to the `<Resource>` context).
titleReactNode—Header title. A string/element renders as an `<h2>`; `false` omits it.

Tips

  • The component prop swaps the element wrapping the children (default "div"). Pass a layout component that accepts className and children to frame the body.
  • Selection lives in the list context too (selectedIds, toggleItem, unselectAll). See Advanced for a bulk toolbar in a custom layout.