limn
    ↑↓ ChooseEnter OpenEsc Close
    GitHub

    Components/States

    Badge

    A small mark over the top right corner of another element, such as an icon button or an avatar, that says there is something new or how many. It is passive: the element under it works exactly as it does without the mark.

    Native componentGuide source

    Preview

    CodeSource
    fn view(state: &State, palette: ComponentPalette) -> Element {
        let bell = IconButton::new("notifications", Icon::Bell, palette)
            .accessibility_label("Notifications")
            .render();
        Badge::new(bell, palette)
            .count(state.unread)
            .description(format!("{} unread", state.unread))
            .render()
    }
    
    fn update(state: &mut State, action: &UiAction) {
        if let UiAction::Clicked { id } = action {
            if id == "notifications" {
                state.unread = 0;
            }
        }
    }

    Examples

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

    Dot
    CodeSource
    row(vec![
        Badge::new(bell("badge-dot"), palette).dot().description("New activity").render(),
        Badge::new(bell("badge-dot-urgent"), palette).dot().color(urgent).description("Needs attention").render(),
    ])
    Maximum and zero
    CodeSource
    row(vec![
        counted("badge-max-9", 12).max(9).render(),
        counted("badge-max-99", 120).render(),
        counted("badge-max-999", 1200).max(999).render(),
        counted("badge-zero", 0).show_zero(true).render(),
        counted("badge-none", 0).render(),
    ])
    Color
    CodeSource
    row(vec![
        counted("badge-default", 5).render(),
        counted("badge-urgent", 5).color(urgent).render(),
        counted("badge-warning", 5).color(AlertColors::for_tone(AlertTone::Warning, palette).accent).render(),
    ])
    Corner and size1 / 2
    CodeSource
    row(
        [
            ("badge-corner-tr", BadgePlacement::TopRight),
            ("badge-corner-br", BadgePlacement::BottomRight),
            ("badge-corner-bl", BadgePlacement::BottomLeft),
            ("badge-corner-tl", BadgePlacement::TopLeft),
        ]
        .into_iter()
        .map(|(id, placement)| counted(id, 2).placement(placement).render())
        .collect(),
    )
    Corner and size2 / 2
    CodeSource
    row(vec![
        // A server's mentions: 17 px of white on red, ringed in the rail's color.
        Badge::new(
            Element::panel(Element::spacer())
                .background(palette.selected)
                .radius(10.0)
                .size(Some(48.0), Some(48.0))
                .accessible(Access::new(Role::Image).id("badge-server").name("Rust workshop")),
            palette,
        )
        .count(12)
        .placement(BadgePlacement::BottomRight)
        .height(23.0)
        .outline(palette.surface, 3.0)
        .color(urgent)
        .ink(Color::rgb(255, 255, 255))
        .font_size(12.0)
        .font_family(FontFamily::SansBold)
        .description(unread(12))
        .render(),
    ])
    On an avatar
    CodeSource
    row(vec![
        // An avatar is no control: it is given a node, so the mark has something to describe.
        Badge::new(
            Avatar::new("Ada Lovelace", palette)
                .render()
                .accessible(Access::new(Role::Image).id("badge-avatar").name("Ada Lovelace")),
            palette,
        )
        .dot()
        .color(AlertColors::for_tone(AlertTone::Success, palette).accent)
        .description("Online")
        .render(),
        Badge::new(
            Avatar::new("Grace Hopper", palette)
                .size(48.0)
                .render()
                .accessible(Access::new(Role::Image).id("badge-avatar-large").name("Grace Hopper")),
            palette,
        )
        .count(7)
        .description(unread(7))
        .render(),
    ])
    In a toolbar
    CodeSource
    row(vec![
        tool(cx, "badge-row-search", Icon::Search, "Search"),
        Badge::new(tool(cx, "badge-row-bell", Icon::Bell, "Notifications"), palette)
            .count(if cx.state.flag("badge-row-bell") { 0 } else { 4 })
            .description(unread(4))
            .render(),
        tool(cx, "badge-row-settings", Icon::Settings, "Settings"),
    ])
    Disabled
    CodeSource
    row(vec![Badge::new(
        IconButton::new("badge-disabled", Icon::Bell, palette)
            .variant(ButtonVariant::Outline)
            .accessibility_label("Notifications")
            .disabled()
            .render(),
        palette,
    )
    .count(2)
    .description(unread(2))
    .render()])

    Badge

    A small mark over the top right corner of another element, such as an icon button or an avatar, that says there is something new or how many. It is passive: the element under it works exactly as it does without the mark.

    Import

    use limn::{Badge, BadgePlacement, Component};

    Usage

    A badge wraps the element it marks. The host owns the count and passes it each frame; the badge reports nothing, so the action handled is the child’s.

    fn view(state: &State, palette: ComponentPalette) -> Element {
        let bell = IconButton::new("notifications", Icon::Bell, palette)
            .accessibility_label("Notifications")
            .render();
        Badge::new(bell, palette)
            .count(state.unread)
            .description(format!("{} unread", state.unread))
            .render()
    }
    
    fn update(state: &mut State, action: &UiAction) {
        if let UiAction::Clicked { id } = action {
            if id == "notifications" {
                state.unread = 0;
            }
        }
    }

    The mark is a pill 16 px high in the palette’s text color, with the count in the surface color and an outline in the surface color that separates it from what is under it. A single digit stands in a circle.

    Count

    Badge::new(bell, palette).count(3).render()                    // 3
    Badge::new(bell, palette).count(120).render()                  // 99+
    Badge::new(bell, palette).count(1200).max(999).render()        // 999+
    Badge::new(bell, palette).count(0).render()                    // no mark
    Badge::new(bell, palette).count(0).show_zero(true).render()    // 0

    max is the largest count written in full: 99 unless set, and at least 1. A larger count shows the maximum followed by +. A count of zero shows no mark unless show_zero(true). With no mark, render returns the child as it was given.

    Dot

    Badge::new(bell, palette).dot().render()

    dot shows a circle 10 px across with no number, whatever the count is: there is something new, without saying how many.

    Color

    Badge::new(bell, palette)
        .count(3)
        .color(AlertColors::for_tone(AlertTone::Error, palette).accent)
        .render()

    color sets the fill of the mark. The count and the outline stay in the surface color, so pass a color the surface reads on, or set them too.

    Corner and size

    Badge::new(server_icon, palette)
        .count(mentions)
        .placement(BadgePlacement::BottomRight)
        .height(23.0)
        .outline(rail_color, 3.0)
        .color(red)
        .ink(Color::rgb(255, 255, 255))
        .font_size(12.0)
        .font_family(FontFamily::SansBold)
        .render()
    OptionDefaultWhat it changes
    placementTopRightThe corner of the child’s box the mark sits in: TopRight, BottomRight, BottomLeft or TopLeft.
    height16The count’s height, its outline included, at least 8. A dot keeps the proportion, 10 to 16.
    outline(color, width)surface, 1The ring that parts the mark from what is under it, drawn inside the mark; 0 draws none. A count keeps 3 px clear of it.
    inksurfaceThe count’s color.
    font_size, font_family11, SansThe count’s text.

    Layout

    The badge takes the place of its child and no more: the same size, the same fixed size and the same flex, so a row of buttons does not move when one gains or loses its mark. The mark lies inside a corner of the child’s box, the top right unless placement says otherwise. A count wider than the child, such as 999+ on a 32 px button, runs past the child’s left edge, over what is beside it.

    Badge or Chip

    Use a badge for a mark that belongs to another element and lies over it: the unread count on a bell, the dot on an avatar. Use a Chip for a count or a label that stands in the flow by itself, such as the number after a section’s title or a tag in a row. A chip takes its own space in the layout; a badge takes none.

    Disabled

    A badge has no disabled state, because it is not a control. A disabled child stays disabled and keeps its mark.

    Keyboard

    None of its own. The badge adds no tab stop; Tab, Enter and Space reach the child as they do without it.

    Actions

    None. A click on the child reports the child’s action, also where the mark lies over it.

    Accessibility

    The badge has no node of its own. What it means reaches the platform as the description of the control inside it, in words the host gives:

    Badge::new(bell, palette).count(3).description("3 unread").render()

    Without description the platform is told nothing of the mark, and nothing is said while no mark shows. The description replaces one the child gave itself. It goes to the outermost control inside the badge; a child that is not a control, such as an avatar, needs a node to carry it:

    let photo = Avatar::new("Ada Lovelace", palette)
        .render()
        .accessible(Access::new(Role::Image).id("author").name("Ada Lovelace"));
    Badge::new(photo, palette).dot().description("Online").render()

    Limits

    • The mark does not hang outside the child’s box.
    • It appears and leaves at once, without a transition.
    • It shows a count or a dot, not an icon.

    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/badge.rs.