PatternsUpdated April 27, 2026

Data Table Pattern

Build production-ready data tables for SaaS dashboards with loading, empty, error, filters, pagination, and row actions.

PatternsData table

OverviewLink to section

Data tables are a core SaaS dashboard pattern.

They power admin views, billing screens, customer lists, logs, subscriptions, projects, invoices, and operational workflows. A production-ready table is not only rows and columns. It is a complete interface for understanding, filtering, acting on, and navigating structured data.

Use this pattern when users need to:

  • scan repeatable records
  • compare structured values
  • filter or search a dataset
  • act on individual rows
  • move through paginated results
  • recover from loading, empty, and error states

Why this matters

Tables are where product complexity becomes visible. If loading, filters, row actions, and pagination feel inconsistent, the entire dashboard feels less reliable.

What you’ll buildLink to section

Table anatomy

Structure the surface into header, toolbar, table, row actions, footer, and async states.

Controls

Add search, filters, bulk actions, and responsive row controls without crowding the UI.

Production states

Handle loading, empty, error, pagination, mutation, and recovery states clearly.

Mental modelLink to section

The table renders structure. The data layer owns truth. The toolbar controls intent. The footer handles navigation.

AreaResponsibility
HeaderPage context and primary actions
ToolbarSearch, filters, bulk actions, and view controls
TableRows, columns, selection, and row rendering
Row actionsContextual actions for one record
FooterPagination, counts, and selection summary
Data layerFetching, sorting, filtering, permissions, and mutations

System rule

A table should not own the data source. Keep fetching, filtering, sorting, permissions, and mutations in the feature layer.

StepsLink to section

Define the anatomyLink to section

A production table is composed from distinct areas. Each area should evolve independently.

components/projects-table.tsx
export function ProjectsTable() {
  return (
    <section className="space-y-4">
      <ProjectsTableHeader />
      <ProjectsTableToolbar />
      <ProjectsTableContent />
      <ProjectsTableFooter />
    </section>
  );
}

This keeps the table flexible when product requirements grow.

Keep data outside the tableLink to section

The table should receive data, status, and callbacks. It should not own fetching, permission checks, or mutation rules.

features/projects/projects-table-container.tsx
export function ProjectsTableContainer() {
  const projects = useProjects();
  const filters = useProjectFilters();

  return (
    <ProjectsTable
      data={projects.data}
      status={projects.status}
      filters={filters.value}
      onFiltersChange={filters.setValue}
      onRetry={projects.refetch}
    />
  );
}

The feature layer owns truth. The table renders the current state.

Design states before rowsLink to section

Every table needs explicit loading, empty, and error states.

No results

Try adjusting your filters.

Failed to load data
Please try again later.

Do not add these states at the end. They define the production feel of the table.

Add controls deliberatelyLink to section

Filters and row actions should support the task without making the table feel crowded.

Use progressive disclosure:

  • inline controls for frequent filters
  • Sheet for advanced filters
  • DropdownMenu for secondary row actions
  • confirmation or recovery for destructive actions
NameStatusActions
AcmeActive

AnatomyLink to section

A scalable table has clear regions.

RegionWhat belongs thereWhat to avoid
Headertitle, description, primary actiondata fetching logic
Toolbarsearch, filters, bulk actionsevery possible control at once
Tablerows, columns, selection, row actionsbusiness rules and server logic
Footerpagination, item count, selected counthidden navigation state

Why this matters

When responsibilities are separated, the table can grow without becoming a single fragile component.

StatesLink to section

A production table handles more than loading and success.

StateComponentGoal
LoadingSkeletonPreserve shape while data is expected
EmptyEmptyStateExplain valid empty results
ErrorAlertExplain failure and recovery
FilteringToolbar stateShow active constraints clearly
MutatingInline state or toastConfirm action progress and recovery
PaginatingFooter stateKeep navigation predictable

Never use the same feedback pattern for every state. Loading data, deleting a row, saving filters, and changing pages are different user experiences.

VariationsLink to section

Simple table

Use when the dataset is small and users mainly need to scan records.

Minimal row actionsSimple paginationClear empty stateNo advanced filters

Decision guideLink to section

Use this pattern if:

  • the page displays structured repeatable data
  • users need to scan, filter, compare, or act on rows
  • the table appears in a dashboard or admin surface
  • loading, empty, and error states matter
  • pagination, row actions, or bulk actions are required

Avoid this pattern if:

  • content is editorial or marketing-focused
  • data is better represented as cards, a timeline, or a feed
  • the user only needs a single summary value
  • mobile-first scanning is more important than tabular comparison
  • the table would hide the primary product action

Prefer

  • explicit loading, empty, and error states
  • controlled pagination and filtering
  • row actions in contextual menus
  • progressive filters for complex datasets
  • clear ownership between data layer and UI

Avoid

  • fetching data inside a visual table primitive
  • using a table for non-tabular content
  • hiding blocking failures behind only a toast
  • crowding the toolbar with every possible filter
  • destructive row actions without confirmation or recovery

Production checklistLink to section

Before shipping a data table, confirm that:

  • loading preserves the expected layout
  • empty state explains the result and next action
  • error state includes recovery or retry
  • pagination is controlled and predictable
  • filters can be cleared
  • active filters are visible
  • row actions are keyboard accessible
  • destructive actions require confirmation or recovery
  • selection state is clear when bulk actions exist
  • mobile behavior is defined

Ready to turn this pattern into a real SaaS surface?

Starter Pro gives you a stronger foundation for production dashboards, protected routes, auth, billing, and backend-backed product flows. → /docs/starter-pro

Common questionsLink to section

Next stepsLink to section