Plugin UI design language

Shared visual and structural rules for all DevTools plugin panels. Implement with components from @/components/plugin-ui/*.

Layout archetypes

Pick one archetype per plugin and follow its skeleton.

ArchetypeUse whenExamples
EditorResizable panes, Monaco/raw editor, optional inspectorjson-editor, xml-editor, yaml-editor, file-editor, sql-formatter
ConverterTransform input → output, optional left options sidebarcsv-to-json, regex-playground, base64, url-encoder
WorkbenchMulti-column domain UIapi-client, password-generator, uuid-generator, jwt-decoder
ColumnGridFixed equal columns, no resizable splitsdate-time, diff-checker

Editor skeleton

PluginShell
├── PluginToolbar or PaneHeader (per pane)
├── ResizablePanelGroup
│   ├── editor pane(s)
│   └── inspector (desktop) / drawer (tablet)
├── ValidationPills (above editor content)
└── StatusBar (footer of editor pane)

Converter skeleton

PluginShell
├── grid: Sidebar (left, options) | main column
│   ├── PluginToolbar (input | transform | export groups)
│   ├── ValidationPills
│   ├── editors (split or stacked)
│   └── StatusBar

Workbench / ColumnGrid skeleton

PluginShell
├── grid columns
│   └── ColumnHeader + domain content per column

Spatial tokens

TokenClassesUsage
Plugin rootflex h-full flex-col bg-background min-h-0All panels
Toolbar (compact)px-4 py-2.5 border-bStandard toolbars
Toolbar (standard)px-4 py-3 border-bToolbars with selects
Pane headerh-10 px-4 border-bResizable pane titles
Sidebar leftw-[280px] bg-background border-rOptions/config
Sidebar rightw-[320px] bg-card border-lReference/cheat sheet
Stats strippx-4 py-2 text-[11px] bg-card/30 border-bValidationPills
Status footerpx-4 py-2 text-[11px] bg-card/60 border-tStatusBar
Section cardrounded-lg border border-border bg-background/60Inspector blocks

Scroll inside panes, not on the plugin root (except single-column scroll tools like jwt-decoder).

Surfaces

  • Main workspace: bg-background
  • Left config sidebar: bg-background border-r
  • Right reference sidebar: bg-card border-l
  • Validation strip: bg-card/30
  • Status footer: bg-card/60
  • Nested cards: bg-background/60

Typography

RoleClasses
Pane titletext-sm font-semibold
Column titletext-sm font-semibold border-b px-4 py-2.5
Section label (in card)text-xs font-semibold uppercase tracking-wide text-muted-foreground
Field labeltext-xs font-medium text-muted-foreground uppercase tracking-wide
Stats / footertext-[11px] text-muted-foreground
Empty titletext-base font-semibold
Empty bodytext-sm text-muted-foreground

Button tiers

TierStyleWhen
Primaryvariant="default" size="sm"One main CTA per view
Toolbarvariant="outline" size="sm" className="h-8 gap-1.5"Open, Paste, Sample, Import, Export, Format
Fieldvariant="ghost" size="sm" className="h-7 gap-1 text-xs"Actions on a field/section row
Icon utilityvariant="ghost" size="sm" className="h-8 px-2" + aria-labelEditor clusters (Copy, Paste, Fullscreen)

Icons: size-3.5 (toolbar), size-3 (field), size-4 (empty-state CTAs only).

Toolbar action order

Use ToolbarGroup + ToolbarDivider between groups:

  1. Input — Open, Paste, Sample
  2. Edit — Clear, New
  3. Transform — Format, mode toggles, dialect selects
  4. Export — Copy, Download, handoff to another tool

Flash messages (useFlashStatus) appear left of the related button group, not between buttons. Persistent stats belong in ValidationPills or StatusBar.

ValidationPills vs StatusBar

ComponentPurposePlacement
ValidationPillsValidity + aggregate stats + optional actionsAbove editor content (border-b)
StatusBarCursor, encoding, size, busyFooter of pane (border-t)

Responsive

BreakpointBehavior
≤768pxSidebars → tabs; toolbars wrap
≤1024pxInspector pane → slide-over drawer
DividersToolbarDivider hidden below sm

Accent color

Use manifest color with COLOR_CLASSES from @/core/plugins/colors for empty-state icons, pane title icons, and success validation pills. Do not invent per-plugin accent colors.

Panel chrome

Do not repeat plugin name or description inside the panel — the tab and sidebar already identify the tool.

Shared components

Import from @/components/plugin-ui/:

ComponentPurpose
PluginShellRoot layout wrapper
PluginToolbarHorizontal toolbar with start/center/end slots
ToolbarGroupButton group wrapper
ToolbarDividerVisual separator between groups
ValidationPillsValidity + stat chips + actions slot
StatusBar / StatusBarItemFooter stats row
PaneHeaderPer-pane title bar
SectionCardBordered section with optional header
ColumnHeaderGrid column title
SidebarLeft/right rail wrapper
EmptyStateEmpty editor placeholder

Use useFlashStatus from @/lib/use-flash-status for transient Copy/Paste feedback.

Minimal panel example (Converter)

import type { PluginPanelProps } from "@/core/plugins/types";
import { PluginShell, PluginToolbar, ToolbarGroup, ToolbarDivider, ValidationPills, StatusBar, StatusBarItem, Sidebar } from "@/components/plugin-ui";
import { useFlashStatus } from "@/lib/use-flash-status";

export default function Panel(_props: PluginPanelProps) {
  const [status, flash] = useFlashStatus();

  return (
    <PluginShell>
      <div className="flex-1 min-h-0 grid grid-cols-1 lg:grid-cols-[280px_1fr] gap-px bg-border">
        <Sidebar side="left">
          {/* options */}
        </Sidebar>
        <div className="flex min-h-0 flex-col bg-background">
          <PluginToolbar
            start={<ToolbarGroup>{status && <span className="text-xs text-muted-foreground">{status}</span>}</ToolbarGroup>}
            end={<ToolbarGroup>{/* export buttons */}</ToolbarGroup>}
          />
          <ValidationPills valid={true} validLabel="Ready" />
          {/* editors */}
          <StatusBar>
            <StatusBarItem>0 lines</StatusBarItem>
          </StatusBar>
        </div>
      </div>
    </PluginShell>
  );
}