Components reference
The MyHub widgets-system ships a baseline of opinionated components every widget can use. They live at apps/web/src/features/widgets-system/system/components/ in the MyHub repo and the canonical, always-up-to-date catalog is generated by:
# from the MyHub repo
npm run sync:widget-skills
The summary below covers the components you'll use 90% of the time.
Containers
Card
The default top-level wrapper. Has a border, padding, and optional header.
{ "type": "Card", "children": ["header", "body"] }
| Prop | Type | Notes |
|---|---|---|
padding | 'compact' | 'default' | default |
tone | tone | Tints the border |
Section
Like Card but no border — useful for sub-grouping inside a Card.
Divider
A thin horizontal rule. No props, no children.
{ "type": "Divider" }
Headers
Header
Title + optional subtitle and right-side actions.
{
"type": "Header",
"props": {
"title": "P&L Pulse",
"subtitle": "May 2026",
"actions": [
{ "type": "Button", "props": { "label": "Refresh", "variant": "ghost" } }
]
}
}
Stats and metrics
Stat
Single-value tile.
{
"type": "Stat",
"props": {
"label": "Outstanding",
"value": 12450,
"format": "currency",
"delta": "+8.4% vs last month",
"tone": "success"
}
}
| Prop | Type | Notes |
|---|---|---|
label | string | Above the value |
value | number / string | The headline number |
format | 'currency' | 'number' | 'percent' | 'date' | |
delta | string | Sub-label, often a comparison |
tone | tone | Colours the value |
StatGrid
Lays out N Stats in a row.
{
"type": "StatGrid",
"props": {
"columns": 3,
"items": [{ "label": "…", "value": 1, "format": "currency" }, …]
}
}
Lists
Table
The workhorse. Iterates rows, renders columns.
{
"type": "Table",
"props": {
"rows": { "$state": "/acme-billing/list_invoices" },
"compact": false,
"columns": [
{ "key": "number", "label": "#" },
{ "key": "total", "label": "Total", "format": "currency" },
{ "key": "status", "label": "Status", "tone": { "$computed": "…" } },
{ "key": "daysLate", "label": "Days late", "value": { "$computed": "…" } }
]
}
}
| Column prop | Purpose |
|---|---|
key | Field on the row to read |
label | Column header |
format | Built-in formatter (currency, number, percent, date) |
value | Override the rendered value (use $computed) |
tone | Semantic colour for the cell |
width | Fixed pixel width or 'auto' |
List
Like Table but renders one element per row instead of cells. Use when you want full layout control per row.
{
"type": "List",
"props": {
"items": { "$state": "/acme/notifications" },
"render": "notif-row"
},
"children": ["notif-row"]
}
Indicators
Badge
{
"type": "Badge",
"props": { "label": "OVERDUE", "tone": "destructive" }
}
Progress
{
"type": "Progress",
"props": { "value": 0.42, "tone": "info" }
}
Inputs
Button
{
"type": "Button",
"props": {
"label": "Mark paid",
"variant": "primary",
"action": { "tool": "acme-billing.mark_paid", "params": { … } }
}
}
| Variant | Use |
|---|---|
primary | Default CTA |
secondary | Less prominent |
ghost | Header actions, "more" |
destructive | Delete / void |
Tones (semantic colours)
Anywhere you see tone, valid values are:
| Tone | Typical meaning |
|---|---|
default | Neutral |
muted | De-emphasised |
info | Information |
success | Positive / paid / authorised |
warning | Needs attention |
destructive | Error / overdue / deleted |
The widget-elements-system skill in MyHub auto-generates a fresh catalog (with prop types and full lists) each time npm run sync:widget-skills runs. Use it when you need the truth.