AquaButton

A glossy pill button inspired by Apple's Aqua interface, with layered highlights and a soft inner glow.

Preview

Loading GPUI

Props

Secondary

Loading GPUI
AquaButton::new("cancel", "Cancel").variant(AquaButtonVariant::Secondary)

Icon Button

Make it a fixed square with a smaller radius for a compact icon button.

Loading GPUI
AquaButton::new("menu", "")
    .square(px(36.0))
    .radius(px(6.0))
    .icon(|scale| disclosure_triangle(scale).into_any_element())

Install

cargo add evilbuttons

Usage

use evilbuttons::{AquaButton, AquaButtonVariant};
use gpui::{div, prelude::*, px, Context, IntoElement, Render, Window};

struct ButtonDemo;

impl Render for ButtonDemo {
    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
        div()
            .flex()
            .gap(px(16.0))
            .child(AquaButton::new("cancel", "Cancel").variant(AquaButtonVariant::Secondary))
            .child(AquaButton::new("save", "Save").on_click(|_, _, _| save()))
    }
}

The icon closure gets the press scale, so the icon shrinks with the button. Paint with window.text_style().color to follow the label colour:

fn disclosure_triangle(scale: f32) -> impl IntoElement {
    canvas(
        |_, _, _| {},
        |bounds, _, window, _| {
            // M1 3 L9 3 L5 8 Z on a 10-unit grid.
            let unit = bounds.size.width / 10.0;
            let p = |x: f32, y: f32| point(bounds.origin.x + unit * x, bounds.origin.y + unit * y);
            let mut path = PathBuilder::fill();
            path.move_to(p(1.0, 3.0));
            path.line_to(p(9.0, 3.0));
            path.line_to(p(5.0, 8.0));
            path.close();
            if let Ok(path) = path.build() {
                let color = window.text_style().color;
                window.paint_path(path, color);
            }
        },
    )
    .size(px(11.0 * scale))
}

Props

AquaButton is a GPUI RenderOnce builder; its press state is keyed by id.

MethodTypeDefaultDescription
new(id, label)impl Into<ElementId>, impl Into<SharedString>-Unique id and the visible label. Pass "" for an icon-only button.
variant(v)AquaButtonVariantPrimaryBlue Aqua gel (Primary) or adaptive silver/dark glass (Secondary).
dark(bool)boolwindow appearanceUse the dark palette for the secondary variant.
square(side)impl Into<Pixels>-Fixed square size with no padding, for icon buttons.
radius(r)impl Into<Pixels>full pillCorner radius.
icon(f)Fn(f32) -> AnyElement-Content after the label; receives the press scale.
font_family(family)impl Into<SharedString>inheritedFont family for the label.
on_click(f)Fn(&ClickEvent, &mut Window, &mut App)-Fired on click.
disabled(bool)boolfalseDims the button and ignores input.

Good to know

  • Sized from a 16px em like the original: 28px tall, at least 96px wide, 32px side padding.
  • Like the live React button, the shadows stay put on hover and press (the original's inline box-shadow overrides its hover and active shadows); hover lifts it 1px, press scales it to 97%.
  • GPUI has no filter: blur(), so the bottom glow is softened with blurred shadows instead.

Search docs

Jump to a button documentation page