Skip to content

Design spec · Shared core

This page is the project-wide single source of truth for UI. Platform differences live in macOS platform and Windows platform; per-control Do/Don’t live in the component appendix.

References (checkable during review): Apple Human Interface Guidelines (macOS), Microsoft Windows app design (Fluent / WinUI 3), JetBrains UI Guidelines; GNOME / KDE HIG for reference.

  • Applies to all frontend UI in src/ (shell, settings, sample plugins) and plugins/ (every tool plugin).
  • Enforcement (all required):
    1. this page + platform docs + appendix: the basis for design and review;
    2. the token layer (src/assets/index.css): every visual value must come from a token, no arbitrary values;
    3. pnpm lint: scripts/lint-design.mjs machine-checks legacy recipes (see §6);
    4. the PR review checklist (§7): self-check on every change.
  • “Must / never” here are hard constraints; “prefer” can be traded off per case, with a written reason in the PR.

The intersection of HIG Design principles, Fluent Design principles and JetBrains, with concrete criteria for this project.

PrincipleCriterion
Clarity firstOne main task per screen; hierarchy through order, spacing and contrast, not decoration; as few settings as possible, offered next to the task
Immediate feedbackInteraction responds on pointer-down; no spinner for <1s operations; long tasks are cancellable with progress; errors explained in place (no modal, no apology)
Forgive over confirmIf it can be undone, don’t block; only irreversible, high-consequence actions use an Alert
Keyboard reachableEverything is keyboard-operable; focus is visible and ordered; shortcuts are visible in the UI; Esc closes overlays, Enter triggers the default action
Restrained decorationColor for semantics and interaction only; material for layering only; motion only to explain cause; no box-in-box (express grouping with padding and alignment)

Base grid 4px, only these steps:

TokenValueTypical use
--space-2xs4icon and text, badge padding
--space-xs8between controls, inline elements, buttons
--space-sm12control and label, content blocks, compact card padding
--space-md16card / panel padding, surface and content
--space-lg24between modules, between groups
--space-xl32page-level sections

Window content margin: compact 16 / regular 20. Do not mix free values like space-y-3/4/6; use the scale semantics, checked by lint.

LevelValueTailwindUse
Control6pxrounded-mdbuttons, inputs, selects, segmented controls, switch tracks, tags
Container8pxrounded-lgpanels, cards, grouped lists, dialog body
Overlay12pxrounded-xlmenus, dropdown panels, popovers, toasts, large tooltips
Pill9999pxrounded-fullbadges, avatars, slider thumbs

Implementation: set --radius to 8px; shadcn’s derived rules give md=6 / lg=8 / xl=12 with no component class changes. rounded-xl is forbidden outside overlays (lint-enforced).

Keep the three user font sizes (root 13 / 14 / 15, --app-font-size); base step = regular (14px).

SemanticsToken / TailwindRegularSmallLargeReference
Body / list primary texttext-sm12.91213.8macOS Body 13 · Fluent Body 14
Secondary / label / captiontext-xs11.81112.7macOS Subheadline 11 · Fluent Caption 12
Page / panel titletext-base141315macOS Title 3 15 · Fluent Body Strong 14
Numeric / monospaceinherit + tabular-nums———tables, coordinates, money, time
  • Line height: body 1.5, titles 1.3, table rows 1.4. Arbitrary values are forbidden.
  • Weight: regular 400; secondary emphasis / selected 500; titles 600. 700+ is forbidden.
  • Body text below 12px is forbidden.
StepTailwindSmall(13)Regular(14)Large(15)Use
Compacth-722.7524.526.25toolbar / inline buttons, table row actions, small Select
Regularh-8262830form controls, panel action buttons (default)
Elevatedh-929.2531.533.75primary actions, page-level inputs (sparingly)

Heights are always expressed in rem (scaling with font size), never hard-coded px. Icon buttons match the same-step height (size-7/8/9); row height: compact 32 / regular 36.

Surface enumeration (no arbitrary transparency such as bg-muted/30, bg-muted/50, bg-muted/80):

LayerTokenUseNesting
basebg-backgroundwindow floor—
cardbg-cardpanel / card / grouped listyes (adjacent, never same layer)
raisedbg-popoveroverlays (menu / dialog / toast)above base / card
insetbg-mutedinput well, code block, read-only areainside card
sunken--surface-sunkenempty / placeholder floorinside card
consolebg-console / text-console-foregroundlog / terminal outputstandalone, the only dark area
  • At most two surface levels per screen: base → card (+ inner inset). No card-in-card; use a divider + title + spacing for subgroups.
  • Color semantics: primary (accent) is only for interactive controls and selected states; destructive / success / warning / info only for their semantics. One color must not mean two things.
  • No accent color on translucent layers (put color on opaque layers, not on the glass foreground).
  • Sole exception: the home Hero may use low-saturation primary decoration; nothing else may.
  • Every border / divider is a 1px hairline (border = --border); do not mix in border-border (lint-enforced).
  • No box-on-box borders: when a container already has a border, separate inner items with divide-y or border-t.
  • Tables: data rows use divide-y (no vertical lines); header border-b + sticky; zebra striping to emphasize rows.
  • Focus uses ring (§2.9), never a border-color change.
TokenValueReferenceUse
--dur-fast120msFluent 83mshover, press, color / opacity changes
--dur-base200msFluent 167msexpand / collapse, tab switch, popover in / out
--dur-slow300msFluent 250msdialogs, page transitions, large material in / out
  • Easing: in cubic-bezier(0.22, 1, 0.36, 1); out cubic-bezier(0.4, 0, 1, 1).
  • Animate only transform and opacity (plus backdrop-filter); never width / height / top / left.
  • prefers-reduced-motion: reduce → replace movement / scale with a 200ms cross-fade.
  • The reka popper positioning wrapper’s transition-duration: 0s exception must stay (a measurement drift fix).

Allowed regions only: sidebar, toolbar / title bar, overlays (menu / dialog / toast / tooltip). Forbidden: scroll content, tables, forms, card bodies.

  • macOS → NSVisualEffectView semantic materials (see macOS platform).
  • Windows → Mica / Mica Alt window base, Acrylic overlays (see Windows platform).
  • An opaque fallback is mandatory: prefers-reduced-transparency: reduce, user-disabled transparency, Windows battery saver / low-end hardware / inactive window → fall back to bg-card / bg-background.
  • At most 2 material regions per screen; never stack material on material; primary text on material uses text-foreground (not text-muted-foreground).
  • Focus ring (the only expression): focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/60, following the control radius; focus-visible is always visible; outline-none with no replacement is forbidden (lint R6).
  • Every custom interactive element must be focusable (tabindex="0"), have a role and a readable name, and respond to Enter / Space.
  • Composite keyboard semantics: segmented role="tablist" + ←/→; list / table rows ↑/↓ + Enter; menus / selects ↑/↓ cycle, Esc closes and restores focus.
  • Global shortcuts: ⌘, settings · ⌘K command palette · ⌘F find · ⌘W close window · ⌘Z / ⇧⌘Z undo / redo · ⌘1..9 switch sidebar group · Esc close overlay. Shortcuts must be visible.
  • Only @lucide/vue; three sizes: 14 (inline) / 16 (buttons, menus) / 20 (toolbar, empty state); stroke width 2.
  • Alongside text use gap-2, baseline-aligned.
  • Icon buttons must have aria-label + tooltip.
  • Icons do not carry state color and never stack to express one action.
SituationUseNot
2–5 mutually exclusive options, persistent selectionSegmenteda row of switches, a select
6+ mutually exclusive options / tight spaceselect (pop-up button)segmented
Fine-grained boolean settingcheckbox (title on the right)switch
Emphasis switch / a whole group’s switchswitch (mini size inside grouped forms)checkbox
Numeric nudgeinput + stepperslider
Instant search / filterSearchField (magnifier, clear, search-as-you-type)plain input + button
Irreversible dangerous actionAlert (safe default button, destructive marked red)plain confirm dialog, toast
Reversible actionexecute + status hint (+ undo entry)confirm dialog
Long task (>1s)progress bar / ring + cancelspinner overlay
Page-level load (>300ms before content)skeletonspinner overlay
Result / log outputread-only text block (bg-console only for logs)editable input

Button hierarchy: at most one primary button per view (default); then secondary; ghost for panel-header actions; destructive only for destructive actions, with an Alert confirmation.

Empty: icon (20) + one headline + one description + one action button; never a blank page. Loading: show nothing under 1s; 1–10s skeleton or progress bar; over 10s must be cancellable with progress. Error (three-way split):

SituationPresentation
Form / in-tool error with a locationin place: text-xs text-destructive under the control; multi-field errors add a top error bar
Current action failed with a clear retryerror bar inside the tool panel (border-destructive/30 bg-destructive/10) + retry button
Background task / system failure (no location)toast.error (the only place a toast may report errors)
Successno toast: express via state change; info toast only for a finished long task

Recoverable notices use info / warning bars, not toasts. Error copy states what happened and what to do next; no apology and no “something went wrong”.

  • Buttons use verbs (save, export, choose again); keep one name per action; no trailing period.
  • Buttons that open an overlay / dialog end with an ellipsis (More options…); immediate actions do not.
  • Terminology:
UseNot
tool / pluginapp, feature module
module panel / panelcard, block
settingsconfig, preferences, options page
export / importsave as, download
worksheet (Excel)sheet, tab
header rowcolumn names, header
  • Numbers and units: right-aligned + tabular-nums; a space between value and unit (5 MB); time as yyyy-MM-dd HH:mm.
  • Error copy does not apologize or use cute wording; placeholders say what can go in.

6. Anti-patterns (lint catches the machine-checkable ones)

Section titled “6. Anti-patterns (lint catches the machine-checkable ones)”
Anti-patternRequirement
Two card recipes coexistingunify on rounded-lg border bg-card (§2.2 / §2.6)
Arbitrary transparency (bg-muted/30, …)use the surface enumeration (§2.5)
rounded-xl outside overlaysonly overlays may use xl (§2.2)
Mixed error feedbackthe three-way split (§4)
Keyboard unreachablecustom interactive elements must be focusable + have a role (§2.9)
Hand-rolled segmented controluse Segmented (§3)
Icon button without a hinttooltip + aria-label (mandatory, §2.10)
Free spacinguse the spacing scale (§2.1)
Global motion suppressionmotion tokens (§2.7)
Card-in-carddivider + title + spacing inside one container (§2.5)
  1. Do all new / changed visual values come from tokens (no arbitrary values, no hard-coded px)?
  2. At most two surface levels per screen (no card-in-card)? Borders not stacked?
  3. Is every module wrapped in a Panel (or a platform equivalent), with none exposed?
  4. Does the boolean / exclusive control choice follow §3?
  5. Are the loading / empty / error states all present, with errors following the §4 three-way split?
  6. Keyboard: sensible Tab order, visible focus, correct Esc / Enter semantics, shortcuts visible?
  7. Do icon buttons have aria-label + tooltip? Are glyphs and sizes from the three steps?
  8. Does the copy follow §5 (verbs, terminology, punctuation, units)?
  9. Does it survive light / dark, all three font sizes, and widths 760 / 1080 / 1440?
  10. Do platform differences stay within the allowed points (material / window shell / menu / dialog semantics / focus visuals / radius)?