Skip to content
PorabuildHeroGPUI component library
v0.14.0 · Apache-2.0

Media

AvatarGroup

Display a stacked or grid group of avatars with overflow counting.

Rust
use herogpui::components::avatar_group::AvatarGroup;

AvatarGroup on docs.rsSource at v0.14.0

Usage

Showing Usage, live: HeroGPUI compiled to WebAssembly. The canvas has no accessibility tree; each example's description and code below carry the same information.

Usage

An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{Avatar, AvatarGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

const USERS: [&str; 5] = [
    "John Doe",
    "Kate Wilson",
    "Emily Chen",
    "Michael Brown",
    "Olivia Davis",
];
fn users(example: &'static str, take: usize) -> Vec<Avatar> {
    USERS
        .iter()
        .take(take)
        .enumerate()
        .map(|(i, name)| Avatar::new((example, i)).name(*name))
        .collect()
}

gpui::div()
    .flex()
    .flex_wrap()
    .items_center()
    .justify_center()
    .gap(px(12.))
    .children(vec![AvatarGroup::new("avatar-group-basic")
        .children(users("ag-basic-user", 4))
        .into_any_element()])

Max

An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{Avatar, AvatarGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

const USERS: [&str; 5] = [
    "John Doe",
    "Kate Wilson",
    "Emily Chen",
    "Michael Brown",
    "Olivia Davis",
];
fn users(example: &'static str, take: usize) -> Vec<Avatar> {
    USERS
        .iter()
        .take(take)
        .enumerate()
        .map(|(i, name)| Avatar::new((example, i)).name(*name))
        .collect()
}

gpui::div()
    .flex()
    .flex_wrap()
    .items_center()
    .justify_center()
    .gap(px(12.))
    .children(vec![AvatarGroup::new("avatar-group-max")
        .max(3)
        .children(users("ag-max-user", 5))
        .into_any_element()])

With Count

An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{Avatar, AvatarGroup, AvatarGroupCount, Size};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

const USERS: [&str; 5] = [
    "John Doe",
    "Kate Wilson",
    "Emily Chen",
    "Michael Brown",
    "Olivia Davis",
];
fn users(example: &'static str, take: usize) -> Vec<Avatar> {
    USERS
        .iter()
        .take(take)
        .enumerate()
        .map(|(i, name)| Avatar::new((example, i)).name(*name))
        .collect()
}

gpui::div()
    .flex()
    .flex_wrap()
    .items_center()
    .justify_center()
    .gap(px(12.))
    .children(vec![AvatarGroup::new("avatar-group-count")
        .size(Size::Sm)
        .children(users("ag-count-user", 3))
        .count(AvatarGroupCount::new("avatar-group-count-total").child(format!("+{}", 12 - 3)))
        .into_any_element()])

Sizes

An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{Avatar, AvatarGroup, Size};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

const USERS: [&str; 5] = [
    "John Doe",
    "Kate Wilson",
    "Emily Chen",
    "Michael Brown",
    "Olivia Davis",
];
fn users(example: &'static str, take: usize) -> Vec<Avatar> {
    USERS
        .iter()
        .take(take)
        .enumerate()
        .map(|(i, name)| Avatar::new((example, i)).name(*name))
        .collect()
}

gpui::div()
    .flex()
    .flex_col()
    .items_start()
    .justify_center()
    .gap(px(12.))
    .children(vec![
        AvatarGroup::new("avatar-group-size-sm")
            .size(Size::Sm)
            .children(users("ag-size-sm-user", 4))
            .into_any_element(),
        AvatarGroup::new("avatar-group-size-md")
            .size(Size::Md)
            .children(users("ag-size-md-user", 4))
            .into_any_element(),
        AvatarGroup::new("avatar-group-size-lg")
            .size(Size::Lg)
            .children(users("ag-size-lg-user", 4))
            .into_any_element(),
    ])

Grid

An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{Avatar, AvatarGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

const USERS: [&str; 5] = [
    "John Doe",
    "Kate Wilson",
    "Emily Chen",
    "Michael Brown",
    "Olivia Davis",
];
fn users(example: &'static str, take: usize) -> Vec<Avatar> {
    USERS
        .iter()
        .take(take)
        .enumerate()
        .map(|(i, name)| Avatar::new((example, i)).name(*name))
        .collect()
}

gpui::div()
    .flex()
    .flex_wrap()
    .items_center()
    .justify_center()
    .gap(px(12.))
    .children(vec![AvatarGroup::new("avatar-group-grid")
        .is_grid(true)
        .max(5)
        .children(users("ag-grid-user", 5))
        .into_any_element()])

Overlap

`ring` is a solid 2px outline in the background color. V3's default `clip` cuts a transparent crescent, which gpui cannot mask; paints that crescent in the background color, so both read the same on a solid background. An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{Avatar, AvatarGroup, AvatarGroupCount, AvatarGroupOverlap, Size};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
use std::sync::Arc;

fn overlap_group(id: &'static str, overlap: AvatarGroupOverlap) -> AvatarGroup {
    AvatarGroup::new(id)
        .overlap(overlap)
        .size(Size::Lg)
        .child(
            Avatar::new((id, 0usize))
                .src(sample_avatar_source())
                .name("JD"),
        )
        .child(Avatar::new((id, 1usize)).name("AB"))
        .child(
            Avatar::new((id, 2usize))
                .src(sample_avatar_source())
                .name("EC"),
        )
        .child(Avatar::new((id, 3usize)).name("SM"))
        .count(AvatarGroupCount::new((id, 4usize)).child("+2"))
}

gpui::div()
    .flex()
    .flex_col()
    .items_start()
    .justify_center()
    .gap(px(12.))
    .children(vec![
        overlap_group("ag-overlap-clip", AvatarGroupOverlap::Clip)
            .into_any_element(),
        overlap_group("ag-overlap-ring", AvatarGroupOverlap::Ring)
            .into_any_element(),
    ])

Also calls sample_avatar_source (included when you copy).

Custom Styles

`overlap_distance` and `seam` stand in for the `--avatar-group-overlap` and `--avatar-group-seam` custom properties. An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{ActiveTheme, Avatar, AvatarGroup, AvatarGroupCount, AvatarGroupOverlap, Size, icons};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

const USERS: [&str; 5] = [
    "John Doe",
    "Kate Wilson",
    "Emily Chen",
    "Michael Brown",
    "Olivia Davis",
];
fn users(example: &'static str, take: usize) -> Vec<Avatar> {
    USERS
        .iter()
        .take(take)
        .enumerate()
        .map(|(i, name)| Avatar::new((example, i)).name(*name))
        .collect()
}
let colors = cx.colors();

gpui::div()
    .flex()
    .flex_wrap()
    .items_center()
    .justify_center()
    .gap(px(12.))
    .children(vec![gpui::div()
        .flex()
        .items_center()
        .gap(px(10.))
        .rounded_full()
        .border_1()
        .border_color(colors.border)
        .bg(colors.surface.background)
        .py(px(4.))
        .pl(px(4.))
        .pr(px(12.))
        .shadow_sm()
        .child(
            AvatarGroup::new("avatar-group-custom")
                .overlap(AvatarGroupOverlap::Clip)
                .size(Size::Sm)
                .overlap_distance(px(11.2))
                .seam(px(2.))
                .children(users("ag-custom-user", 3))
                .child(
                    Avatar::new("avatar-group-custom-icon")
                        .fallback(gpui::svg()
                                      .size(px(16.))
                                      .path(icons::HEART_FILL)
                                      .text_color(cx.colors().foreground)
                                      .into_any_element()),
                )
                .count(AvatarGroupCount::new("avatar-group-custom-count").child("+3")),
        )
        .child(
            gpui::div()
                .text_size(px(14.))
                .font_weight(gpui::FontWeight::MEDIUM)
                .text_color(colors.foreground)
                .child("Assignees"),
        )
        .into_any_element()])

Helpers used by these examples

Plain functions over the public API, defined once here and copied with each example that calls them.

sample_avatar_source
/// One stable custom image source for the Avatar "Custom Image Component"
/// demo: the loader `Arc` is built once, so the avatar's per-source keyed
/// state and its `on_load` keep their identity across frames.
fn sample_avatar_source() -> gpui::ImageSource {
    type AvatarLoader = std::sync::Arc<
        dyn Fn(
                &mut gpui::Window,
                &mut gpui::App,
            )
                -> Option<Result<std::sync::Arc<gpui::RenderImage>, gpui::ImageCacheError>>
            + Send
            + Sync,
    >;
    static LOADER: std::sync::OnceLock<AvatarLoader> = std::sync::OnceLock::new();
    let loader: &AvatarLoader = LOADER.get_or_init(|| {
        let image = std::sync::Arc::new(gpui::Image::from_bytes(
            gpui::ImageFormat::Png,
            include_bytes!("../../../assets/herogpui/sample.png").to_vec(),
        ));
        std::sync::Arc::new(move |window: &mut gpui::Window, cx: &mut gpui::App| {
            image.clone().use_render_image(window, cx).map(Ok)
        })
    });
    gpui::ImageSource::Custom(loader.clone())
}

Anatomy

Rust types

AvatarGroupAvatarGroupCount

AvatarGroup is assembled from these builders. The API reference lists each one.

Customization

Shared appearance belongs on ThemeBuilder::components and .recipe("name"). Typed builders change how AvatarGroup looks at one call site. Every builder also carries sx for placement, width and flex: it takes GPUI's own styling methods and refines them over the component's root after the theme, so an instance override wins. Behaviour builders live in the API reference below.

Styling

AvatarGroup styling
MethodValuesDescription
size(Size)Size::Sm | Size::Md | Size::LgSize applied to direct child avatars (and the overflow count) that omit `size`. A size set on the child wins; an avatar nested inside another element never inherits.
color(Color)Color::Default | Color::Accent | Color::Success | Color::Warning | Color::DangerColor applied to direct child avatars (and the overflow count) that omit `color`.
variant(AvatarVariant)AvatarVariant::Default | AvatarVariant::SoftVariant applied to direct child avatars (and the overflow count) that omit `variant`.
Rust
// The one slot for caller-owned low-level styling: GPUI's
// styling methods, refined over the root element last.
.sx(|el| {
    el.bg(gpui::rgba(0xffa500ff))             // background
        .text_color(gpui::rgba(0x000000ff))   // text
        .w(gpui::px(13.))                     // size
        .h(gpui::px(12.))
})

API reference

Against HeroUI v3.2.6: 12 entries ported, 2 partial, 1 not ported. Partial rows are marked in the tables; entries not ported are listed under each table with the reason.

Builders

AvatarGroup builders
BuilderValuesDefaultDescription
size(Size)Size::Sm | Size::Md | Size::LgSize::MdSize applied to direct child avatars (and the overflow count) that omit `size`. A size set on the child wins; an avatar nested inside another element never inherits.
color(Color)Color::Default | Color::Accent | Color::Success | Color::Warning | Color::Danger—Color applied to direct child avatars (and the overflow count) that omit `color`.
variant(AvatarVariant)AvatarVariant::Default | AvatarVariant::Soft—Variant applied to direct child avatars (and the overflow count) that omit `variant`.
max(usize)number—Maximum number of children to render; the rest collapse into an automatic `+N` count. Omit to show every child.
is_grid(bool)true | falsefalseA wrapping layout with a 12px gap and no overlap.
overlap(AvatarGroupOverlap)PartialAvatarGroupOverlap::Clip | AvatarGroupOverlap::RingAvatarGroupOverlap::ClipStacked overlap style; ignored with `isGrid`. `ring` is the 2px `--background` outline. `clip` is v3's transparent crescent mask, which gpui cannot draw (no alpha masks): paints the same crescent as a 2px `--background` seam, identical on a solid background but not transparent over images or gradients.
sx(|el| ...)Partialstring—The `sx` slot styles the group root; `overlap_distance` and `seam` stand in for the `--avatar-group-overlap` / `--avatar-group-seam` custom properties the Custom Styles example sets.
child(Avatar) / children(avatars) / child_element(el) / count(AvatarGroupCount)AnyElement—Avatar components and an optional `AvatarGroup.Count`. Direct avatars are typed children so the group can fill their omitted props; any other element is a `child_element` that counts toward `max` without inheriting.
child(element)AnyElement—Count content (e.g. `+3`), rendered as the avatar fallback. An explicit count is never truncated by `max` and suppresses the automatic one.
Not ported1 HeroUI entry not ported
AvatarGroup builders not ported
HeroUI v3Why it is not ported
AvatarGroup.Count.className

Parts

AvatarGroup parts
PartDescription
AvatarGroupRoot on the `.avatar-group` BEM class: an `inline-flex items-center` row of direct `Avatar` children, stacked with an 8px overlap or wrapped as a grid. No implicit `role`.
AvatarGroupCountAn `Avatar` whose fallback is the overflow content; `shrink-0` and overlapped like any stacked sibling.