Overview
A lightweight server-rendered table for end-user content display. Zero client JavaScript for table logic - all interactions use URL-based navigation for SEO-friendly sorting and pagination.
Key Features
Architecture
- Server Component with zero client JS for table logic
- URL-based sorting and pagination (SEO-friendly)
- 2-5KB bundle vs 150-200KB for AdminListTableBlock
Display
- Mobile responsive (table on desktop, cards on mobile)
- Three density modes (compact, comfortable, expanded)
- Primary columns with bold text and optional descriptions
Column Features
- columnHelpers for common formatters (text, badge, date, currency)
- Column footers with sum, average, count aggregations
- Three-state sort cycle (asc → desc → unsorted)
Navigation
- Clickable rows with full Link wrapper for SEO
- Grouped rows with labeled section headers
- Sticky table headers (optional, for long lists)
When to Use ListTableBlock
- Public content lists and user-facing dashboards
- Read-only data tables without complex interactions
- SEO-important pages where URL-based navigation matters
Need drag-drop, bulk operations, or inline editing? Use AdminListTableBlock instead.
Examples
Simple Resource List
Primary column with descriptions, user avatars, and expanded density for readability.
| Resource | Created | Author |
|---|---|---|
Getting Started GuideA comprehensive introduction to the platform and its core features | Nov 15, 2024 | SC Sarah Chen |
API DocumentationComplete reference for all available API endpoints and methods | Nov 12, 2024 | MJ Marcus Johnson |
Best Practices WhitepaperIndustry recommendations for optimal implementation strategies | Nov 8, 2024 | ER Emily Rodriguez |
Video Tutorial SeriesStep-by-step visual guides covering common workflows | Oct 28, 2024 | DK David Kim |
<ListTableBlock
items={resources}
columns={[
columnHelpers.primary('name', 'Resource', {
descriptionKey: 'description',
}),
columnHelpers.date('createdAt', 'Created'),
columnHelpers.user('author', 'Author'),
]}
density="expanded"
onRowClick={(r) => `/resources/${r.id}`}
/>Sorting & Pagination
Click column headers to sort. Pagination controls update URL params for server-side rendering.
| Project | Status | Start Date | Participants |
|---|---|---|---|
| Project 1 | completed | Jan 13, 2024 | 22 |
| Project 2 | planning | Oct 24, 2024 | 6 |
| Project 3 | active | Oct 1, 2024 | 27 |
| Project 4 | completed | Sep 6, 2024 | 45 |
| Project 5 | planning | Oct 28, 2024 | 19 |
| Project 6 | active | Mar 1, 2024 | 46 |
| Project 7 | completed | Jan 17, 2024 | 40 |
| Project 8 | planning | Oct 9, 2024 | 39 |
| Project 9 | active | Jan 4, 2024 | 6 |
| Project 10 | completed | May 26, 2024 | 44 |
// Server Component with URL-based sorting/pagination
export default async function Page({ searchParams }) {
const { sort, dir = 'asc', page = '1' } = await searchParams;
// Fetch and sort server-side
const sorted = sortProjects(projects, sort, dir);
const paginated = paginateProjects(sorted, parseInt(page));
return (
<ListTableBlock
items={paginated}
columns={columns}
sortBy={sort}
sortDirection={dir}
currentPage={parseInt(page)}
totalPages={Math.ceil(total / 10)}
pageSize={10}
totalItems={total}
/>
);
}Column Footers with Aggregations
Use aggregation prop on columns to show sum, average, or count in the footer row.
| Project | Participants | Budget | Progress |
|---|---|---|---|
| Project 1 | 22 | $453,201 | 28% |
| Project 2 | 6 | $398,240 | 79% |
| Project 3 | 27 | $156,967 | 98% |
| Project 4 | 45 | $426,046 | 39% |
| Project 5 | 19 | $247,522 | 72% |
| Project 6 | 46 | $210,488 | 89% |
| — | 165 | 1,892,464 | 67.50 |
const columns = [
columnHelpers.text('name', 'Project'),
columnHelpers.number('participants', 'Participants', {
aggregation: 'sum', // Shows total in footer
}),
columnHelpers.currency('budget', 'Budget', {
aggregation: 'sum', // Shows sum in footer
}),
columnHelpers.percentage('completion', 'Progress', {
aggregation: 'average', // Shows average in footer
}),
];
<ListTableBlock
items={projects}
columns={columns}
showColumnFooter
/>Row Numbers
Show row numbers with automatic offset for paginated data.
| # | Project | Status | Start Date |
|---|---|---|---|
| 1 | Project 1 | completed | Jan 13, 2024 |
| 2 | Project 2 | planning | Oct 24, 2024 |
| 3 | Project 3 | active | Oct 1, 2024 |
| 4 | Project 4 | completed | Sep 6, 2024 |
| 5 | Project 5 | planning | Oct 28, 2024 |
<ListTableBlock
items={projects}
columns={columns}
showRowNumbers
rowNumberStart={1} // Or offset for pagination
currentPage={currentPage}
pageSize={10}
/>Grouped Rows
Organize rows into labeled groups with section headers.
| Project | Participants | Progress |
|---|---|---|
Active Projects | ||
| Project 3 | 27 | 98% |
| Project 6 | 46 | 89% |
| Project 9 | 6 | 62% |
Completed Projects | ||
| Project 1 | 22 | 28% |
| Project 4 | 45 | 39% |
| Project 7 | 40 | 65% |
Active Projects
Completed Projects
const groups = [
{
id: 'active',
label: 'Active Projects',
items: projects.filter(p => p.status === 'active'),
},
{
id: 'completed',
label: 'Completed Projects',
items: projects.filter(p => p.status === 'completed'),
},
];
<ListTableBlock
items={[]}
groups={groups}
columns={columns}
/>Empty State
Automatic empty state with customizable icon, title, and description.
No items found
There are no items to display at this time.
Props Reference
Core Props
Essential props for data and column configuration.
| Prop | Type | Default | Description |
|---|---|---|---|
items | T[] | required | Array of data items to display in the table |
columns | ListTableColumn[] | required | Column definitions using columnHelpers or manual config |
groups | ListTableGroup[] | - | Optional grouped row configuration |
onRowClick | (item: T) => string | - | Function returning URL string for row navigation |
Sorting & Pagination
URL-based sorting and pagination props for server-side rendering.
| Prop | Type | Default | Description |
|---|---|---|---|
sortBy | string | - | Currently sorted column key (from URL params) |
sortDirection | 'asc' | 'desc' | 'asc' | Sort direction: 'asc' or 'desc' |
currentPage | number | 1 | Current page number (1-indexed) |
totalPages | number | - | Total number of pages for pagination |
pageSize | number | - | Number of items per page |
totalItems | number | - | Total item count for 'Showing X-Y of Z' display |
Display Options
Visual customization and feature toggles.
| Prop | Type | Default | Description |
|---|---|---|---|
showRowNumbers | boolean | false | Display row numbers in first column |
showColumnFooter | boolean | false | Show footer row with aggregations |
enableStickyHeader | boolean | true | Keep header visible when scrolling (disables horizontal scroll) |
density | 'default' | 'expanded' | 'default' | Row density: 'default' or 'expanded' (shows descriptions) |
Usage Patterns
import { ListTableBlock, columnHelpers } from '@/ui/blocks';
// Server Component - data fetching happens server-side
export default async function ProjectsPage({ searchParams }) {
const { sort, dir = 'asc', page = '1' } = await searchParams;
const projects = await getProjects({ sort, dir, page });
const columns = [
columnHelpers.text('name', 'Project', { sortable: true }),
columnHelpers.badge('status', 'Status', { active: 'success' }),
columnHelpers.date('startDate', 'Start Date', { sortable: true }),
];
return (
<ListTableBlock
items={projects}
columns={columns}
sortBy={sort}
sortDirection={dir}
currentPage={parseInt(page)}
totalPages={10}
onRowClick={(p) => `/projects/${p.id}`}
/>
);
}