GPUI Best Practices
Comprehensive guide for building desktop applications with GPUI, the UI framework powering Zed editor. Contains 40+ rules across 8 categories, prioritized by impact.
When to Apply
Reference these guidelines when:
- Writing new GPUI views or components
- Implementing state management with Entity
- Handling events and keyboard shortcuts
- Working with async tasks and background work
- Building forms, lists, or dialogs
- Styling components with Tailwind-like API
- Testing GPUI applications
Rule Categories by Priority
| Priority | Category | Impact | Prefix |
|---|---|---|---|
| 1 | Core Concepts | CRITICAL | core- |
| 2 | Rendering | CRITICAL | render- |
| 3 | State Management | HIGH | state- |
| 4 | Event Handling | HIGH | event- |
| 5 | Async & Concurrency | MEDIUM-HIGH | async- |
| 6 | Styling | MEDIUM | style- |
| 7 | Components | MEDIUM | comp- |
| 8 | Anti-patterns | CRITICAL | anti- |
Quick Reference
1. Core Concepts (CRITICAL)
core-ownership-model- Understand GPUI's single ownership modelcore-entity-operations- Use read/update/observe/subscribe correctlycore-weak-entity- Use WeakEntity to break circular referencescore-context-types- Know when to use App, Context<T>, AsyncApp
2. Rendering (CRITICAL)
render-render-vs-renderonce- Choose Render for stateful, RenderOnce for componentsrender-element-composition- Build element trees with div() and method chainingrender-conditional- Use .when() and .when_some() for conditional stylingrender-shared-string- Use SharedString to avoid string copyingrender-builder-pattern- Design components with builder pattern
3. State Management (HIGH)
state-notify- Always call cx.notify() after state changesstate-observe- Use cx.observe() to react to Entity changesstate-subscribe- Use cx.subscribe() for typed eventsstate-global- Use Global trait for app-wide statestate-keyed-state- Use window.use_keyed_state() for persistent state
4. Event Handling (HIGH)
event-actions- Define and register actions for keyboard shortcutsevent-listener- Use cx.listener() for view-bound event handlersevent-focus- Manage focus with FocusHandle and key_contextevent-propagation- Understand event bubbling and stop_propagation
5. Async & Concurrency (MEDIUM-HIGH)
async-task-lifecycle- Store or detach tasks to prevent cancellationasync-debounce- Implement debounce with timer + task replacementasync-background-spawn- Use background_spawn for CPU-intensive workasync-weak-entity- Use WeakEntity for safe cross-await accessasync-error-handling- Use .log_err() and .detach_and_log_err()
6. Styling (MEDIUM)
style-flexbox- Use h_flex() and v_flex() for layoutsstyle-theme-colors- Always use cx.theme() for colorsstyle-spacing- Use DynamicSpacing for responsive spacingstyle-elevation- Use elevation system for layered surfaces
7. Components (MEDIUM)
comp-stateless- Prefer RenderOnce with #[derive(IntoElement)]comp-traits- Implement Disableable, Selectable, Sizable traitscomp-focus-ring- Add focus ring for accessibilitycomp-dialog- Use WindowExt for dialog managementcomp-variant- Use variant enums for component styles
8. Anti-patterns (CRITICAL)
anti-silent-error- Never silently discard errors with let _ =anti-drop-task- Never drop Task without storing or detachinganti-drop-subscription- Always detach or store subscriptionsanti-circular-reference- Avoid Entity cycles, use WeakEntityanti-missing-notify- Never forget cx.notify() after state changesanti-unwrap- Avoid unwrap(), use ? or explicit handling
Architecture Overview
┌─────────────────────────────────────────────────────────┐
│ Application (App) │
│ (Single owner of all Entities) │
└─────────────────────────────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
┌───────────┐ ┌───────────┐ ┌──────────┐
│ Entity<A> │ │ Entity<B> │ │ Global<C>│
└───────────┘ └───────────┘ └──────────┘
│ │ │
│ read/update │ read/update │ via App
│ via Context<A> │ via Context<B> │
│
┌─────────────────────────────────────────────────┐
│ UI Rendering (Render trait) │
│ Each frame: fn render(&mut self, ...) │
│ Returns: impl IntoElement (Element tree) │
└─────────────────────────────────────────────────┘
│
├─ observe() → changes trigger render
├─ subscribe() → events trigger reactions
├─ notify() → signal changes
└─ emit() → send typed events
How to Use
Read individual rule files for detailed explanations and code examples:
rules/core-ownership-model.md
rules/render-render-vs-renderonce.md
rules/anti-silent-error.md
Each rule file contains:
- Brief explanation of why it matters
- Incorrect code example with explanation
- Correct code example with explanation
- Additional context and references