Skip to main content

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"] }
PropTypeNotes
padding'compact' | 'default'default
tonetoneTints 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

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"
}
}
PropTypeNotes
labelstringAbove the value
valuenumber / stringThe headline number
format'currency' | 'number' | 'percent' | 'date'
deltastringSub-label, often a comparison
tonetoneColours 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 propPurpose
keyField on the row to read
labelColumn header
formatBuilt-in formatter (currency, number, percent, date)
valueOverride the rendered value (use $computed)
toneSemantic colour for the cell
widthFixed 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": {} }
}
}
VariantUse
primaryDefault CTA
secondaryLess prominent
ghostHeader actions, "more"
destructiveDelete / void

Tones (semantic colours)

Anywhere you see tone, valid values are:

ToneTypical meaning
defaultNeutral
mutedDe-emphasised
infoInformation
successPositive / paid / authorised
warningNeeds attention
destructiveError / 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.