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

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. 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 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. 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, PostsWidget.tsx, and 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

Plugin registration is guarded by the dashboard.widgets.register permission, defined in 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 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), 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 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. This host package exposes the Widget component and shared CSS tokens (including --accent-1 through --accent-4 from 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 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:

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:

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:

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 stores metadata for all available dashboard tiles.
  • The Widget component in 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) 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, requires the dashboard.widgets.register permission and stores the metadata in the central registry at 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 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. 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 and prevents unauthorized extensions from injecting arbitrary UI into the dashboard grid.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →