limn
    ↑↓ ChooseEnter OpenEsc Close
    GitHub

    Components/Actions

    MessageActions

    The quiet row of icon actions under a chat message: copy, retry, edit. Ghost icon buttons 24 px high and 2 apart in muted ink, with muted captions and an overflow menu for what is used less.

    Native componentGuide source

    Preview

    CodeSource
    fn view(palette: ComponentPalette) -> Element {
        MessageActions::new("message-7", palette)
            .action(MessageAction::new("message-7-copy", Icon::Copy, "Copy"))
            .action(MessageAction::new("message-7-retry", Icon::Refresh, "Retry"))
            .render()
    }
    
    fn update(state: &mut State, action: &UiAction) {
        if let UiAction::Clicked { id } = action {
            if id == "message-7-copy" {
                state.copy_message(7);
            }
        }
    }

    Examples

    Each one is the library drawing it. Press, type and hover.

    User message
    CodeSource
    bubble(cx, "messages-user", "Why does the content jump on hover?", MessageActionsVisibility::Hover),
    bubble(cx, "messages-user-shown", "Always show the actions.", MessageActionsVisibility::Always),
    caption(cx, "Below the bubble, aligned to the trailing edge"),
    Element::column(vec![
        Card::new(prose(cx, "Check the narrow layout too."), palette).border(false).render(),
        MessageActions::new("messages-under", palette)
            .caption("14:32")
            .action(MessageAction::new("messages-under-copy", Icon::Copy, "Copy"))
            .action(
                MessageAction::new("messages-under-edit", Icon::Edit, "Edit")
                    .disabled("Wait for the agent to finish before editing an earlier message."),
            )
            .align_end()
            .render(),
    ])
    .gap(4.0),
    Toggles and branches
    CodeSource
    Element::row(vec![
        Pager::new("2 / 3", palette)
            .previous("messages-branch-previous", true)
            .next("messages-branch-next", true)
            .render(),
        MessageActions::new("messages-toggles", palette)
            .action(
                MessageAction::new("messages-pin", Icon::Pin, "Pin to context")
                    .active(cx.state.flag("messages-pin")),
            )
            .action(MessageAction::new("messages-toggles-copy", Icon::Copy, "Copy"))
            .render()
            .flex(1.0),
    ])
    .gap(8.0),
    Text::new("A `Pager` alongside shows the position among versions of a response.", palette)
        .role(TextRole::Small)
        .muted()
        .render(),

    MessageActions

    The quiet row of icon actions under a chat message: copy, retry, edit. Ghost icon buttons 24 px high and 2 apart in muted ink, with muted captions and an overflow menu for what is used less.

    Import

    use limn::{Component, Icon, MessageAction, MessageActions, MessageActionsVisibility};

    Usage

    A row needs an ID that is unique in the window and the palette. Items stand in the order they are added. Each action reports UiAction::Clicked with its own ID; its title is its hint, the only name an icon has.

    fn view(palette: ComponentPalette) -> Element {
        MessageActions::new("message-7", palette)
            .action(MessageAction::new("message-7-copy", Icon::Copy, "Copy"))
            .action(MessageAction::new("message-7-retry", Icon::Refresh, "Retry"))
            .render()
    }
    
    fn update(state: &mut State, action: &UiAction) {
        if let UiAction::Clicked { id } = action {
            if id == "message-7-copy" {
                state.copy_message(7);
            }
        }
    }

    Captions

    MessageActions::new("message-7", palette)
        .action(MessageAction::new("message-7-copy", Icon::Copy, "Copy"))
        .caption("5 min ago · 14 s")
        .render()

    caption adds passive muted text: a relative time, a duration, a model name. Empty text is ignored. A caption is cut with an ellipsis when the row is too narrow; the buttons keep their size.

    Visibility

    Element::column(vec![
        message,
        MessageActions::new("message-3", palette)
            .action(MessageAction::new("message-3-copy", Icon::Copy, "Copy"))
            .visibility(MessageActionsVisibility::Hover)
            .render(),
    ])
    .hover_region()
    VisibilityPainted
    AlwaysAlways: the last message of a conversation. The default.
    HoverWhile the pointer is over the message, while one of the row’s controls has keyboard focus, or while its menu is open.
    HiddenNever. Room only, with no controls: a message that is still being written.

    The row takes the same room in all three, so a message never changes height. For Hover, the message is the nearest Element::hover_region() around the row.

    Done

    MessageAction::new("message-7-copy", Icon::Copy, "Copy")
        .done(mark.shows("message-7-copy", now))

    done(true) shows a check in place of the icon: the action was just taken. The host decides for how long. MessageActionDone::new(id, now) keeps that time: shows(id, now) is true for 1.5 s (MessageActionDone::SHOWN_FOR), and repaint_after(now) gives the time left, or None once the mark has ended.

    Toggles

    MessageAction::new("message-7-pin", Icon::Pin, "Pin to context").active(state.pinned)

    active(true) fills the button: a toggle that is on.

    Disabled

    MessageAction::new("message-2-edit", Icon::Edit, "Edit")
        .disabled("Wait for the agent to finish.")

    A disabled action takes no pointer or focus, and its hint gives the reason under its title.

    Hints

    MessageAction::new("message-2-edit", Icon::Edit, "Edit").shortcut("Enter")

    shortcut shows the keys beside the title. tooltip_with replaces the hint with a whole TooltipSpec, for another placement or palette.

    Overflow

    MessageActions::new("message-7", palette)
        .action(MessageAction::new("message-7-copy", Icon::Copy, "Copy"))
        .overflow(
            vec![
                DropdownMenuItem::new("message-7-plain", "Copy as plain text").into(),
                DropdownMenuEntry::Separator,
                DropdownMenuItem::new("message-7-activity", "Show activity").into(),
            ],
            state.more_open,
            "More actions",
        )
        .render()

    overflow(entries, open, title) adds a ”···” button that shows entries as a menu while open. The host owns open: the trigger reports {id}:more, and a click outside the menu reports {id}:more:dismiss, where {id} is the row’s ID. Without entries nothing is added.

    Aligned to the end

    MessageActions::new("message-2", palette)
        .caption("14:32")
        .action(MessageAction::new("message-2-copy", Icon::Copy, "Copy"))
        .align_end()
        .render()

    align_end pushes the row to the far edge, for a user’s message. The default is the near edge, where an assistant’s text starts.

    For a branch position such as “2 / 3”, put a Pager beside the row.

    Keyboard

    KeyEffect
    TabMoves through the enabled actions and the overflow trigger in order. It reaches a Hover row that is not painted, and the row stays shown while one of its controls has the focus.

    Motion

    A Hover row fades in over enter_ms and out over exit_ms; with reduced motion it switches at once. Only the paint changes: nothing moves. See Tokens.

    Actions

    ActionWhen
    UiAction::Clicked { id } with an action’s IDThat action is activated. With dispatch_as(action_id) it reports action_id instead, while its own ID stays the focus stop.
    UiAction::Clicked { id } with {id}:moreThe overflow trigger is activated.
    UiAction::Clicked { id } with {id}:more:dismissA click lands outside the open menu.
    UiAction::Clicked { id } with a menu item’s IDThat item of the overflow menu is chosen.

    API reference

    The guide covers composition, options and actions. Generate the complete Rust type and builder reference from the checkout:

    cargo doc -p limn --open

    Component API overview · State and actions

    The example is drawn by crates/limn-gallery/src/pages/messages.rs.