Button

The Button element with multiple variants, sizes, and states. Supports icons, loading states, and can be grouped together.

#Import

use gpui_kit::component::{
    Sizable as _,
    button::{Button, ButtonGroup, ButtonVariants as _},
};

#Usage

The marked recipe below is a complete, Tested consumer recipe. The remaining examples are contextual fragments; keep the imports above when using variant or size builders.

use gpui_kit::IntoElement;
use gpui_kit::component::{
    Sizable as _,
    button::{Button, ButtonVariants as _},
};

pub fn primary_command() -> impl IntoElement {
    Button::new("save").primary().small().label("Save changes")
}

#Basic Button

Button::new("my-button")
    .label("Click me")
    .on_click(|_, _, _| {
        println!("Button clicked!");
    })

#Variants

use gpui_kit::component::button::ButtonVariants as _;

// Primary button
Button::new("btn-primary").primary().label("Primary")

// Secondary button (default)
Button::new("btn-secondary").label("Secondary")

// Danger button
Button::new("btn-danger").danger().label("Delete")

// Warning button
Button::new("btn-warning").warning().label("Warning")

// Success button
Button::new("btn-success").success().label("Success")

// Info button
Button::new("btn-info").info().label("Info")

// Ghost button
Button::new("btn-ghost").ghost().label("Ghost")

// Link button
Button::new("btn-link").link().label("Link")

// Text button
Button::new("btn-text").text().label("Text")

#Outline Buttons

Outline style is not a variant itself, but can be combined with other variants.

use gpui_kit::component::button::ButtonVariants as _;

Button::new("btn").primary().outline().label("Primary Outline")
Button::new("btn").danger().outline().label("Danger Outline")

#Compact Button

The compact method reduces the padding of the button for a more condensed appearance.

// Compact (reduced padding)
Button::new("btn")
    .label("Compact")
    .compact()

#Sizeable

The Button supports the Sizable trait for different sizes.

use gpui_kit::component::Sizable as _;

Button::new("btn").xsmall().label("Extra Small")
Button::new("btn").small().label("Small")
Button::new("btn").label("Medium") // default
Button::new("btn").large().label("Large")

#With Icons

The icon method supports multiple types, allowing you to use different visual indicators:

  • Icon / IconName - Static icons for actions and visual cues
  • Spinner - Animated loading indicator for async operations
  • ProgressCircle - Circular progress indicator showing completion percentage

All icon types automatically adapt to the button’s size and can be customized with colors and other properties.

#Icon Types

use gpui_kit::component::{Icon, IconName};

// Using IconName (simplest)
Button::new("btn")
    .icon(IconName::Check)
    .label("Confirm")

// Using Icon with custom size
Button::new("btn")
    .icon(Icon::new(IconName::Heart))
    .label("Like")

// Icon only (no label)
Button::new("btn")
    .icon(IconName::Search)

#Spinner Icon

Use a Spinner to indicate loading or processing state:

use gpui_kit::component::{ActiveTheme as _, spinner::Spinner};

// Basic spinner
Button::new("btn")
    .icon(Spinner::new())
    .label("Loading...")

// Spinner with custom color
Button::new("btn")
    .icon(Spinner::new().color(cx.theme().blue))
    .label("Processing")

// Spinner with icon
Button::new("btn")
    .icon(Spinner::new().icon(IconName::LoaderCircle))
    .label("Syncing")

#ProgressCircle Icon

Use a ProgressCircle to show progress percentage:

use gpui_kit::component::{
    ActiveTheme as _, Sizable as _,
    button::ButtonVariants as _,
    progress::ProgressCircle,
};

// Basic progress circle
Button::new("btn")
    .icon(ProgressCircle::new("install-progress").value(45.0))
    .label("Installing...")

// Progress circle with custom color
Button::new("btn")
    .primary()
    .icon(
        ProgressCircle::new("download-progress")
            .value(75.0)
            .color(cx.theme().primary_foreground)
    )
    .label("Downloading")

// Different sizes
Button::new("btn")
    .small()
    .icon(ProgressCircle::new("progress-1").value(60.0))
    .label("Installing...")

Button::new("btn")
    .large()
    .icon(ProgressCircle::new("progress-2").value(80.0))
    .label("Installing...")

#Dynamic Icon Updates

Icons can be updated dynamically based on component state:

struct InstallButton {
    progress: f32,
    is_installing: bool,
}

impl InstallButton {
    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
        let button = Button::new("install-btn")
            .label(if self.is_installing {
                "Installing..."
            } else {
                "Install"
            });

        if self.is_installing {
            button.icon(
                ProgressCircle::new("install-progress")
                    .value(self.progress)
            )
        } else {
            button.icon(IconName::Download)
        }
    }
}

#Loading State with Icons

When a button is in loading state, it automatically handles icon transitions:

// If icon is already a Spinner or ProgressCircle, it will be shown during loading
Button::new("btn")
    .icon(Spinner::new())
    .label("Processing")
    .loading(true) // Spinner will continue to show

// If icon is a regular Icon, it will be replaced with a Spinner during loading
Button::new("btn")
    .icon(IconName::Save)
    .label("Saving")
    .loading(true) // Icon will be replaced with Spinner

#With a dropdown caret icon

The .dropdown_caret method can allows adding a dropdown caret icon to end of the button.

Button::new("btn")
    .label("Options")
    .dropdown_caret(true)

#Button States

There have disabled, loading, selected state for buttons to indicate different statuses.

use gpui_kit::component::{Disableable as _, Selectable as _};

// Disabled
Button::new("btn")
    .label("Disabled")
    .disabled(true)

// Loading
Button::new("btn")
    .label("Loading")
    .loading(true)

// Selected
Button::new("btn")
    .label("Selected")
    .selected(true)

#Button Group

ButtonGroup::new("btn-group")
    .child(Button::new("btn1").label("One"))
    .child(Button::new("btn2").label("Two"))
    .child(Button::new("btn3").label("Three"))

#Toggle Button Group

use gpui_kit::component::Selectable as _;

ButtonGroup::new("toggle-group")
    .multiple(true) // Allow multiple selections
    .child(Button::new("btn1").label("Option 1").selected(true))
    .child(Button::new("btn2").label("Option 2"))
    .child(Button::new("btn3").label("Option 3"))
    .on_click(|selected_indices, _, _| {
        println!("Selected: {:?}", selected_indices);
    })

#Custom Variant

use gpui_kit::component::{
    ActiveTheme as _, Colorize as _,
    button::{ButtonCustomVariant, ButtonVariants as _},
};

let custom = ButtonCustomVariant::new(cx)
    .color(cx.theme().magenta)
    .foreground(cx.theme().primary_foreground)
    .hover(cx.theme().magenta.opacity(0.1))
    .active(cx.theme().magenta);

Button::new("custom-btn")
    .custom(custom)
    .label("Custom Button")

#API Reference

#Examples

#With Tooltip

Button::new("btn")
    .label("Hover me")
    .tooltip("This is a helpful tooltip")
    .tooltip_placement(Placement::Bottom)

Use .tooltip_placement(...) to prefer a side for either .tooltip(...) or .tooltip_with_action(...). The tooltip still flips when that side does not fit. Omit placement to keep automatic positioning.

#Custom Children

Button::new("btn")
    .child(
        h_flex()
            .items_center()
            .gap_2()
            .child("Custom Content")
            .child(IconName::ChevronDown)
            .child(IconName::Eye)
    )

CC BY 4.0 · Documentation prose and original illustrations GPUI Kit has rights to license may be copied or adapted with attribution. License. Credit GPUI Kit, link to this source page and the CC BY 4.0 license, and indicate any changes. Code examples and software source remain under Apache-2.0. Existing Apache permissions remain; third-party material retains its own terms.