Skip to content
Back to skills

Gpui Component

ASecurity

Community UI component library for GPUI applications

  • 666 stars
  • 0 votes
  • 0 copies
  • 5 views
  • Added February 8, 2026
developmentrustgogitdocumentation

Works with

  • cli

Security analysis

A100/100

Scanned February 10, 2026

npx -y skills add majiayu000/claude-skill-registry --skill gpui-component --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Gpui Component?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Gpui Component
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-gpui-component/badge)](https://www.skillsdirectory.com/skills/majiayu000-gpui-component)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: gpui-component
description: Community UI component library for GPUI applications
triggers:
  - gpui_component
  - gpui-component
  - Input
  - InputState
  - InputEvent
  - Button
  - Root
  - notification
  - IconName
---

# gpui-component

A comprehensive UI component library for building desktop applications with GPUI. Provides 60+ cross-platform components inspired by macOS/Windows controls and shadcn/ui design.

**Repository**: https://github.com/longbridge/gpui-component
**Fork (script-kit-gpui)**: https://github.com/johnlindquist/gpui-component
**Docs**: https://gpui-component.longbridge.xyz/

## Installation

```toml
# In Cargo.toml - using the fork for set_selection() support
gpui-component = { git = "https://github.com/johnlindquist/gpui-component", package = "gpui-component" }

# Required for i18n support
rust-i18n = "3"
```

## Initialization (REQUIRED)

```rust
use gpui_component::Root;

fn main() {
    let app = Application::new();
    app.run(move |cx| {
        // MUST be called before using any gpui-component features
        gpui_component::init(cx);
        
        // Open windows wrapped in Root...
    });
}
```

## Available Components

### Core UI
- **Button** - Primary, secondary, ghost, link variants with icons
- **Input** - Text input with validation, masks, placeholders
- **Checkbox** - Checkable controls
- **Radio** - Radio button groups
- **Switch** - Toggle switches
- **Select** - Dropdown selection
- **Slider** - Range selection

### Layout
- **Root** - Theme/context provider wrapper (REQUIRED for all windows)
- **Dock** - Panel arrangements with resizing
- **Accordion** - Collapsible content sections
- **Collapsible** - Show/hide content
- **Divider** - Visual separators
- **GroupBox** - Grouped content with border

### Feedback
- **Notification** - Toast notifications (Success, Warning, Error, Info)
- **Alert** - Alert dialogs
- **Dialog** - Modal dialogs
- **Sheet** - Slide-in panels
- **Progress** - Progress indicators
- **Spinner** - Loading spinners
- **Skeleton** - Loading placeholders

### Data Display
- **Table** - Virtualized tables (supports 200K+ rows)
- **List** - Virtualized lists
- **Tree** - Tree views
- **Badge** - Status badges
- **Tag** - Categorization tags
- **Avatar** - User avatars
- **Rating** - Star ratings

### Navigation
- **Menu** - Context menus, dropdowns
- **Breadcrumb** - Navigation breadcrumbs
- **Tab** - Tab navigation
- **Sidebar** - Side navigation
- **Pagination** - Page navigation

### Content
- **Icon** - Lucide icons (700+ icons)
- **Label** - Text labels
- **Link** - Hyperlinks
- **Kbd** - Keyboard shortcuts display
- **Text** - Markdown/HTML rendering
- **Chart** - Data visualization

### Advanced
- **ColorPicker** - Color selection
- **DatePicker** - Date/time selection
- **Popover** - Floating content
- **Tooltip** - Hover tooltips
- **Resizable** - Resizable panels

## Usage in script-kit-gpui

### Root Wrapper Pattern
All windows MUST be wrapped in `Root` for theming and context:

```rust
use gpui_component::Root;

// Opening a window
let window: WindowHandle<Root> = cx.open_window(
    WindowOptions { ... },
    |window, cx| {
        let view = cx.new(|cx| MyApp::new(cx));
        cx.new(|cx| Root::new(view, window, cx))
    },
)?;
```

### Input Component
The most commonly used component for text input:

```rust
use gpui_component::input::{Input, InputEvent, InputState};

// Create InputState
let input_state = cx.new(|cx| 
    InputState::new(window, cx)
        .placeholder("Enter text...")
);

// Subscribe to events
cx.subscribe_in(&input_state, window, |this, _, event: &InputEvent, window, cx| {
    match event {
        InputEvent::Focus => { /* input focused */ }
        InputEvent::Blur => { /* input blurred */ }
        InputEvent::Change => { 
            let value = this.input_state.read(cx).value();
        }
        InputEvent::PressEnter { secondary } => { /* enter pressed */ }
    }
});

// Render
Input::new(input_state.clone())
    .size(Size::Medium)
    .cleanable()  // show clear button
```

### Code Editor Mode
For multi-line code editing:

```rust
let editor_state = cx.new(|cx| 
    InputState::new(window, cx)
        .code_editor(true)
        .soft_wrap(true)
        .show_line_numbers(true)
);
```

### Notifications
Toast-style notifications:

```rust
use gpui_component::notification::{Notification, NotificationType};

let notification = Notification::new()
    .title("Success!")
    .description("Operation completed")
    .notification_type(NotificationType::Success);

// NotificationList handles display automatically when using Root
```

### Button Component
```rust
use gpui_component::button::{Button, ButtonVariants};

Button::new("submit")
    .primary()
    .label("Submit")
    .icon(IconName::Check)
    .on_click(|_, window, cx| {
        // handle click
    })
```

### Icons
Uses Lucide icon set:

```rust
use gpui_component::{Icon, IconName, IconNamed};

// As element
Icon::new(IconName::Search)
    .size(px(16.))
    .color(theme.foreground)

// In buttons
Button::new("search").icon(IconName::Search)
```

### Theming Integration
Script-kit-gpui maps its theme to gpui-component's ThemeColor:

```rust
use gpui_component::theme::{Theme as GpuiTheme, ThemeColor, ThemeMode, ActiveTheme};

// Get current theme
let theme = cx.theme();
let colors = &theme.colors;

// Map custom colors
let mut theme_color = *ThemeColor::dark();
theme_color.background = hsla(...);
theme_color.foreground = hsla(...);
theme_color.accent = hsla(...);

// Apply globally
let theme = GpuiTheme::global_mut(cx);
theme.colors = theme_color;
theme.mode = ThemeMode::Dark;
```

### Sizing
Components support consistent sizing:

```rust
use gpui_component::{Sizable, Size};

Input::new(state).size(Size::Small)   // xs, sm, md, lg
Button::new("btn").size(Size::Large)
```

## Fork Modifications

The fork at `johnlindquist/gpui-component` adds two features for snippet/template support:

### 1. `set_selection()` - Programmatic Text Selection
```rust
// Select text range by byte offsets
input_state.update(cx, |state, cx| {
    state.set_selection(start_bytes, end_bytes, window, cx);
});

// Get current selection
let selection: Range<usize> = input_state.read(cx).selection();
```

**Use case**: Snippet tabstop navigation - select placeholder text when moving between tabstops.

### 2. `tab_navigation_mode` - Tab Key Propagation
```rust
let editor_state = cx.new(|cx| 
    InputState::new(window, cx)
        .tab_navigation(true)  // Tab/Shift+Tab propagate instead of indent
);

// Or set dynamically
input_state.update(cx, |state, _| {
    state.set_tab_navigation(true);
});
```

**Use case**: When in snippet mode, Tab navigates to next tabstop instead of inserting indentation.

## Key Patterns

### Window Lifecycle
```rust
// Store window handle
static MY_WINDOW: OnceLock<Mutex<Option<WindowHandle<Root>>>> = OnceLock::new();

// Open window
let handle = cx.open_window(..., |window, cx| {
    cx.new(|cx| Root::new(my_view, window, cx))
})?;

// Store handle
MY_WINDOW.get_or_init(|| Mutex::new(None))
    .lock().unwrap().replace(handle);

// Update window
if let Some(handle) = get_window_handle() {
    handle.update(cx, |root, window, cx| {
        // Access view through root
    }).ok();
}
```

### Event Subscription Pattern
```rust
// Subscribe to entity events
let subscription = cx.subscribe_in(&entity, window, |this, _, event, window, cx| {
    // Handle event
});

// Store subscription to keep it alive
self.subscriptions.push(subscription);
```

### Focus Management
```rust
// Focus input
input_state.update(cx, |state, cx| {
    state.focus(window, cx);
});

// Check focus
if input_state.read(cx).is_focused(window) { ... }
```

## Anti-patterns

### Missing Root Wrapper
```rust
// WRONG - gpui-component widgets won't theme correctly
cx.open_window(..., |window, cx| {
    cx.new(|_| MyApp::new())
});

// CORRECT - wrap in Root
cx.open_window(..., |window, cx| {
    let view = cx.new(|_| MyApp::new());
    cx.new(|cx| Root::new(view, window, cx))
});
```

### Missing init() Call
```rust
// WRONG - components may not initialize properly
app.run(move |cx| {
    cx.open_window(...);
});

// CORRECT - call init before using components
app.run(move |cx| {
    gpui_component::init(cx);  // FIRST
    cx.open_window(...);
});
```

### Forgetting to Store Subscriptions
```rust
// WRONG - subscription dropped immediately, events not received
cx.subscribe_in(&state, window, |_, _, _, _, _| { ... });

// CORRECT - store subscription
self.subscription = Some(cx.subscribe_in(&state, window, |_, _, _, _, _| { ... }));
```

### Using Char Offsets for Selection
```rust
// WRONG - set_selection uses byte offsets, not char offsets
let char_pos = 5;
state.set_selection(char_pos, char_pos + 3, window, cx);

// CORRECT - convert char to byte offset
fn char_to_byte(text: &str, char_offset: usize) -> usize {
    text.char_indices()
        .nth(char_offset)
        .map(|(i, _)| i)
        .unwrap_or(text.len())
}
let start = char_to_byte(&text, 5);
let end = char_to_byte(&text, 8);
state.set_selection(start, end, window, cx);
```

### Not Handling InputEvent Variants
```rust
// WRONG - only handling some events
match event {
    InputEvent::Change => { ... }
    _ => {}  // Missing Focus/Blur handling
}

// CORRECT - handle all relevant events
match event {
    InputEvent::Focus => { self.focused = true; cx.notify(); }
    InputEvent::Blur => { self.focused = false; cx.notify(); }
    InputEvent::Change => { self.handle_change(cx); }
    InputEvent::PressEnter { secondary } => { self.submit(cx); }
}
```

## Common Imports

```rust
// Core
use gpui_component::Root;
use gpui_component::{Sizable, Size};

// Input
use gpui_component::input::{Input, InputEvent, InputState};
use gpui_component::input::{IndentInline, OutdentInline, Position};  // For code editor

// Button
use gpui_component::button::{Button, ButtonVariants};

// Icons
use gpui_component::{Icon, IconName, IconNamed};

// Notifications
use gpui_component::notification::{Notification, NotificationType};

// Theme
use gpui_component::theme::{ActiveTheme, Theme, ThemeColor, ThemeMode};

// Window utilities
use gpui_component::WindowExt;
```

## Resources

- [Upstream Repository](https://github.com/longbridge/gpui-component)
- [Documentation](https://gpui-component.longbridge.xyz/)
- [Icon Reference (Lucide)](https://lucide.dev/icons/)
- [Fork with set_selection()](https://github.com/johnlindquist/gpui-component)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.