Common GPUI patterns including component composition, state management strategies, event handling, and action dispatching. Use when user needs guidance on GPUI patterns, component design, or state management approaches.
GPUI Patterns
Metadata
This skill provides comprehensive guidance on common GPUI patterns and best practices for building maintainable, performant applications.
Instructions
Component Composition Patterns
Basic Component Structure
use gpui::*;
// View component with state
struct MyView {
state: Model<MyState>,
_subscription: Subscription,
}
impl MyView {
fn new(state: Model<MyState>, cx: &mut ViewContext<Self>) -> Self {
let _subscription = cx.observe(&state, |_, _, cx| cx.notify());
Self { state, _subscription }
}
}
impl Render for MyView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let state = self.state.read(cx);
div()
.flex()
.flex_col()
.child(format!("Value: {}", state.value))
}
}
Container/Presenter Pattern
Container (manages state and logic):
struct Container {
model: Model<AppState>,
_subscription: Subscription,
}
impl Container {
fn new(model: Model<AppState>, cx: &mut ViewContext<Self>) -> Self {
let _subscription = cx.observe(&model, |_, _, cx| cx.notify());
Self { model, _subscription }
}
}
impl Render for Container {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let state = self.model.read(cx);
// Pass data to presenter
Presenter::new(state.data.clone())
}
}
Presenter (pure rendering):
struct Presenter {
data: String,
}
impl Presenter {
fn new(data: String) -> Self {
Self { data }
}
}
impl Render for Presenter {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
div().child(self.data.as_str())
}
}
Compound Components
// Parent component with shared context
pub struct Tabs {
items: Vec<TabItem>,
active_index: usize,
}
pub struct TabItem {
label: String,
content: Box<dyn Fn() -> AnyElement>,
}
impl Tabs {
pub fn new() -> Self {
Self {
items: Vec::new(),
active_index: 0,
}
}
pub fn add_tab(
mut self,
label: impl Into<String>,
content: impl Fn() -> AnyElement + 'static,
) -> Self {
self.items.push(TabItem {
label: label.into(),
content: Box::new(content),
});
self
}
fn set_active(&mut self, index: usize, cx: &mut ViewContext<Self>) {
self.active_index = index;
cx.notify();
}
}
impl Render for Tabs {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.flex()
.flex_col()
.child(
// Tab headers
div()
.flex()
.children(
self.items.iter().enumerate().map(|(i, item)| {
tab_header(&item.label, i == self.active_index, || {
self.set_active(i, cx)
})
})
)
)
.child(
// Active tab content
(self.items[self.active_index].content)()
)
}
}
State Management Strategies
Model-View Pattern
// Model: Application state
#[derive(Clone)]
struct AppState {
count: usize,
items: Vec<String>,
}
// View: Observes and renders state
struct AppView {
state: Model<AppState>,
_subscription: Subscription,
}
impl AppView {
fn new(state: Model<AppState>, cx: &mut ViewContext<Self>) -> Self {
let _subscription = cx.observe(&state, |_, _, cx| cx.notify());
Self { state, _subscription }
}
fn increment(&mut self, cx: &mut ViewContext<Self>) {
self.state.update(cx, |state, cx| {
state.count += 1;
cx.notify();
});
}
}
Context-Based State
// Global state via context
#[derive(Clone)]
struct GlobalSettings {
theme: Theme,
language: String,
}
impl Global for GlobalSettings {}
// Initialize in app
fn init_app(cx: &mut AppContext) {
cx.set_global(GlobalSettings {
theme: Theme::Light,
language: "en".to_string(),
});
}
// Access in components
impl Render for MyView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let settings = cx.global::<GlobalSettings>();
div()
.child(format!("Language: {}", settings.language))
}
}
Subscription Patterns
Basic Subscription:
struct Observer {
model: Model<Data>,
_subscription: Subscription,
}
impl Observer {
fn new(model: Model<Data>, cx: &mut ViewContext<Self>) -> Self {
let _subscription = cx.observe(&model, |_, _, cx| {
cx.notify(); // Rerender on change
});
Self { model, _subscription }
}
}
Selective Updates:
impl Observer {
fn new(model: Model<Data>, cx: &mut ViewContext<Self>) -> Self {
let _subscription = cx.observe(&model, |this, model, cx| {
let data = model.read(cx);
// Only rerender if specific field changed
if data.important_field != this.cached_field {
this.cached_field = data.important_field.clone();
cx.notify();
}
});
Self {
model,
cached_field: String::new(),
_subscription,
}
}
}
Multiple Subscriptions:
struct MultiObserver {
model_a: Model<DataA>,
model_b: Model<DataB>,
_subscriptions: Vec<Subscription>,
}
impl MultiObserver {
fn new(
model_a: Model<DataA>,
model_b: Model<DataB>,
cx: &mut ViewContext<Self>,
) -> Self {
let mut subscriptions = Vec::new();
subscriptions.push(cx.observe(&model_a, |_, _, cx| cx.notify()));
subscriptions.push(cx.observe(&model_b, |_, _, cx| cx.notify()));
Self {
model_a,
model_b,
_subscriptions: subscriptions,
}
}
}
Event Handling Patterns
Click Events
div()
.on_click(cx.listener(|this, event: &ClickEvent, cx| {
// Handle click
this.handle_click(cx);
}))
.child("Click me")
Keyboard Events
div()
.on_key_down(cx.listener(|this, event: &KeyDownEvent, cx| {
match event.key.as_str() {
"Enter" => this.submit(cx),
"Escape" => this.cancel(cx),
_ => {}
}
}))
Event Propagation
// Stop propagation
div()
.on_click(|event, cx| {
event.stop_propagation();
// Handle click
})
// Prevent default
div()
.on_key_down(|event, cx| {
if event.key == "Tab" {
event.prevent_default();
// Custom tab handling
}
})
Mouse Events
div()
.on_mouse_down(cx.listener(|this, event, cx| {
this.mouse_down_position = Some(event.position);
}))
.on_mouse_move(cx.listener(|this, event, cx| {
if let Some(start) = this.mouse_down_position {
let delta = event.position - start;
this.handle_drag(delta, cx);
}
}))
.on_mouse_up(cx.listener(|this, event, cx| {
this.mouse_down_position = None;
}))
Action System
Define Actions
use gpui::*;
actions!(app, [
Increment,
Decrement,
Reset,
SetValue
]);
// Action with data
#[derive(Clone, PartialEq)]
pub struct SetValue {
pub value: i32,
}
impl_actions!(app, [SetValue]);
Register Action Handlers
impl Counter {
fn register_actions(&mut self, cx: &mut ViewContext<Self>) {
cx.on_action(cx.listener(|this, _: &Increment, cx| {
this.model.update(cx, |state, cx| {
state.count += 1;
cx.notify();
});
}));
cx.on_action(cx.listener(|this, _: &Decrement, cx| {
this.model.update(cx, |state, cx| {
state.count = state.count.saturating_sub(1);
cx.notify();
});
}));
cx.on_action(cx.listener(|this, action: &SetValue, cx| {
this.model.update(cx, |state, cx| {
state.count = action.value;
cx.notify();
});
}));
}
}
Dispatch Actions
// From within component
fn handle_button_click(&mut self, cx: &mut ViewContext<Self>) {
cx.dispatch_action(Increment);
}
// With data
fn set_specific_value(&mut self, value: i32, cx: &mut ViewContext<Self>) {
cx.dispatch_action(SetValue { value });
}
// Global action dispatch
cx.dispatch_action_on_window(Reset, window_id);
Keybindings
// Register global keybindings
fn register_keybindings(cx: &mut AppContext) {
cx.bind_keys([
KeyBinding::new("cmd-+", Increment, None),
KeyBinding::new("cmd--", Decrement, None),
KeyBinding::new("cmd-0", Reset, None),
]);
}
Element Composition
Builder Pattern
fn card(title: &str, content: impl IntoElement) -> impl IntoElement {
div()
.flex()
.flex_col()
.bg(white())
.border_1()
.rounded_lg()
.shadow_sm()
.p_6()
.child(
div()
.text_lg()
.font_semibold()
.mb_4()
.child(title)
)
.child(content)
}
Conditional Rendering
div()
.when(condition, |this| {
this.bg(blue_500())
})
.when_some(optional_value, |this, value| {
this.child(format!("Value: {}", value))
})
.map(|this| {
if complex_condition {
this.border_1()
} else {
this.border_2()
}
})
Dynamic Children
div()
.children(
items.iter().map(|item| {
div().child(item.name.as_str())
})
)
View Lifecycle
Initialization
impl MyView {
fn new(cx: &mut ViewContext<Self>) -> Self {
// Initialize state
let model = cx.new_model(|_| MyState::default());
// Set up subscriptions
let subscription = cx.observe(&model, |_, _, cx| cx.notify());
// Spawn async tasks
cx.spawn(|this, mut cx| async move {
// Async initialization
}).detach();
Self {
model,
_subscription: subscription,
}
}
}
Update Notifications
impl MyView {
fn update_state(&mut self, new_data: Data, cx: &mut ViewContext<Self>) {
self.model.update(cx, |state, cx| {
state.data = new_data;
cx.notify(); // Trigger rerender
});
}
}
Cleanup
impl Drop for MyView {
fn drop(&mut self) {
// Manual cleanup if needed
// Subscriptions are automatically dropped
}
}
Reactive Patterns
Derived State
impl Render for MyView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let state = self.model.read(cx);
// Compute derived values
let total = state.items.iter().map(|i| i.value).sum::<i32>();
let average = total / state.items.len() as i32;
div()
.child(format!("Total: {}", total))
.child(format!("Average: {}", average))
}
}
Async Updates
impl MyView {
fn load_data(&mut self, cx: &mut ViewContext<Self>) {
let model = self.model.clone();
cx.spawn(|_, mut cx| async move {
let data = fetch_data().await?;
cx.update_model(&model, |state, cx| {
state.data = data;
cx.notify();
})?;
Ok::<_, anyhow::Error>(())
}).detach();
}
}
Resources
Official Documentation
- GPUI GitHub: https://github.com/zed-industries/zed/tree/main/crates/gpui
- Zed Editor Source: Real-world GPUI examples
Common Patterns Reference
- Model-View: State management pattern
- Container-Presenter: Separation of concerns
- Compound Components: Related components working together
- Action System: Command pattern for user interactions
- Subscriptions: Observer pattern for reactive updates
Best Practices
- Store subscriptions to prevent cleanup
- Use
cx.notify()sparingly - Prefer composition over inheritance
- Keep render methods pure
- Handle errors gracefully
- Document component APIs
- Test component behavior
You Might Also Like
Related Skills

cache-components
Expert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.
vercel
component-refactoring
Refactor high-complexity React components in Dify frontend. Use when `pnpm analyze-component --json` shows complexity > 50 or lineCount > 300, when the user asks for code splitting, hook extraction, or complexity reduction, or when `pnpm analyze-component` warns to refactor before testing; avoid for simple/well-structured components, third-party wrappers, or when the user explicitly wants testing without refactoring.
langgenius
web-artifacts-builder
Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use for complex artifacts requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX artifacts.
anthropics
frontend-design
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
anthropics
react-modernization
Upgrade React applications to latest versions, migrate from class components to hooks, and adopt concurrent features. Use when modernizing React codebases, migrating to React Hooks, or upgrading to latest React versions.
wshobson
tailwind-design-system
Build scalable design systems with Tailwind CSS v4, design tokens, component libraries, and responsive patterns. Use when creating component libraries, implementing design systems, or standardizing UI patterns.
wshobson