| name | linear-design-patterns |
| description | Linear-inspired design system patterns for building keyboard-first, high-density, dark-first web applications. Use when (1) building a new design system or design tokens inspired by Linear's aesthetic, (2) implementing keyboard-first navigation with command palettes and vim-style shortcuts, (3) designing admin tools, developer tools, or SaaS UIs that prioritize speed and information density, (4) choosing color systems, typography, animation, or feedback patterns for professional/engineering-focused apps, (5) user mentions Linear, Linear-style, or asks for a clean/minimal/keyboard-first design approach. |
Linear Design Patterns
Apply Linear's design philosophy to web applications. Covers color, typography, layout, keyboard interaction, animation, feedback, and visual polish.
Quick Decision Guide
| Decision | Linear's Answer |
|---|
| Light or dark default? | Dark-first |
| Color space? | LCH (perceptually uniform) |
| How many theme variables? | 3: base, accent, contrast |
| Font? | Inter + Inter Display for headings |
| Spacing base unit? | 4px |
| Animation duration? | ~200ms, ease-out |
| Confirm or undo? | Undo (except irreversible) |
| Loading spinners? | No — optimistic updates |
| Feedback location? | Inline, next to the action |
| Keyboard support? | Full app control, mouse optional |
| Command palette? | Yes, Cmd+K, fuzzy search, context-aware |
| Navigation shortcuts? | g then letter (vim-style) |
| Information density? | High density, low clutter |
| Chrome/decoration? | Minimal — content over chrome |
| Content surface shape? | Sharp edges — no border-radius on data panels |
| Panel separation? | 1px border lines, not gaps or shadows |
| When to elevate? | Only floating overlays (modals, dropdowns, popovers) |
Core Principles
- Keyboard-first, mouse-optional — every click action has a key equivalent
- Dark-first — dark mode is default, light mode is the variant
- Speed is a feature — 100ms interaction target, optimistic updates, no spinners
- Color restraint — near-monochrome, color only for status/accent
- High density, low clutter — pack information in through alignment, not cramming
- Be gentle — everything comfortable, natural, expected, no surprises
⚠️ Common Agent Mistakes — READ THIS FIRST
Agents consistently fail at this design system in the same ways. If you catch yourself doing any of these, stop and fix it immediately.
Mistake #1: Containers within containers
The most common failure. Agents instinctively wrap content in .card > .card-body > .card-content or .panel > .panel-inner. Never do this. The layout is a flat grid. Visual grouping comes from background colors, borders, and spacing — not from nesting.
This applies equally to conversational surfaces. Chat turns, activity feeds, and timeline rows should usually be rendered as flush rows inside a pane, using row padding, alternating tints, and subtle dividers. Do not put each turn inside its own floating card inside the conversation panel.
The same rule applies to toolbars and filter rails. Do not place a bordered filter bar inside a bordered header surface. The header is already the surface. Put the controls directly on it.
WRONG:
<div class="stats-row">
<div class="stat-card">
<div class="stat-body">
<h3>Total Requests</h3>
<p>537,784</p>
</div>
</div>
</div>
RIGHT:
<div class="metric-grid">
<div class="metric-cell">
<span class="metric-label">Total Requests</span>
<span class="metric-value">537,784</span>
</div>
</div>
RIGHT for conversation rows:
<div class="conversation-pane">
<div class="message-row user">...</div>
<div class="message-row agent">...</div>
</div>
Mistake #2: box-shadow on data surfaces
Shadows are ONLY for floating overlays (modals, dropdowns, popovers). Never on tables, cards, panels, grids, or any data surface. Use 1px border lines instead.
Mistake #3: border-radius on data surfaces
Data grids, table rows, list items, content sections — all get border-radius: 0. Rounded corners are ONLY for interactive elements (buttons, pills, badges, inputs) and floating overlays.
Mistake #4: max-width containers that don't fill the viewport
Content should span the full width of its area. No max-width: 800px; margin: 0 auto on page content. The grid fills the space.
Mistake #5: Using traditional table markup for non-tabular data
Comments, blog posts, activity feeds — these are conversational/content, not spreadsheet data. Render them as styled row lists, not <table> elements. Use tables only when the data is genuinely columnar.
Mistake #7: Tinted header regions styled as cards
Header regions with background tints must be flush (edge-to-edge, no border-radius, no outer margin). They are NOT cards — they're just colored zones within a panel. Use a border-bottom: 1px solid in the tint color for separation. Never add border-top, border-radius, or outer margin to a tinted header region.
.panel-header {
background: rgba(163, 113, 247, 0.05);
border-bottom: 1px solid rgba(163, 113, 247, 0.2);
border-radius: 0;
margin: 0;
padding: var(--space-3);
}
.panel-header {
background: rgba(163, 113, 247, 0.05);
border-top: 2px solid #a371f7;
border-radius: var(--radius-md);
margin: 0 0 var(--space-3) 0;
}
Mistake #6: Bland pages with no color differentiation
Color restraint doesn't mean no color. Use the status color system actively:
- Badges with colored dots for status (green=success, amber=warning, red=error)
- Subtle background tints for status rows (e.g.,
rgba(229, 166, 62, 0.05) for pending items)
- Accent color for active states, links, primary actions
- Typography hierarchy (size, weight, opacity) creates visual interest without decoration
Concrete Patterns
Metric Grid
.metric-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
border-bottom: 1px solid var(--color-border-strong);
}
.metric-cell {
padding: var(--space-5);
display: flex;
flex-direction: column;
gap: var(--space-1);
border-right: 1px solid var(--color-border-strong);
}
.metric-cell:last-child { border-right: none; }
Status Badge with Dot
.badge {
display: inline-flex;
align-items: center;
gap: var(--space-1);
padding: 1px var(--space-2);
border-radius: var(--radius-sm);
font-size: var(--text-xs);
font-weight: var(--font-medium);
}
.badge-dot {
width: 6px;
height: 6px;
border-radius: var(--radius-full);
}
.badge-success { background: var(--color-success-bg-subtle); color: var(--color-success); }
.badge-warning { background: var(--color-warning-bg-subtle); color: var(--color-warning); }
.badge-error { background: var(--color-error-bg-subtle); color: var(--color-error); }
Button System
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
border-radius: var(--radius-sm);
font-weight: var(--font-medium);
cursor: pointer;
transition: all var(--duration-fast);
white-space: nowrap;
}
.btn-primary { background: var(--color-accent-default); color: var(--color-on-accent); }
.btn-secondary { background: var(--color-bg-tertiary); color: var(--color-text-primary); border: 1px solid var(--color-border-default); }
.btn-ghost { color: var(--color-text-secondary); }
.btn-ghost:hover { background: var(--color-bg-tertiary); color: var(--color-text-primary); }
Filter Controls
Filter toggles are low-contrast, border-only controls designed for high-density filtering layouts. They differ from primary buttons by using no background fill and promoting the border as the primary visual indicator.
Container:
.filter-bar {
display: flex;
gap: var(--space-1);
align-items: center;
}
Individual toggle:
.filter-toggle {
display: inline-flex;
align-items: center;
gap: var(--space-1);
padding: var(--space-1) var(--space-2);
background: none;
border: 1px solid var(--color-border-default);
border-radius: var(--radius-sm);
color: var(--color-text-secondary);
font-size: var(--text-xs);
font-weight: var(--font-medium);
cursor: pointer;
transition: all var(--duration-fast);
}
.filter-toggle:hover {
border-color: var(--color-border-strong);
color: var(--color-text-primary);
}
.filter-toggle.active {
border-color: var(--color-accent-default);
color: var(--color-accent-default);
}
Key points:
- Default state: subtle border, no background, secondary text color
- Hover state: stronger border, promoted text color, still no background
- Active state: accent color on border and text only — no background fill
- Use square borders (
var(--radius-sm)) NOT rounded (var(--radius-full))
- Prefer icons (from @marcus/roc) over emoji for filter labels
- Reference implementation: Perch's
frontend/src/lib/styles/filters.css
Slide-in Detail Panel
.detail-panel {
position: fixed;
top: 0; right: 0; bottom: 0;
width: 480px;
background: var(--color-bg-secondary);
border-left: 1px solid var(--color-border-strong);
z-index: 51;
display: flex;
flex-direction: column;
transform: translateX(100%);
transition: transform 300ms cubic-bezier(0.16, 1, 0.3, 1);
box-shadow: var(--shadow-lg);
}
.detail-panel.open { transform: translateX(0); }
Content Row (for lists, feeds, comments)
.content-row {
display: flex;
align-items: flex-start;
gap: var(--space-3);
padding: var(--space-4) var(--space-3);
border-bottom: 1px solid var(--color-border-default);
cursor: pointer;
transition: background var(--duration-fast);
}
.content-row:hover { background: var(--color-bg-tertiary); }
Sidebar Navigation
.sidebar {
background: var(--color-bg-inset);
border-right: 1px solid var(--color-border-strong);
display: flex;
flex-direction: column;
}
.nav-item {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-2) var(--space-3);
border-radius: var(--radius-md);
color: var(--color-text-secondary);
font-size: var(--text-sm);
font-weight: var(--font-medium);
}
.nav-item.active {
background: var(--color-bg-tertiary);
color: var(--color-text-primary);
}
.nav-item.active::before {
content: '';
position: absolute;
left: 0; top: 50%;
transform: translateY(-50%);
width: 3px; height: 16px;
background: var(--color-accent-default);
border-radius: var(--radius-full);
}
Implementation
For full design system details covering color, typography, layout, navigation, interaction, animation, feedback, and visual polish patterns, see references/linear-design-system.md.
Key sections:
- A. Color & Theming — LCH color space, 3-variable themes, dark-first
- B. Typography — Inter family, hierarchy through weight/size only
- C. Layout & Navigation — inverted-L, list/detail split, collapsible sidebar
- D. Surface Architecture — flush tiled grids, sharp edges on data, border hierarchy, elevation only for overlays
- E. Information Density — in-place editing, contextual menus
- F. Interaction & Speed — optimistic updates, command palette, keyboard shortcuts
- G. Motion & Animation — 200ms, purposeful micro-interactions only
- H. Feedback — inline over toasts, undo over confirmation
- I. Visual Polish — tight alignment, subtle gradients, "be gentle"
- J. Progressive Disclosure — works out of the box, natural language filters, universal URLs