DataPage
The DataPage component provides a complete page layout for displaying and managing data from queries, including table view, menu actions, and optional detail panels.
Purpose
Section titled “Purpose”DataPage combines a page header, data table, menu bar, and optional detail view into a single, cohesive component for building data-driven pages.
Key Features
Section titled “Key Features”- Integrated data table with filtering and pagination
- Action menu bar with context-aware items
- Support for both queries and observable queries
- Selection management
- Split view with details panel
- Responsive layout using Allotment
Basic Usage
Section titled “Basic Usage”Declare the columns and menu actions with the compound DataPage.Columns and DataPage.MenuItems children, imported alongside DataPage from @cratis/components/DataPage:
import { DataPage, MenuItem } from '@cratis/components/DataPage';import { Column } from '@cratis/components/DataPage';import { FaPlus, FaPencil } from 'react-icons/fa6';import { AllAuthors } from './queries'; // generated query proxy
function Authors() { return ( <DataPage title='Authors' query={AllAuthors} emptyMessage='No authors found'> <DataPage.MenuItems> <MenuItem label='Add' icon={FaPlus} command={() => handleAdd()} /> <MenuItem label='Edit' icon={FaPencil} disableOnUnselected command={() => handleEdit()} /> </DataPage.MenuItems> <DataPage.Columns> <Column field='name' header='Name' sortable /> <Column field='id' header='Id' /> </DataPage.Columns> </DataPage> );}disableOnUnselected greys the menu item out until a row is selected — wire your edit and remove actions to it so they only fire on a selection.
List-and-detail with a details panel
Section titled “List-and-detail with a details panel”Pass a detailsComponent and DataPage adds a resizable split: the table on the left, your component on the right, shown only when a row is selected. The component receives the selected row as item (the IDetailsComponentProps<T> contract):
import { DataPage } from '@cratis/components/DataPage';import { Column } from '@cratis/components/DataPage';import { AllAuthorsWithBooks } from './queries';
const AuthorDetails = ({ item }) => ( <div className='p-4'> <h2>{item.name}</h2> <ul> {item.books.map((b) => ( <li key={String(b.id)}>{b.title}</li> ))} </ul> </div>);
function Authors() { return ( <DataPage title='Authors' query={AllAuthorsWithBooks} emptyMessage='No authors yet' detailsComponent={AuthorDetails} > <DataPage.Columns> <Column field='name' header='Name' sortable /> </DataPage.Columns> </DataPage> );}Selection is managed for you; to drive it yourself, pass selection and onSelectionChange. In controlled mode, clear the selection with null. Omitting selection/passing undefined selects uncontrolled mode. Provide dataKey when query refreshes can replace row object instances.
Required Props
Section titled “Required Props”title: Page titlequery: Constructor for the query type (IQueryFor or IObservableQueryFor)emptyMessage: Message to display when no data is available
Optional Props
Section titled “Optional Props”queryArguments: Arguments to pass to the querydataKey: Unique key field for data itemsselection: Currently selected itemonSelectionChange: Callback when the selection changesglobalFilterFields: Fields to include in loaded-page searchglobalSearchPlaceholder: Placeholder for the search inputglobalSearchAriaLabel: Accessible name for the search input; localize independently from the placeholderdefaultFilters: Initial filter state, aDataTableFilterMeta(a{ value, matchMode }constraint per field)clientFiltering: Deprecated compatibility prop; accepted but ignored because filtering is always scoped to the loaded query pagetablePt/tableClassName: Stable table-part attributes and root classpaginatorPt/paginatorClassName: Stable paginator-part attributes and root classpaginatorAriaLabels: Localized labels for the paginator navigation and controls
The query-backed table inside DataPage suppresses emptyMessage while its first result is still performing, so a pending query is not presented as a confirmed empty result.
onRefresh: Callback triggered to signal a data refresh — forwarded to thedetailsComponentdetailsComponent: Component to render in the resizable details panel when a row is selected
Query Types
Section titled “Query Types”DataPage supports two types of queries:
- IQueryFor: Standard queries with pagination support
- IObservableQueryFor: Observable queries that automatically update when data changes
The component automatically detects the query type and renders the appropriate data table component.
Filtering scope
Section titled “Filtering scope”Column and global filters run against the currently loaded query page. Pagination continues to use the server-reported total so filtering one page never hides later pages.
clientFiltering remains in the public props only so existing applications continue to compile. It is deprecated, has no effect, and should not be used in new code. To filter the complete result set, pass filter values through queryArguments and apply them on the server before paging so the query returns the filtered rows and filtered total. See DataTableForQuery filtering scope for the rationale.
Layout
Section titled “Layout”DataPage uses Allotment for the resizable split when a detailsComponent is provided. The layout consists of:
- Page header with title
- Menu bar with actions
- Data table
- Optional details panel (when item is selected)
Allotment positions its panes from a stylesheet rather than from inline styles, so the split view only works once that stylesheet is on the page. From 3.0 it is vendored into @cratis/components/styles, so importing that one file at your app entry point is all it takes — you do not need allotment/dist/style.css yourself, and if you were importing it you can drop it. Without @cratis/components/styles the details pane grows to its content and clips the paginator. When no detailsComponent is supplied there is nothing to split, so no split view is mounted at all.
Inside the page, the menu bar and the data table share one vertical column. The menu bar keeps the height it needs; the table region takes everything that is left and scrolls its rows internally. Given an ancestor with a real height — the condition described next — the paginator therefore sits at the bottom of the page rather than below its edge, however many rows the query returns, and whether or not the page is split.
DataPage needs an ancestor with a height
Section titled “DataPage needs an ancestor with a height”That division only works if there is a height to divide. Every element from the page root down is sized as a percentage of its parent, so some ancestor of DataPage has to have a definite height — a viewport unit, a pixel height, a grid row, or a flex child that is allowed to shrink. Give it one and the paginator stays on screen no matter how many rows the query returns.
// ✅ the layout gives the page a height to divide<div style={{ height: '100vh' }}> <DataPage title='Authors' query={AllAuthors} emptyMessage='No authors found'> <DataPage.Columns> <Column field='name' header='Name' sortable /> </DataPage.Columns> </DataPage></div>// ❌ nothing above resolves to a height, so the table grows to its content and// the paginator ends up past the bottom of the page<div> <DataPage title='Authors' query={AllAuthors} emptyMessage='No authors found'> <DataPage.Columns> <Column field='name' header='Name' sortable /> </DataPage.Columns> </DataPage></div>A flex or grid child counts as bounded only when it is allowed to shrink — min-height: 0 on the item, or overflow: hidden on the container. Without that, the item’s automatic minimum keeps it at content height, which is the same as having no bound at all.
When no ancestor supplies a height, DataPage falls back to a small fixed height so the page stays usable instead of collapsing to nothing. Treat that fallback as a symptom, not a solution — fix the ancestor.
Integration
Section titled “Integration”DataPage integrates with:
@cratis/arc/queriesfor data fetchingDataTableForQueryandDataTableForObservableQuerycomponents- the semantic Cratis data table plus a Cratis action toolbar of Buttons
- Allotment for split view layout — its stylesheet is vendored into
@cratis/components/styles, so the split view lays out as long as you import that
See Also
Section titled “See Also”- Menu Items - Configuring action menus
- Details Panel - Working with detail views