Skip to content

Accessible data tables in React: start with the interaction model

Choose native table or interactive grid behavior deliberately, then verify labels, keyboard movement, sorting feedback, and application state in a React interface.

Velkin UI team · · Updated · 6 min read

accessibility · react · data-table

A table can be visually clear and still be difficult to use without a pointer. The right repair depends on what the table is supposed to do. Reading a list of records is different from editing cells or selecting a range. Before adding keyboard handlers or ARIA roles, write down the task: which information must a person understand, which actions can they perform, and how will they know an action succeeded?

Choose a table before choosing a grid

For a small read-only report, start with native table structure. Provide a caption, meaningful column headers, and actual links or buttons for available actions. Do not convert the whole row into an ambiguous clickable surface if a named action is sufficient. Native structure often expresses a simple report more clearly than an interaction model that requires special instructions.

An interactive grid has additional responsibilities. Moving through cells, entering an editor, committing a value, leaving the grid, and preserving context after a data update are different operations. The WAI-ARIA APG grid pattern is a primary reference for that interaction model. It is not evidence that an arbitrary component or your integrated page implements the pattern correctly.

Decide whether the application genuinely needs cell navigation. A list of ten records with one details link per row does not automatically need spreadsheet behavior. Conversely, placing hundreds of independent controls in a long tab sequence can make a complex workspace tiring to use. Evaluate the task and the user's expected interaction rather than selecting a role because the design looks like a spreadsheet.

Establish a small native baseline

The following complete React client component uses a native table with a caption and a named action. Velkin supplies the theme and the action button. It is a useful baseline for testing content and action labels before introducing a richer data table. The status is local sample data; it is not a production persistence implementation.

"use client";
import { useState } from "react";
import { VuThemeProvider } from "@velkin/react/theme-provider";
import { VuButton } from "@velkin/react/button";

export default function CustomerReport() {
  const [message, setMessage] = useState("");
  return (
    <VuThemeProvider persist>
      <table>
        <caption>Example customer accounts</caption>
        <thead>
          <tr>
            <th scope="col">Customer</th>
            <th scope="col">Action</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <th scope="row">Example Studio</th>
            <td>
              <VuButton onClick={() => setMessage("Example Studio selected")}>
                Select Example Studio
              </VuButton>
            </td>
          </tr>
        </tbody>
      </table>
      <p role="status">{message}</p>
    </VuThemeProvider>
  );
}

Install the React packages using the quick start. Compile this small example before connecting it to an API. Notice that the button label identifies its row context without depending on the user having just heard a particular cell. If a real action navigates to a customer page, use appropriate navigation semantics instead of announcing a fictional selection.

Evaluate the real data-table contract

When the task needs the Pro Data Table, read the current source-generated API and try the sample-data playground. The published contract distinguishes rowData from a dataProvider and includes options for sorting, filtering, pagination, keyboard navigation, and editing. Enable the behavior you need deliberately. Do not assume a default makes the integrated experience match a particular ARIA pattern.

Start with a small stable data set and explicit row identifiers. Determine which state belongs to the component and which belongs to the application. If a server response changes the order or removes a row, decide what should happen to the active item. Keeping an index alone can refer to a different record after sorting, which is a data correctness problem as well as an interaction problem.

Inspect the documented column definitions before copying examples from another grid. Property names, renderers, event payloads, and provider contracts are library-specific. A familiar columns object can compile poorly or render incomplete content when its fields belong to another package. Ask an MCP-connected editor for the actual table contract, then compare the result with your installed package version.

Test keyboard movement as a user task

Write an explicit sequence: enter the table, find a target record, change the sort order, perform an action, and leave the table. Record the visible focus indicator at each step. Check whether the active cell or control remains understandable after an update. A screenshot of the initial state cannot establish that this sequence works.

If editing is enabled, distinguish moving to a cell from editing its value. Test how the user starts an edit, confirms it, cancels it, and moves away. Include a value the server rejects. The original value and the error must remain recoverable without requiring the user to guess whether a change was saved. Do not introduce a second global key handler that competes with the component's implementation.

Check the boundary around the table. Keyboard users must be able to reach surrounding filters, pagination, and page navigation. A focus loop inside a data region can prevent completion of the wider task. Verify disabled controls and loading states too: disabling a focused control or replacing a row while a request is in flight can create an unexpected focus transition.

Inspect what assistive technology receives

Use the browser accessibility tree to inspect names, roles, states, and relationships. Then test a representative task with a screen reader and record the browser and assistive technology versions. The tree is useful structural evidence; it does not predict every announcement. Avoid promising a particular spoken phrase without recording the environment and reproducing the behavior.

Sorting must communicate more than an icon changing direction. Selection must be distinguishable from focus. Validation needs a meaningful relationship to the relevant field or cell. Loading and empty results should explain what happened without repeatedly interrupting the user. These checks concern the complete page, including the text and state transitions that the application provides.

Virtualization adds another boundary. A row can be represented in application data without being present in the DOM. Test movement across the rendered window, after filtering, and when data arrives slowly. Do not infer large-data accessibility from a two-row example. Likewise, do not claim a performance improvement without measuring the actual workload, device, browser, and production build.

Keep authorization and data ownership outside the widget

A disabled edit button is not authorization. The server must validate who can read and change each record. A successful local cell edit is not evidence that persistence succeeded. Model saving, failure, retry, and conflict explicitly, and keep record identifiers stable through those transitions. Never expose private customer data in a public reproduction or playground.

Review the final implementation against the original user task. If the richer table adds complexity without helping users complete it, retain the native baseline. If the richer behavior is necessary, document its supported interactions and test them as a regression boundary. The result should be an inspectable interface, not an unsupported claim that selecting a grid package makes an application accessible.

Continue with the accessibility guide, Data Table API, and framework installation. This article explains a native-table baseline and an evaluation process. It does not report a completed keyboard or screen-reader audit of Velkin Pro Data Table. Record that evidence for your selected configuration before shipping it.

Record the test configuration

Keep a short fixture description with the final review: number of rows, enabled features, loading strategy, selected theme, browser, and assistive technology. Repeat the same task after changing virtualization or keyboard settings. If a behavior has not been observed, leave it as an open verification item. That makes the tutorial useful to another developer without turning a plausible implementation detail into an unsupported product claim.

Related engineering notes

Start with Velkin UI · Connect your AI editor · Compare MIT and Pro