// in src/posts.tsx
import * as React from "react";
import { List, Datagrid, TextField, ReferenceField, EditButton } from 'react-admin';
export const PostList = () => (
<Datagrid>
<TextField source="id" />
<ReferenceField source="user_id" reference="users" />
<TextField source="title" />
<EditButton />
</Datagrid>
</List>
You can find more advanced examples of <Datagrid>
usage in the demos.
The <Datagrid>
is an iterator component: it gets an array of records from the ListContext
, and iterates to display each record in a row. Other examples of iterator component are <SimpleList>
and <SingleFieldList>
.
Tip: If you need more Datagrid features, check out these two alternative components:
<EditableDatagrid>
lets users edit the content right in the datagrid
<DatagridAG>
adds suport for column reordering, aggregation, pivoting, row grouping, infinite scroll, etc.
Both are Enterprise Edition components.
Props
By default, <Datagrid>
renders its body using <DatagridBody>
, an internal react-admin component. You can pass a custom component as the body
prop to override that default. And by the way, <DatagridBody>
has a row
prop set to <DatagridRow>
by default for the same purpose. <DatagridRow>
receives the row record
, the resource
, and a copy of the <Datagrid>
children. That means you can create custom <Datagrid>
logic without copying several components from the react-admin source.
For instance, the <Datagrid isRowSelectable>
prop allows to disable the selection checkbox for some records. To hide checkboxes instead of disabling them, you can override <DatagridRow>
and <DatagridBody>
as follows:
// in src/PostList.tsx
import * as React from "react";
import {
Datagrid,
DatagridBody,
List,
TextField,
RecordContextProvider,
DatagridRowProps,
DatagridBodyProps,
DatagridProps,
FieldProps,
} from "react-admin";
import { TableCell, TableRow, Checkbox } from "@mui/material";
const MyDatagridRow = ({
onToggleItem,
children,
selected,
selectable,
}: DatagridRowProps) => {
const record = useRecordContext();
return record ? (
<TableRow>
{/* first column: selection checkbox */}
<TableCell padding="none">
{selectable && (
<Checkbox
checked={selected}
onClick={event => {
if (onToggleItem) {
onToggleItem(record.id, event);
</TableCell>
{/* data columns based on children */}
{React.Children.map(children, field =>
React.isValidElement<FieldProps>(field) &&
field.props.source ? (
<TableCell key={`${record.id}-${field.props.source}`}>
{field}
</TableCell>
) : null
</TableRow>
) : null;
const MyDatagridBody = (props: DatagridBodyProps) => (
<DatagridBody {...props} row={<MyDatagridRow />} />
const MyDatagrid = (props: DatagridProps) => (
<Datagrid {...props} body={<MyDatagridBody />} />
const PostList = () => (
<MyDatagrid>
<TextField source="title" />
</MyDatagrid>
</List>
export default PostList;
Bulk action buttons appear when users select one or several rows. Clicking on a bulk action button affects all the selected records. This is useful for actions like mass deletion or mass edition.
You disable this feature by setting the bulkActionButtons
prop to false
:
import { Datagrid, List } from 'react-admin';
export const PostList = () => (
<Datagrid bulkActionButtons={false}>
</Datagrid>
</List>
By default, all Datagrids have a single bulk action button, the bulk delete button. You can add other bulk action buttons by passing a custom element as the bulkActionButtons
prop of the <Datagrid>
component:
import { List, Datagrid, BulkUpdateButton, BulkDeleteButton, BulkExportButton } from 'react-admin';
import { VisibilityOff } from '@mui/icons-material';
const PostBulkActionButtons = () => (
<BulkUpdateButton label="Reset Views" data={{ views: 0 }} icon={<VisibilityOff/>} />
<BulkDeleteButton />
<BulkExportButton />
export const PostList = () => (
<Datagrid bulkActionButtons={<PostBulkActionButtons />}>
</Datagrid>
</List>
<BulkDeleteButton>
(enabled by default)
<BulkExportButton>
to export only the selection
<BulkUpdateButton>
to immediately update the selection
<BulkUpdateFormButton>
to display a form allowing to update the selection
Tip: Users can select a range of rows by pressing the shift key while clicking on a row checkbox.
Your browser does not support the video tag.
You can write a custom bulk action button components using the useListContext
hook to get the following data and callbacks:
selectedIds
: the identifiers of the currently selected items.
onUnselectItems
: a callback to empty the selection.
resource
: the currently displayed resource (eg posts
, comments
, etc.)
filterValues
: the filter values. This can be useful if you want to apply your action on all items matching the filter.
Here is an example leveraging the useUpdateMany
hook, which sets the views
property of all posts to 0
:
// in ./CustomResetViewsButton.tsx
import {
useListContext,
useUpdateMany,
useRefresh,
useNotify,
useUnselectAll,
Button,
} from 'react-admin';
import { VisibilityOff } from '@mui/icons-material';
const CustomResetViewsButton = () => {
const { selectedIds } = useListContext();
const refresh = useRefresh();
const notify = useNotify();
const unselectAll = useUnselectAll('posts');
const [updateMany, { isPending }] = useUpdateMany();
const handleClick = () => {
updateMany(
'posts',
{ ids: selectedIds, data: { views: 0 } },
onSuccess: () => {
notify('Posts updated');
unselectAll();
onError: () => {
notify('Error: posts not updated', { type: 'error' });
refresh();
return (
<Button label="Reset views" onClick={handleClick} disabled={isPending}>
<VisibilityOff />
</Button>
But most of the time, bulk actions are mini-applications with a standalone user interface (in a Dialog). Here is the same <CustomResetViewsAction>
implemented behind a confirmation dialog:
// in ./CustomResetViewsButton.tsx
import { useState } from 'react';
import {
Button,
Confirm,
useListContext,
useUpdateMany,
useNotify,
useRefresh,
useUnselectAll,
} from 'react-admin';
const CustomResetViewsButton = () => {
const { selectedIds } = useListContext();
const [open, setOpen] = useState(false);
const refresh = useRefresh();
const notify = useNotify();
const unselectAll = useUnselectAll('posts');
const [updateMany, { isPending }] = useUpdateMany(
'posts',
{ ids: selectedIds, data: { views: 0 } },
onSuccess: () => {
notify('Posts updated');
unselectAll();
onError: error => {
notify('Error: posts not updated', { type: 'error' });
refresh();
const handleClick = () => setOpen(true);
const handleDialogClose = () => setOpen(false);
const handleConfirm = () => {
updateMany();
setOpen(false);
return (
<Button label="Reset Views" onClick={handleClick} />
<Confirm
isOpen={open}
loading={isPending}
title="Update View Count"
content="Are you sure you want to reset the views for these items?"
onConfirm={handleConfirm}
onClose={handleDialogClose}
export default CustomResetViewsButton;
Tip: <Confirm>
leverages Material UI’s <Dialog>
component to implement a confirmation popup. Feel free to use it in your admins!
Tip: <Confirm>
text props such as title
and content
are translatable. You can pass translation keys in these props. Note: content
is only translatable when value is string
, otherwise it renders the content as a ReactNode
.
Tip: You can customize the text of the two <Confirm>
component buttons using the cancel
and confirm
props which accept translation keys. You can customize the icons by setting the ConfirmIcon
and CancelIcon
props, which accept a SvgIcon type.
Tip: React-admin doesn’t use the <Confirm>
component internally, because deletes and updates are applied locally immediately, then dispatched to the server after a few seconds, unless the user chooses to undo the modification. That’s what we call optimistic rendering. You can do the same for the <ResetViewsButton>
by setting undoable: true
in the last argument of useUpdateMany()
, as follows:
// in ./CustomResetViewsButton.js
import * as React from "react";
import {
Button,
- Confirm,
useListContext,
useUpdateMany,
- useRefresh,
useNotify,
useUnselectAll,
} from 'react-admin';
import { VisibilityOff } from '@mui/icons-material';
const CustomResetViewsButton = () => {
const { selectedIds } = useListContext();
- const refresh = useRefresh();
const notify = useNotify();
const unselectAll = useUnselectAll('posts');
const [updateMany, { isPending }] = useUpdateMany(
'posts',
{ ids: selectedIds, data: { views: 0 } },
onSuccess: () => {
- refresh();
- notify('Posts updated');
+ notify('Posts updated', { undoable: true }); // the last argument forces the display of 'undo' in the notification
unselectAll();
onError: error => notify('Error: posts not updated', { type: 'error' }),
+ mutationMode: 'undoable'
return (
<Button
label="simple.action.resetViews"
disabled={isPending}
onClick={updateMany}
<VisibilityOff />
</Button>
children
<Datagrid>
accepts a list of Field components as children. It inspects each child’s source
and/or label
props to determine the name of the column.
What’s a Field component? Simply a component that reads the record (via useRecordContext
) and renders a value. React-admin includes many Field components that you can use as children of <Datagrid>
(<TextField>
, <NumberField>
, <DateField>
, <ReferenceField>
, and many more). Check the Fields documentation for more information.
You can even create your own field components.
// in src/users.tsx
import * as React from 'react';
import { useRecordContext, List, Datagrid, TextField, DateField } from 'react-admin';
const FullNameField = () => {
const record = useRecordContext();
return <span>{record.firstName} {record.lastName}</span>;
export const UserList = () => (
<Datagrid>
<FullNameField source="last_name" label="Name" />
<DateField source="dob" />
<TextField source="city" />
</Datagrid>
</List>
<Datagrid>
also inspects its children for headerClassName
and cellClassName
props, and gives the class names to the headers and the cells of that column.
Finally, <Datagrid>
inspects children for props that indicate how it should be sorted (see the Customizing The Sort Order For Columns section) below.
empty
It’s possible that a Datagrid will have no records to display. If the Datagrid’s parent component does not handle the empty state, the Datagrid will display a message indicating there are no results. This message is translatable and its key is ra.navigation.no_results
.
You can customize the empty state by passing a component to the empty
prop:
const CustomEmpty = () => <div>No books found</div>;
const PostList = () => (
<Datagrid empty={<CustomEmpty />}>
<TextField source="id" />
<TextField source="title" />
<TextField source="views" />
</Datagrid>
</List>
expand
To show more data from the resource without adding too many columns, you can show data in an expandable panel below the row on demand, using the expand
prop.
For instance, this code shows the body
of a post in an expandable panel:
import { useRecordContext } from 'react-admin';
const PostPanel = () => {
const record = useRecordContext();
return (
<div dangerouslySetInnerHTML={{ __html: record.body }} />
const PostList = () => (
<Datagrid expand={<PostPanel />}>
<TextField source="id" />
<TextField source="title" />
<DateField source="published_at" />
<BooleanField source="commentable" />
<EditButton />
</Datagrid>
</List>
The expand
prop expects a React element as value. When the user chooses to expand the row, the Datagrid renders the component inside a RecordContext
.
Tip: You can actually use a Show Layout component for the expand
prop:
const PostShow = () => (
<SimpleShowLayout>
<RichTextField source="body" />
</SimpleShowLayout>
const PostList = () => (
<Datagrid expand={<PostShow />}>
<TextField source="id" />
<TextField source="title" />
<DateField source="published_at" />
<BooleanField source="commentable" />
<EditButton />
</Datagrid>
</List>
Tip: You can go one step further and use an <Edit>
view as expand
component:
const PostEdit = () => {
const record = useRecordContext();
const resource = useResourceContext();
return (
resource={resource}
id={record.id}
/* disable the app title change when shown */
title=" "
<SimpleForm>
<RichTextInput source="body" />
</SimpleForm>
</Edit>
const PostList = () => (
<Datagrid expand={<PostEdit />}>
<TextField source="id" />
<TextField source="title" />
<DateField source="published_at" />
<BooleanField source="commentable" />
<EditButton />
</Datagrid>
</List>
expandSingle
By default, when using an expand
panel, users can expand as many rows as they want. The expandSingle
prop changes that behavior: when a user clicks on the expand button of a row, other expanded rows collapse. As a consequence, only a single row can be expanded at a time.
export const PostList = () => (
<Datagrid expand={<PostPanel />} expandSingle>
</Datagrid>
</List>
By default, <Datagrid>
renders the table head using <DatagridHeader>
, an internal react-admin component. You can pass a custom component as the header
prop to override that default. This can be useful e.g. to add a second header row, or to create headers spanning multiple columns.
For instance, here is a simple datagrid header that displays column names with no sort and no “select all” button:
import * as React from "react";
import { TableHead, TableRow, TableCell } from "@mui/material";
import { DatagridHeaderProps, FieldProps, List, Datagrid } from "react-admin";
const DatagridHeader = ({ children }: DatagridHeaderProps) => (
<TableHead>
<TableRow>
<TableCell></TableCell>
{/* empty cell to account for the select row checkbox in the body */}
{React.Children.map(children, (child) =>
React.isValidElement<FieldProps>(child) ? (
<TableCell key={child.props.source}>{child.props.source}</TableCell>
) : null
</TableRow>
</TableHead>
const PostList = () => (
<Datagrid header={<DatagridHeader />}>{/* ... */}</Datagrid>
</List>
Tip: To handle sorting in your custom Datagrid header component, check out the Building a custom sort control section.
hover
By default, the rows of the datagrid are highlighted when the user hovers over them. To disable this behavior, set the hover
prop to false
.
const PostList = () => (
<Datagrid hover={false}>
<TextField source="id" />
<TextField source="title" />
<TextField source="views" />
</Datagrid>
</List>
isRowExpandable
You can customize which rows can have an expandable panel by using the isRowExpandable
prop. It expects a function that receives the row record and returns a boolean.
For instance, this code shows an expand button only for rows that have a detail to show:
import { List, Datagrid, EditButton, BooleanField, DateField, TextField, useRecordContext } from 'react-admin';
const PostPanel = () => {
const record = useRecordContext();
return (
<div dangerouslySetInnerHTML={{ __html: record.body }} />
const PostList = () => (
<Datagrid
expand={<PostPanel />}
isRowExpandable={row => row.has_detail}
<TextField source="id" />
<TextField source="title" />
<DateField source="published_at" />
<BooleanField source="commentable" />
<EditButton />
</Datagrid>
</List>
isRowSelectable
You can customize which rows show an enabled selection checkbox using the isRowSelectable
prop. It expects a function that receives the row record and returns a boolean.
For instance, this code enables a checkbox only for rows with an id greater than 300:
import { List, Datagrid } from 'react-admin';
export const PostList = () => (
<Datagrid isRowSelectable={ record => record.id > 300 }>
</Datagrid>
</List>
optimized
When displaying large pages of data, you might experience some performance issues.
This is mostly due to the fact that we iterate over the <Datagrid>
children and clone them.
In such cases, you can opt-in for an optimized version of the <Datagrid>
by setting its optimized
prop to true
.
Be aware that you can’t have dynamic children, such as those displayed or hidden by checking permissions, when using this mode.
import { List, Datagrid, TextField } from 'react-admin';
const PostList = () => (
<Datagrid optimized>
<TextField source="id" />
<TextField source="title" />
<TextField source="views" />
</Datagrid>
</List>
rowClick
By default, <Datagrid>
will look at the current resource definition to determine what to do when the user clicks on a row. If the resource has a show
page, it will redirect to the Show view. If the resource has an edit
page, it will redirect to the Edit view. Otherwise, the row will not be clickable.
You can choose what happens when the user clicks on a row by setting the rowClick
prop. For instance, set the rowClick
prop to "edit"
to redirect to the Edit view:
import { List, Datagrid } from 'react-admin';
export const PostList = () => (
<Datagrid rowClick="edit">
</Datagrid>
</List>
rowClick
accepts the following values:
"edit"
to redirect to the edition view
"show"
to redirect to the show view
"expand"
to open the expand
panel
"toggleSelection"
to trigger the onToggleItem
function
false
to do nothing
a function (id, resource, record) => path
that may return any of the above values or a custom path
Tip: If you pass a function, it can return 'edit'
, 'show'
, false
or a router path. This allows to redirect to either the Edit or Show view after checking a condition on the record. For example:
import { Identifier, RaRecord } from 'react-admin';
const postRowClick = (id: Identifier, resource: string, record: RaRecord) => record.editable ? 'edit' : 'show';
Tip: If you pass a function, it can also return a promise allowing you to check an external API before returning a path. For example:
import { Identifier, RaRecord } from 'react-admin';
import fetchUserRights from './fetchUserRights';
const getPermissions = useGetPermissions();
const postRowClick = (id: Identifier, resource: string, record: RaRecord) =>
useGetPermissions()
.then(permissions => permissions === 'admin' ? 'edit' : 'show');
rowStyle
Deprecated - use rowSx
instead.
You can customize the <Datagrid>
row style (applied to the <tr>
element) based on the record, thanks to the rowStyle
prop, which expects a function. React-admin calls this function for each row, passing the current record and index as arguments. The function should return a style object, which react-admin uses as a <tr style>
prop.
For instance, this allows to apply a custom background to the entire row if one value of the record - like its number of views - passes a certain threshold.
import { List, Datagrid } from 'react-admin';
const postRowStyle = (record, index) => ({
backgroundColor: record.nb_views >= 500 ? '#efe' : 'white',
export const PostList = () => (
<Datagrid rowStyle={postRowStyle}>
</Datagrid>
</List>
rowSx
You can customize the styles of rows and cells in <Datagrid>
(applied to the <DatagridRow>
element) based on the record, thanks to the rowSx
prop, which expects a function. React-admin calls this function for each row, passing the current record and index as arguments. The function should return a Material UI sx
, which react-admin uses as a <TableRow sx>
prop.
For instance, this allows to apply a custom background to the entire row if one value of the record - like its number of views - passes a certain threshold.
import { List, Datagrid } from 'react-admin';
const postRowSx = (record, index) => ({
backgroundColor: record.nb_views >= 500 ? '#efe' : 'white',
export const PostList = () => (
<Datagrid rowSx={postRowSx}>
</Datagrid>
</List>
The <Datagrid>
is designed for a high density of content, so the row padding is low. If you want to add more margin to each cell, set the size
prop to medium
.
export const PostList = () => (
<Datagrid size="medium">
</Datagrid>
</List>
Tip: size
is actually a prop of the Material UI <Table>
component. Just like all additional <Datagrid>
props, it is passed down to the <Table>
component.
sx
: CSS API
The <Datagrid>
component accepts the usual className
prop. You can also override many styles of the inner components thanks to the sx
property (see the sx
documentation for syntax and examples).
This property accepts the following subclasses:
For instance, here is how you can leverage these styles to implement zebra stripes (a.k.a. alternate row styles)
const PostList = () => (
<Datagrid
sx={{
'& .RaDatagrid-rowOdd': {
backgroundColor: '#fee',
<TextField source="id" />
<TextField source="title" />
<TextField source="author" />
<TextField source="year" />
</Datagrid>
</List>
Tip: The Datagrid
component classes
can also be customized for all instances of the component with its global css name "RaDatagrid"
as describe here
<Datagrid>
has sticky headers, which means that the header row will remain visible even when scrolling down the page.
Your browser does not support the video tag.
You don’t need to do anything for this to work, as it’s enabled by default.
Configurable
You can let end users customize the fields displayed in the <Datagrid>
by using the <DatagridConfigurable>
component instead.
When users enter the configuration mode and select the <Datagrid>
, they can show / hide datagrid columns. They can also use the <SelectColumnsButton>
By default, <DatagridConfigurable>
renders all child fields. But you can also omit some of them by passing an omit
prop containing an array of field sources:
// by default, hide the id and author columns
// users can choose to show them in configuration mode
const PostList = () => (
<DatagridConfigurable omit={['id', 'author']}>
<TextField source="id" />
<TextField source="title" />
<TextField source="author" />
<TextField source="year" />
</DatagridConfigurable>
</List>
If you render more than one <DatagridConfigurable>
in the same page, you must pass a unique preferenceKey
prop to each one:
const PostList = () => (
<DatagridConfigurable preferenceKey="posts.datagrid">
<TextField source="id" />
<TextField source="title" />
<TextField source="author" />
<TextField source="year" />
</DatagridConfigurable>
</List>
If you include a <SelectColumnsButton>
in a page that has more than one <DatagridConfigurable>
, you have to link the two components by giving them the same preferenceKey:
const PostListActions = () => (
<TopToolbar>
<SelectColumnsButton preferenceKey="posts.datagrid" />
</TopToolbar>
const PostList = () => (
<List actions={<PostListActions />}>
<DatagridConfigurable preferenceKey="posts.datagrid">
<TextField source="id" />
<TextField source="title" />
<TextField source="author" />
<TextField source="year" />
</DatagridConfigurable>
</List>
The inspector uses the field source
(or label
when it’s a string) to display the column name. If you use non-field children (e.g. action buttons), then it’s your responsibility to wrap them in a component with a label
prop, that will be used by the inspector. You can use a <WrapperField>
for that purpose:
const PostList = () => (
<DatagridConfigurable>
<TextField source="id" />
<TextField source="title" />
<TextField source="author" />
<TextField source="year" />
<WrapperField label="Actions">
<EditButton />
</WrapperField>
</DatagridConfigurable>
</List>
Tip: You may need to clear your local storage to reflect the changes, as react-admin saves the computed column names in the Store. For the same reason, your users may need to log out and in again to see the changes. Alternatively, you can leverage Store Invalidation to do it automatically.
<DatagridConfigurable>
accepts the same props as <Datagrid>
.
Tip: For even more column customization (resizable columns, column grouping, etc.), check out the <DatagridAG>
component.
Your browser does not support the video tag.
Editable Spreadsheet
The separation between list pages and edit pages is not always relevant. Sometimes, you want to let users edit records directly in the list page. React-admin provides two alternative components to edit records in a Datagrid:
<EditableDatagrid>
leverages the react-admin input components to turn a row into an editable form.
<DatagridAG>
provides a spreadsheet-like interface, “à la” Excel, using the ag-Grid library.
<EditableDatagrid>
: Editable Rows
Your browser does not support the video tag.
<EditableDatagrid>
is a drop-in replacement for <Datagrid>
. It expects 2 additional props: createForm
and editForm
, the components to be displayed when a user creates or edits a row. The <RowForm>
component allows to create such forms using react-admin Input components.
import {
List,
ListActions,
TextField,
TextInput,
DateField,
DateInput,
SelectField,
SelectInput,
required,
} from 'react-admin';
import { EditableDatagrid, RowForm } from '@react-admin/ra-editable-datagrid';
const professionChoices = [
{ id: 'actor', name: 'Actor' },
{ id: 'singer', name: 'Singer' },
{ id: 'other', name: 'Other' },
const ArtistList = () => (
<List actions={<ListActions hasCreate />} empty={false}>
<EditableDatagrid
mutationMode="undoable"
createForm={<ArtistForm />}
editForm={<ArtistForm />}
<TextField source="id" />
<TextField source="firstname" />
<TextField source="name" />
<DateField source="dob" label="born" />
<SelectField
source="prof"
label="Profession"
choices={professionChoices}
</EditableDatagrid>
</List>
const ArtistForm = () => (
<RowForm>
<TextField source="id" />
<TextInput source="firstname" validate={required()} />
<TextInput source="name" validate={required()} />
<DateInput source="dob" label="born" validate={required()} />
<SelectInput
source="prof"
label="Profession"
choices={professionChoices}
</RowForm>
Check the <EditableDatagrid>
documentation for more details.
<DatagridAG>
: Spreadsheet-like Interface
Your browser does not support the video tag.
<DatagridAG>
is an advanced Datagrid component based on ag-grid. Here is a (non-exhaustive) list of features that <DatagridAG>
offers:
In place editing of cells or rows
Advanced filtering
Columns resizing and reordering
Automatic page size
Automatic column size
Themes
Row selection and bulk actions
Compatibility with React Admin fields
Additionally, <DatagridAG>
is compatible with the Enterprise version of ag-grid, which offers even more features:
Row Grouping
Aggregation
Tree Data
Pivoting
More advanced filtering
Master Detail views
Range Selection
Excel Export
And more…
Fields And Permissions
You might want to display some fields only to users with specific permissions. Use the usePermissions
hook to get the user permissions and hide Fields accordingly:
import { List, Datagrid, TextField, TextInput, ShowButton, usePermissions } from 'react-admin';
const getUserFilters = (permissions) => ([
<TextInput label="user.list.search" source="q" alwaysOn />,
<TextInput source="name" />,
permissions === 'admin' ? <TextInput source="role" /> : null,
].filter(filter => filter !== null)
export const UserList = ({ permissions, ...props }) => {
const { permissions } = usePermissions();
return (
{...props}
filters={getUserFilters(permissions)}
sort={{ field: 'name', order: 'ASC' }}
<Datagrid>
<TextField source="id" />
<TextField source="name" />
{permissions === 'admin' && <TextField source="role" />}
{permissions === 'admin' && <EditButton />}
<ShowButton />
</Datagrid>
</List>
Note how the permissions
prop is passed down to the custom filters
component to allow Filter customization, too.
It’s up to your authProvider
to return whatever you need to check roles and permissions inside your component. Check the authProvider documentation for more information.
Tip: The ra-rbac module provides a wrapper for the <Datagrid>
with built-in permission check for columns.
Standalone Usage
You can use the <Datagrid>
component to display data that you’ve fetched yourself. You’ll need to pass all the props required for its features:
import { useGetList, Datagrid, TextField } from 'react-admin';
const sort = { field: 'id', order: 'DESC' };
const MyCustomList = () => {
const { data, total, isPending } = useGetList('books', {
pagination: { page: 1, perPage: 10 },
sort,
return (
<Datagrid
resource="books"
data={data}
total={total}
isPending={isPending}
sort={sort}
bulkActionButtons={false}
<TextField source="id" />
<TextField source="title" />
</Datagrid>
This list has no filtering, sorting, or row selection - it’s static. If you want to allow users to interact with the <Datagrid>
, use the useList
hook to build callbacks to manipulate local data. You will have to put the result in a <ListContextProvider>
parent component:
import {
useGetList,
useList,
ListContextProvider,
Datagrid,
TextField
} from 'react-admin';
const sort = { field: 'id', order: 'DESC' };
const MyCustomList = () => {
const { data, isPending } = useGetList('books', {
pagination: { page: 1, perPage: 10 },
sort,
const listContext = useList({ data, isPending });
return (
<ListContextProvider value={listContext}>
<Datagrid>
<TextField source="id" />
<TextField source="title" />
</Datagrid>
</ListContextProvider>
Styling Specific Columns
If you want to style a particular column, you can take advantage of the generated class names per column. For instance, for a column formed for a <TextField source="title" />
both the column header and the cells will have the class column-title
.
Using the sx
prop, the column customization is just one line:
import { List, Datagrid, TextField } from 'react-admin';
const PostList = () => (
<Datagrid
sx={{
'& .column-title': { backgroundColor: '#fee' },
<TextField source="id" />
<TextField source="title" /> {/* will have different background */}
<TextField source="author" />
<TextField source="year" />
</Datagrid>
</List>
You can even style the header cells differently by passing a more specific CSS selector (e.g. & tr.column-title
).
A common practice is to hide certain columns on smaller screens. You can use the same technique:
import { List, Datagrid, TextField } from 'react-admin';
const PostList = () => (
<Datagrid
sx={{
'& .column-title': {
sm: { display: 'none' },
md: { display: 'table-cell' },
<TextField source="id" />
<TextField source="title" />
<TextField source="author" />
<TextField source="year" />
</Datagrid>
</List>
Showing / Hiding Columns
The <SelectColumnsButton>
component lets users hide, show, and reorder datagrid columns.
<TextField source="title" />
<TextField source="author" />
<TextField source="year" />
</DatagridConfigurable>
</List>
<SelectColumnsButton>
must be used in conjunction with <DatagridConfigurable>
, the configurable version of <Datagrid>
, described in the next section.
Tip: For even more column customization (resizable columns, column grouping, etc.), check out the <DatagridAG>
component.
Your browser does not support the video tag.
Hiding Checkboxes
You can hide the checkbox column by passing false
to the bulkActionButtons
prop:
import { Datagrid, List } from 'react-admin';
export const PostList = () => (
<Datagrid bulkActionButtons={false}>
</Datagrid>
</List>
Customizing Column Sort
The column headers are buttons allowing users to change the list sort field and order. This feature requires no configuration and works out fo the box. The next sections explain how you can disable or modify the field used for sorting on a particular column.
Disabling Sorting
It is possible to disable sorting for a specific <Field>
by passing a sortable
property set to false
:
// in src/posts.tsx
import { List, Datagrid, TextField } from 'react-admin';
export const PostList = () => (
<Datagrid>
<TextField source="id" sortable={false} />
<TextField source="title" />
<TextField source="body" />
</Datagrid>
</List>
Specifying A Sort Field
By default, a column is sorted by the source
property. To define another attribute to sort by, set it via the <Field sortBy>
property:
// in src/posts.tsx
import { List, Datagrid, FunctionField, ReferenceField, TextField } from 'react-admin';
export const PostList = () => (
<Datagrid>
<ReferenceField label="Post" source="id" reference="posts" sortBy="title">
<TextField source="title" />
</ReferenceField>
<FunctionField
label="Author"
sortBy="last_name"
render={record => `${record.author.first_name} ${record.author.last_name}`}
<TextField source="body" />
</Datagrid>
</List>
Specifying The Sort Order
By default, when the user clicks on a column header, the list becomes sorted in the ascending order. You change this behavior by setting the sortByOrder
prop to "DESC"
in a <Datagrid>
<Field>
:
// in src/posts.tsx
import { List, Datagrid, FunctionField, ReferenceField, TextField } from 'react-admin';
export const PostList = () => (
<Datagrid>
<ReferenceField label="Post" source="id" reference="posts" sortByOrder="DESC">
<TextField source="title" />
</ReferenceField>
<FunctionField
label="Author"
sortBy="last_name"
sortByOrder="DESC"
render={record => `${record.author.first_name} ${record.author.last_name}`}
<TextField source="body" />
</Datagrid>
</List>