Every component exported by @crudx/mui, rendered live with a copy-pasteable snippet. These are the building blocks that the higher-level CrudPanelView composes.
import { BreadcrumbView } from '@crudx/mui';Navigation trail. Items can be plain links or chips, with an optional icon per crumb. Pass `current` to mark the active path.
<BreadcrumbView
items={[
{ label: 'Home', url: '/', icon: <HomeIcon fontSize="small" /> },
{ label: 'Team', url: '/team' },
{ label: 'Ada Lovelace' },
]}
current="/team/ada"
/>import { ButtonDropdown } from '@crudx/mui';Menu-style dropdown with either button or icon trigger. Each item dispatches its `key` through `onItemClick`.
—<Stack direction="row" spacing={2} alignItems="center">
<ButtonDropdown
type="button"
items={[
{ key: 'edit', title: 'Edit' },
{ key: 'duplicate', title: 'Duplicate' },
{ key: 'archive', title: 'Archive' },
]}
onItemClick={setLast}
>
Actions
</ButtonDropdown>
<Typography variant="caption" color="text.secondary">
Last: <code>{last ?? '—'}</code>
</Typography>
</Stack>import { Dialog, DialogRefProps } from '@crudx/mui';Imperative dialog driven by ref (`open()`, `close()`, `toggle()`). Built-in `confirmation`, `info`, `success`, `error`, `warning`, and `custom` variants.
—<Stack direction="row" spacing={2} alignItems="center">
<Button variant="outlined" onClick={() => ref.current?.open()}>
Open dialog
</Button>
<Typography variant="caption" color="text.secondary">
Last action: <code>{result}</code>
</Typography>
<Dialog
ref={ref}
type="confirmation"
title="Delete this record?"
message="This action can't be undone. Are you sure you want to continue?"
primaryText="Delete"
secondaryText="Cancel"
onClickPrimaryAction={() => setResult('confirmed')}
onClickSecondaryAction={() => setResult('cancelled')}
/>
</Stack>import { NumberFormatView } from '@crudx/mui';Number formatter wrapper. Uses numeral.js format strings and supports prefix / postfix slots.
<Stack direction="row" spacing={3} alignItems="center">
<NumberFormatView
amount={1234567.89}
format="0,0.00"
prefix="$"
postfix=" USD"
/>
<NumberFormatView amount={0.7421} format="0.0%" />
<NumberFormatView amount={2_500_000} format="0.0a" prefix="≈ " />
</Stack>import { RenderFlexView } from '@crudx/mui';Declarative MUI Grid layout. Each row is an array of `xs/sm/md`-sized cells. Useful when you want layout to live in data rather than JSX.
Cell A
Cell B
Full-width row
<RenderFlexView
containerProps={{ spacing: 2 }}
items={[
[
{
xs: 12,
sm: 6,
children: (
<Card variant="outlined">
<CardContent>
<Typography variant="body2">Cell A</Typography>
</CardContent>
</Card>
),
},
{
xs: 12,
sm: 6,
children: (
<Card variant="outlined">
<CardContent>
<Typography variant="body2">Cell B</Typography>
</CardContent>
</Card>
),
},
],
[
{
xs: 12,
children: (
<Card variant="outlined">
<CardContent>
<Typography variant="body2">Full-width row</Typography>
</CardContent>
</Card>
),
},
],
]}
/>import { RenderNodeView } from '@crudx/mui';Inline horizontal stack with a keyed list of nodes — handy for icon + label combos that need stable keys.
<RenderNodeView
spacing={1}
items={[
{
key: 'icon',
content: <FavoriteIcon color="error" fontSize="small" />,
},
{ key: 'label', content: <span>23 favourites</span> },
]}
/>import { Table } from '@crudx/mui';The full table primitive — head, rows and pagination wired up. Use this when you want a turnkey table without going through CrudTableView.
| ID | NAME | ROLE | SALARY |
|---|---|---|---|
| 1 | Ada Lovelace | Engineer | $95,000 |
| 2 | Alan Turing | Researcher | $110,000 |
| 3 | Grace Hopper | Architect | $120,000 |
type Row = { id: number; name: string; role: string; salary: number };
const SAMPLE_ROWS: Row[] = [
{ id: 1, name: 'Ada Lovelace', role: 'Engineer', salary: 95000 },
{ id: 2, name: 'Alan Turing', role: 'Researcher', salary: 110000 },
{ id: 3, name: 'Grace Hopper', role: 'Architect', salary: 120000 },
];
const SAMPLE_COLUMNS = [
{ key: 'id', title: 'ID', width: 60, dataIndex: 'id' as const },
{ key: 'name', title: 'Name', dataIndex: 'name' as const, sortable: true },
{ key: 'role', title: 'Role', dataIndex: 'role' as const },
{
key: 'salary',
title: 'Salary',
align: 'right' as const,
render: (_: unknown, record: Row) => (
<NumberFormatView amount={record.salary} format="0,0" prefix="$" />
),
},
];
<Table<Row>
data={SAMPLE_ROWS}
columns={SAMPLE_COLUMNS}
striped
bordered
page={page}
pageSize={pageSize}
total={SAMPLE_ROWS.length}
onPageChange={setPage}
onPageSizeChange={setPageSize}
/>import { TableHead } from '@crudx/mui';Standalone table header — use it when composing your own `<table>` instead of using `Table`. Drives sort indicators and the bulk-checkbox state.
| ID | NAMEsorted ascending | ROLE | SALARY |
|---|
type Row = { id: number; name: string; role: string; salary: number };
const SAMPLE_COLUMNS = [
{ key: 'id', title: 'ID', width: 60, dataIndex: 'id' as const },
{ key: 'name', title: 'Name', dataIndex: 'name' as const, sortable: true },
{ key: 'role', title: 'Role', dataIndex: 'role' as const },
{
key: 'salary',
title: 'Salary',
align: 'right' as const,
render: (_: unknown, record: Row) => (
<NumberFormatView amount={record.salary} format="0,0" prefix="$" />
),
},
];
<Box
component="table"
sx={{ width: '100%', borderCollapse: 'collapse' }}
>
<TableHead<Row>
columns={SAMPLE_COLUMNS}
checkbox={{ enabled: true }}
checked="partial"
sorting={{ defaultOrder: 'name', defaultDirection: 'asc' }}
/>
</Box>import { TableRow } from '@crudx/mui';Standalone table row — pair with TableHead when you need a custom shell. Renders cells from the same column config as Table.
| 1 | Ada Lovelace | Engineer | $95,000 |
| 2 | Alan Turing | Researcher | $110,000 |
| 3 | Grace Hopper | Architect | $120,000 |
type Row = { id: number; name: string; role: string; salary: number };
const SAMPLE_ROWS: Row[] = [
{ id: 1, name: 'Ada Lovelace', role: 'Engineer', salary: 95000 },
{ id: 2, name: 'Alan Turing', role: 'Researcher', salary: 110000 },
{ id: 3, name: 'Grace Hopper', role: 'Architect', salary: 120000 },
];
const SAMPLE_COLUMNS = [
{ key: 'id', title: 'ID', width: 60, dataIndex: 'id' as const },
{ key: 'name', title: 'Name', dataIndex: 'name' as const, sortable: true },
{ key: 'role', title: 'Role', dataIndex: 'role' as const },
{
key: 'salary',
title: 'Salary',
align: 'right' as const,
render: (_: unknown, record: Row) => (
<NumberFormatView amount={record.salary} format="0,0" prefix="$" />
),
},
];
<Box
component="table"
sx={{ width: '100%', borderCollapse: 'collapse' }}
>
<Box component="tbody">
{SAMPLE_ROWS.map((row, idx) => (
<TableRow<Row>
key={row.id}
position={idx}
data={row}
columns={SAMPLE_COLUMNS}
/>
))}
</Box>
</Box>import { TablePagination } from '@crudx/mui';Page controls. Note that MUI's variant is 0-indexed — `page=0` is the first page.
<TablePagination
page={page}
pageSize={pageSize}
total={142}
pageSizeOptions={[10, 25, 50]}
onPageChange={setPage}
onPageSizeChange={setPageSize}
/>import { TableSelectedBulkOptions } from '@crudx/mui';Bulk-action menu shown above a table when rows are selected. Defaults to a `{count} Item(s) Selected` label.
—<Stack direction="row" spacing={2} alignItems="center">
<TableSelectedBulkOptions
total={3}
items={[
{ key: 'delete', title: 'Delete selected' },
{ key: 'export', title: 'Export selected' },
]}
onChange={(key: string) => setLast(key)}
/>
<Typography variant="caption" color="text.secondary">
Last: <code>{last ?? '—'}</code>
</Typography>
</Stack>import { TableSettingsDensityOptions } from '@crudx/mui';Density picker. Three preset rows — default / small / medium — with a localisable label per option.
default<Stack direction="row" spacing={2} alignItems="center">
<TableSettingsDensityOptions
onChange={(key: string) => setDensity(key)}
/>
<Typography variant="caption" color="text.secondary">
Density: <code>{density}</code>
</Typography>
</Stack>import { TableSettingsOptions } from '@crudx/mui';Generic settings dropdown for table-level actions (column toggles, exports, …). Same item shape as ButtonDropdown.
—<Stack direction="row" spacing={2} alignItems="center">
<TableSettingsOptions
items={[
{ key: 'columns', title: 'Manage columns' },
{ key: 'export', title: 'Export CSV' },
{ key: 'reset', title: 'Reset view' },
]}
onChange={(key: string) => setLast(key)}
/>
<Typography variant="caption" color="text.secondary">
Last: <code>{last ?? '—'}</code>
</Typography>
</Stack>import { TableSettingsSortingOptions, SortingOptionType } from '@crudx/mui';Three-state global sort toggle (DEFAULT / ASC / DESC). Pair with a sortable list view that doesn't pin sort to a single column.
DEFAULT<Stack direction="row" spacing={2} alignItems="center">
<TableSettingsSortingOptions
selected={selected}
onChange={(key: string) => setSelected(key as SortingOptionType)}
/>
<Typography variant="caption" color="text.secondary">
Sort: <code>{selected}</code>
</Typography>
</Stack>import { TabView } from '@crudx/mui';Tab list with content slots. Pass `content` per item, or use `renderContent` for fully controlled rendering.
<TabView
items={[
{
key: 'overview',
label: 'Overview',
content: (
<Box sx={{ p: 2 }}>
<Typography variant="body2">
Overview content rendered for the active tab.
</Typography>
</Box>
),
},
{
key: 'activity',
label: 'Activity',
content: (
<Box sx={{ p: 2 }}>
<Typography variant="body2">No recent activity.</Typography>
</Box>
),
},
{
key: 'settings',
label: 'Settings',
content: (
<Box sx={{ p: 2 }}>
<Typography variant="body2">Settings tab content.</Typography>
</Box>
),
},
]}
/>import { TooltipView } from '@crudx/mui';Tooltip wrapper around an arbitrary trigger. Disabled automatically when `title` is empty unless you force `enabled`.
<Stack direction="row" spacing={2} alignItems="center">
<TooltipView title="Edit this record" arrow>
<Button startIcon={<EditIcon />}>Edit</Button>
</TooltipView>
<TooltipView title="Disabled — no permission" arrow>
<span>
<Button disabled>Save</Button>
</span>
</TooltipView>
</Stack>