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
localStorageKeyon DataTables so column preferences persist - Do use infinite scroll for lists longer than 50 items
- Do show
totalResultsCountwhen using server-side filtering - Don't show raw dates — use
date-fnsformat 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