Domain-Specific Component Design (Desktop)
Most UI has a known pattern (a button is a button everywhere). Domain-specific surfaces don't — a waveform editor, a 3D viewport, or a node graph has to invent its own interaction language. This skill is a process for doing that well, since the specific components can't all be enumerated in advance.
The process
Identify the nearest established domain convention and start there, not from a blank canvas. Almost every domain-specific surface has a category of professional tool that already solved the core interaction problem:
- Audio editing/timeline → DAW conventions (Ableton, Logic, Reaper): waveform rendering, playhead behavior, zoom/scroll linkage, region selection and trim handles.
- 3D manipulation → CAD/3D-tool conventions (Blender, Godot's own editor): gizmo design (move/rotate/scale handles), viewport navigation (orbit/pan/zoom), selection outlining.
- Code/diff → IDE/diff-tool conventions (VS Code, GitHub): line-level add/remove coloring, gutter markers, syntax highlighting restraint.
- Node/graph editing → node-based tool conventions (Blender's shader editor, game-engine visual scripting): port shapes signaling type/direction, connection curves, zoom-to-fit.
Borrowing the convention isn't lack of originality — it's respecting that users bring muscle memory from other tools in the same domain, and fighting that expectation for no reason creates friction without benefit.
Reconcile the borrowed convention with this skill family's token system. The domain convention tells you the interaction shape (how a waveform selection handle behaves); the token system from
desktop-ui-foundationsanddesktop-ui-color-systemtells you the visual language (what color, what elevation, what corner radius) it should be rendered in so it still feels like the rest of the app, not a bolted-on third-party widget.Design the states explicitly — domain-specific components are especially easy to leave half-finished because the "happy path" (a waveform mid-playback, a 3D object mid-manipulation) is the only state that gets built during a demo. At minimum, cover:
- Empty (no audio loaded / no object selected / no diff to show)
- Loading (waveform still decoding, model still loading)
- Active/selected (a region selected, an object selected, a node selected) — with a clear, consistent selection treatment reused from
desktop-ui-color-system's active-state logic - Hover-preview vs. committed-selection, if the tool distinguishes them (hovering a region to preview it vs. actually selecting it)
- Error (corrupt file, unsupported format, a broken node connection)
Performance-check it against the hardware target. Domain-specific surfaces are disproportionately likely to be the most expensive thing on screen (real-time waveform rendering, a live 3D viewport, syntax highlighting a huge file) — apply the same
transform/opacity-only, avoid-real-time-blur discipline fromdesktop-ui-motion, and consider whether the surface needs to re-render on every frame or only when its underlying data actually changes.
Interaction language within the component
- Zoom and scroll should stay linked and predictable — in a timeline or viewport, scrolling shouldn't unexpectedly change zoom level or vice versa unless a modifier key is deliberately used for that (a common, learnable convention: scroll to pan, Ctrl/Cmd+scroll to zoom).
- Selection should always be visually unambiguous — a selected region, node, or object needs a treatment strong enough to spot at a glance (an accent-colored outline or fill, consistent with the rest of the app's active-state color) even against a busy, colorful background like a waveform or a 3D scene.
- Direct manipulation handles (trim handles, resize gizmos, connection anchors) need a hit target larger than their visible size, exactly like the button hit-target rule in
desktop-ui-buttons-controls— precision tools are exactly where an undersized hit target causes the most frustration. - Give real-time feedback during drag operations (a numeric readout while dragging a trim handle, a live preview while scrubbing a timeline) — domain-specific tools are where users most need to know exactly what they're about to commit to before releasing the mouse.
Accessibility
Custom-drawn components (canvas, WebGL, node graphs, waveform/timeline editors) render nothing to the platform's accessibility tree by default — this is the hardest category in the whole family to get right, which is exactly why it needs planning up front rather than being skipped:
- Design a parallel, simplified accessible representation alongside the visual one (e.g. a keyboard-navigable list of nodes/regions/track items that mirrors what's on the canvas) rather than treating the component as unsolvable for keyboard and screen reader users.
- Give the component a full keyboard interaction model — arrow keys to move a selection,
Enter/Spaceto activate, a documented way to reach every action the mouse can reach — before calling it done. Seedesktop-ui-accessibility-i18nfor the baseline patterns to adapt.
Review format
| Before | After | Why |
|---|---|---|
| Waveform selection handles are the same visible width as their hit target (4px) | Visible handle stays thin, but the hit/drag target is padded to at least 12–16px | Precision editing needs a forgiving hit target, not a pixel-perfect one |
| 3D viewport gizmo colors don't match the app's own accent/semantic colors | Gizmo axis colors follow domain convention (X/Y/Z standard colors) but selection highlighting uses the app's own accent color | Respects domain muscle memory for axes while still feeling like part of this app for anything app-specific |
| Node graph only has a "connected" and "not connected" visual state | Adds a distinct hover-preview state before a connection is committed, and an error state for a type-mismatched connection | Domain components are the easiest place to skip states beyond the happy path |