Uncontrolled first, controlled when you need it

`DataTable` works with no configuration beyond columns and rows: sorting, searching, pagination and row selection all function client-side immediately. For the very common case of a few hundred rows already in memory, that is the whole integration.

When your dataset outgrows that, the same component exposes `onSortChange`, `onPageChange` and `onSearchChange`. You take control of the state, issue your own queries, and feed back the current page of rows. The component's rendering does not change — only who decides what to render.

Knowing when to go server-side

The signal is not row count alone, it is payload size and time-to-interactive. A few thousand narrow rows is fine in the browser. A few thousand rows with twenty columns each, including long text, is not — you are shipping a database query result over the wire and asking the browser to sort it.

The other trigger is correctness. If rows change frequently and multiple users act on them, client-side pagination shows a stale snapshot and a user can act on a row that has already moved. Once that matters, server-side is not an optimisation, it is a requirement.

Why a real table element matters

`DataTable` renders an actual `<table>` with proper header cells rather than a grid of divs. That is what lets a screen reader announce the column header alongside each cell, which is the entire mechanism by which a table is comprehensible without sight.

A div grid can be made accessible with enough ARIA, but it means reimplementing semantics the browser already provides, and every re-implementation is a chance to get it wrong. Using the element that already means 'table' is both less code and more reliable.

Sorting that announces itself

A sortable column header needs to communicate three things: that it is sortable, the current sort direction, and that the direction changed when the user activates it. `aria-sort` on the header cell handles the first two natively.

The one people miss is the third. If a user sorts a table and nothing is announced, the interaction appears to do nothing — the visual reordering is invisible to them. Getting this right is a large part of why sortable tables are harder than they look.

Row selection and the header checkbox

Selection introduces a state most implementations get wrong: the header checkbox when some but not all rows are selected. It is not checked and not unchecked — it is indeterminate, which is a real DOM property rather than a visual style.

It also needs an accessible name that says what it does, because 'checkbox' alone is meaningless in a header. And selection should survive pagination or explicitly not, but it must be one or the other consistently — silently dropping selections when the user changes page is a genuine data-loss bug, not a UI quirk.

Exporting without creating a vulnerability

The library's `toCsv` helper guards against spreadsheet formula injection. A cell whose value begins with `=`, `+`, `-`, `@`, a tab or a carriage return is neutralised before export, because Excel and Sheets will otherwise treat it as a formula and execute it when the file is opened.

This is a real attack path, not a theoretical one: user-supplied text lands in your database, gets exported by an administrator, and executes on their machine. If you write your own CSV export from table data, add this guard yourself. It is two lines and it closes a genuine hole.

Key takeaways

  • Uncontrolled by default for in-memory data; onSortChange, onPageChange and onSearchChange hand you control when needed
  • Move server-side based on payload size and data freshness, not row count alone
  • A real <table> gives header-cell association for free — a div grid means reimplementing it
  • Sorting must announce the change, or the interaction appears to do nothing for screen reader users
  • The header checkbox needs a genuine indeterminate state, and selection must survive pagination or explicitly not
  • Guard CSV exports against formula injection — a cell starting = or + executes when opened in Excel

Conclusion

A data table is where accessibility, performance and security all show up in one component. Use the real table element, announce sort changes, handle the indeterminate header checkbox honestly, and never export a CSV without a formula-injection guard.

Enjoyed this article?

Vivek Kumar Singh

Vivek Kumar Singh

Technical Expert · Full Stack Cloud Engineer · Tokyo, Japan