Avatar

The Avatar component displays user profile images with intelligent fallbacks. When no image is provided, it shows user initials or a placeholder icon. The component supports various sizes and can be grouped together for team displays.

Install

#Import

use gpui_kit::component::avatar::{Avatar, AvatarGroup};
// Installed in ui/components. That directory is on the Slint include path, so import by file name:
import { AvatarSize, Avatar, AvatarGroup } from "avatar.slint";
use iced::widget::{column, container, mouse_area, row, scrollable, stack, svg, text};
use iced::{Alignment, Color, Element, Length, Padding, Task, Theme};

#Usage

#Image

GPUINo GPUI version of this example yet.
SlintNo Slint version of this example yet.
// The image wins when there is one; the initials behind it are the fallback
// Slint also writes into its first box.
let image = row![shown().initials("JL"), shown()]
    .spacing(theme::SPACING_MD)
    .align_y(Alignment::Center);

You can create an Avatar by providing an image source URL and a user name:

Avatar::new()
    .name("John Doe")
    .src("https://example.com/avatar.jpg")
VerticalLayout {
    spacing: 12px;
    alignment: start;
    Avatar {
        source: root.portrait;
        initials: "JL";
    }
    Avatar {
        source: root.portrait;
    }
}

#Fallback

GPUINo GPUI version of this example yet.
SlintNo Slint version of this example yet.
// Neither image nor initials: the fill alone. The live avatar repeats the
// three fallbacks and takes the switches below.
let fallback = column![
    row![
        Avatar::new().initials("JL"),
        Avatar::new(),
        Avatar::new().icon(user_icon(32.0 * 0.45)),
    ]
    .spacing(theme::SPACING_MD)
    .align_y(Alignment::Center),
    clipped(switchable),
    row![
        switch(
            "image",
            app.fallback == Fallback::Image,
            Message::Fallback(Fallback::Image)
        ),
        switch(
            "initials",
            app.fallback == Fallback::Initials,
            Message::Fallback(Fallback::Initials)
        ),
        switch(
            "icon",
            app.fallback == Fallback::Icon,
            Message::Fallback(Fallback::Icon)
        ),
    ]
    .spacing(theme::SPACING_SM),
]
.spacing(theme::SPACING_MD);

When no image source is provided, the Avatar displays user initials with an automatically generated color background:

// Shows "JD" initials with colored background
Avatar::new()
    .name("John Doe")

// Shows "JS" initials
Avatar::new()
    .name("Jane Smith")
VerticalLayout {
    spacing: 12px;
    alignment: start;
    Avatar {
        initials: "JL";
    }
    Avatar {}
    Avatar {
        icon: "box";
    }
}

The color is derived from the initials, so the same person always gets the same one. It comes from a ring of 12 evenly spaced OkLCH hues held at a fixed lightness and chroma, which keeps every avatar at the same visual weight and its text above WCAG AA contrast in both the light and dark themes. The outline follows the same hue; an Avatar showing an image keeps the neutral border.

#Group

GPUINo GPUI version of this example yet.
SlintNo Slint version of this example yet.
// Three overlapping avatars, and the overflow row: five shown behind a chip
// because eleven names is more than the limit Slint sets.
let group = column![
    avatar_group(vec![shown(), shown(), shown()], false),
    avatar_group((0..5).map(|_| shown()).collect(), true),
]
.spacing(theme::SPACING_LG);

For anonymous users or when no name is provided:

use gpui_kit::component::IconName;

// Default user icon placeholder
Avatar::new()

// Custom placeholder icon
Avatar::new()
    .placeholder(IconName::Building2)
VerticalLayout {
    spacing: 20px;
    alignment: start;
    AvatarGroup {
        Avatar {
            source: root.portrait;
            horizontal-stretch: 0;
        }
        Avatar {
            source: root.portrait;
            horizontal-stretch: 0;
        }
        Avatar {
            source: root.portrait;
            horizontal-stretch: 0;
        }
    }
    AvatarGroup {
        limit: 5;
        ellipsis: true;
        overflow-count: 11;
        avatar-size: AvatarSize.medium;
        Avatar {
            source: root.portrait;
            horizontal-stretch: 0;
        }
        Avatar {
            source: root.portrait;
            horizontal-stretch: 0;
        }
        Avatar {
            source: root.portrait;
            horizontal-stretch: 0;
        }
        Avatar {
            source: root.portrait;
            horizontal-stretch: 0;
        }
        Avatar {
            source: root.portrait;
            horizontal-stretch: 0;
        }
    }
}

#Custom shape

Avatar::new()
    .src("https://example.com/avatar.jpg")
    .with_size(px(100.))
    .border_3()
    .border_color(cx.theme().foreground)
    .shadow_sm()
    .rounded(px(20.))  // Custom border radius
Avatar {
    source: root.portrait;
    custom-edge: 100px;
    radius: 20px;
}
// A custom edge overrides the size ladder, and the radius is the caller's,
// so this is a rounded rectangle rather than the default circle.
let custom_shape = shown().edge(100.0).radius(20.0);

#Custom style

// The avatar automatically generates colors based on the name
// Different names will get different colors from the color palette
Avatar::new().name("Alice")    // Gets one color
Avatar::new().name("Bob")      // Gets a different color
Avatar::new().name("Charlie")  // Gets another color
Avatar {
    source: root.portrait;
    custom-edge: 100px;
    border-width: 3px;
    border-color: Theme.foreground;
    shadow-blur: 4px;
    shadow-offset-y: 2px;
    shadow-color: Theme.shadow;
}
// The 3px edge and the drop shadow are the chrome Slint draws; the fill and
// the initials colour are the two colours a caller can override.
let custom_style = row![
    labelled("edge 100, chrome", chrome(shown().edge(100.0).radius(20.0))),
    labelled(
        "custom fill",
        Avatar::new()
            .initials("JL")
            .fill(Color::from_rgb(0.13, 0.13, 0.13))
            .initials_color(Color::WHITE),
    ),
]
.spacing(theme::SPACING_XL)
.align_y(Alignment::Start);

#Avatar Sizes

Avatar::new()
    .name("John Doe")
    .xsmall()

Avatar::new()
    .name("John Doe")
    .small()

Avatar::new()
    .name("John Doe")   // 48px (default medium)

Avatar::new()
    .name("John Doe")
    .large()

// Custom size
Avatar::new()
    .name("John Doe")
    .with_size(px(100.))
Avatar {
    initials: "XS";
    size: AvatarSize.xsmall;
}
Avatar {
    initials: "SM";
    size: AvatarSize.small;
}
Avatar {
    initials: "MD";
    size: AvatarSize.medium;
}
Avatar {
    initials: "LG";
    size: AvatarSize.large;
}
// The Slint ladder is 20 / 24 / 32 / 40 / 56.
let sizes = row![
    labelled(
        "xs 20",
        Avatar::new().initials("XS").size(AvatarSize::XSmall)
    ),
    labelled(
        "sm 24",
        Avatar::new().initials("SM").size(AvatarSize::Small)
    ),
    labelled(
        "md 32",
        Avatar::new().initials("MD").size(AvatarSize::Medium)
    ),
    labelled(
        "lg 40",
        Avatar::new().initials("LG").size(AvatarSize::Large)
    ),
    labelled(
        "xl 56",
        Avatar::new().initials("XL").size(AvatarSize::XLarge)
    ),
]
.spacing(theme::SPACING_MD)
.align_y(Alignment::Start);

#Team Display

use gpui_kit::component::{h_flex, v_flex};

v_flex()
    .gap_4()
    .child("Development Team")
    .child(
        AvatarGroup::new()
            .limit(4)
            .ellipsis()
            .child(Avatar::new().name("Alice Johnson").src("https://example.com/alice.jpg"))
            .child(Avatar::new().name("Bob Smith").src("https://example.com/bob.jpg"))
            .child(Avatar::new().name("Charlie Brown"))
            .child(Avatar::new().name("Diana Prince"))
            .child(Avatar::new().name("Eve Wilson"))
    )
SlintNo Slint version of this example yet.

#User Profile Header

h_flex()
    .items_center()
    .gap_4()
    .child(
        Avatar::new()
            .src("https://example.com/profile.jpg")
            .name("John Doe")
            .large()
            .border_2()
            .border_color(cx.theme().primary)
    )
    .child(
        v_flex()
            .child("John Doe")
            .child("Software Engineer")
    )
SlintNo Slint version of this example yet.

#Anonymous User

use gpui_kit::component::IconName;

Avatar::new()
    .placeholder(IconName::UserCircle)
    .medium()
SlintNo Slint version of this example yet.

#AvatarGroup

The AvatarGroup component allows you to display multiple avatars in a compact, overlapping layout:

#Basic Group

AvatarGroup::new()
    .child(Avatar::new().src("https://example.com/user1.jpg"))
    .child(Avatar::new().src("https://example.com/user2.jpg"))
    .child(Avatar::new().src("https://example.com/user3.jpg"))
    .child(Avatar::new().name("John Doe"))
SlintNo Slint version of this example yet.

#Group with Limit

AvatarGroup::new()
    .limit(3)  // Show maximum 3 avatars
    .child(Avatar::new().src("https://example.com/user1.jpg"))
    .child(Avatar::new().src("https://example.com/user2.jpg"))
    .child(Avatar::new().src("https://example.com/user3.jpg"))
    .child(Avatar::new().src("https://example.com/user4.jpg"))  // Hidden
    .child(Avatar::new().src("https://example.com/user5.jpg"))  // Hidden
SlintNo Slint version of this example yet.

#Group with Ellipsis

Show an ellipsis indicator when avatars are hidden due to the limit.

In this example, only 3 avatars are shown, and ”…” indicates there are more:

AvatarGroup::new()
    .limit(3)
    .ellipsis()  // Shows "..." when limit is exceeded
    .child(Avatar::new().src("https://example.com/user1.jpg"))
    .child(Avatar::new().src("https://example.com/user2.jpg"))
    .child(Avatar::new().src("https://example.com/user3.jpg"))
    .child(Avatar::new().src("https://example.com/user4.jpg"))
    .child(Avatar::new().src("https://example.com/user5.jpg"))
SlintNo Slint version of this example yet.

#Group Sizes

The [Sizeable] trait can also be applied to AvatarGroup, and it will set the size for all contained avatars.

// Extra small group
AvatarGroup::new()
    .xsmall()
    .child(Avatar::new().name("A"))
    .child(Avatar::new().name("B"))
    .child(Avatar::new().name("C"))

// Small group
AvatarGroup::new()
    .small()
    .child(Avatar::new().name("A"))
    .child(Avatar::new().name("B"))

// Medium group (default)
AvatarGroup::new()
    .child(Avatar::new().name("A"))
    .child(Avatar::new().name("B"))

// Large group
AvatarGroup::new()
    .large()
    .child(Avatar::new().name("A"))
    .child(Avatar::new().name("B"))
SlintNo Slint version of this example yet.

#Adding Multiple Avatars

let avatars = vec![
    Avatar::new().src("https://example.com/user1.jpg"),
    Avatar::new().src("https://example.com/user2.jpg"),
    Avatar::new().name("John Doe"),
];

AvatarGroup::new()
    .children(avatars)
    .limit(5)
    .ellipsis()
SlintNo Slint version of this example yet.

#API Reference

#AvatarSize

xsmall, small, medium, large, xlarge

#Avatar

A user portrait. Mirrors GPUI Component's `Avatar`: an image, initials or icon fallback, sizes, grouped overlap, and optional custom radius or chrome.

NameKindTypeDefaultDescription
sourceinimage
initialsinstring
sizeinAvatarSizeAvatarSize.medium
fillincolorTheme.secondary
initials-colorincolorTheme.secondary-foregroundThe initials must contrast with `fill`; a caller that darkens the fill sets this too.
iconinstring"user"
custom-edgeinlengthOverrides `size` when greater than zero.
radiusinlengthZero uses a circle. Set an explicit length for rounded-rectangle avatars.
border-widthinlength1px
border-colorincolorTheme.border
shadow-blurinlength
shadow-offset-yinlength1px
shadow-colorincolorTheme.shadow

#AvatarGroup

Overlapping avatars. GPUI defaults to showing three; set `limit` and `ellipsis` for overflow rows like the story gallery.

NameKindTypeDefault
limitinint3
ellipsisinbool
overflow-countinint
avatar-sizeinAvatarSizeAvatarSize.medium