Centrim Design System — a token-first, accessible React component system.
Data visualization & metrics
One charting foundation, one metric treatment and one visualization palette. Charts reuse the V2 typography, spacing, radius, elevation and semantic colour tokens, the existing Card, Select, Date and Menu components, and the shared loading, empty and error states.
What is this?
Metrics communicate one number, trends communicate how it moved, and charts communicate a relationship across many values. Everything is built on the shared ChartContainer frame and the semantic chart series ramp.
When to use it
- Metric — a single summary value that matters on its own.
- Chart — a trend, comparison or part-to-whole relationship.
- Table — exact values, or many columns users compare record by record.
How to use it
- Wrap dashboard visualizations in DashboardChartCard and render the chart with appearance="plain".
- Give every chart a title and a summary sentence, and keep the data table alternative on.
- Compose filters from the existing Select, DateRangePicker, Checkbox and Button components.
What to avoid
- Per-chart colours, fonts or spacing outside the visualization tokens.
- Communicating positive / negative movement with colour alone.
- Decorative animation, or an area chart where the filled volume means nothing.
Metric
One labelled number with optional trend, supporting line and lifecycle states.
Metric
Present a single important value using the shared display typography, so no dashboard invents its own number size.
import { Metric, MetricGroup, MetricCard } from "@/design-system";
When to use
- A headline number in a page header, summary strip or card.
- A value whose movement matters — pair it with a trend.
When not to use
- Several values that should be compared — use a chart or a table.
- Long text or status — use Badge or the data display components.
Summary strip
Lifecycle states
Inside a card
Trend indicator
How a value moved — direction, magnitude and period, never colour alone.
TrendIndicator
State the change in a value with a directional arrow, the magnitude and an optional period label.
import { TrendIndicator } from "@/design-system";
When to use
- Beside a metric, in a table cell or on a chart card header.
- When the direction of change carries meaning for the reader.
When not to use
- As a status pill — use Badge.
- When the comparison period is unknown or arbitrary.
Directions
Inverted sentiment and subtle appearance
Line chart
Trends over a continuous scale, usually time.
LineChart
Show how one or more values move over time, with axis labels, tooltip, legend and the accessible data table.
import { LineChart } from "@/design-system";
When to use
- Activity, usage, requests or performance over time.
- Comparing two or three series over the same period.
When not to use
- Comparing unrelated categories — use a bar chart.
- A single value — use a metric.
Single series
Last 6 months
Residents grew steadily from 1,148 in September to 1,248 in February.
View data as table
| Month | Residents |
|---|---|
| Sep | 1,148 |
| Oct | 1,176 |
| Nov | 1,194 |
| Dec | 1,201 |
| Jan | 1,226 |
| Feb | 1,248 |
Multiple series
Requests raised against requests resolved
- Requests raised
- Requests resolved
Requests raised rose from 182 to 236 while resolutions tracked slightly below them each month.
View data as table
| Month | Requests raised | Requests resolved |
|---|---|---|
| Sep | 182 | 168 |
| Oct | 204 | 191 |
| Nov | 176 | 170 |
| Dec | 148 | 139 |
| Jan | 221 | 198 |
| Feb | 236 | 214 |
Bar chart
Comparison across discrete categories.
BarChart
Compare values across categories, grouped for series comparison or stacked for composition.
import { BarChart } from "@/design-system";
When to use
- Requests by service, occupancy by site, tasks by status.
- Long category names — switch to horizontal bars.
When not to use
- Time series with many points — use a line chart.
- Exact values users must read precisely — use a table.
Vertical bars
Last 30 days
Cleaning received the most requests at 96, followed by maintenance at 84, security at 60 and grounds at 38.
View data as table
| Service | Requests |
|---|---|
| Cleaning | 96 |
| Maintenance | 84 |
| Security | 60 |
| Grounds | 38 |
Horizontal bars
Horizontal bars keep long category names readable
Cleaning 96, maintenance 84, security 60, grounds 38 requests.
View data as table
| Service | Requests |
|---|---|
| Cleaning | 96 |
| Maintenance | 84 |
| Security | 60 |
| Grounds | 38 |
Bars with a trend line
Hours on site as bars, visitor count as an overlay line
- Visitors
- Hours
Hours peak in November at 358 and August at 333; visitors peak in October at 26 and August at 24.
View data as table
| Month | Visitors | Hours |
|---|---|---|
| Oct | 26 | 312 |
| Nov | 15 | 358 |
| Dec | 4 | 48 |
| Jan | 14 | 190 |
| Feb | 5 | 46 |
| Mar | 2 | 4 |
| Apr | 3 | 24 |
| May | 17 | 309 |
| Jun | 7 | 71 |
| Jul | 15 | 240 |
| Aug | 24 | 333 |
| Sep | 2 | 24 |
Area chart
Only where the filled volume means something.
AreaChart
Show cumulative volume or a total over time, with a restrained low-opacity fill.
import { AreaChart } from "@/design-system";
When to use
- Totals or cumulative volume over time.
- Composition of a total across two or three stacked series.
When not to use
- Purely decorative emphasis on a line chart.
- More than three overlapping series — readability collapses.
Stacked volume
Raised and resolved, stacked
- Requests raised
- Requests resolved
Total request volume peaked in February at 450 across raised and resolved.
View data as table
| Month | Requests raised | Requests resolved |
|---|---|---|
| Sep | 182 | 168 |
| Oct | 204 | 191 |
| Nov | 176 | 170 |
| Dec | 148 | 139 |
| Jan | 221 | 198 |
| Feb | 236 | 214 |
Donut chart
Part-to-whole for a small number of categories.
DonutChart
Show how a total splits across a handful of categories, with each slice named and valued in the legend.
import { DonutChart } from "@/design-system";
When to use
- Two to five categories that add up to a meaningful whole.
- A total worth showing in the centre of the ring.
When not to use
- More than about six categories — use a bar chart.
- Comparing precise category sizes against each other.
Service distribution
Share of requests by service
- Cleaning · 40%
- Maintenance · 35%
- Security · 25%
Cleaning accounts for 40%, maintenance 35% and security 25% of all requests.
View data as table
| Service | Share |
|---|---|
| Cleaning | 40% |
| Maintenance | 35% |
| Security | 25% |
Progress
Completion as a data point, built on the existing Progress bar.
ProgressMetric
Show how far something has progressed, with the value in text beside the label.
import { ProgressMetric, Progress } from "@/design-system";
When to use
- Profile or onboarding completion.
- Service or task completion against a known total.
When not to use
- Loading feedback where real progress is unknown — use a loading state.
- Comparing many values — use a bar chart.
Determinate
Indeterminate
Legend & chart tooltip
Series identity and on-hover detail — reusing the shared overlay treatment.
ChartLegend
Identify each plotted series with a colour and, for line charts, its dash pattern; optionally toggle series on and off.
import { ChartLegend, ChartTooltipContent } from "@/design-system";
When to use
- More than one series is plotted.
- Users need to isolate a series.
When not to use
- A single series that the chart title already names.
Static and interactive legends
- Requests raised
- Requests resolved
Tooltip content
Category, series name and value — kept to three concise lines.
| Month | Requests raised | Requests resolved |
|---|---|---|
| Feb | 236 | 214 |
Dashboard chart card
Title, supporting line, filters, chart, legend and actions — composed from the existing Card.
DashboardChartCard
Place a chart on a dashboard with its heading, filters and actions in one consistent container.
import { DashboardChartCard } from "@/design-system";
When to use
- Any chart on a dashboard or summary page.
- Charts that need a filter, a legend or a 'View details' action.
When not to use
- A chart already inside another card or drawer — render it plain instead.
Filtered chart card
Monthly activity
Requests raised against requests resolved
- Requests raised
- Requests resolved
Requests raised rose from 182 to 236 while resolutions tracked slightly below them each month.
View data as table
| Month | Requests raised | Requests resolved |
|---|---|---|
| Sep | 182 | 168 |
| Oct | 204 | 191 |
| Nov | 176 | 170 |
| Dec | 148 | 139 |
| Jan | 221 | 198 |
| Feb | 236 | 214 |
Chart states
One lifecycle, reusing the existing skeleton, empty and error components.
Chart states
Cover default, loading, no-data and error consistently through ChartContainer rather than per-chart feedback.
import { LineChart, NoResultsState } from "@/design-system";
When to use
- Any chart backed by an asynchronous request.
- Charts behind filters that can exclude every value.
When not to use
- Bespoke chart-only spinners, banners or error cards.
Switch state
Last 6 months
- Requests raised
- Requests resolved
Requests raised rose from 182 to 236 across the period.
View data as table
| Month | Requests raised | Requests resolved |
|---|---|---|
| Sep | 182 | 168 |
| Oct | 204 | 191 |
| Nov | 176 | 170 |
| Dec | 148 | 139 |
| Jan | 221 | 198 |
| Feb | 236 | 214 |
Partial data
December is incomplete — two sites had not reported
- Requests raised
- Requests resolved
Requests dipped to 148 in December, where data is incomplete, before rising to 236 in February.
View data as table
| Month | Requests raised | Requests resolved |
|---|---|---|
| Sep | 182 | 168 |
| Oct | 204 | 191 |
| Nov | 176 | 170 |
| Dec | 148 | 139 |
| Jan | 221 | 198 |
| Feb | 236 | 214 |
No results, not no data
Filters excluded every value — use the No results state, not the empty chart.
No results found
No requests match “Grounds · Elm Lodge” in the selected range. Clear a filter to see data again.
Centrim Life dashboard
Metrics, trend, line, donut, bar and progress on one page — all from the components above.
Monthly activity
Requests raised against requests resolved
- Requests raised
- Requests resolved
Requests raised rose from 182 in September to 236 in February, with resolutions tracking just below.
View data as table
| Month | Requests raised | Requests resolved |
|---|---|---|
| Sep | 182 | 168 |
| Oct | 204 | 191 |
| Nov | 176 | 170 |
| Dec | 148 | 139 |
| Jan | 221 | 198 |
| Feb | 236 | 214 |
Service distribution
Share of requests by service
- Cleaning · 40%
- Maintenance · 35%
- Security · 25%
Cleaning 40%, maintenance 35% and security 25% of all requests.
View data as table
| Service | Share |
|---|---|
| Cleaning | 40% |
| Maintenance | 35% |
| Security | 25% |
Requests by service
Last 30 days
Cleaning 96, maintenance 84, security 60 and grounds 38 requests.
View data as table
| Service | Requests |
|---|---|
| Cleaning | 96 |
| Maintenance | 84 |
| Security | 60 |
| Grounds | 38 |
Completion
Across resident and service records
Choosing a representation
Chart, table or metric — pick by the question the reader is asking.
| Representation | Answers | Use when | Avoid when |
|---|---|---|---|
| Metric | How much, right now? | One number carries the message | The reader needs the breakdown |
| Chart | How is this moving or splitting? | Trend, comparison or part-to-whole | Exact values matter |
| Table | What exactly is each value? | Detailed records, many columns | The shape of the data is the point |
| Metric + chart | How much, and how did it get there? | A headline number with its history | Space is tight on mobile |
Why one visualization foundation
Every chart renders inside ChartContainer, so titles, legends, lifecycle states and the data table alternative behave the same everywhere.
Series colours come from the chart token ramp, so light and dark themes and adjacent-series contrast are handled centrally rather than per chart.
Dash patterns, point shapes and legends keep series distinguishable without colour perception, and animation is short and disabled entirely under reduced-motion.
Do
- Give every chart a title and a summary sentence.
- Keep the 'View data as table' alternative available.
- Compose filters and actions from the existing Select, DateRangePicker, Button, IconButton and menu components.
- Reduce axis label density on mobile rather than shrinking type.
- Use the No results state when filters exclude everything, and the empty chart state when there is genuinely no data.
Don't
- Do not introduce per-chart colours, fonts, spacing or another chart library.
- Do not signal positive or negative movement with colour alone.
- Do not use an area chart when the filled volume is meaningless.
- Do not use a donut chart beyond about six categories.
- Do not add decorative animation or chart-specific feedback components.