Skip to content

Button

A focusable button that emits a message when pressed. Tab moves between buttons; Enter, Space, or a left click presses one.

ButtonSize::Small is the default: a single row, no border.

rust
use ratcn::Button;

ratcn.render(frame, state, &state.theme, |ctx| {
    let save = Button::new("Save")
        .disabled(state.saving)
        .on_press(|| Msg::Save);

    ctx.render_component("save", save, save_area);
});

A button holds no state — its label and disabledness are values you pass at declaration, so they come straight from app state.

Variants

Five variants set visual weight. They only change colors — all are pressed the same way. Each has a shorthand builder, or pass one to .variant(...).

VariantShorthandUse for
DefaultThe main action on a screen. Filled with the primary color.
Secondary.secondary()A supporting action. Filled, but muted.
Outline.outline()A quiet action that still needs an edge. Border, no fill.
Ghost.ghost()The quietest. No fill or border until focused or hovered.
Destructive.destructive()Deleting or discarding. Filled with the destructive color.

Outline needs a Large button to have a border to draw. At Small it looks like Ghost, so the demo above leaves it out.

Large

ButtonSize::Large is three rows: room for a border, or a fill cap above and below the label. A large button with fewer than three rows does not participate in focus or pointer interaction; any nonzero width remains usable and clips the label as needed. If the supplied area is taller than the button, only the first one or three rows participate; blank excess rows are neither painted nor focus or click targets.

rust
use ratcn::{Button, ButtonSize};

Button::new("Delete")
    .destructive()
    .size(ButtonSize::Large)
    .on_press(|| Msg::Delete)

Use .height() or ButtonSize::Large.height() for layout constraints rather than hard-coding a number, and .width() to size a button to its own label.

Disabled

A disabled button is greyed out, skipped by Tab, and ignores events. Pass the value from state — current state is in scope during declaration. An ignored click can bubble to a containing component, but it does not click through to an overlapping sibling behind the button.

rust
Button::new("Save")
    .disabled(!state.can_save)
    .on_press(|| Msg::Save)

Styling

Colors derive from the theme and the variant, so most buttons need no styling call. To recolor one button, override the resolved style. The closure receives the active theme each render, so a style built from it follows theme switches:

rust
use ratcn::{ButtonStyle, ButtonVariant};

Button::new("Archive")
    .on_press(|| Msg::Archive)
    .style(|theme| {
        let mut style = ButtonStyle::from_theme(theme, ButtonVariant::Default);
        style.background = theme.accent;
        style.border = theme.accent;
        style
    })

ButtonStyle::fallback() is the no-theme starting point: plain ANSI colors that render on any terminal. For deeper changes, copy the component module into your project and edit it there.

Paint-Only Widget

ButtonWidget draws a button without focus or events. It is an ordinary Ratatui widget, so it works in a plain Ratatui app with no Ratcn runtime. It has the same variant and size builders, and you supply the interaction states — .focused(...), .hovered(...), and .disabled(...):

rust
use ratcn::{ButtonSize, ButtonWidget};

frame.render_widget(
    ButtonWidget::new("Save")
        .secondary()
        .size(ButtonSize::Large)
        .themed(&theme)
        .focused(is_focused)
        .hovered(is_hovered),
    area,
);

Full API

Every method, with parameter and edge-case detail: Button, ButtonWidget, ButtonVariant, ButtonSize, ButtonStyle.