FSAI Design System
Patterns

Data Display

Patterns for tables, lists, cards, and data visualization.

Overview

Data display patterns ensure consistent presentation of information across tables, lists, grids, and detail views.


DataTable Patterns

Standard List Page

The most common pattern: a full-width DataTable with search, filters, and selection:

<DataTable
  items={items}
  columns={columns}
  isLoading={isLoading}
  handleSearchChange={setSearch}
  searchPlaceholder="Search items..."
  selectionMode="multiple"
  checkedIds={checkedIds}
  setCheckedIds={setCheckedIds}
  modifiers={bulkActions}
  availableFilters={filters}
  appliedFilters={appliedFilters}
  onApplyFilters={setAppliedFilters}
  emptyState={{
    default: { title: 'No items yet', description: 'Create your first item to get started.', action: { label: 'Create Item', onClick: handleCreate } },
    search: { title: 'No results', description: 'Try adjusting your search or filters.' },
  }}
  localStorageKey="items-table-preferences"
/>

Master-Detail Pattern

Table with a side panel for viewing/editing the selected row:

<DataTable
  items={items}
  columns={columns}
  onRowClick={(item) => setSelected(item)}
  isPanelOpen={!!selected}
  panelContent={selected && <DetailPanel item={selected} />}
  panelWidth={400}
  closePanel={() => setSelected(null)}
/>

Infinite Scroll

For large datasets, use infinite scroll rather than pagination:

<DataTable
  items={allLoadedItems}
  columns={columns}
  hasNextPage={hasNextPage}
  fetchNextPage={fetchNextPage}
  isFetchingNextPage={isFetchingNextPage}
  totalResultsCount={totalCount}
/>

List Patterns

Simple List

For compact displays without full table features:

<div className="flex flex-col divide-y divide-default">
  {items.map((item) => (
    <div key={item.id} className="flex items-center justify-between py-3 px-4">
      <div className="flex items-center gap-3">
        <Avatar user={item.user} size="sm" />
        <div>
          <p className="text-base font-medium text-strong">{item.name}</p>
          <p className="text-sm text-subtle">{item.description}</p>
        </div>
      </div>
      <Badge label={item.status} color={item.statusColor} />
    </div>
  ))}
</div>

Empty States

Every data display should handle three states:

Loading State

Use the isLoading prop on DataTable, or Skeleton for custom layouts:

import { Skeleton } from '@fsai/shared-ui';

<div className="flex flex-col gap-3">
  <Skeleton className="h-10 w-full" />
  <Skeleton className="h-10 w-full" />
  <Skeleton className="h-10 w-full" />
</div>

Empty State (No Data)

Show an illustration, message, and call-to-action:

emptyState={{
  default: {
    title: 'No franchisees yet',
    description: 'Add your first franchisee to get started.',
    action: { label: 'Add Franchisee', onClick: handleCreate },
  },
}}

Empty Search Results

emptyState={{
  search: {
    title: 'No results found',
    description: 'Try adjusting your search or filters.',
  },
}}

Detail Views

DetailsGrid

Use DetailsGrid for structured key-value display:

import { DetailsGrid } from '@fsai/shared-ui';

<DetailsGrid
  items={[
    { label: 'Name', value: entity.name },
    { label: 'Email', value: entity.email },
    { label: 'Status', value: <StatusTag label={entity.status} color={entity.color} /> },
    { label: 'Created', value: format(entity.createdAt, 'MMM d, yyyy') },
  ]}
/>

Guidelines

  • Do always provide empty states for every data display
  • Do use localStorageKey on DataTables so column preferences persist
  • Do use infinite scroll for lists longer than 50 items
  • Do show totalResultsCount when using server-side filtering
  • Don't show raw dates — use date-fns format consistently
  • Don't truncate text without a tooltip showing the full value
  • Don't use DataTable for simple 2-3 field lists — use a simple list instead

On this page