All five built-in plugins - and every example under examples/ - share one design system, @antfu/design, so they look and feel like one product across frameworks (git is React/Next, terminals is Svelte, code-server is Vue, inspect is Vue, a11y is Solid, the examples are Preact/Next/vanilla). It's a dev dependency consumed at build time: its UnoCSS preset and shipped styles drive every surface, and its Vue components are the canonical reference every framework matches. There is no shared internal design package - each app wires the preset itself and owns its own component ports.
Building or changing any UI here MUST follow the antfu and antfu-design skills (UnoCSS-first, class-based semantic tokens, dual light/dark, anti-slop) - load them first. The surfaces deliberately echo the upstream devtools they descend from; reference their UI/UX when in doubt: antfu/node-modules-inspector, antfu/vite-plugin-inspect, eslint/config-inspector, and vitejs/devtools → packages/rolldown.
Each consumer's uno.config.ts composes the same stack: presetAnthonyDesign({ primary }) (from @antfu/design/unocss, tuned to devframe's sage green) + a Wind base + presetIcons() (Phosphor) + transformerDirectives() + transformerVariantGroup(), plus the named z-* layers the nav/overlay surfaces reference (z-nav, z-dropdown, z-tooltip, z-toast, z-modal-*, z-drawer-*) - presetAnthonyDesign blocks plain z-<number> so every layer is named. The shared design/uno.config.ts exposes this as designConfig (the default, on presetWind4()) and a createDesignConfig({ base }) factory. Keep the block identical across apps so the surfaces stay consistent.
Ordinary surfaces (plugins served in iframes, examples in the page) use presetWind4(). A surface whose stylesheet is injected into a shadow root (@devframes/hub-ui's dock custom element, @devframes/json-render-ui's renderer module, @devframes/hub-ui-onboard's floating button) MUST build on presetWind3() instead - via createDesignConfig({ base: presetWind3() }), or presetWind3() directly. Wind4 keeps @antfu/design's theme in a document :root {} block and registers its --un-* custom properties with @property { inherits: false }, neither of which reaches a shadow tree - its color-mix(var(--colors-*)) semantic utilities (bg-base, color-base, …) resolve to nothing inside a shadow root. Wind3 bakes the same shortcuts to concrete rgb() + .dark variants, self-contained in the shadow tree.
Two shadow-root gotchas the ahead-of-time CSS builder MUST compensate for (both handled in the shared design/build-shadow-css.ts pipeline, consumed by packages/{hub-ui,hub-ui-onboard,json-render-ui}/scripts/build-css.ts; a surface that renders none of @antfu/design's Vue components passes scanDesignComponents: false to keep its stylesheet small; the Vite unocss/vite path for standalone SPAs and Storybook is not affected):
- Plain-vs-variant shortcut drop. When a semantic shortcut also appears variant-prefixed in the scanned sources (e.g.
@antfu/design's Tabs emitsdata-[state=active]:bg-base), a single-passgenerate(tokens)drops the plain.bg-base/.color-baserule - so emit the surface tokens (design/uno.config.ts's exportedshadowSurfaceSafelist) in a dedicatedgenerate()pass and append them. --un-*collision with a Wind4 host.@propertyregistrations are document-global, so a host page built on Wind4 registers--un-bg-opacity/--un-border-opacity/--un-text-opacityas@property { syntax: '<percentage>' }for the whole document, including our shadow tree - which invalidates the unitless values Wind3 writes (--un-border-opacity: 0.13) and collapses the dependentrgb(… / var(--un-*))color (a visibly wrong border/background). Rename every--un-in the shadow stylesheet to a private prefix withdesign/uno.config.ts's exportednamespaceShadowCssVars()so it's immune to whatever the host registered.
- Tokens are semantic shortcuts. UI MUST build from
@antfu/design's class vocabulary - surfacesbg-base/bg-secondary/bg-active, textcolor-base/color-muted/color-faint/color-active,border-base,op-fade/op-mute- never a hardcoded palette. Import@antfu/design/styles.css(or cherry-pick@antfu/design/styles/base.css+scrollbar.css) once per page; dark mode is the.darkclass on<html>, flipped from the OS preference in the SPA entry. - Vue uses the components directly; other frameworks port them. The Vue surface (inspect) imports components straight from
@antfu/design/components/*(ActionButton,ActionIconButton,DisplayBadge,LayoutTabs,LayoutToolbar,LayoutCard, …). Every non-Vue surface ports the components it needs into its own framework - React in git and the Next examples, Svelte in terminals, Solid in a11y, Preact in the Preact examples, vanilla DOM helpers in the Vite hub - mirroring the upstream component's markup, classes and behavior so it renders identically. Port on demand: recreate only what a surface uses, and keep each port faithful to its@antfu/designsource. - One nav, three buttons, one tab selector - strictly. Every surface opens with the same top bar - a
LayoutToolbar-style row led by a brand block (a primary-tintedi-ph:*icon + the product name). Buttons come in exactly three forms: a text button (ActionButton→btn-action/btn-primary), a bordered icon button (ActionIconButton→btn-icon-square), and a borderless icon button (roundbtn-icon). Multi-view tools (inspect, git) switch views with the one shared segmented selector (LayoutTabsvariant="segment": abg-secondarytrack withdata-[state=active]:bg-basetriggers). Bespoke nav bars, button shapes, or tab styles MUST NOT be invented. - Icons come from the shared Phosphor set (
i-ph:*, duotone preferred) viapresetIcons- everywhere, instead of per-consumer icon libraries or bespoke SVG. - A surface keeping its own component CSS (inspect, a11y) sources every color from
@antfu/design's semantic shortcuts via--at-apply(expanded bytransformerDirectives) rather than hardcoding a palette, so it tracks the shared theme and the.darkclass. - Plain
.ts/vanilla views MUST opt.tsinto UnoCSS extraction (content.pipeline.includefor Vite, orcontent.filesystemglobs for the@unocss/postcsssetup Next uses), since UnoCSS only scans framework files by default. - Before making new components or inline DOM elements, always check whether an
@antfu/designcomponent can be reused. - hub-ui text goes through
t(). Every user-facing string inpackages/hub-ui/src/client(templates, tooltips,aria-labels, command titles, confirm copy) MUST be a key inclient/i18n/locales/en.jsonrendered witht()/tAround()fromclient/i18n, never a literal. A new key lands in every locale file in the same change; the i18n test enforces the lockstep. Brand names andDFdiagnostics stay untranslated.
Each plugin's storybook follows one setup - co-located *.stories.*, a viteFinal that adds the framework plugin + unocss/vite (pointed at the plugin's uno.config), @antfu/design/styles.css, a theme toggle on the .dark class, and a bg-base color-base decorator. The Vue surface (inspect, @storybook/vue3-vite) showcases the @antfu/design components in real use - the visual reference the React/Svelte/Solid/vanilla ports match, mirroring @antfu/design's own storybook.