InfiniteList

An infinite-scroll variant of <List>, backed by useInfiniteListController from ra-core.

Installation

pnpm dlx shadcn@latest add @shadmin/infinite-list

<InfiniteList> renders the same chrome as <List> (breadcrumb, title, filters, actions) but replaces page-based pagination with a "Load more" affordance: as the user scrolls, the next page of records is fetched automatically — or they can press the button rendered at the bottom of the list.

Usage

<InfiniteList> is a drop-in replacement for <List> in most cases — children, filters, actions, sort, and permanent filters all work identically.

import { DataTable, InfiniteList } from "@/components/admin";
 
export const PostList = () => (
  <InfiniteList perPage={20}>
    <DataTable>
      <DataTable.Col source="id" />
      <DataTable.Col source="title" />
      <DataTable.Col source="published_at" />
    </DataTable>
  </InfiniteList>
);

It pairs nicely with <SimpleList> for mobile-friendly screens:

import { InfiniteList, SimpleList } from "@/components/admin";
 
export const PostList = () => (
  <InfiniteList perPage={20}>
    <SimpleList
      primaryText={(record) => record.title}
      secondaryText={(record) => record.author}
    />
  </InfiniteList>
);

Props

PropRequiredTypeDefaultDescription
childrenOptional*ReactNode-Component(s) that display the records
renderOptional*(ctx) => ReactNode-Alternate render function receiving the list context
actionsOptionalReactNodedefault action barCustom actions area (right side of header)
asideOptionalReactNode-Side panel rendered alongside the list content
componentOptionalElementType"div"Override the root element wrapping the list content
emptyOptionalReactNode | false<Empty />Replacement for the default empty-state component
debounceOptionalnumber500Debounce (ms) for filter & sort changes
disableAuthenticationOptionalbooleanfalseSkip auth check for this page
disableBreadcrumbOptionalbooleanfalseHide the default breadcrumb
disableSyncWithLocationOptionalbooleanfalseKeep list params local (not in the URL)
exporterOptionalfalse | Exporter-Custom export logic (set false to hide Export button)
filtersOptionalReactElement[]-Array of filter input elements (displayed inline)
filterOptionalobject-Permanent filters always applied
filterDefaultValuesOptionalobject-Initial filter form values
paginationOptionalReactNode<InfinitePagination />Custom pagination component
perPageOptionalnumber10Records per page
queryOptionsOptionalobject-Extra TanStack Query options
resourceOptionalstringinferredResource name, defaults to the current <ResourceContext>
sortOptional{ field: string; order: 'ASC' | 'DESC' }-Initial sort
storeKeyOptionalstring | falsederivedStorage key for persisted params; false disables persistence
titleOptionalstring | ReactNode | falseresource plural labelPage title

* Provide either children or render.

empty

When the list has no records and no active filters, <InfiniteList> renders the default <Empty> component. Pass a custom node to replace it, or false to suppress:

import { InfiniteList, DataTable } from "@/components/admin";
 
export const PostList = () => (
  <InfiniteList
    empty={
      <p className="text-center py-8 text-muted-foreground">No posts yet.</p>
    }
  >
    <DataTable>
      <DataTable.Col source="id" />
      <DataTable.Col source="title" />
    </DataTable>
  </InfiniteList>
);

component

By default, <InfiniteList> wraps the list content in a <div>. Pass any React element type to component to replace it:

import { InfiniteList, DataTable } from "@/components/admin";
import { Card } from "@/components/ui/card";
 
export const PostList = () => (
  <InfiniteList component={Card}>
    <DataTable>
      <DataTable.Col source="id" />
      <DataTable.Col source="title" />
    </DataTable>
  </InfiniteList>
);

aside

Pass any React node as aside to display a side panel next to the infinite list content:

import { InfiniteList, DataTable } from "@/components/admin";
 
const ListHelp = () => (
  <div className="p-4 bg-muted rounded text-sm w-64">
    <p className="font-medium mb-2">Tips</p>
    <p>Scroll down to load more records automatically.</p>
  </div>
);
 
export const PostList = () => (
  <InfiniteList aside={<ListHelp />}>
    <DataTable>
      <DataTable.Col source="id" />
      <DataTable.Col source="title" />
    </DataTable>
  </InfiniteList>
);

pagination

By default, <InfiniteList> renders an <InfinitePagination>. Override it to customize the loader or hide it entirely:

import { InfiniteList, InfinitePagination } from "@/components/admin";
 
<InfiniteList pagination={<InfinitePagination className="py-8" />}>
  {/* ... */}
</InfiniteList>;

When to choose <InfiniteList> vs <List>

  • Prefer <InfiniteList> for activity feeds, search results, mobile-first views, and any UI where the user mostly scans down a single column.
  • Prefer <List> for tables that need direct access to a known page (e.g. "go to page 12"), bulk actions across all records, or total-aware UI.