
Gpui Component
- 114 installs
- 12.4k repo stars
- Updated August 4, 2026
- longbridge/gpui-component
Build new GPUI controls in Rust with the same structure, traits, and checklist Longbridge uses for Button, Input, and Select-style components.
About
GPUI Component is an agent skill that encodes Longbridge’s internal code style for Rust GPUI UI primitives so solo builders cloning or extending gpui-component can match production patterns. It is for developers shipping desktop or embedded-agent UIs on GPUI who need consistency across Button, Checkbox, Input, Select, and similar controls—not for web React stacks. Invoke it when scaffolding a new interactive element, aligning variants and disabled states, or reviewing a PR that diverges from FluentBuilder and theme conventions. The guide walks through struct field ordering, required trait implementations, size handling, and applying user style refinements without fighting the framework. On Prism it lives on Build → Frontend as a niche but high-signal reference for the Rust GPUI ecosystem. It does not replace GPUI API docs or design mocks; it standardizes how code should look and behave in the Longbridge ui crate style.
- Documents standard stateless GPUI component anatomy (identity, config, content, callbacks)
- Requires and optional traits: Sizable, Disableable, StyledExt, FluentBuilder conditionals
- Variants pattern and callback signatures aligned with crates/ui components
- Checklist for new components including theme colors and user style overrides
- Import organization and doc-comment conventions for RenderOnce GPUI elements
Gpui Component by the numbers
- 114 all-time installs (skills.sh)
- +16 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #62 of 121 Rust skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/longbridge/gpui-component --skill gpui-componentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 114 |
|---|---|
| repo stars | ★ 12.4k |
| Last updated | August 4, 2026 |
| Repository | longbridge/gpui-component ↗ |
What it does
Build new GPUI controls in Rust with the same structure, traits, and checklist Longbridge uses for Button, Input, and Select-style components.
Files
Documentation
- Full reference: fetch
https://longbridge.github.io/gpui-component/llms-full.txt - Per-component API: fetch
https://longbridge.github.io/gpui-component/docs/components/{name}.md - e.g.
button.md,input.md,select.md,dialog.md,data-table.md - Any site page can be fetched as Markdown by appending
.mdto the URL
Quick Reference
Setup — always required:
gpui_component::init(cx); // in app.run(), must be first
Root::new(view, window, cx) // first-level view in every windowStateless — use directly in render:
Button::new("id").primary().label("OK").on_click(|_, _, _| {})Stateful — hold Entity<State> in struct, pass ref in render:
// in new(): let input = cx.new(|cx| InputState::new(window, cx));
// in render: Input::new(&self.input)Sizes: .xsmall() .small() .medium() (default) .large()
Theme: cx.theme().primary · .background · .foreground · .border · .muted
Component Catalog
When you need a component, find it here. For full API, fetch its .md doc.
Input & Form
| Component | Import | Notes |
|---|---|---|
Input | input::{Input, InputState} | Stateful. Text, password, mask, validation |
NumberInput | number_input::{NumberInput, NumberInputState} | Stateful. Numeric with step |
OtpInput | otp_input::{OtpInput, OtpInputState} | Stateful. One-time password |
Select | select::{Select, SelectState} | Stateful. Dropdown picker |
Combobox | combobox::{Combobox, ComboboxState} | Stateful. Searchable select |
Checkbox | checkbox::Checkbox | Stateless. `on_click( |
Switch | switch::Switch | Stateless. Toggle |
Radio | radio::{Radio, RadioGroup} | Stateless. |
Slider | slider::{Slider, SliderState} | Stateful. |
Toggle | toggle::Toggle | Stateless. |
Rating | rating::Rating | Stateless. |
Stepper | stepper::Stepper | Stateless. Increment/decrement |
ColorPicker | color_picker::{ColorPicker, ColorPickerState} | Stateful. |
DatePicker | time::date_picker::{DatePicker, DatePickerState} | Stateful. |
Form | form::{v_form, h_form, field} | Layout container for form fields |
Display & Feedback
| Component | Import | Notes |
|---|---|---|
Button | button::{Button, ButtonGroup} | Stateless. Primary UI action |
Icon | {Icon, IconName} | Stateless. Lucide icons |
Badge | badge::Badge | Stateless. |
Tag | tag::Tag | Stateless. Closable tags |
Avatar | avatar::Avatar | Stateless. |
Label | label::Label | Stateless. Form label |
Kbd | kbd::Kbd | Stateless. Keyboard key display |
Alert | alert::Alert | Stateless. Info/success/warning/error |
Spinner | spinner::Spinner | Stateless. Loading indicator |
Skeleton | skeleton::Skeleton | Stateless. Loading placeholder |
Progress | progress::{ProgressBar, ProgressCircle} | Stateless. |
Tooltip | tooltip::Tooltip | Via .tooltip() on elements |
HoverCard | hover_card::{HoverCard, HoverCardState} | Stateful. |
Image | image::Image | Stateless. |
Clipboard | clipboard::Clipboard | Stateless. Copy button |
Overlay & Popups
| Component | Import | Notes |
|---|---|---|
Dialog | dialog::Dialog + WindowExt | Via window.open_modal(...) |
AlertDialog | WindowExt | Via window.open_alert_dialog(...) |
Sheet | sheet::Sheet + WindowExt | Side panel, via window.open_sheet(...) |
Notification | notification::Notification + WindowExt | Via window.push_notification(...) |
Popover | popover::Popover | Floating overlay |
Menu | menu::{PopupMenu, DropdownMenu} | Context menus |
DropdownButton | button::DropdownButton | Button with dropdown menu |
Navigation & Layout
| Component | Import | Notes |
|---|---|---|
Tabs / TabBar | tab::{Tab, TabBar} | Tabbed interface |
Sidebar | sidebar::{Sidebar, SidebarMenu, ...} | App navigation panel |
TitleBar | title_bar::TitleBar | Window title bar |
Breadcrumb | breadcrumb::Breadcrumb | Navigation breadcrumb |
Pagination | pagination::Pagination | Page navigation |
Accordion | accordion::Accordion | Collapsible sections |
Collapsible | collapsible::Collapsible | Single collapsible |
GroupBox | group_box::GroupBox | Labeled container |
Resizable | resizable::Resizable | Draggable split panes |
Scrollable | scroll::Scrollbar | Custom scrollbar |
FocusTrap | focus_trap::FocusTrap | Keyboard trap for modals |
Data Display
| Component | Import | Notes |
|---|---|---|
DataTable | table::{DataTable, TableState, TableDelegate} | Stateful. Full-featured table |
Table | table::{Table, ...} | Simpler table |
VirtualList | {v_virtual_list, h_virtual_list} | High-perf large lists |
List | list::{List, ListState, ListDelegate} | Stateful. Searchable list |
Tree | tree::{Tree, TreeState, TreeDelegate} | Stateful. Hierarchy |
DescriptionList | description_list::DescriptionList | Key-value pairs |
Settings | settings::Settings | Settings panel |
Charts
| Component | Import | Notes |
|---|---|---|
Chart | chart::Chart | Bar, line, area, pie charts |
Plot | plot::Plot | #[derive(IntoPlot)] for data |
Reference Files
- usage.md — setup patterns, component types, common examples
- style-guide.md — code style for contributors
GPUI Component Code Style Guide
Based on analysis of Button, Checkbox, Input, Select, and other components in crates/ui/src.
Contents: Component Structure · Required Traits · Optional Traits · Variants Pattern · Callback Signatures · Import Organization · Doc Comments · Applying User Style Overrides · FluentBuilder Conditionals · Theme Colors · Size Handling · Checklist
Component Structure
Standard Stateless Component
use std::rc::Rc;
use crate::{ActiveTheme, Disableable, Sizable, Size, StyledExt as _, /* ... */};
use gpui::{
AnyElement, App, Div, ElementId, InteractiveElement, IntoElement,
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement,
StyleRefinement, Styled, Window, div, prelude::FluentBuilder as _,
};
/// A MyComponent element.
#[derive(IntoElement)]
pub struct MyComponent {
// 1. Identity
id: ElementId,
base: Div,
style: StyleRefinement,
// 2. Configuration
size: Size,
disabled: bool,
selected: bool,
tab_stop: bool,
tab_index: isize,
// 3. Content
label: Option<SharedString>,
children: Vec<AnyElement>,
// 4. Callbacks (last)
on_click: Option<Rc<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
}
impl MyComponent {
/// Create a new MyComponent with the given id.
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
base: div(),
style: StyleRefinement::default(),
size: Size::default(),
disabled: false,
selected: false,
tab_stop: true,
tab_index: 0,
label: None,
children: Vec::new(),
on_click: None,
}
}
/// Set the label.
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
self.label = Some(label.into());
self
}
/// Set the click handler.
pub fn on_click(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
self.on_click = Some(Rc::new(handler));
self
}
}Stateful Component (Interactive, Needs .id())
Components with mouse interactions (hover, click tracking) use Stateful<Div>:
use gpui::{Stateful, StatefulInteractiveElement as _, /* ... */};
#[derive(IntoElement)]
pub struct Button {
id: ElementId,
base: Stateful<Div>, // Not Div — needs stateful for interaction tracking
// ...
}
impl Button {
pub fn new(id: impl Into<ElementId>) -> Self {
let id = id.into();
Self {
id: id.clone(),
base: div().flex_shrink_0().id(id), // .id() makes it Stateful<Div>
// ...
}
}
}
impl InteractiveElement for Button {
fn interactivity(&mut self) -> &mut Interactivity {
self.base.interactivity()
}
}---
Required Trait Implementations
// All components that accept children
impl ParentElement for MyComponent {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements)
}
}
// All components with styleable outer div
impl Styled for MyComponent {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
// For interactive components (mouse events, hover, click)
impl InteractiveElement for MyComponent {
fn interactivity(&mut self) -> &mut Interactivity {
self.base.interactivity()
}
}
// Required if InteractiveElement is implemented
impl StatefulInteractiveElement for MyComponent {}
// Rendering
impl RenderOnce for MyComponent {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
self.base
.id(self.id)
// Apply user style overrides last
.refine_style(&self.style)
.children(self.children)
}
}---
Optional Traits
impl Disableable for MyComponent {
fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
}
impl Selectable for MyComponent {
fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
fn is_selected(&self) -> bool {
self.selected
}
}
impl Sizable for MyComponent {
fn with_size(mut self, size: impl Into<Size>) -> Self {
self.size = size.into();
self
}
}Implementing Sizable gives .xsmall(), .small(), .medium(), .large() for free via StyleSized.
---
Variants Pattern
Use a Variants trait with default method impls:
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
pub enum AlertVariant {
#[default]
Info,
Success,
Warning,
Error,
}
pub trait AlertVariants: Sized {
fn with_variant(self, variant: AlertVariant) -> Self;
fn info(self) -> Self { self.with_variant(AlertVariant::Info) }
fn success(self) -> Self { self.with_variant(AlertVariant::Success) }
fn warning(self) -> Self { self.with_variant(AlertVariant::Warning) }
fn error(self) -> Self { self.with_variant(AlertVariant::Error) }
}
impl AlertVariants for MyAlert {
fn with_variant(mut self, variant: AlertVariant) -> Self {
self.variant = variant;
self
}
}---
Callback Signatures
// Click event (ClickEvent first)
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>
// State change (state value first)
on_change: Option<Rc<dyn Fn(&bool, &mut Window, &mut App) + 'static>>
on_change: Option<Rc<dyn Fn(&str, &mut Window, &mut App) + 'static>>
on_change: Option<Rc<dyn Fn(&usize, &mut Window, &mut App) + 'static>>Always Rc<dyn Fn> — components are cloned and called multiple times.
---
Import Organization
// 1. std
use std::rc::Rc;
// 2. crate imports (project internals)
use crate::{
ActiveTheme, Disableable, Icon, IconName,
Selectable, Sizable, Size, StyledExt as _,
h_flex, v_flex,
};
// 3. gpui imports
use gpui::{
AnyElement, App, Div, ElementId, InteractiveElement, IntoElement,
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement,
StyleRefinement, Styled, Window, div,
prelude::FluentBuilder as _,
px, rems, relative,
};---
Doc Comments
/// A Checkbox element. ← struct: one-line with capital, period
#[derive(IntoElement)]
pub struct Checkbox { ... }
impl Checkbox {
/// Create a new Checkbox with the given id. ← constructor
pub fn new(id: impl Into<ElementId>) -> Self { ... }
/// Set the label for the checkbox. ← setter
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, ...) -> Self { ... }
}- Struct doc:
/// A {Name} element. - Constructor:
/// Create a new {Name} with the given id. - Setters:
/// Set the {field}. - No redundant comments — only document non-obvious behavior
---
Applying User Style Overrides
Use refine_style to merge user's Styled calls onto the root element:
impl RenderOnce for MyComponent {
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
div()
.flex()
.items_center()
// Apply component defaults first, then user overrides
.refine_style(&self.style)
.children(self.children)
}
}---
FluentBuilder for Conditionals
div()
.when(self.disabled, |this| this.opacity(0.5).cursor_not_allowed())
.when(self.selected, |this| this.bg(cx.theme().primary))
.when_some(self.label.as_ref(), |this, label| {
this.child(div().child(label.clone()))
})Always use gpui::prelude::FluentBuilder as _; for .when() / .when_some().
---
Theme Colors
// In render, access via cx.theme() (requires ActiveTheme import)
use crate::ActiveTheme;
div()
.bg(cx.theme().surface)
.text_color(cx.theme().foreground)
.border_color(cx.theme().border)
.when(is_active, |el| el.bg(cx.theme().primary))---
Size Handling
// Get pixel values based on Size
let (width, height) = self.size.input_size();
// Or use match
let font_size = match self.size {
Size::XSmall => rems(0.75),
Size::Small => rems(0.875),
Size::Medium | Size::Size(_) => rems(1.0),
Size::Large => rems(1.125),
};---
Checklist for New Components
- [ ]
#[derive(IntoElement)] - [ ] Fields:
id: ElementId,base: Div(orStateful<Div>),style: StyleRefinement - [ ]
impl RenderOnce— calls.refine_style(&self.style)on root element - [ ]
impl Styledreturning&mut self.style - [ ]
impl ParentElementif accepts children - [ ]
impl InteractiveElement+StatefulInteractiveElementif interactive - [ ]
impl Sizableif has size variants - [ ]
impl Disableableif can be disabled - [ ]
impl Selectableif can be selected - [ ] Callbacks as
Option<Rc<dyn Fn(...)>> - [ ] Doc comment on struct and public methods
- [ ] Import
prelude::FluentBuilder as _
gpui-component Usage Guide
Contents: Setup · Component Types · Common Components (Button, Input, Select, Checkbox, Icon, Dialog, Notification, Tabs, Tooltip, Form, List) · Theming · Layout Helpers · Overlay Layers · Shared Traits
Setup
1. Cargo.toml
[dependencies]
gpui = { git = "https://github.com/zed-industries/zed" }
gpui_platform = { git = "https://github.com/zed-industries/zed", features = ["font-kit"] }
gpui-component = { git = "https://github.com/longbridge/gpui-component" }
gpui-component-assets = { git = "https://github.com/longbridge/gpui-component" } # optional icons2. Initialization
fn main() {
gpui_platform::application()
.with_assets(gpui_component_assets::Assets)
.run(move |cx| {
gpui_component::init(cx); // MUST be first
cx.spawn(async move |cx| {
cx.open_window(WindowOptions::default(), |window, cx| {
let view = cx.new(|_| MyApp);
cx.new(|cx| Root::new(view, window, cx)) // Root wraps first view
}).expect("Failed to open window");
}).detach();
});
}`Root` is required as the first-level child of every window — it enables dialogs, sheets, and notifications.
---
Component Types
Stateless (most components)
Used directly in render, no stored state:
use gpui_component::button::Button;
impl Render for MyView {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
Button::new("btn").primary().label("Submit")
.on_click(|_, _, _| println!("clicked"))
}
}Stateful (Input, Select, Combobox, etc.)
Require an Entity<State> stored in your view:
use gpui_component::input::{Input, InputState};
struct MyView {
name: Entity<InputState>,
}
impl MyView {
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
Self {
name: cx.new(|cx| InputState::new(window, cx).placeholder("Your name")),
}
}
}
impl Render for MyView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
Input::new(&self.name)
}
}---
Common Components
Button
use gpui_component::button::{Button, ButtonGroup};
// Variants
Button::new("btn").label("Default")
Button::new("btn").primary().label("Primary")
Button::new("btn").danger().label("Delete")
Button::new("btn").warning().label("Warning")
Button::new("btn").success().label("Success")
Button::new("btn").ghost().label("Ghost")
Button::new("btn").link().label("Link")
// States
Button::new("btn").label("Text").disabled(true)
Button::new("btn").label("Text").loading(true)
Button::new("btn").label("Text").selected(true)
// With icon
Button::new("btn").icon(IconName::Plus).label("Add")
// Sizes
Button::new("btn").xsmall().label("XS")
Button::new("btn").small().label("S")
Button::new("btn").large().label("L")
// Group
ButtonGroup::new("group")
.child(Button::new("a").label("A"))
.child(Button::new("b").label("B"))
.on_click(|indices, _, _| { /* selected indices */ })Input
use gpui_component::input::{Input, InputState};
// State setup (in new/init)
let input = cx.new(|cx| InputState::new(window, cx)
.placeholder("Enter text...")
.default_value("Hello")
);
// Render
Input::new(&input)
Input::new(&input).cleanable(true) // clear button
Input::new(&input).disabled(true)
Input::new(&input).prefix(Icon::new(IconName::Search).small())
Input::new(&input).suffix(Button::new("b").ghost().icon(IconName::X).xsmall())
Input::new(&input).mask_toggle() // password reveal toggle
Input::new(&input).appearance(false) // remove default border/bg
// Reading value
let value = input.read(cx).value();
// Events
cx.subscribe_in(&input, window, |view, state, event, window, cx| {
match event {
InputEvent::Change => { let v = state.read(cx).value(); }
InputEvent::PressEnter { .. } => { /* submit */ }
InputEvent::Focus | InputEvent::Blur => {}
}
});Select
use gpui_component::select::{Select, SelectState};
// Simple string list
let state = cx.new(|cx| {
SelectState::new(vec!["Apple", "Orange", "Banana"], Some(IndexPath::default()), window, cx)
});
// Render
Select::new(&state)
Select::new(&state).placeholder("Pick one")
// Reading selection
let selected = state.read(cx).selected_item();Checkbox / Switch / Radio
use gpui_component::{Checkbox, Switch};
// Stateless (controlled)
Checkbox::new("cb").checked(self.checked)
.on_click(|checked, _, cx| { /* &bool */ })
Switch::new("sw").checked(self.enabled)
.on_click(|checked, _, cx| {})Icon
use gpui_component::{Icon, IconName};
Icon::new(IconName::Check)
Icon::new(IconName::Search).small()
Icon::new(IconName::Plus).large().text_color(cx.theme().primary)Dialog
use gpui_component::dialog::Dialog;
// Open from window context
window.open_modal(cx, |modal, _, cx| {
modal
.title("Confirm")
.child(div().child("Are you sure?"))
.footer(|this, _, cx| {
this.child(Button::new("cancel").label("Cancel"))
.child(Button::new("ok").primary().label("OK")
.on_click(|_, window, cx| { window.close_modal(cx); }))
})
});Notification
// Simple string message
window.push_notification("Saved successfully!", cx);
// With type variant
window.push_notification(
Notification::new("Upload complete").info().message("File uploaded"),
cx,
);Tabs
use gpui_component::tab::{Tab, TabBar};
TabBar::new("tabs")
.child(Tab::new("tab1").child("Overview"))
.child(Tab::new("tab2").child("Settings"))
.child(Tab::new("tab3").child("Logs"))Tooltip
// On any element with .id(), add .tooltip():
div()
.id("my-btn")
.tooltip(|window, cx| Tooltip::new("Delete item").build(window, cx))
.child("Delete")
// Or on a Button directly:
Button::new("btn").icon(IconName::Trash).tooltip("Delete")Form
use gpui_component::form::{v_form, h_form, field};
// Vertical form
v_form()
.child(field().label("Name").child(Input::new(&self.name)))
.child(field().label("Email").child(Input::new(&self.email)))
.child(Button::new("submit").primary().label("Submit"))
// Horizontal label alignment
h_form()
.child(field().label("Username").child(Input::new(&self.username)))List (searchable, virtualized)
use gpui_component::list::{List, ListState, ListDelegate, ListItem, ListEvent};
// Implement ListDelegate for your data type, then:
let list_state = cx.new(|cx| ListState::new(MyDelegate::new(), window, cx));
// Render
List::new(&list_state)
// Events
cx.subscribe(&list_state, |this, _, event, cx| {
if let ListEvent::Select(index_path) = event {
// handle selection
}
});---
Theming
use gpui_component::ActiveTheme as _;
// Access colors
cx.theme().primary
cx.theme().background
cx.theme().foreground
cx.theme().border
cx.theme().surface
cx.theme().muted
cx.theme().destructive
// Use in styles
div()
.bg(cx.theme().surface)
.text_color(cx.theme().foreground)
.border_color(cx.theme().border)Switch Theme
use gpui_component::Theme;
// Toggle light/dark
cx.update_global::<Theme, _>(|theme, cx| {
theme.toggle_mode(cx);
});
// Load a named theme
Theme::global_mut(cx).apply_config(&theme_config);---
Layout Helpers
gpui-component extends GPUI with convenient layout methods:
h_flex() // div().flex().flex_row().items_center()
v_flex() // div().flex().flex_col()
// Common patterns
h_flex().gap_2().items_center()
.child(Icon::new(IconName::User))
.child(label("Username"))
v_flex().gap_4().p_4()
.child(Input::new(&self.name))
.child(Input::new(&self.email))
.child(Button::new("submit").primary().label("Submit"))---
Overlay Layers (Dialogs, Sheets, Notifications)
To render overlays, add these to your first-level view's render:
impl Render for MyApp {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.size_full()
.child(self.main_content(window, cx))
.children(Root::render_dialog_layer(cx))
.children(Root::render_sheet_layer(cx))
.children(Root::render_notification_layer(cx))
}
}---
Shared Traits
All components follow the builder pattern Component::new("id").method().method():
Sizable:.xsmall()/.small()/.medium()(default) /.large()Disableable:.disabled(bool)Selectable:.selected(bool)Styled: any GPUI style methods (.w(),.bg(),.p_2(), etc.)
For any component not covered here, fetch its doc from: https://longbridge.github.io/gpui-component/docs/components/{name}.md