Centrim Design System — a token-first, accessible React component system.
Data display
Components for presenting structured records — tables, filters, lists, metrics, timelines and trees — plus the file handling layer. Every example renders the real production component.
Table
Low-level table primitives for hand-built tables: Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell and TableCaption.
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from "@/design-system";
When to use
- The columns and rows are known up front and need no sorting or filtering.
- You need full control over the markup inside cells.
- A summary row belongs in a TableFooter.
When not to use
- The dataset needs sorting, search, selection or pagination — use DataTable.
- The content is a set of label/value pairs — use DescriptionList.
- There are only two columns of loose metadata — use KeyValue.
Row states
Default, hover, selected and disabled rows.
| Member | Role | Seats |
|---|---|---|
| Aria Whitfield | Owner | 1 |
| Malik Osei | Editor | 1 |
| Freya Lindqvist | Deactivated | 0 |
Density
Comfortable is the default; compact stays readable and keeps usable touch targets.
compact
| Plan | Seats | Monthly |
|---|---|---|
| Starter | 3 | £29 |
| Growth | 12 | £99 |
| Total | £128 | |
default
| Plan | Seats | Monthly |
|---|---|---|
| Starter | 3 | £29 |
| Growth | 12 | £99 |
| Total | £128 | |
comfortable
| Plan | Seats | Monthly |
|---|---|---|
| Starter | 3 | £29 |
| Growth | 12 | £99 |
| Total | £128 | |
DataTable
A generic, typed data table built on the Table system: sorting, search, column filters, row selection with a BulkActionBar, Pagination, row actions, expandable rows and the shared loading/empty/no-results/error states.
import { DataTable, type DataTableColumn } from "@/design-system";
When to use
- A dataset needs any combination of sorting, filtering, selection or pagination.
- Rows carry per-row actions or expandable detail.
- The same table has to express loading, empty, no-results and error states.
When not to use
- The table is static — use the Table primitives directly.
- You are showing a single record's attributes — use DescriptionList.
- The dataset is a short, scannable set of entities with avatars — a List reads better.
Sortable, filterable, selectable
Sort a column, search, filter by status, select rows and expand a row for detail.
| Expand row | Status | Actions | ||||
|---|---|---|---|---|---|---|
| INV-1042 | AW Aria Whitfield aria@northfern.example | £4,200.00 | ||||
| INV-1043 | MO Malik Osei malik@brightloop.example | £1,180.00 | ||||
| INV-1044 | FL Freya Lindqvist freya@nordvik.example | £860.00 | ||||
| INV-1045 | DS Diego Salgado diego@cantera.example | £3,260.00 | ||||
| INV-1046 | PC Priya Chandran priya@ambervale.example | £990.00 |
Showing 1–5 of 10 invoices
Loading
Skeleton rows reuse the existing Skeleton component.
| Expand row | Status | Actions | |||||
|---|---|---|---|---|---|---|---|
Empty
No data exists yet — renders EmptyState. Searching with no matches renders NoResultsState instead.
| Expand row | Status | Actions | |||||
|---|---|---|---|---|---|---|---|
Nothing here yet There is nothing here yet. | |||||||
Error
Loading failed — renders ErrorState with a retry action.
| Expand row | Status | Actions | |||||
|---|---|---|---|---|---|---|---|
Something went wrong The invoices service returned an error. Try again. | |||||||
Responsive
Low-priority columns are hidden below their breakpoint (hideBelow) and the table scrolls horizontally rather than shrinking text.
| Status | |||
|---|---|---|---|
| INV-1042 | AW Aria Whitfield aria@northfern.example | £4,200.00 | |
| INV-1043 | MO Malik Osei malik@brightloop.example | £1,180.00 | |
| INV-1044 | FL Freya Lindqvist freya@nordvik.example | £860.00 |
Showing 1–3 of 3 rows
FilterBar
Toolbar that groups a search field, filter controls and active-filter chips above a data set. Filters sit inline from md up and collapse behind a Filters toggle below that.
import { FilterBar } from "@/design-system";
When to use
- A table or list has two or more filter dimensions.
- Users need to see and remove what is currently applied.
- Filtering is expensive and needs an explicit Apply action.
When not to use
- There is a single search box and nothing else — render SearchInput on its own.
- The filters belong to a form submission — use the Form components.
Search, filters and chips
Resize the preview to see the filters collapse behind the Filters toggle.
BulkActionBar
Contextual bar shown while rows are selected: the selected count, a primary action, secondary actions, a separated destructive action and clear selection.
import { BulkActionBar } from "@/design-system";
When to use
- A table or list supports multi-select and actions apply to the selection.
- Users need a fast way to clear the current selection.
When not to use
- Actions apply to a single row — use row actions instead.
- The action is a page-level primary action — put it in the page header.
With a selection
3 invoices selected
List
A vertical list of records: List, ListItem, ListHeader, ListContent and ListActions, with leading/trailing slots for Avatar, Badge and action buttons.
import { List, ListItem, ListHeader, ListContent, ListActions } from "@/design-system";
When to use
- Entities are best scanned as rows of a name plus supporting metadata.
- The presentation must stay readable on narrow screens where a table would not.
- Each row has one or two actions.
When not to use
- Records need to be compared across many columns — use a Table or DataTable.
- The content is a single record's attributes — use DescriptionList.
States and slots
- Workspace members
- AW
Aria Whitfield
Owner
Last active 2 hours ago
- MO
Malik Osei
Editor
Last active yesterday
Support rota
Group of 6
Shared inbox
- FL
Freya Lindqvist
No longer has access
DescriptionList
Semantic dl/dt/dd for a labelled record summary, in horizontal or vertical layout.
import { DescriptionList, DescriptionItem } from "@/design-system";
When to use
- Showing the attributes of one record: status, owner, created date.
- A detail panel or drawer needs a compact summary block.
When not to use
- Comparing several records — use a Table.
- There is exactly one fact to show — use KeyValue or Stat.
Horizontal
- Status
- Active
- Created
- 20 August 2026
- Owner
- Aria Whitfield
Vertical grid
- Company
- Northfern Ltd
- VAT number
- GB 928 4471 02
- Billing email
- billing@northfern.example
- Country
- United Kingdom
KeyValue
Lightweight label/value pair with an optional description, icon and loading state. Deliberately surface-less.
import { KeyValue } from "@/design-system";
When to use
- Dense metadata rows inside a panel, popover or card you already have.
- A value is fetched separately and needs a placeholder while loading.
When not to use
- The pair deserves its own card — it almost never does.
- Several related attributes belong together semantically — use DescriptionList.
Metadata block
Metric
A labelled numeric value with an optional description, icon and trend delta communicated by icon, text and colour together. MetricGroup lays several out responsively.
import { Metric, MetricGroup } from "@/design-system";
When to use
- Summarising a handful of headline numbers above a data set.
- A number changes over time and the direction matters.
When not to use
- There is a single KPI with supporting copy — use Stat.
- The number is one attribute among many — use DescriptionList.
Group
Sizes
Stat
A restrained KPI readout: label, primary value, supporting text, trend and an optional comparison. Hierarchy comes from typography and spacing, not coloured panels.
import { Stat } from "@/design-system";
When to use
- A dashboard header needs one or two headline figures with context.
- The figure benefits from a comparison baseline ("vs. previous period").
When not to use
- You need four evenly weighted numbers in a row — use MetricGroup.
- The figure needs a coloured hero treatment — that is a product decision, not a system one.
With and without a card
Monthly recurring revenue
£48,290
Increase: 8.4% vs. previous period
Updated 5 minutes ago
Open support tickets
37
Increase: 12 more than last week
Timeline
Vertical sequence of dated events with a connecting line and status markers: completed, current, upcoming and error.
import { Timeline, TimelineItem } from "@/design-system";
When to use
- A record has an ordered history: an order, an approval chain, an audit trail.
- Each event has a time, a title and optional detail.
When not to use
- The steps are a form flow the user is completing — use Stepper.
- The entries are actor-driven social activity — use ActivityFeed.
Order history
Completed: Order placed
Order #4821 confirmed.
Completed: Payment captured
Charged the card on file.
Error: Address check failed
Delivery postcode could not be verified.
Current: Preparing shipment
Packed at the northern warehouse.
Upcoming: Out for delivery
Expected by end of day.
ActivityFeed
Chronological actor + action log with avatar or icon, timestamp, metadata, optional quoted content and an optional trailing action.
import { ActivityFeed, ActivityItem } from "@/design-system";
When to use
- Showing who did what and when: comments, audit trails, notifications.
- Entries may carry quoted content or a follow-up action.
When not to use
- The events describe a single record's lifecycle stages — use Timeline.
- The entries are unread notifications in a menu — use Notification.
Recent activity
- PC
Priya Chandran left a comment on invoice INV-1046
Can we resend this to their new billing contact? Tom Fairweather was added to the Finance workspace
Invoice INV-1047 marked as overdue
No payment received 14 days after the due date.
Tree
Hierarchical list with expand/collapse, selection, disabled nodes and full keyboard navigation.
import { Tree, TreeItem } from "@/design-system";
When to use
- Content is genuinely nested: folders, categories, org structures.
- Users need to expand only the branch they care about.
When not to use
- The hierarchy is one level deep — a List with headers is simpler.
- The tree is site navigation — use Sidebar with sub-items.
File tree
Focus the first node, then navigate with the arrow keys.
- design-system
- tokens
- colour.css
- spacing.css
- components
- archive (read only)
FileUpload / Dropzone
FileUpload is a button-triggered hidden file input. Dropzone adds a large drag-and-drop target with a keyboard-activatable browse fallback.
import { FileUpload, Dropzone } from "@/design-system";
FileUpload
Dropzone
Drag files here
PDF, PNG or JPG up to 10MB
Uploads unavailable
Reconnect to continue
Drag files here
That file type isn't supported.
FileList / FileItem
Shows selected or uploading files with type icon, size, progress bar and success/error states.
import { FileList } from "@/design-system";
Mixed states
brand-guidelines.pdf
2.3 MB
Uploaded
hero-banner.png
1.0 MB
budget-2024.xlsx
332.0 KB
Network error, try again.
ImagePreview
Thumbnail preview for an uploaded image with a required alt text, loading spinner and broken-image fallback.
import { ImagePreview } from "@/design-system";
States
