Skip to main content

Extension Architecture

Widgemo's extension surface is built around a central registry — widgemoRegistry — that lets you register custom implementations before a Widgemo renders. There are five extension points, each scoped to a distinct concern:

Extension pointRegistry functionScope
renderAs rendererregisterWidgemoRenderAsHow a single field value is displayed
Field typeregisterWidgemoFieldTypeA new value category with its own rendering rules
Content moderegisterWidgemoModeAn entirely new content area layout strategy
IconregisterWidgemoIconA custom icon name resolvable in config
Lifecycle hookregisterWidgemoHookRuntime interception at named core lifecycle points

All registrations are resolved by name at render time. You can register before or after a Widgemo mounts — the registry is global and persistent for the lifetime of the page.

When to Extend vs When to Configure​

Use config first. Extensions add new behavior that config cannot express — they are not a shortcut for config-driven features.

GoalPreferReason
Display a field value differentlyrenderAs built-in or customSmallest surface; stays config-driven
Reuse a new value type across many configscustom field typeFirst-class field category with its own rendering and type identity
Replace the entire content area layoutcustom modetable / grid / board / carousel don't fit the use case
Reference your design system icons in configcustom iconKeeps icon names consistent without hardcoding SVG inline
Intercept renders, mode changes, or drag-droplifecycle hookRuntime wiring without modifying layout or config

A good adoption sequence:

  1. Start with built-in mode, fields, renderAs, and theming
  2. Confirm the Widgemo shape and data contract
  3. Introduce custom extensions only when built-in surfaces cannot express the requirement cleanly

Built-In Surface

This Widgemo uses only built-in config — no custom extensions required.

Extension Architecture

Built-in config handles the entire surface below

Region
Status
Health
Budget
North Regionhealthy
High92
$420,000.00
EMEA Regionwatch
Medium68
$315,000.00

Live Extension Demo​

The Widgemo below uses two custom extensions registered directly on this page — a gauge renderAs renderer and a flag-risk icon. Neither exists in widgemo-core; both are wired through widgemoRegistry before the Widgemo renders.

Custom Extensions Live

gauge renderAs (SVG speedometer) + flag-risk icon — both registered via widgemoRegistry on this page

Regional Health

Custom renderAs and icon in action

Region
Status
Health
Alerts
North Americahealthy881
EMEAwatch544
APACcritical319
LATAMhealthy722

The registration code for this demo (runs at module scope in the MDX file):

import { widgemoRegistry } from '@widgemo/widgemo-core';

// Custom renderAs: SVG half-circle speed gauge
// Background arc spans the full semicircle; value arc fills proportionally.
// A needle rotates from left (0) to right (100). Color: green ≥75, amber ≥45, red <45.
widgemoRegistry.registerWidgemoRenderAs({
name: 'gauge',
render: (value) => {
const cx = 30, cy = 32, r = 22, nLen = 18;
const score = Math.max(0, Math.min(100, Number(value) || 0));
// Angle in radians: π at score=0 (needle left), 0 at score=100 (needle right)
const angle = Math.PI * (1 - score / 100);
const arcX = (cx + r * Math.cos(angle)).toFixed(2);
const arcY = (cy - r * Math.sin(angle)).toFixed(2); // SVG y is inverted
const nx = (cx + nLen * Math.cos(angle)).toFixed(2);
const ny = (cy - nLen * Math.sin(angle)).toFixed(2);
const color = score >= 75 ? '#22c55e' : score >= 45 ? '#f59e0b' : '#ef4444';
return (
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
<svg width={60} height={36} viewBox="0 0 60 36">
{/* Background track */}
<path d="M 8 32 A 22 22 0 0 1 52 32"
stroke="#e5e7eb" strokeWidth={5} fill="none" strokeLinecap="round" />
{/* Value arc */}
{score > 0 && (
<path d={`M 8 32 A 22 22 0 0 1 ${arcX} ${arcY}`}
stroke={color} strokeWidth={5} fill="none" strokeLinecap="round" />
)}
{/* Needle */}
<line x1={cx} y1={cy} x2={nx} y2={ny}
stroke="#374151" strokeWidth={1.5} strokeLinecap="round" />
{/* Pivot dot */}
<circle cx={cx} cy={cy} r={2.5} fill="#374151" />
</svg>
<span style={{ fontSize: '0.75rem', color: '#6b7280', minWidth: 28 }}>{score}</span>
</span>
);
},
});

// Custom icon: a flag SVG referenced by name in config
widgemoRegistry.registerWidgemoIcon({
name: 'flag-risk',
component: ({ size = 16, color = 'currentColor' }) => (
<svg width={size} height={size} viewBox="0 0 16 16" fill="none">
<path d="M3 2v12M3 2h8l-2 3.5L11 9H3"
stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
),
});

In config the extensions are referenced by name just like built-ins:

// renderAs
{ key: 'health', label: 'Health', renderAs: 'gauge' }

// icon in an action
{ id: 'flag-region', label: 'Flag', icon: 'flag-risk', placement: 'pinned' }

Custom renderAs​

Register a custom renderAs when you want a field value to render in a new way while the rest of the field pipeline (label, layout, sorting) stays intact.

When to use: domain-specific value formatting — status pills, compact sparkline variants, image thumbnails, risk scores.

When not to use: if an existing renderAs type covers it, use config options rather than a new registration.

import { widgemoRegistry } from '@widgemo/widgemo-core';

widgemoRegistry.registerWidgemoRenderAs({
name: 'riskBadge',
render: ({ value }) => (
<span className={`badge badge--${value}`}>{value}</span>
),
});

In config:

{ key: 'risk', label: 'Risk', renderAs: 'riskBadge' }

See Extension API for the full RenderAsRegistryEntry shape.

Custom Field Types​

Register a custom field type when the value behaves like a distinct category — not just a display variant — and you want that category to carry its own rendering rules and type identity across many configs.

When to use: color swatches, avatar clusters, structured KPI tiles, composite values with their own internal layout.

When not to use: if you only need a different visual for a standard value, use custom renderAs instead.

widgemoRegistry.registerWidgemoFieldType({
name: 'colorSwatch',
render: ({ value }) => (
<span style={{ background: value, width: 16, height: 16, display: 'inline-block', borderRadius: 2 }} />
),
});

In config:

{ key: 'brandColor', label: 'Color', type: 'colorSwatch' }

See Extension API for the full FieldTypeRegistryEntry shape.

Custom Modes​

Register a custom mode when the entire content area needs a layout strategy that none of the built-in modes (table, grid, board, carousel) can provide.

When to use: timeline, radial planner, dependency map, org chart — anything where the data structure and spatial layout are fundamentally different from list-based rendering.

When not to use: if you need a different arrangement of the same rows, use grid with layout options. Only register a mode when the layout concept itself is new.

widgemoRegistry.registerWidgemoMode({
name: 'timeline',
render: ({ data, config }) => (
<TimelineRenderer data={data} config={config} />
),
});

In config:

zones: { content: { mode: 'timeline', item: { ... } } }

See Extension API for the full ModeRegistryEntry shape.

Custom Icons​

Register custom icons when your config should reference icon names from your own design system or icon pack rather than the built-in set.

When to use: product-specific action icons, financial or operational domain icons, host design system icon packs.

widgemoRegistry.registerWidgemoIcon({
name: 'risk-flag',
render: () => <RiskFlagIcon />,
});

In config:

{ id: 'flag', label: 'Flag', icon: 'risk-flag', placement: 'pinned' }

See Extension API for the full IconRegistryEntry shape.

Lifecycle Hooks​

Register a lifecycle hook when you need runtime interception or wrapping behavior — analytics on mode changes, wrapping rendered output, logging — rather than a new display primitive.

Widgemo ships five built-in hook names: preRender, postRender, onItemClick, onModeChange, onDragDrop. Registering a custom name only creates a trigger point if your code explicitly calls executeWidgemoHook('<name>', ...).

When to use: analytics instrumentation, rendering wrappers (e.g. error boundaries around a mode), drag-drop side effects.

When not to use: action and interaction handling — use interactions.onEvent and zones.content.gestures for those instead.

widgemoRegistry.registerWidgemoHook({
name: 'onModeChange',
hook: (payload) => {
analytics.track('widgemo_mode_change', {
from: payload.previousMode,
to: payload.nextMode,
});
},
});

See Extension API for all five built-in hook signatures and payload shapes.

References​

  • Extension API — registry functions, entry shapes, and hook payload reference
  • Widgemo Config — interactions.onEvent and gestures for config-driven interaction handling