# How Instatic's Dashboard Widget System Works: Registry-Driven Architecture Explained

> Discover Instatic's registry-driven dashboard widgets. Learn how this system enables consistent, extensible tiles with drag-and-drop editing and runtime plugin registration.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: internals
- Published: 2026-07-31

---

**Instatic's dashboard widget system uses a central metadata registry and a primitive `Widget` component to render consistent, extensible tiles that support drag-and-drop editing and runtime plugin registration.**

The CoreBunch/Instatic repository implements a component-centric dashboard architecture that keeps every tile visually consistent while allowing plugins to contribute new functionality. This system balances strict UI uniformity with flexible extensibility through a registry-driven pattern that governs how widgets are defined, rendered, and manipulated.

## The Widget Primitive and Visual Chrome

At the heart of the system lies the **`Widget` component** located in [`src/ui/components/Widget/Widget.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/ui/components/Widget/Widget.tsx). This primitive provides the visual "chrome"—headers, icons, tint colors, drag handles, and overflow menus—that surrounds every tile on the dashboard.

Both first-party widgets (such as *Storage*, *Posts*, and *Media*) and third-party plugin widgets compose this component to inherit consistent styling and interaction patterns. The component accepts several key props that dictate its appearance and behavior:

- **`widgetId`** – Unique identifier used by the drag-and-drop layer for tracking tile positions
- **`title`**, **`icon`**, **`tint`** – Visual metadata rendered in the header
- **`span`** – Grid column width (1–12) forwarded as a `data-span` attribute
- **`editing`** – Boolean flag that swaps the kebab menu for a drag handle when the dashboard is in customize mode
- **`loading`** – Triggers a skeleton placeholder while async data resolves

The **tint** system deserves special attention. In [`src/ui/components/Widget/Widget.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/ui/components/Widget/Widget.tsx) at lines 49–52, the tint token is converted into a CSS custom property `--tint`. This allows child chart components to access the color value via `var(--tint)` without prop-drilling, ensuring that data visualizations automatically match their parent widget's theme.

## Widget Registration and the Central Registry

Instatic manages widget availability through a **central registry** defined in [`src/core/dashboard/registry.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/dashboard/registry.ts). This store maintains metadata for every available widget, including identifiers, display names, icon references, default tint values, and grid span configurations.

The registry populates through two distinct pathways:

1. **Built-in widgets** – Hard-coded registrations in `src/admin/pages/dashboard/widgets/*.tsx` (such as [`StorageWidget.tsx`](https://github.com/CoreBunch/Instatic/blob/main/StorageWidget.tsx), [`PostsWidget.tsx`](https://github.com/CoreBunch/Instatic/blob/main/PostsWidget.tsx), and [`MediaWidget.tsx`](https://github.com/CoreBunch/Instatic/blob/main/MediaWidget.tsx))
2. **Plugin contributions** – Runtime registration via the plugin SDK method `api.dashboard.widgets.register(...)`, type-defined in [`src/core/plugin-sdk/types/dashboardWidgets.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugin-sdk/types/dashboardWidgets.ts)

Plugin registration is guarded by the **`dashboard.widgets.register`** permission, defined in [`src/core/plugin-sdk/types/permissions.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugin-sdk/types/permissions.ts), ensuring that only authorized extensions can inject new tiles into the dashboard grid.

## Rendering States and Data Flow

When the dashboard grid renders a tile, it performs a registry lookup to retrieve the widget's metadata, then instantiates the `Widget` component with the appropriate props.

### Loading States and Placeholders

During the brief window between plugin registration and component hydration, the system renders a **`WidgetSkeleton`** (defined in [`src/ui/components/Widget/Widget.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/ui/components/Widget/Widget.tsx) at lines 85–100). This placeholder paints an outline with a title shimmer and body skeleton, maintaining layout stability and signaling to users that content is loading. While the widget fetches data asynchronously through the `apiRequest` client ([`src/core/http/apiRequest.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/http/apiRequest.ts)), the `loading` prop triggers `SkeletonBlock` elements and marks the section `aria-busy` for accessibility.

### Drag-and-Drop Editing Mode

When users activate dashboard customization, the `editing` prop becomes `true`, causing the `Widget` header to display a **`DragAndDropSolidIcon`** instead of the standard menu. The surrounding drag-and-drop system in [`src/admin/pages/dashboard/DashboardGrid.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/dashboard/DashboardGrid.tsx) uses the `widgetId` attribute to track tile movements, persisting new layouts directly to the site document.

## Plugin Integration via Host UI

Plugins integrate with the dashboard system through the **`@instatic/host-ui`** package, re-exported from [`src/admin/plugin-host-ui/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/plugin-host-ui/index.ts). This host package exposes the `Widget` component and shared CSS tokens (including `--accent-1` through `--accent-4` from [`src/styles/globals.css`](https://github.com/CoreBunch/Instatic/blob/main/src/styles/globals.css)), ensuring third-party widgets match the exact styling and accessibility markup of native tiles.

For widgets displaying tabular data, the **`WidgetList`** component in [`src/ui/components/WidgetList/WidgetList.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/ui/components/WidgetList/WidgetList.tsx) provides standardized row styling and spacing, extending the chrome system into list-based visualizations.

## Code Examples

### Registering a Plugin Widget

Plugins register custom widgets through the SDK with full TypeScript support:

```typescript
import { api } from '@core/plugin-sdk'

api.dashboard.widgets.register({
  widgetId: 'my-custom-widget',
  title: 'My Custom Widget',
  icon: require('pixel-art-icons/icons/star-solid'),
  tint: 'sky',
  defaultSize: { span: 4 },
  render: (props) => <MyCustomBody {...props} />,
})

```

### First-Party Widget Implementation

Built-in widgets follow the same compositional pattern as plugins:

```tsx
import { Widget } from '@instatic/host-ui'
import { StackedBar } from '@ui/components/charts/StackedBar'

export function StorageWidget() {
  const { data, loading } = useAsyncResource(fetchStorageStats)

  return (
    <Widget
      widgetId="storage"
      title="Storage"
      icon={HardDriveSolidIcon}
      tint="mint"
      span={4}
      editing={isEditing}
      loading={loading}
    >
      <StackedBar data={data} />
    </Widget>
  )
}

```

### Placeholder for Loading Widgets

Use the skeleton component to maintain layout stability during async widget loading:

```tsx
import { WidgetSkeleton } from '@instatic/host-ui'

function EmptySlot({ widgetId, span }: { widgetId: string; span: number }) {
  return <WidgetSkeleton widgetId={widgetId} span={span} />
}

```

## Summary

- **CoreBunch/Instatic** implements a registry-driven widget system where [`src/core/dashboard/registry.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/dashboard/registry.ts) stores metadata for all available dashboard tiles.
- The **`Widget` component** in [`src/ui/components/Widget/Widget.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/ui/components/Widget/Widget.tsx) provides consistent chrome, CSS tint variables, and accessibility features to both built-in and plugin widgets.
- Plugins register widgets via **`api.dashboard.widgets.register()`** (defined in [`src/core/plugin-sdk/types/dashboardWidgets.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugin-sdk/types/dashboardWidgets.ts)) after obtaining the `dashboard.widgets.register` permission.
- The **`editing`** prop activates drag-and-drop handles, while **`WidgetSkeleton`** prevents layout shift during loading states.
- Third-party widgets consume the **`@instatic/host-ui`** package to inherit exact styling tokens and behavior from the core system.

## Frequently Asked Questions

### How do plugins register new widgets in Instatic?

Plugins call `api.dashboard.widgets.register()` with a configuration object containing `widgetId`, `title`, `icon`, `tint`, and a render function. This method, defined in [`src/core/plugin-sdk/types/dashboardWidgets.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugin-sdk/types/dashboardWidgets.ts), requires the `dashboard.widgets.register` permission and stores the metadata in the central registry at [`src/core/dashboard/registry.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/dashboard/registry.ts).

### What is the purpose of the WidgetSkeleton component?

**`WidgetSkeleton`** serves as a placeholder that renders during the gap between plugin registration and component code loading, or while a widget fetches initial data. Implemented in [`src/ui/components/Widget/Widget.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/ui/components/Widget/Widget.tsx) at lines 85–100, it maintains grid layout stability and provides visual feedback through a shimmer effect and `aria-busy` markup.

### How does Instatic ensure consistent styling across different widget sources?

All widgets—whether built-in or plugin-provided—compose the same **`Widget`** primitive from `@instatic/host-ui`, which injects standardized CSS custom properties including `--tint` and accent color tokens from [`src/styles/globals.css`](https://github.com/CoreBunch/Instatic/blob/main/src/styles/globals.css). This ensures that charts, lists, and headers automatically inherit the host application's theme without manual configuration.

### What permissions are required to register dashboard widgets?

The system checks for the **`dashboard.widgets.register`** permission before allowing a plugin to execute `api.dashboard.widgets.register()`. This permission is defined in [`src/core/plugin-sdk/types/permissions.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugin-sdk/types/permissions.ts) and prevents unauthorized extensions from injecting arbitrary UI into the dashboard grid.