Open UI

Avatar

A visual representation of a user or entity in the product.

import { Avatar } from "@opengovsg/oui"export const Example = () => {  return (    <Avatar name="John Doe">      <Avatar.Fallback />    </Avatar>  )}

Usage

import { Avatar, AvatarGroup } from "@opengovsg/oui"
<Avatar name="John Doe">
  <Avatar.Image src="https://example.com/avatar.jpg" />
  <Avatar.Fallback />
</Avatar>

Alternatively, install the component as local source via the shadcn CLI:

npx shadcn@latest add https://oui.open.gov.sg/r/avatar.json
pnpm dlx shadcn@latest add https://oui.open.gov.sg/r/avatar.json
npx shadcn@latest add https://oui.open.gov.sg/r/avatar.json
bunx --bun shadcn@latest add https://oui.open.gov.sg/r/avatar.json

OUI exports 2 avatar-related components:

  • Avatar: The main component to display an avatar. It has compound components Avatar.Image and Avatar.Fallback.
  • AvatarGroup: A wrapper component to display a group of avatars.

Anatomy

Avatar consists of:

  • Avatar — the root container; manages size, color, and fallback state
    • Avatar.Image — the <img> element; triggers fallback when the image fails to load
    • Avatar.Fallback — rendered when the image is absent or fails; shows initials (from name) or a default user icon

Examples

Sizes

Use the size prop to change the size of the avatar. Available sizes are 2xs, xs, sm, and md.

import { Avatar } from "@opengovsg/oui"export const Example = () => {  return (    <div className="flex items-center gap-4">      <Avatar size="2xs" name="2XS">        <Avatar.Fallback />      </Avatar>      <Avatar size="xs" name="XS">        <Avatar.Fallback />      </Avatar>      <Avatar size="sm" name="SM">        <Avatar.Fallback />      </Avatar>      <Avatar size="md" name="MD">        <Avatar.Fallback />      </Avatar>    </div>  )}

Radius

Use the radius prop to change the border radius of the avatar.

import { Avatar } from "@opengovsg/oui"export const Example = () => {  return (    <div className="flex items-center gap-4">      <Avatar radius="none" name="None">        <Avatar.Fallback />      </Avatar>      <Avatar radius="sm" name="SM">        <Avatar.Fallback />      </Avatar>      <Avatar radius="md" name="MD">        <Avatar.Fallback />      </Avatar>      <Avatar radius="lg" name="LG">        <Avatar.Fallback />      </Avatar>      <Avatar radius="full" name="Full">        <Avatar.Fallback />      </Avatar>    </div>  )}

Colors

Use the prominence and color props to customize the avatar's appearance.

  • prominence: strong (filled background) or subtle (light background)
  • color: primary or white
import { Avatar } from "@opengovsg/oui"export const Example = () => {  return (    <div className="flex flex-col gap-4">      <div className="flex items-center gap-4">        <Avatar prominence="strong" color="primary" name="Strong Primary">          <Avatar.Fallback />        </Avatar>        <Avatar prominence="subtle" color="primary" name="Subtle Primary">          <Avatar.Fallback />        </Avatar>        <Avatar prominence="subtle" color="white" name="Subtle White">          <Avatar.Fallback />        </Avatar>      </div>    </div>  )}

Avatar Fallbacks

If the image fails to load, the avatar will display a fallback. There are 2 fallback options:

  1. If there's a name prop, the initials are generated from the name.
  2. If there's no name prop, a default user icon is displayed.
import { Avatar } from "@opengovsg/oui"export const Example = () => {  return (    <div className="flex items-center gap-4">      {/* Default fallback (user icon) */}      <Avatar>        <Avatar.Image src="https://invalid-url.com/image.jpg" />        <Avatar.Fallback />      </Avatar>      {/* Name-based fallback (initials) */}      <Avatar name="John Doe">        <Avatar.Image src="https://invalid-url.com/image.jpg" />        <Avatar.Fallback />      </Avatar>    </div>  )}

Custom Fallback

You can provide a custom fallback component to be displayed when the image fails to load.

import { SettingsIcon } from "lucide-react"import { Avatar } from "@opengovsg/oui"export const Example = () => {  return (    <Avatar>      <Avatar.Image src="https://invalid-url.com/image.jpg" />      <Avatar.Fallback>        <SettingsIcon />      </Avatar.Fallback>    </Avatar>  )}

Custom Initials Logic

You can customize the logic used to generate initials by passing a function to the getInitials prop. By default, we use the first character of each word in the name prop.

<Avatar
  name="John Doe"
  getInitials={(name) =>
    name
      .split(" ")
      .map((n) => n[0])
      .join("")
      .toUpperCase()
  }
>
  <Avatar.Image src="https://example.com/avatar.jpg" />
  <Avatar.Fallback />
</Avatar>

Avatar Group

Use the AvatarGroup component to display a group of avatars together.

import { Avatar, AvatarGroup } from "@opengovsg/oui"export const Example = () => {  return (    <AvatarGroup>      <Avatar name="John Doe">        <Avatar.Image src="https://gravatar.com/avatar/193a23ae8a55c4144ae512bb64567c9f?s=400&d=monsterid&r=x" />        <Avatar.Fallback />      </Avatar>      <Avatar name="Jane Smith">        <Avatar.Image src="https://gravatar.com/avatar/577cd0a8c4a76de416036642cfb4b53c?s=400&d=monsterid&r=x" />        <Avatar.Fallback />      </Avatar>      <Avatar name="Bruce Wayne">        <Avatar.Image src="https://gravatar.com/avatar/746aa139bc568ddbb7a9dd6ee5f98ba0?s=400&d=monsterid&r=x" />        <Avatar.Fallback />      </Avatar>      <Avatar name="Bob Wilson">        <Avatar.Image src="https://gravatar.com/avatar/8f62f3c9f639118db002a94e64823d18?s=400&d=monsterid&r=x" />        <Avatar.Fallback />      </Avatar>    </AvatarGroup>  )}

Max Count

You can limit the number of avatars displayed by passing the max prop to the AvatarGroup component.

import { Avatar, AvatarGroup } from "@opengovsg/oui"export const Example = () => {  return (    <AvatarGroup max={3}>      <Avatar name="John Doe">        <Avatar.Fallback />      </Avatar>      <Avatar name="Jane Smith">        <Avatar.Fallback />      </Avatar>      <Avatar name="Bruce Wayne">        <Avatar.Fallback />      </Avatar>      <Avatar name="Bob Wilson">        <Avatar.Fallback />      </Avatar>    </AvatarGroup>  )}

Total Count

You can display the total number of avatars by passing the total prop to the AvatarGroup component. This is useful when you have more avatars than what you're displaying.

import { Avatar, AvatarGroup } from "@opengovsg/oui"export const Example = () => {  return (    <AvatarGroup max={3} total={10}>      <Avatar name="John Doe">        <Avatar.Image src="https://gravatar.com/avatar/193a23ae8a55c4144ae512bb64567c9f?s=400&d=monsterid&r=x" />        <Avatar.Fallback />      </Avatar>      <Avatar name="Jane Smith">        <Avatar.Image src="https://gravatar.com/avatar/577cd0a8c4a76de416036642cfb4b53c?s=400&d=monsterid&r=x" />        <Avatar.Fallback />      </Avatar>      <Avatar name="Bruce Wayne">        <Avatar.Image src="https://gravatar.com/avatar/746aa139bc568ddbb7a9dd6ee5f98ba0?s=400&d=monsterid&r=x" />        <Avatar.Fallback />      </Avatar>      <Avatar name="Bob Wilson">        <Avatar.Image src="https://gravatar.com/avatar/8f62f3c9f639118db002a94e64823d18?s=400&d=monsterid&r=x" />        <Avatar.Fallback />      </Avatar>    </AvatarGroup>  )}

Custom Count

AvatarGroup provides a renderCount prop to customize the count displayed.

import { Avatar, AvatarGroup } from "@opengovsg/oui"export const Example = () => {  return (    <AvatarGroup      max={3}      prominence="subtle"      total={10}      renderCount={(count) => (        <p className="prose-body-1 text-base-content-medium ms-2">          +{count} others        </p>      )}    >      <Avatar name="John Doe">        <Avatar.Image src="https://gravatar.com/avatar/6dacc144e568aad3a645388f3635f795?s=400&d=robohash&r=x" />        <Avatar.Fallback />      </Avatar>      <Avatar name="Jane Smith">        <Avatar.Image src="https://gravatar.com/avatar/a05a9551ef03ec76331ee9ad60fae0e7?s=400&d=robohash&r=x" />        <Avatar.Fallback />      </Avatar>      <Avatar name="Bruce Wayne">        <Avatar.Image src="https://gravatar.com/avatar/193a23ae8a55c4144ae512bb64567c9f?s=400&d=robohash&r=x" />        <Avatar.Fallback />      </Avatar>    </AvatarGroup>  )}

Accessibility

  • Avatar is a presentational element; pass aria-label or use the name prop (used as alt text for the image) to identify it to assistive technology.
  • When used in an AvatarGroup, provide an accessible label on the group container via aria-label or aria-labelledby.
  • The overflow count avatar is informational; ensure surrounding context conveys the total number of members.
  • The data-loaded attribute on the root element reflects image load state for CSS targeting.

Slots

Slots are named regions of the component you can target with custom Tailwind classes via the classNames prop. Each slot below corresponds to a key on the classNames object.

Avatar slots:

  • base: Avatar wrapper — includes styles for position and general appearance.
  • image: The <img> element — includes styles for opacity transition and size.
  • fallback: The fallback content container shown when the image fails to load — includes styles for centering.
  • icon: The icon element rendered inside the fallback when no name is provided.

AvatarGroup slots:

  • base: The wrapper <div> for the avatar group.
  • count: The overflow count avatar element.

Data Attributes

Avatar has the following attributes on the base element:

  • data-loaded: Set when the avatar image has loaded successfully.

Props

Avatar

PropTypeDefaultDescription
childrenReactNode-The avatar content (Avatar.Image and Avatar.Fallback)
namestring-The name used for initials generation and image alt text
size"2xs" | "xs" | "sm" | "md""md"The size of the avatar
radius"none" | "sm" | "md" | "lg" | "full""full"The border radius of the avatar
prominence"strong" | "subtle""strong"The visual prominence of the avatar
color"primary" | "white""primary"The color scheme of the avatar
getInitials(name: string) => string-Custom function to generate initials from name
classNamesSlotsToClasses<AvatarSlots>-Custom Tailwind classes for component slots
classNamestring-Additional class applied to the base element

ref is React's standard ref forwarding to the underlying <span> element; not surfaced as a separate doc-table prop.

Avatar.Image

PropTypeDescription
srcstringThe image source URL
...-All native <img> attributes

Avatar.Fallback

PropTypeDescription
childrenReactNodeCustom fallback content (defaults to icon/initials)

AvatarGroup

PropTypeDefaultDescription
childrenReactNode-The Avatar components to display
maxnumber5Maximum number of visible avatars before overflow
totalnumber-Total count used for calculating overflow (useful when not all avatars are rendered)
renderCount(count: number) => ReactNode-Custom render function for the overflow count element
countPropsPartial<AvatarProps>-Props spread onto the overflow count avatar (e.g., custom classNames or name)
size"2xs" | "xs" | "sm" | "md"-Size applied to all child avatars
color"primary" | "white"-Color applied to all child avatars
prominence"strong" | "subtle"-Prominence applied to all child avatars
radius"none" | "sm" | "md" | "lg" | "full"-Radius applied to all child avatars
classNamesSlotsToClasses<"base" | "count">-Custom Tailwind classes for group slots

Templates

Usage with Menu

You can use the Avatar component as a trigger for a Menu to create a user profile menu.

import { ChevronDown } from "lucide-react"import {  Avatar,  Button,  Menu,  MenuItem,  MenuSection,  MenuTrigger,  SubmenuTrigger,} from "@opengovsg/oui"export const Example = () => {  return (    <MenuTrigger>      <Button        variant="clear"        className="px-2"        endContent={<ChevronDown className="group-pressed:rotate-180" />}      >        <Avatar size="sm" name="User Name">          <Avatar.Fallback />        </Avatar>      </Button>      <Menu>        <MenuSection title="Actions">          <SubmenuTrigger>            <MenuItem id="open">Settings</MenuItem>            <Menu>              <MenuItem id="open-new">Change Avatar</MenuItem>              <MenuItem id="open-current">Update Profile Information</MenuItem>              <SubmenuTrigger>                <MenuItem id="more">More</MenuItem>                <Menu>                  <MenuItem id="open-email">Change Email Address</MenuItem>                  <MenuItem id="open-in-alt">Change Password</MenuItem>                </Menu>              </SubmenuTrigger>            </Menu>          </SubmenuTrigger>          <MenuItem>Logout</MenuItem>        </MenuSection>      </Menu>    </MenuTrigger>  )}

On this page