---
title: "Build Your Own Hub UI"
description: "A hub UI provider implements two contracts, the node-side ui slot and the browser-side context. @devframes/hub-ui is the reference."
canonical_url: "https://devfra.me/guide/build-your-own-hub-ui"
---
# Build Your Own Hub UI

> A hub UI provider implements two contracts, the node-side ui slot and the browser-side context. @devframes/hub-ui is the reference.

A hub UI provider implements two contracts, the node-side `ui` slot and the browser-side context. `@devframes/hub-ui` is the reference.

## The node seam: `DevframeHubUi`

`initHub({ ui })` takes pure data (see [the `ui`
slot](https://devfra.me/guide/hub-initiate#the-ui-slot)):

```ts
interface DevframeHubUi {
  viewer?: { distDir: string } // a standalone SPA served at the hub base
  embedded?: { entry: string } // a self-contained bootstrap at <base>embedded.js
  assets?: Record<string, () => string | Uint8Array> // extra UI-owned files
  setup?: (ctx) => void | Promise<void> // publish static config via ctx.staticConfig
}
```

Ship a function returning this object (`createUi()`) with prebuilt assets using
relative paths.

`setup(ctx)` runs once at hub init: config in `ctx.staticConfig` is serialized
into `ConnectionMeta.configs` and read by the RPC client at handshake.

## The browser-side contracts

A hub UI provider renders from the hub's shared state via
[`createDevframeClientRuntime()`](https://devfra.me/guide/client-context), which assembles the
`DevframeClientContext` (docks, commands, renderers, when-clauses, connection)
and loads dock client scripts. Honor:

### Dock entry types

Render the built-in variants of the open dock union
(`DevframeDockEntryRegistry`, `@devframes/hub/types`): `iframe` (the entry's
`url` in a kept-alive iframe, honoring `subTabs` soft nav), `action` (a
dock-rail button running its client script, shown as active while a
`toggle: true` action is on, which also closes `panel.session.open` without
clearing the selection, so show the panel only while it is `true`), `custom-render` (a container its
client script mounts into), `launcher` (a launch call-to-action reflecting
`launcher.status`), `group` (one dock-rail button collapsing its members), and
`~builtin` (your native views for reserved ids). What to render per type is in
the [Hub API reference](https://devfra.me/references/hub-api#dock-entry-types).

Honor `when` / `visibility`, `category` grouping (order from
`DEFAULT_CATEGORIES_ORDER`, `@devframes/hub/constants`), and the
`hub:docks:activate` broadcast. Show titles through
`resolveTitle(entry, locale)` so a registrant's `titleLocales` translations
reach the user.

An `iframe` entry serving a [remote assets package](https://devfra.me/guide/client-assets) can
report it unreachable: its fallback page posts a `RemoteAssetsErrorMessage`
(`DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE`, `@devframes/hub/constants`) to
`window.parent`. Match it against the iframe's `contentWindow` to offer install + retry.

### The renderer registry and its fallback

**Every other dock type routes through the dock-renderer registry**: build it
with `createDockRenderersContext()` (`@devframes/hub/client`), wiring local
registrations and the hub's [renderer manifest](https://devfra.me/guide/hub-initiate#renderer-modules):

```ts
import { createDockRenderersContext } from '@devframes/hub/client'

const renderers = createDockRenderersContext({
  context: () => context,
  manifest: () => manifestState.value(), // the devframe:dock-renderers slot
})

const result = await renderers.mount(entry, container)
```

Show a state per mount-result variant:

- `{ status: 'mounted', dispose }`: the renderer owns the container; call
`dispose` on unmount.
- `{ status: 'missing-renderer' }`: render a fallback (`renderers.has(type)`
answers up front).
- `{ status: 'load-error', error }`: import failed or the renderer threw;
render the error with retry.

### The theme contract for renderers

Renderer modules self-style (sometimes via a shadow root). Keep a live
`dark` class on the mount container and let CSS custom properties inherit; a
`--devframe-primary` ancestor rebrands rendered content.

## Reference points

- `packages/hub-ui`: the full reference hub UI provider (Vue, `@antfu/design`).
- [`examples/custom-hub-vite`](https://github.com/devframes/devframe/tree/main/examples/custom-hub-vite) and
[`examples/custom-hub-next`](https://github.com/devframes/devframe/tree/main/examples/custom-hub-next): hand-rolled
hub UI providers in vanilla DOM and React.


## Sitemap

See the full [sitemap](https://devfra.me/sitemap.md) for all pages.
