DataView
The same records as a table on a wide screen and as a list on a narrow one. The server renders both and a container query shows one, by the view's own width, so nothing changes on hydration.
Import
import { DataView } from '@nizivo/react/data-view';
// Once per app (Getting started), after
// @nizivo/tokens/tokens.css and @nizivo/react/css/base.css
import '@nizivo/react/css/data-view.css';Static or interactive
Server. No client JavaScript. The server renders the table and the list, and CSS shows one of them, so no script measures the screen and the server HTML is final. How to choose.
Parts
DataView.Root holds a DataView.Table (the wide presentation: put a Table.Container in it)
and a DataView.List (the narrow one: DataView.Item rows). From 40rem of the root's own width
the table shows; below that, the list. It is the view's width, not the screen's, so a DataView in
a narrow column shows the list on a wide screen too.
List rows
A DataView.Item is a row of Figma's Order List Item: 14 / 16 px padding, 12 px between its
parts, at least 44 px tall, with a hairline below. Lay out what goes in it (an avatar, a title and
a line under it, a value and a badge at the end). With interactive, the row's
DataView.ItemLink is a stretched link, so the whole row is one hit area and one tab stop, as on
Table.Row; other buttons and links in the row stay clickable above it.
Both presentations are in the page
The hidden presentation is display: none: out of the accessibility tree and the tab order. But
both are in the HTML, so the records are rendered twice. Give the list an aria-label (the table
has its caption), and do not put form controls with the same id or name in both; a selectable
table keeps its selection in the table presentation only.
Examples
Basic
Wide (720 px): the table presentation, 08's Recent orders.
- Maya Rao#1042 · $112.00Unfulfilled
- Jonah Okafor#1041 · $64.50Fulfilled
- Priya Shah#1040 · $238.00On hold
import type { JSX } from 'react';
import { Avatar } from '@nizivo/react/avatar';
import { Badge, type BadgeTone } from '@nizivo/react/badge';
import { Inline } from '@nizivo/react/inline';
import { Table } from '@nizivo/react/table';
import { DataView } from '@nizivo/react/data-view';
interface Order {
id: string;
placed: string;
customer: string;
fulfilment: [string, BadgeTone];
total: number;
}
/** Fixed data, as a server would load it; times are UTC instants. */
const ORDERS: readonly Order[] = [
{
id: '1042',
placed: '2026-10-14T09:12:00Z',
customer: 'Maya Rao',
fulfilment: ['Unfulfilled', 'warning'],
total: 112,
},
{
id: '1041',
placed: '2026-10-14T08:47:00Z',
customer: 'Jonah Okafor',
fulfilment: ['Fulfilled', 'success'],
total: 64.5,
},
{
id: '1040',
placed: '2026-10-13T17:05:00Z',
customer: 'Priya Shah',
fulfilment: ['On hold', 'neutral'],
total: 238,
},
];
// An explicit locale and time zone: the server and the browser print the same text (§16.3).
const TIME = new Intl.DateTimeFormat('en-US', {
hour: '2-digit',
minute: '2-digit',
hourCycle: 'h23',
timeZone: 'UTC',
});
const MONEY = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
/** The same orders as table rows and as list rows; the root's width picks one. */
function Orders({ width }: { width: number }): JSX.Element {
return (
<DataView.Root style={{ inlineSize: width }}>
<DataView.Table>
<Table.Container>
<Table.Root>
<Table.Caption visuallyHidden>Recent orders</Table.Caption>
<Table.Header>
<Table.Row>
<Table.Head>Order</Table.Head>
<Table.Head>Customer</Table.Head>
<Table.Head>Fulfilment</Table.Head>
<Table.Head numeric>Total</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{ORDERS.map((order) => (
<Table.Row key={order.id} interactive>
<Table.Cell>
<Table.RowLink href={`#order-${order.id}`}>#{order.id}</Table.RowLink>
</Table.Cell>
<Table.Cell>
<Inline gap="2">
<Avatar name={order.customer} size="sm" decorative />
{order.customer}
</Inline>
</Table.Cell>
<Table.Cell>
<Badge tone={order.fulfilment[1]}>{order.fulfilment[0]}</Badge>
</Table.Cell>
<Table.Cell numeric>{MONEY.format(order.total)}</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
</Table.Container>
</DataView.Table>
<DataView.List aria-label="Recent orders">
{ORDERS.map((order) => (
<DataView.Item key={order.id} interactive>
<Avatar name={order.customer} size="lg" decorative />
<span style={{ display: 'grid', flex: 1, gap: 2, minInlineSize: 0 }}>
<DataView.ItemLink href={`#order-${order.id}`}>{order.customer}</DataView.ItemLink>
<span style={{ font: 'var(--nz-font-mono-m)', color: 'var(--nz-text-tertiary)' }}>
#{order.id} ·{' '}
<time dateTime={order.placed}>{TIME.format(new Date(order.placed))}</time>
</span>
</span>
<span style={{ display: 'grid', gap: 4, justifyItems: 'end' }}>
<span style={{ font: 'var(--nz-font-numeric-m)' }}>{MONEY.format(order.total)}</span>
<Badge tone={order.fulfilment[1]}>{order.fulfilment[0]}</Badge>
</span>
</DataView.Item>
))}
</DataView.List>
</DataView.Root>
);
}
export function DataViewExample(): JSX.Element {
return <Orders width={720} />;
}
Narrow
Narrow (360 px): the same orders as list rows, 08 Responsive's Order List Item.
- Maya Rao#1042 · $112.00Unfulfilled
- Jonah Okafor#1041 · $64.50Fulfilled
- Priya Shah#1040 · $238.00On hold
import type { JSX } from 'react';
import { Avatar } from '@nizivo/react/avatar';
import { Badge, type BadgeTone } from '@nizivo/react/badge';
import { Inline } from '@nizivo/react/inline';
import { Table } from '@nizivo/react/table';
import { DataView } from '@nizivo/react/data-view';
interface Order {
id: string;
placed: string;
customer: string;
fulfilment: [string, BadgeTone];
total: number;
}
/** Fixed data, as a server would load it; times are UTC instants. */
const ORDERS: readonly Order[] = [
{
id: '1042',
placed: '2026-10-14T09:12:00Z',
customer: 'Maya Rao',
fulfilment: ['Unfulfilled', 'warning'],
total: 112,
},
{
id: '1041',
placed: '2026-10-14T08:47:00Z',
customer: 'Jonah Okafor',
fulfilment: ['Fulfilled', 'success'],
total: 64.5,
},
{
id: '1040',
placed: '2026-10-13T17:05:00Z',
customer: 'Priya Shah',
fulfilment: ['On hold', 'neutral'],
total: 238,
},
];
// An explicit locale and time zone: the server and the browser print the same text (§16.3).
const TIME = new Intl.DateTimeFormat('en-US', {
hour: '2-digit',
minute: '2-digit',
hourCycle: 'h23',
timeZone: 'UTC',
});
const MONEY = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
/** The same orders as table rows and as list rows; the root's width picks one. */
function Orders({ width }: { width: number }): JSX.Element {
return (
<DataView.Root style={{ inlineSize: width }}>
<DataView.Table>
<Table.Container>
<Table.Root>
<Table.Caption visuallyHidden>Recent orders</Table.Caption>
<Table.Header>
<Table.Row>
<Table.Head>Order</Table.Head>
<Table.Head>Customer</Table.Head>
<Table.Head>Fulfilment</Table.Head>
<Table.Head numeric>Total</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{ORDERS.map((order) => (
<Table.Row key={order.id} interactive>
<Table.Cell>
<Table.RowLink href={`#order-${order.id}`}>#{order.id}</Table.RowLink>
</Table.Cell>
<Table.Cell>
<Inline gap="2">
<Avatar name={order.customer} size="sm" decorative />
{order.customer}
</Inline>
</Table.Cell>
<Table.Cell>
<Badge tone={order.fulfilment[1]}>{order.fulfilment[0]}</Badge>
</Table.Cell>
<Table.Cell numeric>{MONEY.format(order.total)}</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
</Table.Container>
</DataView.Table>
<DataView.List aria-label="Recent orders">
{ORDERS.map((order) => (
<DataView.Item key={order.id} interactive>
<Avatar name={order.customer} size="lg" decorative />
<span style={{ display: 'grid', flex: 1, gap: 2, minInlineSize: 0 }}>
<DataView.ItemLink href={`#order-${order.id}`}>{order.customer}</DataView.ItemLink>
<span style={{ font: 'var(--nz-font-mono-m)', color: 'var(--nz-text-tertiary)' }}>
#{order.id} ·{' '}
<time dateTime={order.placed}>{TIME.format(new Date(order.placed))}</time>
</span>
</span>
<span style={{ display: 'grid', gap: 4, justifyItems: 'end' }}>
<span style={{ font: 'var(--nz-font-numeric-m)' }}>{MONEY.format(order.total)}</span>
<Badge tone={order.fulfilment[1]}>{order.fulfilment[0]}</Badge>
</span>
</DataView.Item>
))}
</DataView.List>
</DataView.Root>
);
}
export function DataViewNarrowExample(): JSX.Element {
return <Orders width={360} />;
}
Parts
Use the parts through the namespace (DataView.Root) or by their flat names (DataViewRoot); both work from Server Components.
DataView.Root
Server component Renders data-slot="data-view" with the class nz-data-view. Props: DataViewRootProps. Source: src/data-view/data-view.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
children | ReactNode | – | A |
render | RenderProp<NoState> | – | Must render a |
- Also takes every attribute of
<div>;childrenis redeclared above. (ComponentPropsWithRef<"div">)
render takes an element, or, in client code, a function of the props and no state.
DataView.Table
Server component Renders data-slot="data-view-table" with the class nz-data-view-table. Props: DataViewTableProps. Source: src/data-view/data-view.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
children | ReactNode | – | The wide presentation: a |
render | RenderProp<NoState> | – | Must render a |
- Also takes every attribute of
<div>;childrenis redeclared above. (ComponentPropsWithRef<"div">)
render takes an element, or, in client code, a function of the props and no state.
DataView.List
Server component Renders data-slot="data-view-list" with the class nz-data-view-list. Props: DataViewListProps. Source: src/data-view/data-view.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
children | ReactNode | – | The narrow presentation: |
render | RenderProp<NoState> | – | Must render a |
- Also takes every attribute of
<ul>;childrenis redeclared above. (ComponentPropsWithRef<"ul">)
render takes an element, or, in client code, a function of the props and no state.
DataView.Item
Server component Renders data-slot="data-view-item" with the class nz-data-view-item. Props: DataViewItemProps. Source: src/data-view/data-view.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
interactive | boolean | false | The whole row is the hit area of its |
render | RenderProp<NoState> | – | Must render an |
- Also takes every attribute of
<li>. (ComponentPropsWithRef<"li">)
render takes an element, or, in client code, a function of the props and no state.
DataView.ItemLink
Server component Renders data-slot="data-view-item-link" with the class nz-data-view-item-link. Props: DataViewItemLinkProps. Source: src/data-view/data-view.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
render | RenderProp<NoState> | – | Must render an |
- Also takes every attribute of
<a>. (ComponentPropsWithRef<"a">)
render takes an element, or, in client code, a function of the props and no state.
Other exports
DataViewItemLinkPropstypeimport type { DataViewItemLinkProps } from '@nizivo/react/data-view'The props of
DataView.ItemLink, listed above.DataViewItemPropstypeimport type { DataViewItemProps } from '@nizivo/react/data-view'The props of
DataView.Item, listed above.DataViewListPropstypeimport type { DataViewListProps } from '@nizivo/react/data-view'The props of
DataView.List, listed above.DataViewRootPropstypeimport type { DataViewRootProps } from '@nizivo/react/data-view'The props of
DataView.Root, listed above.DataViewTablePropstypeimport type { DataViewTableProps } from '@nizivo/react/data-view'The props of
DataView.Table, listed above.
Data attributes and slots
Style parts by class (.nz-<slot>) and state by data attribute. Attribute meanings come from contracts/vocabulary.json.
| Slot | Rendered by | Data attributes |
|---|---|---|
data-view.nz-data-view | DataView.Rootroot (takes render) | none |
data-view-item.nz-data-view-item | DataView.Itemroot (takes render) |
|
data-view-item-link.nz-data-view-item-link | DataView.ItemLinkroot (takes render); interactive, focus: self | none |
data-view-list.nz-data-view-list | DataView.Listroot (takes render) |
|
data-view-table.nz-data-view-table | DataView.Tableroot (takes render) |
|
CSS hooks
Set a hook on the component's class or any ancestor to restyle it without specificity games; leave it unset for the default shown.
| Hook | Default | Read as |
|---|---|---|
--nz-data-view-item-bg-hover | --nz-bg-hover |
|
Layers: nz.components. Classes: .nz-data-view, .nz-data-view-item, .nz-data-view-item-link, .nz-data-view-list, .nz-data-view-table.
Tokens this stylesheet reads (9)
--nz-bg-hover, --nz-border-subtle, --nz-border-width-default, --nz-font-label-m-strong, --nz-size-touch-min, --nz-space-0-5, --nz-space-3, --nz-space-4, --nz-tracking-label-m-strong
Accessibility
These parts are interactive. Each gets the focus outline and touch-action rule from @nizivo/react/css/base.css (the nz.base layer), so the floor survives a restyle:
data-view-item-link: draws the focus outline itself on keyboard focus.
Keyboard
| Keys | Action |
|---|---|
| Tab | Moves through the links and controls of the presentation that is shown; the hidden one is skipped. |
Source: native links and controls; the hidden presentation is display none (data-view.css). The browser lane (test/browser/data-view.spec.tsx) checks which presentation shows at each width, Tab and the list rows' stretched link.
Figma
Node ids from design/component-map.json, which nz gen writes from component.json. The links open the Nizivo Figma library, which may ask you to sign in.
- Canonical: Figma node
764:3483 - Ghost masters: none
Container
Centres page content: at most --nz-layout-content-max wide (358, 976, 1280 px by viewport), with the responsive page margin --nz-grid-margin (16, 24, 32 px) on both sides.
DateRangePicker
Unstable: a range of dates from a trigger in Select's look, through a popover with presets, one month of calendar and Cancel and Apply. Values are ISO plain dates; "today" is counted in the time zone you pass. Import it from @nizivo/react/unstable/date-range-picker.