Table layout

Click a column header to sort. The page re-renders server-side — sort state and page number live in the URL. This demo on the docs page is itself using SSR pagination.

LanguageParadigmFirst appearedType systemWeekly downloads
TypeScriptMulti-paradigm2012Static53,200,000
JavaScriptMulti-paradigm1995Dynamic61,800,000
PythonMulti-paradigm1991Dynamic29,400,000
RustMulti-paradigm2015Static4,600,000
GoConcurrent2009Static5,100,000
1–5 of 12
// In your page handler, read state from the URL:
const page  = Number(ctx.url.searchParams.get('page') ?? 1)
const sort  = ctx.url.searchParams.get('sort') ?? undefined
const order = (ctx.url.searchParams.get('order') ?? 'asc') as 'asc' | 'desc'

// Sort and paginate server-side, then pass the slice:
const sorted   = sort ? [...data].sort(...) : data
const pageData = sorted.slice((page - 1) * 20, page * 20)

const columns: DataViewColumn[] = [
  { key: 'name',    label: 'Language',  sortable: true },
  { key: 'year',    label: 'Year',      sortable: true, align: 'right' },
  { key: 'weekly',  label: 'Downloads', sortable: true, align: 'right',
    render: (row) => Number(row.weekly).toLocaleString() },
]

<DataView
  layout="table"
  columns={columns}
  data={pageData}
  page={page}
  pageSize={20}
  totalRows={data.length}
  sort={sort}
  order={order}
  urlParams={Object.fromEntries(ctx.url.searchParams)}
/>

Cards layout

Each row renders as a card. Assign role: 'title' and role: 'subtitle' to promote columns to the card header; remaining columns render as labeled key-value fields. If no column has a title role, the first column is used.

  • TypeScript
    Multi-paradigm
    Type system
    Static
    First appeared
    2012
    Downloads
    53,200,000
  • JavaScript
    Multi-paradigm
    Type system
    Dynamic
    First appeared
    1995
    Downloads
    61,800,000
  • Python
    Multi-paradigm
    Type system
    Dynamic
    First appeared
    1991
    Downloads
    29,400,000
  • Rust
    Multi-paradigm
    Type system
    Static
    First appeared
    2015
    Downloads
    4,600,000
  • Go
    Concurrent
    Type system
    Static
    First appeared
    2009
    Downloads
    5,100,000
  • Kotlin
    Multi-paradigm
    Type system
    Static
    First appeared
    2011
    Downloads
    3,200,000
const columns: DataViewColumn[] = [
  { key: 'name',     label: 'Language',  role: 'title' },
  { key: 'paradigm', label: 'Paradigm',  role: 'subtitle' },
  { key: 'typing',   label: 'Type system' },
  { key: 'year',     label: 'First appeared' },
]

<DataView layout="cards" columns={columns} data={data} pageSize={0} />

Grid layout

A compact CSS grid — each item shows the title prominently and remaining fields as small key-value pairs. Columns default to 3; pass cols to override. The grid collapses to 2 columns at 768px and 1 column at 480px automatically.

  • TypeScript
    Type system
    Static
    First appeared
    2012
    Downloads
    53,200,000
  • JavaScript
    Type system
    Dynamic
    First appeared
    1995
    Downloads
    61,800,000
  • Python
    Type system
    Dynamic
    First appeared
    1991
    Downloads
    29,400,000
  • Rust
    Type system
    Static
    First appeared
    2015
    Downloads
    4,600,000
  • Go
    Type system
    Static
    First appeared
    2009
    Downloads
    5,100,000
  • Kotlin
    Type system
    Static
    First appeared
    2011
    Downloads
    3,200,000
  • Swift
    Type system
    Static
    First appeared
    2014
    Downloads
    2,900,000
  • C#
    Type system
    Static
    First appeared
    2000
    Downloads
    6,800,000
  • Ruby
    Type system
    Dynamic
    First appeared
    1995
    Downloads
    2,300,000
<DataView layout="grid" columns={columns} data={data} cols={3} pageSize={0} />

Table styling

The table layout accepts the same border and spacing props as Table.

LanguageParadigmType system
TypeScriptMulti-paradigmStatic
JavaScriptMulti-paradigmDynamic
PythonMulti-paradigmDynamic
RustMulti-paradigmStatic
GoConcurrentStatic
<DataView
  layout="table"
  columns={columns}
  data={data}
  pageSize={0}
  striped
  highlightOnHover
  withTableBorder
/>

Row selection

Add selectable to render a checkbox column. Wrap DataView in a <form> to handle the selected values — each checkbox uses selectName as its field name and the rowKey value as its value. A minimal inline script handles the select-all toggle with no framework overhead.

LanguageParadigmType system
TypeScriptMulti-paradigmStatic
JavaScriptMulti-paradigmDynamic
PythonMulti-paradigmDynamic
RustMulti-paradigmStatic
GoConcurrentStatic
KotlinMulti-paradigmStatic
<form method="post" action="/delete-languages">
  <DataView
    layout="table"
    columns={columns}
    data={data}
    pageSize={0}
    selectable
    rowKey="name"
    selectName="language"
  />
  <button type="submit">Delete selected</button>
</form>

{/* Server reads: ctx.body.getAll('language') → ['TypeScript', 'Rust'] */}

Selection in cards layout

Selection works the same way in cards and grid layouts. Each card shows a checkbox in the top-right corner; a select-all control appears above the list.

  • TypeScript
    Multi-paradigm
    Type system
    Static
    First appeared
    2012
    Downloads
    53,200,000
  • JavaScript
    Multi-paradigm
    Type system
    Dynamic
    First appeared
    1995
    Downloads
    61,800,000
  • Python
    Multi-paradigm
    Type system
    Dynamic
    First appeared
    1991
    Downloads
    29,400,000
  • Rust
    Multi-paradigm
    Type system
    Static
    First appeared
    2015
    Downloads
    4,600,000
<form method="post" action="/action">
  <DataView layout="cards" columns={columns} data={data} selectable rowKey="name" />
  <button type="submit">Submit</button>
</form>

Grid-table layout

A flex-row layout that looks like a table but is built with CSS — columns are responsive. Set span on each column to control proportional widths (same as the Grid component). Use hideBelow to remove a column entirely below a breakpoint, or collapseBelow to move it into an expandable row beneath the main row. Resize your browser window to see the columns adapt — no JavaScript required.

TypeScript
Multi-paradigm
2012
Static
53,200,000
ParadigmMulti-paradigm
First appeared2012
Weekly downloads53,200,000
JavaScript
Multi-paradigm
1995
Dynamic
61,800,000
ParadigmMulti-paradigm
First appeared1995
Weekly downloads61,800,000
Python
Multi-paradigm
1991
Dynamic
29,400,000
ParadigmMulti-paradigm
First appeared1991
Weekly downloads29,400,000
Rust
Multi-paradigm
2015
Static
4,600,000
ParadigmMulti-paradigm
First appeared2015
Weekly downloads4,600,000
Go
Concurrent
2009
Static
5,100,000
ParadigmConcurrent
First appeared2009
Weekly downloads5,100,000
Kotlin
Multi-paradigm
2011
Static
3,200,000
ParadigmMulti-paradigm
First appeared2011
Weekly downloads3,200,000
const columns: DataViewColumn[] = [
  { key: 'name',     label: 'Language',        span: 3 },
  { key: 'paradigm', label: 'Paradigm',         span: 2, collapseBelow: 'sm' },
  { key: 'year',     label: 'First appeared',   span: 1, align: 'right', collapseBelow: 'md' },
  { key: 'typing',   label: 'Type system',      span: 2, hideBelow: 'sm' },
  { key: 'weekly',   label: 'Weekly downloads', span: 2, align: 'right', collapseBelow: 'sm',
    render: (row) => Number(row.weekly).toLocaleString() },
]

<DataView layout="grid-table" columns={columns} data={data} pageSize={0} />

The span prop also accepts a breakpoint object for fine-grained control:

// 6-unit column at base, 4 units at sm and above, 2 units at lg and above:
{ key: 'name', label: 'Language', span: { base: 6, sm: 4, lg: 2 } }

Layout switching with belowLayout

Pass belowLayout to render a fallback layout below a breakpoint. Both layouts are included in the HTML and CSS controls which is visible — zero JavaScript required. The default breakpoint is 'sm' (768px); override with belowBreakpoint.

TypeScript
Multi-paradigm
2012
Static
53,200,000
ParadigmMulti-paradigm
First appeared2012
Weekly downloads53,200,000
JavaScript
Multi-paradigm
1995
Dynamic
61,800,000
ParadigmMulti-paradigm
First appeared1995
Weekly downloads61,800,000
Python
Multi-paradigm
1991
Dynamic
29,400,000
ParadigmMulti-paradigm
First appeared1991
Weekly downloads29,400,000
Rust
Multi-paradigm
2015
Static
4,600,000
ParadigmMulti-paradigm
First appeared2015
Weekly downloads4,600,000
  • TypeScript
    Paradigm
    Multi-paradigm
    First appeared
    2012
    Type system
    Static
    Weekly downloads
    53,200,000
  • JavaScript
    Paradigm
    Multi-paradigm
    First appeared
    1995
    Type system
    Dynamic
    Weekly downloads
    61,800,000
  • Python
    Paradigm
    Multi-paradigm
    First appeared
    1991
    Type system
    Dynamic
    Weekly downloads
    29,400,000
  • Rust
    Paradigm
    Multi-paradigm
    First appeared
    2015
    Type system
    Static
    Weekly downloads
    4,600,000
{/* On desktop: grid-table. Below 992px: cards. */}
<DataView
  layout="grid-table"
  belowLayout="cards"
  belowBreakpoint="md"
  columns={columns}
  data={data}
  pageSize={0}
/>

Empty state

Pass emptyState to customise the message shown when data is empty. Accepts any renderable content — a string, JSX, or a full call-to-action block.

<DataView
  layout="table"
  columns={columns}
  data={[]}
  pageSize={0}
  emptyState={<span>No results. <a href="/add">Add one →</a></span>}
/>

SSR pagination pattern

The key to SSR pagination is passing urlParams — the current page's full search params as a plain object. DataView merges them into every link it generates, so the active sort column survives page navigation and the active page survives sort changes.

export default definePage(async (ctx) => {
  const page  = Number(ctx.url.searchParams.get('page') ?? 1)
  const sort  = ctx.url.searchParams.get('sort') ?? undefined
  const order = (ctx.url.searchParams.get('order') ?? 'asc') as 'asc' | 'desc'

  // Your database query uses sort + page directly:
  const { rows, total } = await db.query({
    orderBy: sort,
    direction: order,
    limit: 20,
    offset: (page - 1) * 20,
  })

  return (
    <DataView
      layout="table"
      columns={columns}
      data={rows}
      page={page}
      pageSize={20}
      totalRows={total}
      sort={sort}
      order={order}
      urlParams={Object.fromEntries(ctx.url.searchParams)}
    />
  )
})

Props

PropTypeDefaultDescription
layout'table' | 'list' | 'cards' | 'grid' | 'grid-table''table'Visual layout to render
columnsDataViewColumn[]Column definitions — shared across all layouts
dataRecord<string, unknown>[]Current page's rows — pre-sliced, pre-sorted by the server
pagenumber1Current page number (read from URL by the server)
pageSizenumber10Rows per page. 0 disables pagination
totalRowsnumberTotal rows across all pages — required for multi-page pagination links
pageParamstring'page'URL query param name for the page number
sortstringActive sort column key (read from URL by the server)
order'asc' | 'desc''asc'Active sort direction (read from URL by the server)
sortParamstring'sort'URL query param name for the sort column
orderParamstring'order'URL query param name for sort direction
urlParamsRecord<string, string>{}Current URL search params as a plain object — merged into every generated link so sort state survives page changes and page resets on sort. Pass Object.fromEntries(ctx.url.searchParams).
selectablebooleanfalseAdds a checkbox column. Wrap in a <form> to handle submissions
rowKeystringRow field used as checkbox value. Falls back to row index
selectNamestring'selection'Form field name for selection checkboxes
emptyStatechildrenContent rendered when data is empty. Defaults to "No results found"
colsnumber3Number of columns in the grid layout
renderCard(row, columns, index) => stringCustom card renderer for list, cards, and grid layouts. Replaces built-in card content; DataView still wraps each item and injects the checkbox when selectable is set.
renderRow(row, columns, index) => stringCustom row renderer for grid-table layout. Replaces built-in row cells; DataView still handles the row wrapper, expand toggle, collapsed fields, and checkbox injection.
belowLayoutDataViewLayoutFallback layout shown when the viewport is narrower than belowBreakpoint. Both layouts are rendered in the HTML; CSS controls visibility — zero client JavaScript.
belowBreakpointnumber | 'xs' | 'sm' | 'md' | 'lg' | 'xl''sm'Breakpoint at which belowLayout activates. Named values map to standard widths (xs=576px, sm=768px, md=992px, lg=1200px, xl=1400px). Custom pixel values are also accepted.
stripedboolean | 'odd' | 'even'falseAlternating row shading (table only)
highlightOnHoverbooleanfalseHover highlight on rows (table only)
withTableBorderbooleanfalseBorder around the entire table (table only)
withColumnBordersbooleanfalseVertical borders between columns (table only)
withRowBordersbooleantrueHorizontal borders between rows (table only)
borderColorstringCSS color for all table borders (table only)
horizontalSpacingUiSpacingCell horizontal padding (table only)
verticalSpacingUiSpacingCell vertical padding (table only)
tableLayout'auto' | 'fixed''auto'CSS table-layout algorithm (table only)
captionstringAccessible caption text (table only)
captionSide'top' | 'bottom''bottom'Caption placement (table only)

DataViewColumn

FieldTypeDescription
keystringRow object key to read
labelstringColumn header text and field label in cards/grid
sortablebooleanRenders the column header as a sort link (table layout). Server must re-query on navigation.
align'left' | 'center' | 'right'Text alignment for header and cells (table layout)
widthnumber | stringFixed column width — pixel number or CSS string. Most useful with tableLayout="fixed"
role'title' | 'subtitle' | 'detail'Display role in cards and grid layouts. 'title' is shown prominently in the card header; 'subtitle' is shown beneath it; all other columns render as labeled key-value fields. If no column has role: 'title', the first column is used as the title.
render(row) => string | number | …Custom cell renderer. Receives the full row object and returns a display value. Use this for formatted numbers, dates, computed fields, or combining multiple fields into one display string.
spannumber | { base?, xs?, sm?, md?, lg?, xl? }1Proportional column width in grid-table layout (flex weight). Pass a number for a fixed proportion, or a breakpoint object for responsive widths — { base: 6, sm: 3 } means 6 units by default, 3 units at sm and above.
hideBelow'xs' | 'sm' | 'md' | 'lg' | 'xl'Hide this column entirely below the given breakpoint in grid-table layout. The column does not appear in the expanded section.
collapseBelow'xs' | 'sm' | 'md' | 'lg' | 'xl'Move this column to an expandable section below the row when the viewport is narrower than the given breakpoint (grid-table only). A chevron toggle appears automatically.