Overview
Code style guide derived from gpui-component implementation patterns.
Based on: Analysis of Button, Checkbox, Input, Select, and other components in crates/ui
Component Structure
Basic Component Pattern
use gpui::{ div, prelude::FluentBuilder as _, AnyElement, App, Div, ElementId, InteractiveElement, IntoElement, ParentElement, RenderOnce, StatefulInteractiveElement, StyleRefinement, Styled, Window, };
#[derive(IntoElement)] pub struct MyComponent { id: ElementId, base: Div, style: StyleRefinement,
// Configuration fields
size: Size,
disabled: bool,
selected: bool,
// Content fields
label: Option<Text>,
children: Vec<AnyElement>,
// Callbacks (use Rc for Clone)
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
}
impl MyComponent { pub fn new(id: impl Into<ElementId>) -> Self { Self { id: id.into(), base: div(), style: StyleRefinement::default(), size: Size::default(), disabled: false, selected: false, label: None, children: Vec::new(), on_click: None, } }
// Builder methods
pub fn label(mut self, label: impl Into<Text>) -> Self {
self.label = Some(label.into());
self
}
pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
self.on_click = Some(Rc::new(handler));
self
}
}
impl InteractiveElement for MyComponent { fn interactivity(&mut self) -> &mut gpui::Interactivity { self.base.interactivity() } }
impl StatefulInteractiveElement for MyComponent {}
impl Styled for MyComponent { fn style(&mut self) -> &mut StyleRefinement { &mut self.style } }
impl RenderOnce for MyComponent { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { // Implementation self.base } }
Stateful Component Pattern
#[derive(IntoElement)] pub struct Select { state: Entity<SelectState>, style: StyleRefinement, size: Size, // ... }
pub struct SelectState { open: bool, selected_index: Option<usize>, // ... }
impl Select { pub fn new(state: &Entity<SelectState>) -> Self { Self { state: state.clone(), size: Size::default(), style: StyleRefinement::default(), } } }
Trait Implementations
Sizable
impl Sizable for MyComponent { fn with_size(mut self, size: impl Into<Size>) -> Self { self.size = size.into(); self } }
Selectable
impl Selectable for MyComponent { fn selected(mut self, selected: bool) -> Self { self.selected = selected; self }
fn is_selected(&self) -> bool {
self.selected
}
}
Disableable
impl Disableable for MyComponent { fn disabled(mut self, disabled: bool) -> Self { self.disabled = disabled; self }
fn is_disabled(&self) -> bool {
self.disabled
}
}
Variant Patterns
Enum Variants
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] pub enum ButtonVariant { Primary, #[default] Secondary, Danger, Success, Warning, Ghost, Link, }
Trait-Based Variant API
pub trait ButtonVariants: Sized { fn with_variant(self, variant: ButtonVariant) -> Self;
/// With the primary style for the Button.
fn primary(self) -> Self {
self.with_variant(ButtonVariant::Primary)
}
/// With the danger style for the Button.
fn danger(self) -> Self {
self.with_variant(ButtonVariant::Danger)
}
// ... more variants
}
Custom Variant Pattern
#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct ButtonCustomVariant { color: Hsla, foreground: Hsla, border: Hsla, hover: Hsla, active: Hsla, shadow: bool, }
impl ButtonCustomVariant { pub fn new(cx: &App) -> Self { Self { color: cx.theme().transparent, foreground: cx.theme().foreground, // ... shadow: false, } }
pub fn color(mut self, color: Hsla) -> Self {
self.color = color;
self
}
// ... more builder methods
}
Action and Keybinding Patterns
Context Constant
const CONTEXT: &str = "Select";
Init Function
pub(crate) fn init(cx: &mut App) { cx.bind_keys([ KeyBinding::new("up", SelectUp, Some(CONTEXT)), KeyBinding::new("down", SelectDown, Some(CONTEXT)), KeyBinding::new("enter", Confirm { secondary: false }, Some(CONTEXT)), KeyBinding::new("escape", Cancel, Some(CONTEXT)), ]) }
Action Usage
use crate::actions::{Cancel, Confirm, SelectDown, SelectUp};
div() .key_context(CONTEXT) .on_action(cx.listener(Self::on_action_select_up)) .on_action(cx.listener(Self::on_action_confirm))
Trait Definitions
Item Traits
pub trait SelectItem: Clone { type Value: Clone;
fn title(&self) -> SharedString;
fn display_title(&self) -> Option<AnyElement> {
None
}
fn render(&self, _: &mut Window, _: &mut App) -> impl IntoElement {
self.title().into_element()
}
fn value(&self) -> &Self::Value;
fn matches(&self, query: &str) -> bool {
self.title().to_lowercase().contains(&query.to_lowercase())
}
}
Implement for Common Types
impl SelectItem for String { type Value = Self;
fn title(&self) -> SharedString {
SharedString::from(self.to_string())
}
fn value(&self) -> &Self::Value {
&self
}
}
impl SelectItem for SharedString { /* ... / } impl SelectItem for &'static str { / ... */ }
Icon Pattern
IconNamed Trait
pub trait IconNamed { fn path(self) -> SharedString; }
impl<T: IconNamed> From<T> for Icon { fn from(value: T) -> Self { Icon::build(value) } }
IconName Enum
#[derive(IntoElement, Clone)] pub enum IconName { ArrowDown, ArrowUp, Check, Close, // ... all icon names }
Documentation Patterns
Component Documentation
/// A Checkbox element. #[derive(IntoElement)] pub struct Checkbox { }
Method Documentation
impl Checkbox { /// Create a new Checkbox with the given id. pub fn new(id: impl Into<ElementId>) -> Self { }
/// Set the label for the checkbox.
pub fn label(mut self, label: impl Into<Text>) -> Self { }
/// Set the click handler for the checkbox.
///
/// The `&bool` parameter indicates the new checked state after the click.
pub fn on_click(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self { }
}
Import Organization Pattern
// 1. External crate imports use std::rc::Rc;
// 2. Crate imports use crate::{ ActiveTheme, Disableable, FocusableExt, Icon, IconName, Selectable, Sizable, Size, StyledExt, };
// 3. GPUI imports use gpui::{ div, prelude::FluentBuilder as _, px, relative, rems, AnyElement, App, Div, ElementId, InteractiveElement, IntoElement, ParentElement, RenderOnce, StatefulInteractiveElement, StyleRefinement, Styled, Window, };
Field Organization
pub struct Component { // 1. Identity id: ElementId, base: Div, style: StyleRefinement,
// 2. Configuration
size: Size,
disabled: bool,
selected: bool,
tab_stop: bool,
tab_index: isize,
// 3. Content/children
label: Option<Text>,
children: Vec<AnyElement>,
prefix: Option<AnyElement>,
suffix: Option<AnyElement>,
// 4. Callbacks (last)
on_click: Option<Rc<dyn Fn(Args, &mut Window, &mut App) + 'static>>,
}
Common Patterns
Optional Elements
pub fn prefix(mut self, prefix: impl IntoElement) -> Self { self.prefix = Some(prefix.into_any_element()); self }
Callback Patterns
// Pattern 1: Event parameter first pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self { self.on_click = Some(Rc::new(handler)); self }
// Pattern 2: State parameter pub fn on_change(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self { self.on_change = Some(Rc::new(handler)); self }
Static Handler Functions
fn handle_click( on_click: &Option<Rc<dyn Fn(&bool, &mut Window, &mut App) + 'static>>, checked: bool, window: &mut Window, cx: &mut App, ) { let new_checked = !checked; if let Some(f) = on_click { (f)(&new_checked, window, cx); } }
Boolean Methods
// Enable/disable patterns pub fn cleanable(mut self, cleanable: bool) -> Self { self.cleanable = cleanable; self }
// Toggle methods (no parameter) pub fn mask_toggle(mut self) -> Self { self.mask_toggle = true; self }
Size Methods
Size Trait
impl Sizable for Component { fn with_size(mut self, size: impl Into<Size>) -> Self { self.size = size.into(); self } }
Convenience Size Methods (from StyleSized trait)
Components get .xsmall() , .small() , .medium() , .large() automatically via StyleSized trait.
Rendering Patterns
RenderOnce Pattern
impl RenderOnce for MyComponent { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { let (width, height) = self.size.input_size();
self.base
.id(self.id)
.flex()
.items_center()
.gap(px(8.))
.min_w(width)
.h(height)
.when(self.disabled, |this| {
this.opacity(0.5).cursor_not_allowed()
})
.children(self.children)
}
}
Theme Usage
// Access theme colors cx.theme().surface cx.theme().foreground cx.theme().border cx.theme().primary cx.theme().transparent
// Use in components div() .bg(cx.theme().surface) .text_color(cx.theme().foreground) .border_color(cx.theme().border)
Reference Documentation
Component Examples: See component-examples.md
-
Full component implementations
-
Common patterns in action
Trait Patterns: See trait-patterns.md
-
Detailed trait implementation guides
-
Custom trait design patterns
Quick Checklist
When creating a new component in crates/ui:
-
#[derive(IntoElement)] on struct
-
Include id: ElementId , base: Div , style: StyleRefinement
-
Implement InteractiveElement , StatefulInteractiveElement , Styled
-
Implement RenderOnce trait
-
Implement Sizable if component has sizes
-
Implement Selectable if component can be selected
-
Implement Disableable if component can be disabled
-
Use Rc<dyn Fn> for callbacks
-
Use Option<AnyElement> for optional child elements
-
Import prelude::FluentBuilder as _
-
Use theme colors via cx.theme()
-
Follow field organization pattern