Agent skill
gpui-component
Community UI component library for GPUI applications
Install this agent skill to your Project
npx add-skill https://github.com/johnlindquist/script-kit-next/tree/main/.opencode/skill/gpui-component
SKILL.md
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
# 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)
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:
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:
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:
let editor_state = cx.new(|cx|
InputState::new(window, cx)
.code_editor(true)
.soft_wrap(true)
.show_line_numbers(true)
);
Notifications
Toast-style notifications:
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
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:
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:
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:
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
// 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
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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
Generate Component Documentation
Based on existing docs styles and specific API implementations, and referencing same name stories, generate comprehensive documentation for the new component.
Generate Component Story
Generate a comprehensive story for a new component for as example.
new-component
How to write a new component of GPUI Component.
troubleshooting
Diagnose and fix common Script Kit issues. Use when the user reports bugs, crashes, missing features, or unexpected behavior in Script Kit GPUI.
script-authoring
Create and manage TypeScript scripts for Script Kit. Use when the user wants to write a new script, edit an existing script, or understand Script Kit's SDK and metadata system.
agents
Create mdflow-backed agent files for Script Kit. Use when the user wants to create AI agents, configure agent backends (Claude, Gemini, Codex), or manage agent metadata.
Didn't find tool you were looking for?