MixBoard Design System
Design Intent & Feel
This app should feel like a well-organized DJ booth — everything within reach, nothing hidden, both decks visible at once. Pro density with craft and care.
Feel Attributes
- Professional — information-dense, respects the user's expertise
- Unhurried — generous padding, no cramped toolbars, content breathes
- Grounded — playlist is always visible, never yanked away by navigation
- Responsive — immediate feedback on interactions, micro-animations on state changes
- Tactile — hover states, spatial grouping, controls feel like physical objects
Anti-Goals
- NOT a streaming app (no oversized artwork cards dominating the screen)
- NOT a spreadsheet (no raw data grids without typographic hierarchy)
- NOT retro-first (skins change colors, not layout structure)
Motion Preferences
- Panel slide: 200ms ease-out
- State transitions (play/pause, selection): 150ms ease-in-out
- Toast messages: 300ms ease-in-out with opacity
- Reduced-motion: all animations replaced with instant cuts
Token Registry (Designer-managed)
Spacing
| Token |
Value |
Usage |
spacing.xs |
4pt |
Inline icon gaps |
spacing.sm |
8pt |
Between related elements |
spacing.md |
12pt |
Section padding, control groups |
spacing.lg |
16pt |
Panel margins, major separators |
spacing.xl |
24pt |
Section breaks, panel insets |
Typography
| Token |
Spec |
Usage |
type.trackTitle |
.system(size: 13, weight: .medium) |
Track title in list |
type.trackMeta |
.system(size: 11) + .secondary |
Artist, album in list |
type.dataPill |
.system(size: 10, design: .monospaced) |
BPM, key, duration |
type.sectionHeader |
.system(size: 11, weight: .semibold) + .tertiary |
UPPERCASE sidebar/list sections |
type.playerTitle |
.system(size: 13, weight: .semibold) |
Now-playing track in player bar |
type.playerArtist |
.system(size: 11) + .secondary |
Now-playing artist in player bar |
type.time |
.system(size: 11, design: .monospaced) |
Elapsed / remaining time |
Sizing
| Token |
Value |
Usage |
playerBar.height |
64pt |
Bottom player bar |
playerBar.artwork |
44pt |
Artwork thumbnail in player bar |
waveform.height |
48pt |
Waveform display |
browsePanel.minWidth |
280pt |
Cloud browser panel minimum |
browsePanel.idealWidth |
340pt |
Cloud browser panel default |
browsePanel.maxWidth |
420pt |
Cloud browser panel maximum |
trackRow.height |
28pt |
Playlist track row |
sidebar.minWidth |
180pt |
Sidebar minimum |
Corner Radii
| Token |
Value |
Usage |
radius.sm |
4pt |
Buttons, pills, metadata badges |
radius.md |
8pt |
Cards, panels, popovers |
radius.lg |
12pt |
Artwork, modal sheets |
Colors
Managed by AppTheme skin system. No hardcoded colors in views — always reference theme.* properties.
Navigation & Flow
Layout Architecture
Three-region layout with slide-out panel:
┌─────────┬──────────────────────────┬──────────────────┐
│ Sidebar │ Detail (always playlist) │ Browse Panel │
│ 180-300 │ (flex) │ 280-420 (toggle) │
│ │ │ │
│ │ │ │
│ ├──────────────────────────┴──────────────────┤
│ │ Player Bar (64pt, full width) │
└─────────┴─────────────────────────────────────────────┘
Panel Behavior
- Primary toggle:
cloud.fill button in playlist header toolbar, rightmost position, larger than other toolbar buttons (20pt icon, prominent style)
- No sidebar Cloud entry — removed entirely. Panel is toggled only from the toolbar button or ⌘B.
- Sidebar "Queue" click → opens browse panel with Queue tab selected (kept in sidebar)
- ⌘B → toggles browse panel
- Panel persists state — scroll position, navigation depth preserved when hidden
- Small windows (<1200px) → panel overlays as sheet instead of inline
- Panel slides from trailing edge, 200ms ease-out
Chad Music Icon
- Toolbar button:
cloud.fill SF Symbol at 20pt, accent-colored when panel is open, secondary when closed
- Boar asset (
chadmusic-boar): kept in Assets.xcassets for future use (e.g. panel header branding), not used in toolbar
- Toolbar button style: Larger than neighboring buttons (Add, Group, Settings, Export). Should visually stand out — use
.controlSize(.regular) or explicit frame while other buttons use .controlSize(.small).
Sidebar Decisions
- No "Chad Music" row — removed from sidebar. Cloud browsing is accessed via toolbar button only.
- Clicking a playlist always shows it in the detail area
- Active sidebar item gets rounded-rect highlight background
Player Bar Decisions
- No vertical dividers — spatial grouping only
- Three zones: Transport (left) | Track Info with artwork (center, flex) | Time + Volume (right)
- Waveform sits above player bar at 48pt height
- Small artwork thumbnail (44pt) in player bar when track is playing
Decision Log (append-only)
2026-03-17 — UI Revamp Direction
- Options: (A) Modern Streaming, (B) Pro Player Refined, (C) Hybrid density modes
- Choice: B — Pro Player, Refined
- Reason: MixBoard is a music tool (mix targets, cue points, BPM, DAW export). Stripping density would betray power users. Instead: better typography, spatial hierarchy, subtle animation, eliminate visual noise.
- Learned pattern: Density is the feature, not the problem. The craft applied to the density is what was missing.
2026-03-17 — Cloud Browser Navigation
- Options: (1) Tabs at top of detail, (2) Slide-out right panel, (3) Dual split panes
- Choice: 2 — Slide-out browse panel
- Reason: The browse workflow is asymmetric — user spends most time in playlists, occasionally dips into cloud. A panel respects this asymmetry. Playlist never disappears. Direct drag-and-drop between panel and playlist.
- Learned pattern: Asymmetric workflows need asymmetric layouts. Don't give equal space to unequal tasks.
Compromises
(None yet)
2026-03-18 — Panel Toggle Spatial Fix
- Problem: Sidebar button (left) opens panel (right) — spatially disconnected, unintuitive
- Options: (A) Toggle button in playlist header toolbar right-aligned, (B) Move panel to left side, (C) Animation cue from sidebar to panel
- Choice: A — Boar icon button in playlist toolbar, right-aligned
- Reason: Trigger and effect become spatially adjacent. Sidebar "Chad Music" stays as secondary entry point for discovery. Button uses кабанчик (boar) logo matching Chad Music branding.
- Learned pattern: Trigger and effect must be spatially connected. If UI action X opens panel Y, the button for X should be adjacent to Y.
2026-03-18 — Chad Music Icon
- Choice: Replace
cloud.fill with custom кабанчик (boar) image asset
- Reason: Brand identity — Chad Music has its own logo, generic cloud icon doesn't convey what it is
- Revised 2026-03-18: Reverted to
cloud.fill for toolbar button. Boar asset kept for future panel branding.
2026-03-18 — Remove Sidebar Cloud Entry
- Choice: Remove "Chad Music" from sidebar entirely. Panel toggle lives only in playlist toolbar (rightmost, larger button) + ⌘B.
- Reason: Sidebar button was spatially disconnected from the panel (left→right). Having the toggle in the toolbar right next to where the panel appears is more intuitive. Also fixes the SwiftUI List deselection bug (clicking non-tagged rows inside
List(selection:) clears selectedPlaylist).
- Learned pattern: If a UI element's only purpose is toggling a panel, put the toggle where the panel is — not in a navigation list.
2026-03-19 — Upload/Download Action Buttons (Apple Music-inspired)
- Options: (A) Make existing status icons clickable at all levels, (B) Add dedicated action buttons at group header + playlist toolbar, keep track rows status-only, (C) Full hover-action rows with revealed buttons on every track
- Choice: B — Prominent Header Buttons
- Reason: Apple Music's core insight is that batch actions at the container level (album, playlist) are the primary interaction. Per-track is secondary and suits macOS right-click conventions. Track rows at 28pt with 11pt icons are too tight for reliable hover targets. Direction B gives the biggest discoverability win (actions visible without right-click) with the least disruption.
- Scope:
PlaylistUploadButton — mirrors existing PlaylistDownloadButton, placed after it in toolbar
- Group header cloud action button —
arrow.up.to.cloud / arrow.down.to.line / stop.circle, placed after upload summary indicator
- Track row status icons remain read-only (no change)
- Icon vocabulary: filled = status/completed, outlined/cloud = actionable buttons
- Color vocabulary:
.secondary at rest, theme.accentColor on hover, .orange in progress, .green completed, .red error (all existing tokens)
- Learned pattern: Batch-level buttons > per-item buttons for discoverability in dense lists. Status and action are separate concerns — don't overload one icon with both.
Components
PlaylistUploadButton
- Purpose: Playlist-toolbar-level upload action, mirrors
PlaylistDownloadButton
- Placement: After
PlaylistDownloadButton, before Export button in PlaylistHeader toolbar
- Visibility: Only when playlist has local tracks AND Chad Music is configured
- Label:
↑ N/M (uploaded count / total local tracks)
- Icon:
arrow.up.circle
- States: idle → click uploads eligible tracks; uploading → click cancels
- Typography: Matches
PlaylistDownloadButton (inherits .controlSize(.small))
Group Header Cloud Action Button
- Purpose: One-click upload/download for group (album) batches
- Placement: After
uploadSummary in GroupHeaderView HStack, before Spacer()
- Icon mapping:
arrow.up.to.cloud (upload), arrow.down.to.line (download), stop.circle (cancel)
- Size: 11pt icon, 20×20pt hit target frame
- Hover:
.secondary → theme.accentColor, 150ms ease-in-out (instant on reduced-motion)
- Priority: Upload > Download when both directions have actionable tracks
- Hidden when: All tracks are synced (no actionable state)
Backlog
Drop cloud tracks between playlist rows (positional insert)
- Current: Cloud tracks/albums can only be dropped onto the playlist header bar — appends to end
- Wanted: Drop between specific tracks in the track list to insert at a position (like reordering, but from an external source)
- Interaction: Drag from Cloud panel → hover between rows in playlist → drop indicator line appears → release inserts at that position
- Complexity: Medium — requires per-row drop targets with insertion index calculation, not just a single playlist-level drop zone