Skip to content

DataTable

DataTable combines a native HTML table with optional sorting, search, and pagination. Compose its ten components to control the columns, cell content, labels, and row actions.

Use DataTable for structured records such as users, projects, or orders. For a small static comparison, omit search and sortable headers. For editable spreadsheets or virtualized datasets, use a dedicated grid solution: DataTable uses ordinary table navigation and renders every supplied row.

Search for “longer” to find a record beyond the first page, sort Users, or move to the next page. Long project names wrap while column widths stay stable.

Showing 1 to 4 of 4 entries

Projects Column headers with buttons are sortable.
Acme Starter 1,280 2026-02-10
Beacon Dashboard 642 2026-01-22
Citrine Docs 2,890 2026-02-17
A project with a longer descriptive name 35 2026-03-01
---
import {
DataTable, DataTableHeader, DataTableColumn, DataTableBody,
DataTableRow, DataTableCell, DataTableSearch, DataTableInfo,
DataTablePagination,
} from 'accessible-astro-components'
const projects = [
{ name: 'Acme Starter', users: 1280, updated: '2026-02-10' },
{ name: 'Beacon Dashboard', users: 642, updated: '2026-01-22' },
{ name: 'Citrine Docs', users: 2890, updated: '2026-02-17' },
{ name: 'A project with a longer descriptive name', users: 35, updated: '2026-03-01' },
]
---
<DataTable caption="Projects" pageSize={3} style="--data-table-min-width: 32rem">
<DataTableSearch slot="search" label="Search projects" />
<DataTableInfo slot="info" start={1} end={projects.length} total={projects.length} />
<DataTableHeader>
<DataTableColumn sortable width="50%">Project</DataTableColumn>
<DataTableColumn sortable numeric width="20%">Users</DataTableColumn>
<DataTableColumn sortable width="30%">Updated</DataTableColumn>
</DataTableHeader>
<DataTableBody>
{projects.map((project) => (
<DataTableRow>
<DataTableCell as="th">{project.name}</DataTableCell>
<DataTableCell numeric sortValue={project.users}>
{project.users.toLocaleString('en-US')}
</DataTableCell>
<DataTableCell sortValue={project.updated}>{project.updated}</DataTableCell>
</DataTableRow>
))}
</DataTableBody>
<DataTablePagination slot="footer" client ariaLabel="Projects pagination" />
</DataTable>

Install the package with npm install accessible-astro-components and import the components from its root. DataTable is available in version 5.8.0 and later.

ComponentRendered element / purpose
DataTableContainer, captioned table, scroll area, and polite live region
DataTableHeaderthead containing a single tr
DataTableColumnth scope=“col”; adds a native button when sortable
DataTableBodytbody
DataTableRowtr
DataTableCelltd, or a th with as=“th”
DataTableActionstd excluded from search by default
DataTableSearchLabeled search input and clear button
DataTableInfoVisible result summary
DataTablePaginationClient pagination buttons or page navigation links

Use the default slot for DataTableHeader and DataTableBody. Place DataTableSearch in slot="search", DataTableInfo in slot="info" between the search and table, and pagination in slot="footer". Header, row, cell, and action components accept their content in the default slot. Keep each row’s cells aligned with the header columns; the client logic expects a single body and simple columns rather than spanning or nested tables.

Search uses a case-insensitive substring match across the text of filterable cells. It runs after a 300 ms typing pause; Enter and a submit button are not required. The clear button removes the query, restores all matching records, and returns focus to the input. Use filterable={false} to exclude an individual cell; action cells are excluded by default.

For client pagination, render the complete dataset, set pageSize on DataTable, and add DataTablePagination client. Sorting and filtering operate on all supplied rows before selecting the visible page. Changing the search or sort returns to page one. Client pagination does not fetch data from a server.

For page-reload pagination, omit pageSize and client, render the current page’s records, and supply URLs or a baseUrl:

<DataTablePagination
slot="footer"
baseUrl="/users"
currentPage={2}
totalPages={5}
/>

This generates /users for page one and /users/2, /users/3, and so on. In this mode, built-in search and sorting cover only the rendered page. Implement filtering and sorting on your server if they must cover records that are not present in the document. The component does not provide server request hooks or a public JavaScript API.

A new sortable column starts ascending. Activating it again switches between ascending and descending. Only one column is sorted at a time. Unsorted columns show a paired arrow; the active column shows a single up or down arrow. Equal values retain their original row order.

Set numeric on both the column and its cells for numeric sorting and alignment. Prefer a raw sortValue for formatted numbers and ISO dates. Search still uses the displayed text, not sortValue.

<DataTableColumn sortable numeric sortKey="revenue">Revenue</DataTableColumn>
<!-- In the corresponding row: -->
<DataTableCell numeric sortKey="revenue" sortValue={1234.5}>€1,234.50</DataTableCell>

Matching sortKey values select a cell explicitly; otherwise the column position is used. Set sorted="ascending" or sorted="descending" on at most one sortable column to initialize its order. Render rows in that order on the server too if the initial HTML must match the indicated state without JavaScript.

Place buttons or links in DataTableActions. Include the record name in each accessible label, for example aria-label="View details for Albert Flores". A details action can open a Drawer; follow the Drawer documentation for its trigger and dialog content. DataTable itself does not open dialogs or implement deletion.

All components accept class and additional HTML attributes. Attributes go to their rendered wrapper, except DataTableSearch, which forwards additional attributes to the input, and DataTable’s id, which identifies the table. Keep IDs unique across instances.

PropTypeDefaultDescription
captionstringRequiredAccessible table title
idstringGeneratedID of the table
captionHiddenbooleanfalseVisually hide the caption, preserving its accessible name
pageSizenumberPositive client page size; omit for unpaginated or server-paginated rows
initialPagenumber1Initial client page; clamped to the available pages
labelsobjectSee belowSort announcements, hint, and fallback result summaries
PropTypeDefaultDescription
sortablebooleanfalseEnable the sort button
sorted’ascending’ | ‘descending’Initial order for a sortable column
numericbooleanfalseNumeric sorting and end alignment
sortKeystringMatch a row cell with this key
widthstringCSS length or percentage; unspecified columns share remaining space
PropTypeDefaultDescription
as’td’ | ‘th''td’Cell element; use th for row headings
scope’row’ | ‘col’ | ‘rowgroup’ | ‘colgroup''row’ for thHeader scope; ignored for td
numericbooleanfalseEnd alignment and tabular numerals
sortValuestring | numberCell textUnformatted value used for sorting
sortKeystringMatch a column key
filterablebooleantrueInclude displayed cell text in search
PropTypeDefaultDescription
idstringGeneratedInput ID
labelstring’Search’Input label
showLabelbooleantrueSet false to visually hide the label
placeholderstring’Search entries…’Input hint; does not replace the label
clearLabelstring’Clear search’Accessible name of the clear button
PropTypeDefaultDescription
start, end, totalnumberRequiredServer-rendered summary values; use zero for an empty dataset
templatestringShowing {start} to {end} of {total} entriesUnfiltered summary
filteredTemplatestringShowing {start} to {end} of {filtered} entries matching “{query}“Matching result summary
emptyLabelstringNo entries matching “{query}“No-match summary

The client updates the summary after initialization. For client pagination, provide the full rendered range initially so the summary also matches the rows when JavaScript is unavailable. Supported replacement tokens are {start}, {end}, {total}, {filtered}, and {query} during client updates; the server-rendered template supports {start}, {end}, and {total}.

PropTypeDefaultDescription
clientbooleanfalseUse client buttons with DataTable pageSize
pageTemplatestringPage {page} of {pages}Client progress text
currentPage, totalPagesstring | number1Link-mode page numbers; client state comes from DataTable
baseUrlstringGenerate link-mode routes
firstPage, previousPage, nextPage, lastPagestring | null | undefinedExplicit routes; missing routes fall back to baseUrl or disabled controls
ariaLabelstring’Table pagination’Navigation name
firstPageLabelstring’Go to the first page’First control label
previousPageLabelstring’Go to the previous page’Previous control label
nextPageLabelstring’Go to the next page’Next control label
lastPageLabelstring’Go to the last page’Last control label
renderProgress({ currentPage, totalPages }) => stringLink-mode progress callback
renderPageLabel({ type, page }) => stringLink-mode control label callback

Callback page values are string | number; type is 'first' | 'previous' | 'next' | 'last'. See Pagination for callback examples. Client mode uses pageTemplate and the individual control labels instead.

DataTableHeader, DataTableBody, DataTableRow, and DataTableActions have no additional component-specific props beyond class and HTML attributes.

DataTable labels supports these values:

KeyDefault
sortAscendingascending
sortDescendingdescending
sortAnnouncementSorted by {column} {direction}
sortableHintColumn headers with buttons are sortable.
resultsShowing {start} to {end} of {total} entries
filteredResultsShowing {start} to {end} of {filtered} entries matching “{query}“
emptyResultsNo entries matching “{query}”

The three result labels are fallbacks when DataTableInfo is omitted. When it is present, its templates supply both visible and announced summaries. Translate those templates together with the search and pagination labels. Do not add another live region to DataTableInfo: the table already announces updates through its own polite, atomic region.

  • Native table, caption, column headers, and optional row headers preserve table relationships.
  • Tab and Shift+Tab move between interactive controls. Enter or Space activates sort, clear, and client pagination buttons. There is no spreadsheet-style arrow-key navigation.
  • The sorted header exposes aria-sort; the table announces the column and direction without moving focus away from the button.
  • Search announces the result summary after its typing delay, including the query and no-match state. Pagination announces the updated range. When a focused page button becomes disabled at a boundary, focus moves to the progress text within the navigation.
  • The scroll area enters the tab order when it overflows, allowing keyboard scrolling. Long text wraps, while action labels stay on one line.
  • Focus outlines, forced-color styles, and reduced-motion overrides are included. Test colors and focus styling in your consuming theme.

Without JavaScript, all supplied rows remain readable and client pagination stays hidden. Search and sorting require JavaScript; page-reload pagination links work without it. Use server-rendered sorting/filtering when those features must work without scripts.

Pass a custom class to style a particular table. The class is applied to the outer container; use a global selector to reach it from an Astro style block. Most component selectors use :where() so theme styles can override them easily.

<DataTable caption="Projects" class="project-table">
<!-- Headers and rows -->
</DataTable>
<style>
:global(.project-table) {
--data-table-min-width: 32rem;
--data-table-border: var(--color-default-border);
--data-table-header-surface: var(--color-primary-bg);
}
</style>

The table uses fixed layout so sorting and filtering do not resize columns based on their contents. Set width on column headers to control proportions. Long text wraps, including unbroken strings such as emails, rather than being truncated.

Set --data-table-min-width on the root when a dense table needs horizontal scrolling instead of increasingly narrow columns. Reserve enough width for action buttons, which do not wrap.

<DataTable caption="Projects" class="project-table">
<DataTableHeader>
<DataTableColumn sortable width="50%">Project</DataTableColumn>
<DataTableColumn sortable numeric width="20%">Users</DataTableColumn>
<DataTableColumn sortable width="30%">Updated</DataTableColumn>
</DataTableHeader>
<!-- DataTableBody with matching cells -->
</DataTable>
PropertyPurpose
—data-table-min-widthMinimum table width; defaults to 100% of its container
—data-table-borderOuter and search borders; defaults to the shared Input token —color-default-border
—data-table-dividerRow dividers
—data-table-surfaceTable and cell background
—data-table-header-surfaceHeader background
—data-table-row-hoverHovered rows and active sort-button background
—data-table-mutedHeaders and result summary text
—data-table-focusComponent focus outline color
  • Pagination for page navigation outside a table.
  • Drawer for supplementary record details.
  • Button for row actions.