agskills.dev
MARKETPLACE

gpui-style-guide

GPUI Component project style guide based on gpui-component code patterns. Use when writing new components, reviewing code, or ensuring consistency with existing gpui-component implementations. Covers component structure, trait implementations, naming conventions, and API patterns observed in the actual codebase.

longbridge10.4k470

Vorschau

SKILL.md
Metadata
name
gpui-style-guide
description
GPUI Component project style guide based on gpui-component code patterns. Use when writing new components, reviewing code, or ensuring consistency with existing gpui-component implementations. Covers component structure, trait implementations, naming conventions, and API patterns observed in the actual codebase.

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