| name | flowstate-ui-ux |
| emoji | 🎯 |
| description | FlowState-specific UI/UX design and implementation skill covering visual design principles, color theory, typography, spacing systems, layout composition, accessibility (WCAG 2.2), animation, web standards compliance, and systematic implementation workflows. Use for FlowState app work only — for other projects use frontend-ux-ui-design. |
UI/UX Design & Implementation
Version: 2.0.0
Category: Design + Implement
Related Skills: dev-storybook, dev-vue, dev-css-design-system
Overview
Comprehensive skill for both designing and implementing UI/UX. Covers visual design principles (color, typography, spacing, layout, hierarchy), accessibility compliance (WCAG 2.2), animation patterns, and systematic implementation workflows.
When to Activate This Skill
Invoke this skill when:
- Designing color palettes or visual systems
- Creating typography hierarchies
- Building spacing and layout systems
- Implementing UI/UX fixes from audits
- Ensuring WCAG 2.2 accessibility compliance
- Adding animations and microinteractions
- Avoiding AI-generic design patterns
- Matching reference designs
Component Best Practices
For component-level interaction patterns, anti-patterns, and FlowState component mapping, read references/component-patterns.md. It covers:
- Quick rules for 12 common components (buttons, modals, toasts, forms, etc.)
- Focus management, loading states, validation, destructive actions
- Anti-pattern checklist (rainbow badges, modal-in-modal, disabled submit, etc.)
- Generic-to-FlowState component mapping table
PART 1: COLOR THEORY & PALETTE GENERATION
1.1 HSL Color Model
HSL = Hue, Saturation, Lightness
Hue (0-360°):
- 0° = Red
- 60° = Yellow
- 120° = Green
- 180° = Cyan
- 240° = Blue
- 300° = Magenta
Saturation (0-100%):
- 0% = Grayscale
- 100% = Pure color
Lightness (0-100%):
- 0% = Black
- 50% = Pure color
- 100% = White
1.2 Color Wheel Relationships
| Relationship | Formula | Degrees | Use Case |
|---|
| Analogous | H ± 30° | -30°, 0°, +30° | Harmonious, safe |
| Complementary | H + 180° | Opposite | High contrast |
| Triadic | H + 120°, H + 240° | Equally spaced | Vibrant, balanced |
| Split-Complementary | H + 150°, H + 210° | 150° apart | Less harsh |
| Tetradic | H + 90°, H + 180°, H + 270° | 90° apart | Complex but harmonious |
Example: Building Palette from Blue (210°)
Primary Blue: 210° (hsl(210, 70%, 50%))
Analogous (210° ± 30°):
- 180° Cyan
- 210° Blue ← Primary
- 240° Indigo
Complementary:
- 30° Orange
Triadic:
- 210° Blue
- 330° Magenta
- 90° Yellow-Green
1.3 Tints, Shades, and Tones
:root {
--hue-primary: 210;
--sat-primary: 70%;
--color-primary-50: hsl(var(--hue-primary), var(--sat-primary), 95%);
--color-primary-100: hsl(var(--hue-primary), var(--sat-primary), 90%);
--color-primary-200: hsl(var(--hue-primary), var(--sat-primary), 80%);
--color-primary-300: hsl(var(--hue-primary), var(--sat-primary), 70%);
--color-primary-400: hsl(var(--hue-primary), var(--sat-primary), 60%);
--color-primary-500: hsl(var(--hue-primary), var(--sat-primary), 50%);
--color-primary-600: hsl(var(--hue-primary), var(--sat-primary), 40%);
--color-primary-700: hsl(var(--hue-primary), var(--sat-primary), 30%);
--color-primary-800: hsl(var(--hue-primary), var(--sat-primary), 20%);
--color-primary-900: hsl(var(--hue-primary), var(--sat-primary), 10%);
}
1.4 Color Psychology
| Color | Hue | Psychology | UI Uses |
|---|
| Red | 0° | Energy, urgency, danger | Error states, delete actions, alerts |
| Orange | 30° | Enthusiasm, warmth | Friendly CTAs, warnings |
| Yellow | 60° | Happiness, caution | Warning messages, highlights |
| Green | 120° | Growth, safety, success | Success states, "go" actions |
| Blue | 240° | Trust, stability, calm | Primary brand, links, info |
| Purple | 270° | Creativity, luxury | Premium features, AI/magic |
| Gray | Neutral | Balance, sophistication | Secondary actions, disabled |
1.5 The 60-30-10 Rule
60% - Primary (dominant, background/large areas)
30% - Secondary (supporting, surface colors)
10% - Accent (emphasis, CTAs, highlights)
:root {
--color-60: #F5F5F5;
--color-30: #FFFFFF;
--color-10: #0EA5E9;
}
body { background: var(--color-60); }
.card { background: var(--color-30); }
button { background: var(--color-10); }
1.6 WCAG Contrast Requirements
| Content Type | Minimum Ratio | Example |
|---|
| Normal text | 4.5:1 | Black on white = 21:1 ✓ |
| Large text (18px+) | 3:1 | Navy on light blue = 8.6:1 ✓ |
| UI components | 3:1 | Button border on background |
| Focus indicator | 3:1 | Focus outline on element |
Quick Reference: Common Contrast Ratios
White #FFFFFF on:
- Black #000000 = 21:1 ✓ Excellent
- Navy #003366 = 11.3:1 ✓ Excellent
- Blue #0EA5E9 = 5.74:1 ✓ Good (AA)
- Gray #6B7280 = 7.5:1 ✓ Good (AAA)
- Light gray #D1D5DB = 2.1:1 ✗ Fail
1.7 Dark Mode Strategy
Key Principle: Adjust LIGHTNESS and SATURATION, not HUE.
:root {
--color-primary: hsl(210, 70%, 50%);
--color-text: hsl(0, 0%, 15%);
--color-bg: hsl(0, 0%, 97%);
}
@media (prefers-color-scheme: dark) {
:root {
--color-primary: hsl(210, 60%, 65%);
--color-text: hsla(0, 0%, 100%, 0.87);
--color-text-secondary: hsla(0, 0%, 100%, 0.60);
--color-bg: hsl(0, 0%, 12%);
--color-surface: hsl(0, 0%, 18%);
}
}
Dark Mode Elevation (Lighter = More Elevated)
@media (prefers-color-scheme: dark) {
:root {
--color-surface-base: hsl(210, 10%, 12%);
--color-surface-1: hsl(210, 10%, 16%);
--color-surface-2: hsl(210, 10%, 20%);
--color-surface-3: hsl(210, 10%, 24%);
--color-surface-4: hsl(210, 10%, 28%);
}
body { background: var(--color-surface-base); }
.card { background: var(--color-surface-2); }
.modal { background: var(--color-surface-3); }
}
PART 2: TYPOGRAPHY SYSTEM
2.1 Type Scale Ratios
| Ratio | Name | Best For |
|---|
| 1.067 | Minor Second | Small UI, dense |
| 1.125 | Major Second | Apps, compact |
| 1.200 | Minor Third | Balanced web |
| 1.250 | Major Third | Web apps (recommended) |
| 1.333 | Perfect Fourth | Editorial |
| 1.414 | Augmented Fourth | Magazine |
| 1.500 | Perfect Fifth | Large displays |
| 1.618 | Golden Ratio | Art, luxury |
Complete Type Scale (1.25 ratio, 16px base)
-2 (11px) - caption, small labels
-1 (13px) - fine print, helper text
0 (16px) - body text, default
1 (20px) - subheading, emphasis
2 (25px) - section heading
3 (31px) - large section heading
4 (39px) - page heading
5 (49px) - hero text
2.2 Fluid Typography with clamp()
:root {
--text-xs: clamp(11px, 1.5vw, 13px);
--text-sm: clamp(12px, 1.8vw, 14px);
--text-base: clamp(14px, 2vw, 16px);
--text-md: clamp(16px, 2.2vw, 18px);
--text-lg: clamp(18px, 2.5vw, 20px);
--text-xl: clamp(20px, 3vw, 24px);
--text-2xl: clamp(24px, 4vw, 30px);
--text-3xl: clamp(28px, 5vw, 36px);
--text-4xl: clamp(32px, 6vw, 42px);
--text-5xl: clamp(36px, 8vw, 52px);
}
body { font-size: var(--text-base); }
h3 { font-size: var(--text-2xl); }
h2 { font-size: var(--text-3xl); }
h1 { font-size: var(--text-5xl); }
2.3 Line Height Guidelines
| Context | Line Height | Usage |
|---|
| Body text | 1.5 - 1.7 | Comfortable reading |
| Headings | 1.1 - 1.3 | Compact, readable |
| Dense UI | 1.4 - 1.5 | Forms, lists |
| Code/mono | 1.6 - 1.8 | Extra clarity |
p { line-height: 1.6; }
h1, h2, h3 { line-height: 1.2; }
label { line-height: 1.5; }
code { line-height: 1.7; }
2.4 Line Length (Measure)
Optimal: 45-75 characters (65ch ideal)
.prose {
max-width: 65ch;
margin: 0 auto;
}
2.5 Font Weight Usage
| Weight | Name | Usage |
|---|
| 400 | Regular | Body text (default) |
| 500 | Medium | Subtle emphasis, labels |
| 600 | Semi-bold | UI elements, captions |
| 700 | Bold | Strong emphasis, headings |
Rule: Use only 2-3 weights for clarity (400, 600, 700)
body { font-weight: 400; }
label { font-weight: 600; }
h1, h2, strong { font-weight: 700; }
2.6 Font Pairing
| Heading | Body | Best For |
|---|
| Poppins | Inter | Tech, SaaS |
| Playfair Display | Lato | Luxury, editorial |
| Montserrat | Open Sans | Modern, friendly |
:root {
--font-display: 'Poppins', sans-serif;
--font-body: 'Inter', -apple-system, sans-serif;
--font-mono: 'Fira Code', monospace;
}
h1, h2, h3 { font-family: var(--font-display); }
body { font-family: var(--font-body); }
code { font-family: var(--font-mono); }
PART 3: SPACING & RHYTHM SYSTEMS
3.1 8-Point Grid System
| Token | Value | Usage |
|---|
| 0.5 | 2px | Hairlines, minimal |
| 1 | 4px | Micro spacing |
| 2 | 8px | Small gaps (xs) |
| 3 | 12px | Compact |
| 4 | 16px | Standard (sm) |
| 5 | 20px | Medium |
| 6 | 24px | Comfortable (md) |
| 8 | 32px | Large (lg) |
| 10 | 40px | Extra large (xl) |
| 12 | 48px | Huge (2xl) |
| 16 | 64px | Page spacing (3xl) |
| 20 | 80px | Section gaps |
| 24 | 96px | Hero spacing |
:root {
--space-0: 0;
--space-0-5: 2px;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-8: 32px;
--space-10: 40px;
--space-12: 48px;
--space-16: 64px;
--space-20: 80px;
--space-24: 96px;
}
.btn { padding: var(--space-2) var(--space-4); }
.card { padding: var(--space-6); }
section { padding: var(--space-16) 0; }
3.2 Fibonacci Spacing (Alternative)
8 × 1 = 8px
8 × 2 = 16px
8 × 3 = 24px
8 × 5 = 40px
8 × 8 = 64px
8 × 13 = 104px
8 × 21 = 168px
Use Fibonacci for: Editorial, landing pages, organic feel
Use 8-Point for: Apps, forms, UI components
3.3 Golden Ratio Spacing (1.618)
:root {
--golden-1: 8px;
--golden-2: 13px;
--golden-3: 21px;
--golden-4: 34px;
--golden-5: 55px;
--golden-6: 89px;
}
3.4 Proximity Principle
Rule: Related items < 16px apart, unrelated > 24px apart
.form-group {
margin-bottom: 24px;
}
.form-group label {
margin-bottom: 8px;
}
PART 4: LAYOUT COMPOSITION & GRIDS
4.1 Golden Ratio Layouts (1.618:1)
For container W pixels wide:
Main content = W ÷ 1.618
Sidebar = W - Main content
1200px container:
Main: 742px, Sidebar: 458px
.golden-layout {
display: grid;
grid-template-columns: 1fr 0.618fr;
gap: 24px;
max-width: 1200px;
}
@media (max-width: 768px) {
.golden-layout {
grid-template-columns: 1fr;
}
}
4.2 Rule of Thirds
Place focal points at grid intersections for natural visual flow.
.rule-of-thirds {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr;
}
.focal-point {
grid-column: 2 / 3;
grid-row: 1 / 2;
place-self: end;
}
4.3 12-Column Grid
.grid-12 {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 24px;
max-width: 1200px;
}
.col-4 { grid-column: span 4; }
.col-6 { grid-column: span 6; }
.col-8 { grid-column: span 8; }
.col-12 { grid-column: span 12; }
4.4 Container Widths
| Size | Width | Use Case |
|---|
| xs | 480px | Mobile |
| sm | 640px | Large mobile |
| md | 768px | Tablet |
| lg | 1024px | Desktop |
| xl | 1280px | Wide desktop |
| 2xl | 1536px | Maximum |
.container {
max-width: 1024px;
margin: 0 auto;
padding: 0 24px;
}
.prose-container {
max-width: 65ch;
}
4.5 Auto-Fit Responsive Grids
.grid-responsive {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
}
PART 5: VISUAL HIERARCHY & BALANCE
5.1 Creating Hierarchy
Size (1.2-1.5× each level)
.caption { font-size: 13px; }
.body { font-size: 16px; }
.subhead { font-size: 20px; }
.h3 { font-size: 25px; }
.h2 { font-size: 31px; }
.h1 { font-size: 39px; }
Color (Opacity)
.text-primary { color: rgba(0, 0, 0, 0.87); }
.text-secondary { color: rgba(0, 0, 0, 0.60); }
.text-tertiary { color: rgba(0, 0, 0, 0.38); }
.text-disabled { color: rgba(0, 0, 0, 0.26); }
Weight
.body { font-weight: 400; }
.label { font-weight: 500; }
.subhead { font-weight: 600; }
.h1 { font-weight: 700; }
5.2 Gestalt Principles
| Principle | Rule | CSS Example |
|---|
| Proximity | Related < 16px | gap: 8px between label and input |
| Similarity | Same style = related | All buttons have same padding |
| Continuity | Aligned elements are path | align-items: start |
| Closure | Mind completes shapes | Dashed borders, outlined buttons |
| Figure-Ground | Elevated stands out | Cards with shadow on page |
5.3 Focal Point Creation
.cta-primary {
background: var(--color-primary);
color: white;
padding: 16px 32px;
font-size: 18px;
font-weight: 600;
border-radius: 8px;
box-shadow: 0 8px 16px rgba(0,0,0,0.15);
margin: 48px 0;
}
.cta-primary:hover {
transform: translateY(-2px);
box-shadow: 0 12px 24px rgba(0,0,0,0.2);
}
PART 6: WCAG 2.2 ACCESSIBILITY (2024-2025)
6.1 All 9 New WCAG 2.2 Criteria
2.4.11 Focus Not Obscured (Minimum) - Level A
*:focus {
scroll-margin-top: 80px;
}
2.4.12 Focus Not Obscured (Enhanced) - Level AA
No part of focused component hidden.
2.4.13 Focus Appearance - Level AAA
2px thick, 3:1 contrast.
button:focus-visible {
outline: 2px solid #0066CC;
outline-offset: 2px;
}
2.5.7 Dragging Movements - Level AA
Provide single-pointer alternative to drag.
<input type="range" id="slider">
<button onclick="setValue(0)">Min</button>
<button onclick="setValue(100)">Max</button>
2.5.8 Target Size (Minimum) - Level AA
24×24px minimum, 44×44px recommended
button, a, input[type="checkbox"] {
min-width: 24px;
min-height: 24px;
}
.btn {
min-width: 44px;
min-height: 44px;
}
.icon-btn {
width: 20px;
height: 20px;
padding: 12px;
}
3.2.6 Consistent Help - Level A
Help in same location on all pages.
3.3.7 Redundant Entry - Level A
Don't ask for same info twice. Use autocomplete.
3.3.8 Accessible Authentication (Minimum) - Level AA
No CAPTCHA as ONLY option.
<button onclick="sendEmailLink()">Email me a sign-in link</button>
<button onclick="useWebAuthn()">Sign in with passkey</button>
<input type="password" autocomplete="current-password">
3.3.9 Accessible Authentication (Enhanced) - Level AAA
No cognitive test at all.
6.2 Complete Focus Implementation
*:focus { outline: none; }
*:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
@media (prefers-contrast: more) {
*:focus-visible {
outline-width: 3px;
outline-color: Highlight;
}
}
.skip-link {
position: absolute;
top: -100px;
}
.skip-link:focus {
top: 8px;
left: 8px;
padding: 12px 16px;
background: white;
}
PART 7: MICROINTERACTIONS & ANIMATION
7.1 Animation Timing
| Duration | Name | Use Case |
|---|
| 100-150ms | Micro | Instant feedback, hover |
| 150-200ms | Fast | Click, press |
| 200-300ms | Normal | Standard UI, modal |
| 300-400ms | Moderate | Panel slide |
| 400-500ms | Slow | Page transition |
:root {
--duration-micro: 100ms;
--duration-fast: 150ms;
--duration-normal: 250ms;
--duration-moderate: 350ms;
--duration-slow: 450ms;
}
7.2 Easing Curves
:root {
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-in: cubic-bezier(0.4, 0, 1, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--ease-standard: cubic-bezier(0.4, 0, 0.6, 1);
}
.modal-enter { animation: fadeIn 250ms var(--ease-out); }
.modal-exit { animation: fadeOut 200ms var(--ease-in); }
button:hover { transition: all 150ms var(--ease-standard); }
7.3 Reduced Motion Support
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
PART 8: AVOIDING AI-GENERIC DESIGN
8.1 Anti-Patterns to Avoid
Blue/Purple Gradients
.hero { background: linear-gradient(135deg, #667eea, #764ba2); }
.hero { background: var(--brand-primary); }
Excessive Rounding
* { border-radius: 12px; }
.card { border-radius: 8px; }
.button { border-radius: 6px; }
.badge { border-radius: 16px; }
.input { border-radius: 4px; }
Safe Sans-Serif Only
body { font-family: -apple-system, sans-serif; }
:root {
--font-display: 'Poppins', sans-serif;
--font-body: 'Inter', -apple-system, sans-serif;
}
8.2 Creating Distinctive Aesthetics
:root {
--color-ocean: #0891B2;
--color-sunset: #F59E0B;
--color-forest: #10B981;
--radius-sm: 4px;
--radius-brand: 6px;
--radius-lg: 8px;
}
.btn:hover {
transform: translateY(-2px) scale(1.02);
box-shadow: 0 8px 16px rgba(0,0,0,0.15);
}
PART 9: IMPLEMENTATION WORKFLOWS
9.1 Design Token Migration
grep -rn "color: #\|background: #" src/components --include="*.vue"
grep -rn "padding: [0-9]\|margin: [0-9]" src/components --include="*.vue"
<!-- BEFORE -->
<style scoped>
.button {
color: #4ECDC4;
background: #000;
padding: 12px 24px;
}
</style>
<!-- AFTER -->
<style scoped>
.button {
color: var(--brand-primary);
background: var(--surface-elevated);
padding: var(--space-3) var(--space-6);
}
</style>
9.2 Button Consolidation
<!-- BEFORE -->
<button class="menu-icon-button" @click="handleClick">
<TrashIcon />
</button>
<!-- AFTER -->
<BaseIconButton
variant="ghost"
size="sm"
aria-label="Delete item"
@click="handleClick"
>
<TrashIcon />
</BaseIconButton>
9.3 Accessibility Implementation
grep -rn "<button" src/components --include="*.vue" | grep -v "aria-label"
<!-- Icon-only buttons MUST have aria-label -->
<BaseIconButton aria-label="Delete task" @click="deleteTask">
<TrashIcon />
</BaseIconButton>
<!-- Decorative icons should be aria-hidden -->
<BaseButton @click="save">
<CheckIcon aria-hidden="true" />
Save Changes
</BaseButton>
9.4 Theme Consistency
grep -rn "background.*white\|background.*#fff" src/components --include="*.vue"
<!-- BEFORE: Breaks in dark theme -->
<style>
.card { background: white; color: black; }
</style>
<!-- AFTER: Respects theme -->
<style>
.card {
background: var(--surface-elevated);
color: var(--text-primary);
}
</style>
PART 10: QUALITY CHECKLISTS
Color Checklist
Typography Checklist
Spacing Checklist
Layout Checklist
Accessibility Checklist (WCAG 2.2 AA)
Animation Checklist
Distinctiveness Checklist
MANDATORY USER VERIFICATION REQUIREMENT
CRITICAL: Before claiming ANY issue is "fixed" or "complete":
- Technical Verification: Run tests, verify no console errors
- Ask User: Use
AskUserQuestion to request verification
- Wait for Confirmation: Do NOT claim success until user confirms
Remember: The user is the final authority on whether something is fixed.
PART 11: APP ICON GENERATION (Tauri/Desktop/PWA)
11.1 Icon Size Requirements
| Platform | Sizes Required |
|---|
| Tauri/Desktop | 32x32, 128x128, 256x256 (128x128@2x), 512x512 |
| Windows ICO | 16, 32, 48, 64, 128, 256 (multi-size) |
| macOS ICNS | 512x512 source |
| Windows Store | 44, 71, 89, 107, 142, 150, 284, 310 |
| PWA/Web | 180x180 (apple-touch-icon), favicon.ico |
11.2 ImageMagick Icon Pipeline
CRITICAL: Scaling Without Cropping
When source image has different aspect ratio than target (e.g., wide 1312x736 → square 512x512):
magick convert source.png -resize x420 -gravity center -extent 512x512 icon.png
magick convert source.png \
-trim +repage \
-resize 500x500 \
-gravity center \
-background none \
-extent 512x512 \
icon.png
Key insight: -resize 500x500 scales proportionally until the LARGER dimension hits 500px. For a wide image, width becomes 500px, height scales proportionally (e.g., ~280px). Then -extent 512x512 centers it with transparent padding.
Complete Icon Generation Script
#!/bin/bash
cd src-tauri/icons
magick convert icon.png -resize 32x32 32x32.png
magick convert icon.png -resize 128x128 128x128.png
magick convert icon.png -resize 256x256 128x128@2x.png
magick convert icon.png -define icon:auto-resize=256,128,64,48,32,16 icon.ico
png2icns icon.icns icon.png 2>/dev/null || \
magick convert icon.png -resize 512x512 icon.icns
for size in 44 71 89 107 142 150 284 310; do
magick convert icon.png -resize ${size}x${size} Square${size}x${size}Logo.png
done
magick convert icon.png -resize 50x50 StoreLogo.png
cd ../../public
magick convert ../src-tauri/icons/icon.png -resize 180x180 apple-touch-icon.png
magick convert ../src-tauri/icons/icon.png -define icon:auto-resize=48,32,16 favicon.ico
echo "All icons generated!"
11.3 Image Processing for Icons
Remove Background (Flood Fill)
magick convert input.png \
-fuzz 10% \
-transparent white \
-trim +repage \
output.png
Prepare Wide Image for Square Icon
magick convert wide-source.png \
-trim +repage \
-resize 500x500 \
-gravity center \
-background none \
-extent 512x512 \
icon.png
11.4 Verification
magick identify icon.png
magick identify icon.ico
11.5 Tauri Icon Files
src-tauri/icons/
├── icon.png # 512x512 master (REQUIRED)
├── icon.ico # Windows (multi-size)
├── icon.icns # macOS
├── 32x32.png # Small
├── 128x128.png # Medium
├── 128x128@2x.png # Retina (256x256)
├── Square*.png # Windows Store
└── StoreLogo.png # Windows Store
11.6 Reinstall & Refresh (Linux/KDE)
After regenerating icons, rebuild and reinstall:
npm run tauri build
sudo dpkg -i src-tauri/target/release/bundle/deb/AppName_*.deb
kbuildsycoca6 --noincremental
kquitapp6 plasmashell && sleep 1 && kstart plasmashell &
PART 12: KANBAN BOARD DESIGN
Industry-standard measurements based on research across Linear, Todoist, TickTick, Notion, Trello, GitHub Projects, and Asana.
12.1 Industry Consensus Measurements
These values appeared consistently across 5+ top apps:
| Property | Industry Standard | FlowState Value |
|---|
| Card padding | 8px (Trello, Linear, GitHub) | 8px |
| Card border-radius | 6-8px (Primer=6px, Trello=8px) | 8px |
| Card background | Subtle surface, semi-transparent | rgba(35, 32, 55, 0.4) |
| Title font size | 14px (universal) | 14px |
| Title font weight | 500 (medium, universal) | 500 |
| Title line-height | 1.25 | 1.25 (1.3+ causes bloat — 2px/line adds up) |
| Metadata font size | 12px (universal) | 12px / var(--text-meta) |
| Metadata font weight | 400 (regular) | 400 |
| Card-to-card gap | 6-8px | 6px |
| Column width | 260-320px (272=Trello, 300=common) | 300px |
| Column background | Transparent (Linear, GitHub, Notion) | transparent |
12.2 Card Anatomy
┌────────────────────────────────────────┐
│ ● Title text here (14px/500) [⊕][▶] │ ← priority dot + title + hover actions (HORIZONTAL)
│ Description preview max 2 lines... │ ← 12px, --text-tertiary, line-clamp: 2
│ 📅 Jan 15 ☑ 3/5 🍅 2 [Tag1] │ ← 12px badges + tags, same row
└────────────────────────────────────────┘
6px gap to next card
Visual Hierarchy (3 tiers)
- Primary (title): 14px, weight 500,
--text-primary — scanned first
- Secondary (description): 12px, weight 400,
--text-tertiary — read if interested
- Tertiary (badges/tags): 12px icons + text,
--text-muted — glanced for context
12.3 Column Rules
- Columns are TRANSPARENT — cards are the visual focus
- Column header: title (13px, 600 weight) + count pill + add button
- No column background, no glass, no container border
- 1px left-border separator between columns (
--glass-border-light)
- Column padding:
var(--space-2) (8px)
- Tasks container gap: 6px between cards
12.4 Card Rules
| Rule | Value | Rationale |
|---|
| Border-radius | 8px | Industry standard (Primer medium, Trello post-2022) |
| Padding | 8px | Matches Trello, Linear, GitHub. Anything >12px is "Asana-bulky" |
| Hover | border-color only | No translateY, no shadow. Linear/GitHub pattern |
| Action buttons | Hover-only, HORIZONTAL, position: absolute | MUST be absolutely positioned so they don't steal title width. In normal flow, 4 buttons = 80px width stolen = title wraps = card doubles in height |
| Action button size | 18px | Minimal footprint, fits 4 in a row |
| Description | Optional, 12px, 2-line clamp | Linear/Todoist don't show it at all. TickTick shows snippet |
| Separator lines | NEVER | Visual hierarchy via font size/weight/color only |
| Empty metadata | Hidden | No empty footers, no empty badge rows |
| Card background | Semi-transparent (0.4 opacity) | Cards should breathe, not be opaque blocks |
| min-height | unset | Cards should be as small as their content |
12.5 Anti-Patterns (Verified Against Industry)
| Anti-Pattern | Why It's Wrong | Evidence |
|---|
| Glass morphism column containers | Distracting from cards | Linear, GitHub, Notion all use transparent |
border-top separator in card | Visual noise | Zero top apps use internal separators |
translateY hover lift + shadow | Floaty, not grounded | Only Trello uses shadow on hover (others: bg shift) |
border-radius: 12px+ on cards | Too bubbly, wastes corner space | Primer=6px, Trello=8px, max industry=8px |
padding: 12px+ on cards | Too spacious, "Asana problem" | Asana at 16px is criticized by own users |
| Vertical action button stack | Takes ~100px height, destroys density | All apps use horizontal row or single icon |
| Always-visible action buttons | Wastes space on every card | Asana's pattern, universally criticized |
| Description always visible | Adds height to every card | Linear/Todoist/GitHub don't show it |
12.6 Progressive Disclosure Layers
| Layer | Trigger | Content | Reference App |
|---|
| Glance (card face) | Always visible | Title + priority + 2-3 badges | All apps |
| Hover (<500ms dwell) | Mouse hover | Action buttons appear, border highlights | Linear, GitHub |
| Peek (keyboard) | Space bar / quick preview | Full description, all metadata | Linear only |
| Full Detail (click) | Click card | Everything: description, subtasks, comments | All apps |
12.7 Reference App Card Comparison
| App | Padding | Radius | Gap | Width | Column BG | Description | Actions |
|---|
| Linear | 8px | 6-8px | 6-8px | 260-280px | Transparent | Never | Hover context menu |
| Todoist | 8-10px | 8px | 8px | 260-280px | Transparent | Never | Minimal |
| TickTick | 10-12px | 8px | 8px | 375px | Light grey | Snippet | Hover icons |
| Trello | 8px | 8px | 8px | 272px | #f1f2f4 | Never | Hover pencil |
| GitHub | 8-12px | 6px | 8px | 320px | Transparent | Never | Hover 3-dot |
| Notion | 10-12px | 6px | 8px | 260-300px | Transparent | Configurable | Open button |
| Asana | 16px | 8px | 12-16px | 300-340px | Light grey | Configurable | Always visible |
12.8 FlowState Implementation Files
| File | Controls | Override Source |
|---|
global-overrides.css | ALL visual card/column styles (with !important) | Last-loaded, wins everything |
TaskCard.css | Base card styles (overridden by above) | Component CSS |
TaskCardActions.vue (scoped) | Action button layout | Scoped styles |
TaskCardBadges.vue (scoped) | Badge layout | Scoped styles |
KanbanColumn.css | Column base styles (overridden by global) | Component CSS |
styles.css | --kanban-card-glass-bg token (overridden by global) | Design tokens |
CRITICAL: Always edit global-overrides.css for kanban visual changes. Component CSS files are completely overridden by global !important rules.
12.9 CSS Debugging Protocol (MANDATORY for card size changes)
NEVER trust that top-level CSS properties (padding, radius, background) are the only thing controlling card size.
Step 1: Measure EVERY child element height
Before making any card CSS change, run this in Playwright/DevTools:
document.querySelectorAll('.task-card')[0].children
Step 2: Known Hidden Space Wasters
| Element | Trap | Fix |
|---|
.card-details | Empty div with margin-top: 8px + padding-top: 8px + border-top = 17px wasted | display: none !important in global-overrides |
.compact-actions (action buttons) | In normal flow = steals 80px title width, forces text wrap to 2+ lines, doubling card height | position: absolute !important — float over card, don't take layout space |
.priority-dot | margin-inline-end: var(--space-3) = 12px right margin | Override to 4px |
.task-title | line-height: 1.5 = 22.5px per line instead of 17.5px. Also margin-bottom: var(--space-1) | line-height: 1.25 !important; margin: 0 !important |
.drag-area (vuedraggable) | display: block — CSS gap on parent .tasks-container doesn't reach cards | Must set .drag-area { display: flex; flex-direction: column; gap: 6px } |
Step 3: Verify ACTUAL gap between cards
const cards = document.querySelectorAll('.task-card');
cards[1].getBoundingClientRect().top - cards[0].getBoundingClientRect().bottom
Step 4: CSS Specificity Stack (load order)
1. design-tokens.css — CSS custom properties
2. styles.css — base styles + some !important token overrides
3. Component .css/.vue — TaskCard.css, KanbanColumn.css
4. Scoped <style> — TaskCardActions.vue [data-v-xxx] (HIGHEST for component internals)
5. global-overrides.css — !important on everything (wins for non-scoped selectors)
Scoped styles with !important beat global !important because of the [data-v-xxx] attribute selector. For scoped component internals (like .action-btn inside TaskCardActions.vue), you MUST either:
- Edit the scoped component directly, OR
- Use a more specific selector in global-overrides:
.task-card .compact-actions .action-btn
Anti-Pattern: Surface-Level CSS Tweaking
WRONG approach (wastes hours):
"Card too big" → adjust padding from 12px to 8px → "still big" → adjust radius → "still big"
CORRECT approach (measure first):
"Card too big" → measure ALL children heights → find .card-details=17px, actions=80px width steal
→ fix root causes → card goes from 73px to 38px
PART 13: WEB STANDARDS & CODE QUALITY
Rules adapted from Vercel Web Interface Guidelines for Vue 3 / FlowState.
13.1 Forms Best Practices
| Rule | Vue Implementation |
|---|
Add autocomplete to inputs | <BaseInput autocomplete="email" type="email" /> |
Use correct type and inputmode | type="email" for email, inputmode="numeric" for codes |
| NEVER block paste | No @paste.prevent anywhere |
| Disable spellcheck on codes/emails | :spellcheck="false" |
Labels clickable via wrapping or for | <label :for="inputId"> or wrap <label><input></label> |
| Submit button enabled until request | Show spinner during request, don't pre-disable |
| Errors inline next to fields | Focus first error on submit with el.focus() |
| Warn before nav with unsaved changes | onBeforeRouteLeave guard or beforeunload |
| No dead zones in checkbox/radio | Label + control share single hit target |
<!-- CORRECT form pattern -->
<BaseInput
v-model="email"
type="email"
autocomplete="email"
:spellcheck="false"
name="user-email"
label="Email"
/>
13.2 Performance Rules
| Rule | When | Implementation |
|---|
| Virtualize large lists | >50 items | Use content-visibility: auto or virtual scroll |
| No layout reads in render | Always | Never getBoundingClientRect() in computed/watch |
| Batch DOM reads/writes | When measuring | Read all, then write all — never interleave |
| Uncontrolled inputs preferred | High-frequency typing | Use defaultValue / @change over v-model for search |
| Preconnect CDN domains | Always | <link rel="preconnect" href="https://cdn.example.com"> |
| Preload critical fonts | Above fold | <link rel="preload" as="font" crossorigin> with font-display: swap |
| Images need width/height | Always | Prevents CLS (Cumulative Layout Shift) |
| Below-fold images lazy | Always | loading="lazy" |
| Above-fold images priority | Hero/banner | fetchpriority="high" |
13.3 Touch & Interaction
* { touch-action: manipulation; }
button, a { -webkit-tap-highlight-color: rgba(78, 205, 196, 0.1); }
.modal-overlay, .drawer, .bottom-sheet {
overscroll-behavior: contain;
}
.dragging {
user-select: none;
-webkit-user-select: none;
}
autoFocus sparingly — desktop only, single primary input; avoid on mobile
- Full-bleed layouts need
env(safe-area-inset-*) for notched devices
13.4 Content Handling
.truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.flex-child { min-width: 0; }
- ALWAYS handle empty states — show helpful message, not broken UI
- User-generated content: anticipate short, average, and very long inputs
- Use
break-words or overflow-wrap: break-word for URLs/long strings
Compact Chips & Pills in Constrained Panels (SOP-066)
When placing chips/pills in bottom sheets or modals with fixed max-width:
| Pitfall | Fix |
|---|
flex: 1 (= flex: 1 1 0%) starts element at 0 width — text truncates even with overflow: visible | Use flex: 0 0 auto for natural content width |
-webkit-line-clamp persists through overrides | Must explicitly set -webkit-line-clamp: unset AND -webkit-box-orient: unset |
overflow-x: auto + overflow-y: visible = both become auto (CSS spec) | Don't mix; use min-width: 0 on flex parent + overflow-x: auto only |
display: inline inside flex container is blockified (CSS spec) | Use display: block instead |
Selected chip outline/border clipped by parent padding: 0 | Add padding: var(--space-1) var(--space-2) to grid container |
| Compact focus states too heavy (3px outline + offset) | Override to outline: none; border-color: var(--brand-primary) in compact mode |
Reference: docs/sop/SOP-066-compact-chip-overflow.md
13.5 Navigation & URL State
- URL MUST reflect state — filters, tabs, pagination, sort order in query params
- Links use
<RouterLink> (supports Cmd+click, middle-click, right-click)
- Deep-link all stateful UI — if it uses
ref(), consider URL sync via useRouteQuery
- Destructive actions (delete, discard) need
ConfirmationModal — NEVER immediate
- Back button must work correctly with all view states
13.6 Typography Micro-Rules
| Rule | Example |
|---|
Use ellipsis character ... not three dots | Loading... not Loading... |
| Use curly quotes in UI copy | "Welcome back" not "Welcome back" |
| Non-breaking spaces in units | 10 MB, Cmd K |
font-variant-numeric: tabular-nums | Number columns, prices, timers, stats |
text-wrap: balance on headings | Prevents widows/orphans |
| Active voice in UI copy | "Save changes" not "Changes will be saved" |
| Specific button labels | "Save API Key" not "Continue" or "Submit" |
| Error messages include next step | "File too large. Max size is 10 MB." not just "Error" |
13.7 Dark Mode & Theming (FlowState-Specific)
html { color-scheme: dark; }
select {
background-color: var(--surface-component);
color: var(--text-primary);
}
<meta name="theme-color" content="var(--bg-primary)">
13.8 Code Anti-Patterns (ALWAYS FLAG)
When reviewing or writing code, flag these immediately:
| Anti-Pattern | Fix |
|---|
user-scalable=no or maximum-scale=1 | Remove — don't disable zoom |
@paste.prevent | Remove — never block paste |
transition: all | List properties explicitly |
outline: none without :focus-visible | Add focus-visible replacement |
<div @click> or <span @click> | Use <button> or <BaseButton> |
<img> without width/height | Add dimensions to prevent CLS |
.map() on 50+ items without virtualization | Add virtual scroll |
<input> without <label> or aria-label | Add accessible label |
Icon button without aria-label | <BaseIconButton aria-label="..."> |
Hardcoded date formats (MM/DD/YYYY) | Use Intl.DateTimeFormat |
Hardcoded number formats ($${n}) | Use Intl.NumberFormat |
v-html with user content | Sanitize first or use MarkdownRenderer |
PART 14: CODE AUDIT MODE
When asked to "review UI", "audit design", or "check accessibility", output findings in terse file:line format:
src/components/TaskCard.vue:42 — icon button missing aria-label
src/components/Modal.vue:18 — outline:none without focus-visible replacement
src/views/BoardView.vue:95 — <div @click> should be <button>
src/components/SearchBar.vue:12 — transition: all, list properties explicitly
Audit Checklist
- Accessibility: aria-labels, semantic HTML, focus states, heading hierarchy, target sizes
- Performance: CLS (image dimensions), virtualization, transition:all, layout reads
- Forms: autocomplete, input types, labels, paste blocking, spellcheck
- Touch: touch-action, overscroll-behavior, safe areas, tap highlight
- Content: overflow handling, empty states, long content, min-w-0 on flex children
- Design tokens: hardcoded colors, spacing, radii (should use CSS vars)
- Anti-patterns: div-as-button, disabled zoom, hardcoded formats, v-html
Skill Keywords: UI design, UX, color theory, typography, spacing, layout, grid, accessibility, WCAG 2.2, animation, design tokens, visual hierarchy, Gestalt, app icons, ImageMagick, Tauri, web standards, forms, performance, touch, code audit
Standards: WCAG 2.2 (June 2024), Vercel Web Interface Guidelines, Material Design 3, Apple HIG
Last Updated: March 2026