Widgets
Generated from DESIGN_SYSTEM.md
Rules
A widget is a self-contained block on a hub page: it frames one job, owns its own states, and never takes the page down with it.
- Isolated by construction. Every block view is wrapped in the error boundary and the isolation shell, so a crash renders that block's own error with a retry while the page around it keeps working, and typing or pasting inside a widget never reaches the editor around it.
- One frame.
widget-panel(large radius, border, no padding of its own) plus exactly one padding tier from Spacing (section-pad-xs,section-pad-smorsection-pad-lg). No hand-set padding, no shadow. - Header: icon, a bold title that truncates rather than wraps, then an optional count or meta line in
label-secondary tabular-nums; actions sit at the trailing edge. - Three states inside the frame. Loading, empty and error render in the widget at its loaded size (Three states). A failed call never renders as an empty table or chart.
- Zero configuration. A new widget shows something useful from its defaults the moment it is placed; when the default data is genuinely empty it shows the empty state and the action that fills it, never a blank panel.
- Settings in place. Configuration opens inside the widget, in a popover or in a side panel with a live preview, never a full-screen modal. Choices are tokens (a color from the palette, a size from the scale), never raw hex or pixel input. Raw overrides live behind an Advanced section that is collapsed by default.
Widget blocks on a hub page share ONE vertical rhythm: my-6 (24px, collapsing). Every Tiptap block view uses it, so two adjacent widgets sit 24px apart and a widget sits 24px from surrounding text. A block that needs to read as a separate section gets a heading or rule above it, never a larger margin of its own.
Live specimens
Loading
Empty
Error