Back to skills
extension
Category: Development & EngineeringNo API key required

gpui-elements

UI element creation and composition with GPUI. Use when building element trees, creating custom components, implementing layouts, or working with conditional rendering.

personAuthor: jakexiaohubgithub

GPUI Elements

This skill covers creating and composing UI elements in GPUI applications.

Overview

Elements are the building blocks of GPUI UIs:

  • Built with div() as the base element
  • Styled with chainable methods
  • Composed with .child() and .children()
  • Support conditional rendering with .when()
  • Use flexbox for layout

Basic Element Creation

div() Element

use gpui::*;

impl Render for MyView {
    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        div()
            .child("Hello, GPUI!")
    }
}

Adding Children

div()
    .child("First child")
    .child(
        div()
            .child("Nested child")
    )
    .child("Third child")

Multiple Children with Vector

let items = vec!["One", "Two", "Three"];

div().children(
    items.into_iter().map(|text| {
        div().child(text)
    })
)

Text Elements

String Types

use gpui::*;

impl Render for MyView {
    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        div()
            // &str
            .child("Static text")
            // String
            .child(format!("Count: {}", self.count))
            // SharedString (most efficient)
            .child(self.title.clone())
    }
}

SharedString

use gpui::*;

struct MyView {
    title: SharedString,
}

impl MyView {
    fn new() -> Self {
        Self {
            title: "Default Title".into(),
        }
    }
    
    fn set_title(&mut self, title: impl Into<SharedString>) {
        self.title = title.into();
    }
}

// SharedString implements IntoElement
impl Render for MyView {
    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        div().child(self.title.clone())
    }
}

Conditional Rendering

when() Method

div()
    .when(condition, |this| {
        this.bg(rgb(0x3b82f6))
            .child("Condition is true")
    })

when_some() for Options

div()
    .when_some(maybe_text, |this, text| {
        this.child(text)
    })

Practical Example

struct MyView {
    is_active: bool,
    error: Option<String>,
}

impl Render for MyView {
    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        div()
            .when(self.is_active, |this| {
                this.bg(rgb(0x22c55e))
            })
            .when_some(self.error.clone(), |this, error| {
                this.child(
                    div()
                        .p_2()
                        .bg(rgb(0xef4444))
                        .text_color(rgb(0xffffff))
                        .child(error)
                )
            })
    }
}

Custom Components

RenderOnce Components

use gpui::*;

#[derive(IntoElement)]
struct Button {
    label: SharedString,
    on_click: Option<Box<dyn Fn(&mut App) + 'static>>,
}

impl Button {
    fn new(label: impl Into<SharedString>) -> Self {
        Self {
            label: label.into(),
            on_click: None,
        }
    }
    
    fn on_click(mut self, handler: impl Fn(&mut App) + 'static) -> Self {
        self.on_click = Some(Box::new(handler));
        self
    }
}

impl RenderOnce for Button {
    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
        let mut element = div()
            .px_4()
            .py_2()
            .bg(rgb(0x3b82f6))
            .text_color(rgb(0xffffff))
            .rounded(px(6.0))
            .cursor_pointer()
            .child(self.label);
        
        if let Some(handler) = self.on_click {
            element = element.on_click(move |_event, _window, cx| {
                handler(cx);
            });
        }
        
        element
    }
}

// Usage
impl Render for MyView {
    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        div().child(
            Button::new("Click Me")
                .on_click(|cx| {
                    println!("Button clicked!");
                })
        )
    }
}

Render Components

struct Card {
    title: SharedString,
    content: SharedString,
}

impl Render for Card {
    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        div()
            .p_4()
            .bg(rgb(0x1a1a1a))
            .rounded(px(8.0))
            .flex()
            .flex_col()
            .gap_2()
            .child(
                div()
                    .text_lg()
                    .font_bold()
                    .child(self.title.clone())
            )
            .child(
                div()
                    .text_sm()
                    .child(self.content.clone())
            )
    }
}

Layout Patterns

Vertical Stack

div()
    .flex()
    .flex_col()
    .gap_4()
    .child("Item 1")
    .child("Item 2")
    .child("Item 3")

Horizontal Row

div()
    .flex()
    .flex_row()
    .gap_4()
    .items_center()
    .child("Left")
    .child("Center")
    .child("Right")

Grid-like Layout

div()
    .flex()
    .flex_wrap()
    .gap_4()
    .children((0..12).map(|i| {
        div()
            .w(px(100.0))
            .h(px(100.0))
            .bg(rgb(0x3b82f6))
            .child(format!("Item {}", i))
    }))

Centering

div()
    .flex()
    .items_center()
    .justify_center()
    .size_full()
    .child("Centered content")

Dynamic Lists

Simple List

struct ListView {
    items: Vec<String>,
}

impl Render for ListView {
    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        div()
            .flex()
            .flex_col()
            .gap_2()
            .children(
                self.items.iter().map(|item| {
                    div()
                        .p_2()
                        .bg(rgb(0x1a1a1a))
                        .rounded(px(4.0))
                        .child(item.clone())
                })
            )
    }
}

List with Index

div()
    .children(
        self.items.iter().enumerate().map(|(i, item)| {
            div()
                .child(format!("{}. {}", i + 1, item))
        })
    )

Nested Components

Component Composition

struct Header;
impl Render for Header {
    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        div()
            .h(px(64.0))
            .bg(rgb(0x1a1a1a))
            .child("Header")
    }
}

struct Sidebar;
impl Render for Sidebar {
    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        div()
            .w(px(250.0))
            .bg(rgb(0x2a2a2a))
            .child("Sidebar")
    }
}

struct AppLayout {
    header: Entity<Header>,
    sidebar: Entity<Sidebar>,
}

impl Render for AppLayout {
    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        div()
            .size_full()
            .flex()
            .flex_col()
            .child(self.header.clone())
            .child(
                div()
                    .flex()
                    .flex_1()
                    .child(self.sidebar.clone())
                    .child(
                        div()
                            .flex_1()
                            .child("Main content")
                    )
            )
    }
}

Element Patterns

Clickable Card

div()
    .p_4()
    .bg(rgb(0x1a1a1a))
    .rounded(px(8.0))
    .cursor_pointer()
    .hover(|style| style.bg(rgb(0x2a2a2a)))
    .on_click(|_event, _window, cx| {
        println!("Card clicked");
    })
    .child("Click me")

Input Field

struct InputField {
    value: SharedString,
}

impl Render for InputField {
    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
        div()
            .px_3()
            .py_2()
            .bg(rgb(0x1a1a1a))
            .border_1()
            .border_color(rgb(0x4a4a4a))
            .rounded(px(4.0))
            .child(self.value.clone())
            .on_click(cx.listener(|this, _event, _window, cx| {
                // Handle focus
            }))
    }
}

Toggle Switch

struct Toggle {
    enabled: bool,
}

impl Render for Toggle {
    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
        div()
            .w(px(44.0))
            .h(px(24.0))
            .rounded(px(12.0))
            .bg(if self.enabled { 
                rgb(0x22c55e) 
            } else { 
                rgb(0x4a4a4a) 
            })
            .cursor_pointer()
            .on_click(cx.listener(|this, _event, _window, cx| {
                this.enabled = !this.enabled;
                cx.notify();
            }))
    }
}

Best Practices

  1. Use SharedString for text: Avoids unnecessary string copies
  2. Use .when() for conditions: More readable than if/else in render
  3. Extract reusable components: Create RenderOnce components for common patterns
  4. Keep render pure: Don't mutate state directly in render() method
  5. Use children() for lists: More efficient than multiple .child() calls
  6. Chain methods: Build elements with method chaining for clarity

Common Mistakes

| Mistake | Problem | Solution | |---------|---------|----------| | Mutating state in render | Race conditions | Use event handlers to mutate | | Not using SharedString | Extra allocations | Use SharedString for text | | Deep nesting | Hard to read | Extract into components | | Missing IntoElement | Compile error | Ensure types implement IntoElement | | Forgetting .clone() | Borrow errors | Clone SharedString when using multiple times |

Summary

  • Use div() as the base element
  • Add children with .child() or .children()
  • Use .when() for conditional rendering
  • Create components with RenderOnce or Render
  • Use SharedString for efficient text handling
  • Compose with flexbox layout methods

References