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.
| Archetype | Use when | Examples |
|---|---|---|
| Editor | Resizable panes, Monaco/raw editor, optional inspector | json-editor, xml-editor, yaml-editor, file-editor, sql-formatter |
| Converter | Transform input → output, optional left options sidebar | csv-to-json, regex-playground, base64, url-encoder |
| Workbench | Multi-column domain UI | api-client, password-generator, uuid-generator, jwt-decoder |
| ColumnGrid | Fixed equal columns, no resizable splits | date-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
| Token | Classes | Usage |
|---|---|---|
| Plugin root | flex h-full flex-col bg-background min-h-0 | All panels |
| Toolbar (compact) | px-4 py-2.5 border-b | Standard toolbars |
| Toolbar (standard) | px-4 py-3 border-b | Toolbars with selects |
| Pane header | h-10 px-4 border-b | Resizable pane titles |
| Sidebar left | w-[280px] bg-background border-r | Options/config |
| Sidebar right | w-[320px] bg-card border-l | Reference/cheat sheet |
| Stats strip | px-4 py-2 text-[11px] bg-card/30 border-b | ValidationPills |
| Status footer | px-4 py-2 text-[11px] bg-card/60 border-t | StatusBar |
| Section card | rounded-lg border border-border bg-background/60 | Inspector 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
| Role | Classes |
|---|---|
| Pane title | text-sm font-semibold |
| Column title | text-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 label | text-xs font-medium text-muted-foreground uppercase tracking-wide |
| Stats / footer | text-[11px] text-muted-foreground |
| Empty title | text-base font-semibold |
| Empty body | text-sm text-muted-foreground |
Button tiers
| Tier | Style | When |
|---|---|---|
| Primary | variant="default" size="sm" | One main CTA per view |
| Toolbar | variant="outline" size="sm" className="h-8 gap-1.5" | Open, Paste, Sample, Import, Export, Format |
| Field | variant="ghost" size="sm" className="h-7 gap-1 text-xs" | Actions on a field/section row |
| Icon utility | variant="ghost" size="sm" className="h-8 px-2" + aria-label | Editor 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:
- Input — Open, Paste, Sample
- Edit — Clear, New
- Transform — Format, mode toggles, dialect selects
- 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
| Component | Purpose | Placement |
|---|---|---|
| ValidationPills | Validity + aggregate stats + optional actions | Above editor content (border-b) |
| StatusBar | Cursor, encoding, size, busy | Footer of pane (border-t) |
Responsive
| Breakpoint | Behavior |
|---|---|
| ≤768px | Sidebars → tabs; toolbars wrap |
| ≤1024px | Inspector pane → slide-over drawer |
| Dividers | ToolbarDivider 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/:
| Component | Purpose |
|---|---|
PluginShell | Root layout wrapper |
PluginToolbar | Horizontal toolbar with start/center/end slots |
ToolbarGroup | Button group wrapper |
ToolbarDivider | Visual separator between groups |
ValidationPills | Validity + stat chips + actions slot |
StatusBar / StatusBarItem | Footer stats row |
PaneHeader | Per-pane title bar |
SectionCard | Bordered section with optional header |
ColumnHeader | Grid column title |
Sidebar | Left/right rail wrapper |
EmptyState | Empty 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>
);
}