<TabbedShowLayout>
<TabbedShowLayout>
renders a set of <Tabs>
, each of which contains a list of record fields in a single-column layout (via Material UI’s <Stack>
component).
<TabbedShowLayout>
delegates the actual rendering of fields to its children, which should be <TabbedShowLayout.Tab>
elements. <TabbedShowLayout.Tab>
wraps each field inside a <Labeled>
component to add a label.
Switching tabs will update the current url. By default, it uses the tabs indexes and the first tab will be displayed at the root url. You can customize the path by providing a path
prop to each <TabbedShowLayout.Tab>
component. If you’d like the first one to act as an index page, just omit the path
prop.
Usage
Use <TabbedShowLayout>
as descendant of a <Show>
component (or any component creating a <RecordContext>
), define the tabs via <TabbedShowLayout.Tab>
children, and set the fields to be displayed as children of each tab:
import { Show, TabbedShowLayout } from 'react-admin'
export const PostShow = () => (
<Show>
<TabbedShowLayout>
<TabbedShowLayout.Tab label="summary">
<TextField label="Id" source="id" />
<TextField source="title" />
<TextField source="teaser" />
</TabbedShowLayout.Tab>
<TabbedShowLayout.Tab label="body" path="body">
<RichTextField source="body" label={false} />
</TabbedShowLayout.Tab>
<TabbedShowLayout.Tab label="Miscellaneous" path="miscellaneous">
<TextField label="Password (if protected post)" source="password" type="password" />
<DateField label="Publication date" source="published_at" />
<NumberField source="average_note" />
<BooleanField label="Allow comments?" source="commentable" defaultValue />
<TextField label="Nb views" source="views" />
</TabbedShowLayout.Tab>
<TabbedShowLayout.Tab label="comments" path="comments">
<ReferenceManyField reference="comments" target="post_id" label={false}>
<Datagrid>
<TextField source="body" />
<DateField source="created_at" />
<EditButton />
</Datagrid>
</ReferenceManyField>
</TabbedShowLayout.Tab>
</TabbedShowLayout>
</Show>
);
Props
The <TabbedShowLayout>
component accepts the following props:
Prop | Required | Type | Default | Description |
---|---|---|---|---|
children |
Required | ReactNode |
The components rendering the record fields | |
className |
Optional | string |
The class name applied to the root element | |
divider |
Optional | ReactElement |
Optional element to render between each field | |
record |
Optional | object |
The record to render. Passed to the RecordContext |
|
spacing |
Optional | number |
1 |
The spacing between fields. Passed to the <Stack> component |
sx |
Optional | object |
Override the styles applied to the root element | |
syncWith Location |
Optional | boolean |
true |
Set to false to disable storing the active tab in the url |
tabs |
Optional | ReactElement |
Custom tabs component |
Additional props are passed to the root component (<div>
).
children
Children of <TabbedShowLayout>
must be <TabbedShowLayout.Tab>
components.
The <TabbedShowLayout.Tab>
component renders tabs headers and the active tab. It manages the tab change, either via the URL, or an internal state.
It accepts the following props:
label
: The string displayed for each tabicon
: The icon to show before the label (optional). Must be a component.path
: The string used for custom urls (optional)count
: the number of items in the tab (displayed close to the label)
// in src/posts.js
import * as React from "react";
import FavoriteIcon from '@mui/icons-material/Favorite';
import PersonPinIcon from '@mui/icons-material/PersonPin';
import { Show, TabbedShowLayout, TextField } from 'react-admin';
export const PostShow = () => (
<Show>
<TabbedShowLayout>
<TabbedShowLayout.Tab label="Content" icon={<FavoriteIcon />}>
<TextField source="title" />
<TextField source="subtitle" />
</TabbedShowLayout.Tab>
<TabbedShowLayout.Tab label="Metadata" icon={<PersonIcon />} path="metadata">
<TextField source="category" />
</TabbedShowLayout.Tab>
</TabbedShowLayout>
</Show>
);
divider
<Stack>
accepts an optional divider
prop - a component rendered between each row. <TabbedShowLayout>
also accepts this props, and passes it to the <Stack>
component.
import { Divider } from '@mui/material';
const PostShow = () => (
<Show>
<TabbedShowLayout divider={<Divider flexItem />}>
<TabbedShowLayout.Tab label="main">
<TextField source="title" />
<TextField source="subtitle" />
</TabbedShowLayout.Tab>
</TabbedShowLayout>
</Show>
);
spacing
<TabbedShowLayout.Tab>
renders a Material UI <Stack>
. You can customize the spacing of each row by passing a spacing
prop:
const PostShow = () => (
<Show>
<TabbedShowLayout spacing={2}>
<TabbedShowLayout.Tab label="main">
<TextField source="title" />
<TextField source="subtitle" />
</TabbedShowLayout.Tab>
</TabbedShowLayout>
</Show>
);
The default spacing is 1
.
syncWithLocation
To let users come back to a previous tab by using the browser’s back button, <TabbedShowLayout>
stores the active tab in the location by default. You can opt out the location synchronization by passing false
to the syncWithLocation
prop. This allows e.g. to have several <TabbedShowLayout>
components in a page.
import { TabbedShowLayout, Tab } from 'react-admin'
export const PostShow = () => (
<Show>
<TabbedShowLayout syncWithLocation={false}>
<TabbedShowLayout.Tab label="summary">
<TextField label="Id" source="id" />
<TextField source="title" />
<TextField source="teaser" />
</TabbedShowLayout.Tab>
<TabbedShowLayout.Tab label="body" path="body">
<RichTextField source="body" label={false} />
</TabbedShowLayout.Tab>
<TabbedShowLayout.Tab label="Miscellaneous" path="miscellaneous">
<TextField label="Password (if protected post)" source="password" type="password" />
<DateField label="Publication date" source="published_at" />
<NumberField source="average_note" />
<BooleanField label="Allow comments?" source="commentable" defaultValue />
<TextField label="Nb views" source="views" />
</TabbedShowLayout.Tab>
<TabbedShowLayout.Tab label="comments" path="comments">
<ReferenceManyField reference="comments" target="post_id" label={false}>
<Datagrid>
<TextField source="body" />
<DateField source="created_at" />
<EditButton />
</Datagrid>
</ReferenceManyField>
</TabbedShowLayout.Tab>
</TabbedShowLayout>
</Show>
);
Tip: When syncWithLocation
is false
, the path
prop of the <TabbedShowLayout.Tab>
components is ignored.
record
By default, <TabbedShowLayout>
reads the record from the RecordContext
. But by passing a record
prop, you can render the component outside a RecordContext
.
const StaticPostShow = () => (
<TabbedShowLayout record={{ id: 123, title: 'Hello world' }}>
<TabbedShowLayout.Tab label="main">
<TextField source="title" />
</TabbedShowLayout.Tab>
</TabbedShowLayout>
);
When passed a record
, <TabbedShowLayout>
creates a RecordContext
with the given record.
tabs
By default, <TabbedShowLayout>
renders its tabs using <TabbedShowLayoutTabs>
, an internal react-admin component. You can pass a custom component as the tabs
prop to override that default. Also, props passed to <TabbedShowLayoutTabs>
are passed to the Material UI’s <Tabs>
component inside <TabbedShowLayoutTabs>
. That means you can create a custom tabs
component without copying several components from the react-admin source.
For instance, to make use of scrollable <Tabs>
, you can pass variant="scrollable"
and scrollButtons="auto"
props to <TabbedShowLayoutTabs>
and use it in the tabs
prop from <TabbedShowLayout>
as follows:
import {
Show,
TabbedShowLayout,
TabbedShowLayoutTabs,
} from 'react-admin';
const ScrollableTabbedShowLayout = () => (
<Show>
<TabbedShowLayout tabs={<TabbedShowLayoutTabs variant="scrollable" scrollButtons="auto" />}>
...
</TabbedShowLayout>
</Show>
);
export default ScrollableTabbedShowLayout;
<TabbedShowLayout.Tab>
<TabbedShowLayout.Tab>
renders each child inside a <Labeled>
component. This component uses the humanized source as label by default. You can customize it by passing a label
prop to the fields:
const PostShow = () => (
<Show>
<TabbedShowLayout>
<TabbedShowLayout.Tab label="main">
<TextField label="My Custom Title" source="title" />
</TabbedShowLayout.Tab>
</TabbedShowLayout>
</Show>
);
The <Labeled label>
uses the humanized source by default. You can customize it by passing a label
prop to the fields:
const PostShow = () => (
<Show>
<TabbedShowLayout>
<TabbedShowLayout.Tab label="main">
<TextField label="My Custom Title" source="title" />
<TextField label="my.custom.translationKey" source="description" />
</TabbedShowLayout.Tab>
</TabbedShowLayout>
</Show>
);
// translates to
const PostShow = () => (
<Show>
<TabbedShowLayout>
<Labeled label="My Custom Title">
<TextField source="title" />
</Labeled>
<Labeled label="my.custom.translationKey">
<TextField source="description" />
</Labeled>
</TabbedShowLayout>
</Show>
);
You can disable the <Labeled>
decoration by passing setting label={false}
on a field:
const PostShow = () => (
<Show>
<TabbedShowLayout>
<TabbedShowLayout.Tab label="main">
<TextField label={false} source="title" />
</TabbedShowLayout.Tab>
</TabbedShowLayout>
</Show>
);
<TabbedShowLayout.Tab>
children can be anything you want. Try passing your own components:
const PostTitle = () => {
const record = useRecordContext();
return <span>Post "{record.title}"</span>;
};
const PostShow = () => (
<Show>
<TabbedShowLayout>
<TabbedShowLayout.Tab label="main">
<PostTitle label="title" />
</TabbedShowLayout.Tab>
</TabbedShowLayout>
</Show>
);
sx
: CSS API
The <TabbedShowLayout>
component accepts the usual className
prop, but you can override many class names injected to the inner components by React-admin thanks to the sx
property (see the sx
documentation for syntax and examples). This property accepts the following subclasses:
Rule name | Description |
---|---|
& .RaTabbedShowLayout-content |
Applied to the content zone (under the tabs) |
To override the style of all instances of <TabbedShowLayout>
using the application-wide style overrides, use the RaTabbedShowLayout
key.
To style the tabs, the <TabbedShowLayout.Tab>
component accepts two props:
className
is passed to the tab headercontentClassName
is passed to the tab content
Controlled Mode
By default, <TabbedShowLayout>
reads the record from the RecordContext
. But by passing a record
prop, you can render the component outside a RecordContext
.
const StaticPostShow = () => (
<TabbedShowLayout record={{ id: 123, title: 'Hello world' }}>
<TabbedShowLayout.Tab label="main">
<TextField source="title" />
</TabbedShowLayout.Tab>
</TabbedShowLayout>
);
When passed a record
, <TabbedShowLayout>
creates a RecordContext
with the given record.
Access Control
If you need to hide some tabs based on a set of permissions, use the <TabbedShowLayout>
component from the @react-admin/ra-rbac
package.
-import { TabbedShowLayout } from 'react-admin';
+import { TabbedShowLayout } from '@react-admin/ra-rbac';
Use it in conjunction with <TabbedShowLayout.Tab>
and add a name
prop to the Tab
to define the resource on which the user needs to have the ‘read’ permissions for.
import { Show, TextField } from 'react-admin';
import { TabbedShowLayout } from '@react-admin/ra-rbac';
const authProvider = {
// ...
canAccess: async ({ action, record, resource }) =>
canAccessWithPermissions({
permissions: [
{ action: ['list', 'show'], resource: 'products' },
{ action: 'read', resource: 'products.tab.description' },
// { action: 'read', resource: 'products.tab.stock' },
{ action: 'read', resource: 'products.tab.images' },
{ action: 'read', resource: 'products.reference' },
{ action: 'read', resource: 'products.width' },
{ action: 'read', resource: 'products.height' },
{ action: 'read', resource: 'products.thumbnail' },
],
action,
record,
resource,
}),
};
const ProductShow = () => (
<Show>
<TabbedShowLayout>
<TabbedShowLayout.Tab label="Description" name="description">
<TextField source="reference" />
<TextField source="width" />
<TextField source="height" />
<TextField source="description" />
</TabbedShowLayout.Tab>
{/* This tab is not displayed for the user */}
<TabbedShowLayout.Tab label="Stock" name="stock">
<TextField source="stock" />
</TabbedShowLayout.Tab>
<TabbedShowLayout.Tab label="Images" name="images">
<TextField source="image" />
<TextField source="thumbnail" />
</TabbedShowLayout.Tab>
</TabbedShowLayout>
</Show>
);
<TabbedShowLayout.Tab>
also renders only the child fields for which the user has the ‘read’ permissions.
import { Show, TextField } from 'react-admin';
import { TabbedShowLayout } from '@react-admin/ra-rbac';
const authProvider = {
// ...
canAccess: async ({ action, record, resource }) =>
canAccessWithPermissions({
permissions: [
{ action: ['list', 'show'], resource: 'products' },
{ action: 'read', resource: 'products.reference' },
{ action: 'read', resource: 'products.width' },
{ action: 'read', resource: 'products.height' },
// 'products.description' is missing
{ action: 'read', resource: 'products.thumbnail' },
// 'products.image' is missing
{ action: 'read', resource: 'products.tab.description' },
// 'products.tab.stock' is missing
{ action: 'read', resource: 'products.tab.images' },
],
action,
record,
resource,
}),
};
const ProductShow = () => (
<Show>
<TabbedShowLayout>
<TabbedShowLayout.Tab label="Description" name="description">
<TextField source="reference" />
<TextField source="width" />
<TextField source="height" />
{/* Field Description is not displayed */}
<TextField source="description" />
</TabbedShowLayout.Tab>
{/* Tab Stock is not displayed */}
<TabbedShowLayout.Tab label="Stock" name="stock">
<TextField source="stock" />
</TabbedShowLayout.Tab>
<TabbedShowLayout.Tab label="Images" name="images">
{/* Field Image is not displayed */}
<TextField source="image" />
<TextField source="thumbnail" />
</TabbedShowLayout.Tab>
</TabbedShowLayout>
</Show>
);
See Also
- Field components
- Show Guesser guesses the fields based on the record type
- SimpleShowLayout provides a simpler layout with no tabs