4. Polish

Add search and filters, custom menu icons, and a dashboard home page.

You have a working CRUD admin. Now let's make it feel finished: a search box, a custom menu icon, and a dashboard home page.

Adding search and filters to the list

The list controller already sorts and paginates. To show pagination controls, render <ListPagination /> below the table. To let users search, add filter inputs to the list's actions slot with <FilterForm> and <FilterButton>.

Chelone has no react-admin filters prop on <List> — filtering is plain composition. Declare an array of filter inputs, then render <FilterForm> for the visible inputs and <FilterButton> to toggle optional filters. Merge the imports into src/posts.tsx and replace PostList; keep the create, edit, and show views from the previous step:

src/posts.tsx
import {
  DataTable,
  FilterButton,
  FilterForm,
  List,
  ListPagination,
  ReferenceInput,
  SearchInput,
  SelectInput,
} from "@chelone/react";
 
const postFilters = [
  <SearchInput source="q" alwaysOn key="q" />,
  <ReferenceInput source="author_id" reference="authors" key="author_id">
    <SelectInput label="Author" optionText="name" />
  </ReferenceInput>,
];
 
export function PostList() {
  return (
    <List
      actions={
        <>
          <FilterForm filters={postFilters} />
          <FilterButton filters={postFilters} />
        </>
      }
    >
      <DataTable>
        <DataTable.Col source="title" />
        {/* …the other columns… */}
      </DataTable>
      <ListPagination />
    </List>
  );
}

The q filter is an alwaysOn search — Chelone forwards it to your data provider's getList under filter.q. The wrapper from Setup matches a case-insensitive substring of the post's title or body before pagination. Type On to find "On Computable Numbers". A production provider must implement its own q search. The author_id filter is optional: the <FilterButton> dropdown toggles it on, and because it's a <ReferenceInput> its <SelectInput> is already populated with the authors.

FilterButton toggles visibility; FilterForm renders the search box and any enabled filters. Both use the same postFilters array.

Customizing the menu icon

The sidebar shows a default icon for every resource. Pass an icon to a <Resource> to change it — any React component works, so a Lucide icon drops straight in. First define an authors list:

src/authors.tsx
import { DataTable, List, ListPagination } from "@chelone/react";
 
export function AuthorList() {
  return (
    <List>
      <DataTable>
        <DataTable.Col source="name" />
      </DataTable>
      <ListPagination />
    </List>
  );
}

The following App.tsx snippets update the App from Setup: keep its dataProvider, queryClient, resources, view imports, and surrounding <BrowserRouter>.

src/App.tsx
import { Admin, Layout, Resource } from "@chelone/react";
import { StickyNote, Users } from "lucide-react";
import { AuthorList } from "./authors";
// …
<Admin dataProvider={dataProvider} queryClient={queryClient} resources={resources}>
  <Admin.Layout>
    <Layout />
  </Admin.Layout>
  <Resource name="posts" icon={StickyNote} list={PostList} edit={PostEdit} create={PostCreate} show={PostShow} />
  <Resource name="authors" icon={Users} list={AuthorList} />
</Admin>;

Using a custom home page

By default <Admin> shows the first resource's list as the home page. To show your own component instead, pass it as the dashboard prop:

src/dashboard.tsx
export function Dashboard() {
  return (
    <section>
      <h1>Welcome to the administration</h1>
      <p>Manage posts and authors from the sidebar.</p>
    </section>
  );
}
src/App.tsx
import { Admin } from "@chelone/react";
import { Dashboard } from "./dashboard";
 
<Admin dataProvider={dataProvider} queryClient={queryClient} resources={resources} dashboard={<Dashboard />}>
  {/* …resources… */}
</Admin>;

Next: auth & production.