AvatarGroup
Display a stacked or grid group of avatars with overflow counting.
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.
An element expression with window: &mut Window and cx: &mut App in scope.
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()])An element expression with window: &mut Window and cx: &mut App in scope.
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()])An element expression with window: &mut Window and cx: &mut App in scope.
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()])An element expression with window: &mut Window and cx: &mut App in scope.
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(),
])An element expression with window: &mut Window and cx: &mut App in scope.
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()])`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.
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).
`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.
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.
/// 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
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
| Method | Values | Description |
|---|---|---|
size(Size) | Size::Sm | Size::Md | Size::Lg | Size 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`. |
// 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
| Builder | Values | Default | Description |
|---|---|---|---|
size( | Size:: | Size:: | Size 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 applied to direct child avatars (and the overflow count) that omit `color`. |
variant( | AvatarVariant:: | — | Variant applied to direct child avatars (and the overflow count) that omit `variant`. |
max( | number | — | Maximum number of children to render; the rest collapse into an automatic `+N` count. Omit to show every child. |
is_grid( | true | | false | A wrapping layout with a 12px gap and no overlap. |
overlap(Partial | AvatarGroupOverlap:: | AvatarGroupOverlap:: | Stacked 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(Partial | string | — | 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( | 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( | 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
| HeroUI v3 | Why it is not ported |
|---|---|
AvatarGroup.Count.className |
Parts
| Part | Description |
|---|---|
AvatarGroup | Root 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`. |
AvatarGroupCount | An `Avatar` whose fallback is the overflow content; `shrink-0` and overlapped like any stacked sibling. |