HextaUI

Avatar

Fotos de usuário com fallback de iniciais, badges de status e grupos empilhados que se recolhem em uma contagem.

ALGH
ALATGH2 more
import {
  Avatar,
  AvatarBadge,
  AvatarFallback,
  AvatarGroup,
  AvatarImage,
} from "@/components/ui/avatar"

export function AvatarDemo() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-8">
      <Avatar size="xl">
        <AvatarImage src="/preview/landscape.svg" alt="" />
        <AvatarFallback>AL</AvatarFallback>
        <AvatarBadge status="online" />
      </Avatar>
      <Avatar size="xl" shape="square">
        <AvatarFallback>GH</AvatarFallback>
      </Avatar>
      <AvatarGroup size="lg" max={4}>
        <Avatar>
          <AvatarImage src="/preview/landscape.svg" alt="" />
          <AvatarFallback>AL</AvatarFallback>
        </Avatar>
        <Avatar>
          <AvatarFallback>AT</AvatarFallback>
        </Avatar>
        <Avatar>
          <AvatarFallback>GH</AvatarFallback>
        </Avatar>
        <Avatar>
          <AvatarFallback>KJ</AvatarFallback>
        </Avatar>
        <Avatar>
          <AvatarFallback>LT</AvatarFallback>
        </Avatar>
      </AvatarGroup>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/avatar.json

Adiciona o componente, os tokens de tema do HextaUI e quaisquer componentes do HextaUI dos quais ele depende.

import {
  Avatar,
  AvatarBadge,
  AvatarFallback,
  AvatarGroup,
  AvatarImage,
} from "@/components/ui/avatar"
<Avatar>
  <AvatarImage src="/ada.jpg" alt="" />
  <AvatarFallback>AL</AvatarFallback>
</Avatar>
Avatar
├── AvatarImage
├── AvatarFallback
└── AvatarBadge

AvatarGroup
├── Avatar
└── AvatarGroupCount

Tamanhos e formas

Cinco tamanhos, como círculos ou quadrados. As iniciais e o ícone de usuário escalam com a caixa, e os cantos quadrados diminuem com o tamanho. Um <AvatarFallback /> vazio exibe o ícone de usuário.

import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"

const sizes = ["xs", "sm", "default", "lg", "xl"] as const
const shapes = ["circle", "square"] as const

export function AvatarSizes() {
  return (
    <div className="flex flex-col gap-4">
      {shapes.map((shape) => (
        <div key={shape} className="flex flex-wrap items-center gap-3">
          {sizes.map((size) => (
            <Avatar key={`photo-${size}`} size={size} shape={shape}>
              <AvatarImage src="/preview/landscape.svg" alt="" />
              <AvatarFallback>AL</AvatarFallback>
            </Avatar>
          ))}
          {sizes.map((size) => (
            <Avatar key={`initials-${size}`} size={size} shape={shape}>
              <AvatarFallback>LT</AvatarFallback>
            </Avatar>
          ))}
          {sizes.map((size) => (
            <Avatar key={`empty-${size}`} size={size} shape={shape}>
              <AvatarFallback />
            </Avatar>
          ))}
        </div>
      ))}
    </div>
  )
}

Carregando

As iniciais aparecem enquanto a foto carrega, e então a foto surge com fade sobre elas. Uma foto quebrada mantém o fallback. Passe delay para esperar antes de exibir as iniciais, para que fotos rápidas nunca as mostrem num flash.

"use client"

import * as React from "react"

import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"

export function AvatarLoading() {
  const [version, setVersion] = React.useState(0)
  const [loading, setLoading] = React.useState(false)
  const timer = React.useRef<number>(undefined)

  React.useEffect(() => () => window.clearTimeout(timer.current), [])

  const reload = () => {
    window.clearTimeout(timer.current)
    setLoading(true)
    timer.current = window.setTimeout(() => {
      setVersion((value) => value + 1)
      setLoading(false)
    }, 1200)
  }

  return (
    <div className="flex flex-wrap items-center gap-3">
      <Avatar size="xl">
        {loading ? null : (
          <AvatarImage src={`/preview/landscape.svg?v=${version}`} alt="" />
        )}
        <AvatarFallback>AT</AvatarFallback>
      </Avatar>
      <Avatar size="xl">
        <AvatarImage src="/preview/missing.png" alt="" />
        <AvatarFallback>BI</AvatarFallback>
      </Avatar>
      <Avatar size="xl">
        <AvatarImage src="/preview/landscape.svg" alt="" />
        <AvatarFallback delay={600}>GH</AvatarFallback>
      </Avatar>
      <Button variant="outline" size="sm" onClick={reload}>
        Load a new photo
      </Button>
    </div>
  )
}

Iniciais

getInitials() escolhe a primeira e a última inicial. Ele lida com endereços de e-mail, emoji, nomes CJK e RTL, marcas combinantes e nomes sem nenhuma letra.

"use client"

import { Avatar, AvatarFallback, getInitials } from "@/components/ui/avatar"

const names = [
  "Ada Lovelace",
  "Madonna",
  "jean-luc picard",
  "[email protected]",
  "(Admin) John",
  "👩‍👩‍👧‍👦 Family",
  "山田 太郎",
  "محمد علي",
  "Z̷̢̛͖͓̰̈́algo T̵ext",
  "Mary Ann Evans Cross",
  "!!! ???",
  "",
]

export function AvatarInitials() {
  return (
    <ul className="grid w-full max-w-md grid-cols-1 gap-2 sm:grid-cols-2">
      {names.map((name) => (
        <li key={name} className="flex min-w-0 items-center gap-2 text-sm">
          <Avatar>
            <AvatarFallback>{getInitials(name)}</AvatarFallback>
          </Avatar>
          <span className="min-w-0 truncate text-muted-foreground">
            {name || "(empty)"}
          </span>
        </li>
      ))}
    </ul>
  )
}

Status

<AvatarBadge /> fica na borda em todos os tamanhos e formas. Defina status para um ponto colorido com rótulo acessível, ou passe um ícone. Mudar o status executa um único pulso.

"use client"

import * as React from "react"
import { IconCheck } from "@tabler/icons-react"

import {
  Avatar,
  AvatarBadge,
  AvatarFallback,
  AvatarImage,
  type AvatarStatus,
} from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"

const sizes = ["xs", "sm", "default", "lg", "xl"] as const
const shapes = ["circle", "square"] as const
const statuses: AvatarStatus[] = ["online", "away", "busy", "offline"]

export function AvatarStatusDemo() {
  const [index, setIndex] = React.useState(0)
  const status = statuses[index]

  return (
    <div className="flex flex-col gap-4">
      {shapes.map((shape) => (
        <div key={shape} className="flex flex-wrap items-center gap-3">
          {sizes.map((size) => (
            <Avatar key={size} size={size} shape={shape}>
              <AvatarImage src="/preview/landscape.svg" alt="" />
              <AvatarFallback>GH</AvatarFallback>
              <AvatarBadge status={status} />
            </Avatar>
          ))}
          <Avatar size="lg" shape={shape}>
            <AvatarFallback>AT</AvatarFallback>
            <AvatarBadge>
              <IconCheck />
            </AvatarBadge>
          </Avatar>
        </div>
      ))}
      <div className="flex items-center gap-3">
        <Button
          variant="outline"
          size="sm"
          onClick={() => setIndex((index + 1) % statuses.length)}
        >
          Next status
        </Button>
        <span className="text-sm text-muted-foreground">{status}</span>
      </div>
    </div>
  )
}

Grupo

<AvatarGroup /> sobrepõe seus avatares e define o tamanho e a forma deles. max agrupa o restante em uma contagem.

"use client"

import {
  Avatar,
  AvatarFallback,
  AvatarGroup,
  AvatarImage,
  getInitials,
} from "@/components/ui/avatar"

const people = [
  { name: "Ada Lovelace", image: "/preview/landscape.svg" },
  { name: "Alan Turing", image: "/preview/landscape.svg" },
  { name: "Grace Hopper", image: "/preview/landscape.svg" },
  { name: "Katherine Johnson", image: null },
  { name: "Linus Torvalds", image: null },
  { name: "Margaret Hamilton", image: null },
  { name: "Tim Berners-Lee", image: null },
  { name: "Barbara Liskov", image: null },
]

const sizes = ["xs", "sm", "default", "lg", "xl"] as const

export function AvatarGroupDemo() {
  return (
    <div className="flex flex-col items-start gap-4">
      {sizes.map((size) => (
        <AvatarGroup key={size} size={size} max={4}>
          {people.map((person) => (
            <Avatar key={person.name}>
              {person.image ? <AvatarImage src={person.image} alt="" /> : null}
              <AvatarFallback>{getInitials(person.name)}</AvatarFallback>
            </Avatar>
          ))}
        </AvatarGroup>
      ))}
      <AvatarGroup shape="square" max={5}>
        {people.map((person) => (
          <Avatar key={person.name}>
            {person.image ? <AvatarImage src={person.image} alt="" /> : null}
            <AvatarFallback>{getInitials(person.name)}</AvatarFallback>
          </Avatar>
        ))}
      </AvatarGroup>
    </div>
  )
}

Grupo com links

Renderize os avatares como links com render e dê a cada um um aria-label. Um avatar em foco sobe acima de seus vizinhos para que o anel nunca seja cortado. Adicione <AvatarGroupCount /> você mesmo quando o total vier dos seus dados.

"use client"

import {
  Avatar,
  AvatarFallback,
  AvatarGroup,
  AvatarGroupCount,
  getInitials,
} from "@/components/ui/avatar"

const team = [
  "Ada Lovelace",
  "Alan Turing",
  "Grace Hopper",
  "Linus Torvalds",
  "Margaret Hamilton",
]

export function AvatarLinkedGroup() {
  return (
    <AvatarGroup aria-label="Team" size="lg">
      {team.map((name) => (
        <Avatar key={name} render={<a href="#" aria-label={name} />}>
          <AvatarFallback>{getInitials(name)}</AvatarFallback>
        </Avatar>
      ))}
      <AvatarGroupCount count={3} />
    </AvatarGroup>
  )
}

Layout

Os avatares nunca encolhem em linhas apertadas. Uma classe de tamanho como size-20 escala as iniciais e o badge junto, e iniciais longas nunca transbordam.

import {
  Avatar,
  AvatarBadge,
  AvatarFallback,
  AvatarImage,
} from "@/components/ui/avatar"

export function AvatarLayout() {
  return (
    <div className="flex flex-col items-start gap-4">
      <div className="flex w-64 max-w-full items-center gap-2 rounded-lg border p-2 text-sm">
        <Avatar>
          <AvatarImage src="/preview/landscape.svg" alt="" />
          <AvatarFallback>AL</AvatarFallback>
        </Avatar>
        <span className="min-w-0 truncate">
          Supercalifragilisticexpialidocious-team-workspace-name
        </span>
      </div>
      <div className="flex flex-wrap items-center gap-3">
        <Avatar className="size-20">
          <AvatarImage src="/preview/landscape.svg" alt="" />
          <AvatarFallback>KJ</AvatarFallback>
        </Avatar>
        <Avatar className="size-20" shape="square">
          <AvatarFallback>KJ</AvatarFallback>
          <AvatarBadge status="online" />
        </Avatar>
        <Avatar size="sm">
          <AvatarFallback>WWWWWWWW</AvatarFallback>
        </Avatar>
      </div>
    </div>
  )
}

Da direita para a esquerda

O badge permanece no canto final, que é o esquerdo em RTL, e os grupos se sobrepõem a partir da direita.

"use client"

import {
  Avatar,
  AvatarBadge,
  AvatarFallback,
  AvatarGroup,
  AvatarImage,
  getInitials,
} from "@/components/ui/avatar"

const people = [
  { name: "Ada Lovelace", image: "/preview/landscape.svg" },
  { name: "Alan Turing", image: "/preview/landscape.svg" },
  { name: "Grace Hopper", image: "/preview/landscape.svg" },
  { name: "Katherine Johnson", image: null },
  { name: "Linus Torvalds", image: null },
  { name: "Margaret Hamilton", image: null },
  { name: "Tim Berners-Lee", image: null },
  { name: "Barbara Liskov", image: null },
]

export function AvatarRtl() {
  return (
    <div dir="rtl" className="flex flex-wrap items-center gap-4">
      <Avatar size="lg">
        <AvatarFallback>{getInitials("محمد علي")}</AvatarFallback>
        <AvatarBadge status="online" />
      </Avatar>
      <AvatarGroup max={4}>
        {people.map((person) => (
          <Avatar key={person.name}>
            {person.image ? <AvatarImage src={person.image} alt="" /> : null}
            <AvatarFallback>{getInitials(person.name)}</AvatarFallback>
          </Avatar>
        ))}
      </AvatarGroup>
    </div>
  )
}

Avatares não são focáveis por conta própria. Renderizados como link ou botão, ganham as teclas habituais.

TeclaAção
TabMove o foco para o próximo avatar com link.
EnterAcompanha o link em foco.
  • Use alt="" quando o nome da pessoa já estiver ao lado do avatar, e o nome dela como texto alternativo quando não estiver.
  • Badges com status são anunciados como “Online”, “Away”, “Busy” ou “Offline”. Offline é desenhado como um anel, então o status nunca depende só da cor.
  • Os grupos têm role="group". A contagem é lida como “3 more”, não “+3”.
  • Com movimento reduzido ativado, as fotos aparecem sem fade e as mudanças de status não pulsam.

Construído sobre o avatar do Base UI. Cada parte aceita os atributos do elemento que renderiza. Os estilos são exportados como avatarVariants e avatarBadgeVariants.

PropTipoPadrão
sizeHerdado do grupo quando omitido.
"xs" | "sm" | "default" | "lg" | "xl""default"
shapeHerdado do grupo quando omitido.
"circle" | "square""circle"
render
ReactElement | (props, state) => ReactElement<span>
AtributoDescrição
data-slot="avatar"Seleciona os avatares no CSS.
data-sizeO tamanho resolvido.
data-shapeA forma resolvida.
--avatar-radiusO raio dos cantos, compartilhado por todas as camadas.
PropTipoPadrão
src
string–
alt
string–
onLoadingStatusChange
(status: "idle" | "loading" | "loaded" | "error") => void–
keepMountedCarrega a imagem no lugar em vez de pré-carregá-la, para loading="lazy" ou next/image.
booleanfalse
render
ReactElement | (props, state) => ReactElement<img>
AtributoDescrição
data-slot="avatar-image"Seleciona as imagens no CSS.
data-loadingPresente enquanto a imagem carrega.
data-errorPresente quando a imagem falhou ao carregar.
data-starting-stylePresente enquanto a imagem aparece com fade.
data-ending-stylePresente enquanto a imagem some com fade.
PropTipoPadrão
childrenVazio ou só com espaços exibe o ícone de usuário.
ReactNode<IconUser />
delayMilissegundos de espera antes de exibi-lo.
number0
render
ReactElement | (props, state) => ReactElement<span>
AtributoDescrição
data-slot="avatar-fallback"Seleciona os fallbacks no CSS.
data-readyfalse até o delay passar.
PropTipoPadrão
statusColore o ponto e o rotula para tecnologias assistivas. Sem isso, o badge usa a cor primary.
"online" | "away" | "busy" | "offline"–
childrenUm ícone dentro do badge. Oculto nos tamanhos xs e sm.
ReactNode–
AtributoDescrição
data-slot="avatar-badge"Seleciona os badges no CSS.
data-statusO status atual.
data-slot="avatar-badge-pulse"O pulso executado após uma mudança de status.
PropTipoPadrão
size
"xs" | "sm" | "default" | "lg" | "xl""default"
shape
"circle" | "square""circle"
maxQuantos itens exibir, incluindo a contagem. Valores abaixo de 2 são elevados a 2.
number–
AtributoDescrição
data-slot="avatar-group"Selecione grupos no CSS.
data-sizeO tamanho do grupo.
PropTipoPadrão
countExibido como +3, ou 99+ acima de 99.
number–
childrenSubstitui a contagem, por exemplo por um ícone.
ReactNode–
sizeHerdado do grupo quando omitido.
"xs" | "sm" | "default" | "lg" | "xl"–
shapeHerdado do grupo quando omitido.
"circle" | "square"–
AtributoDescrição
data-slot="avatar-group-count"Selecione a contagem no CSS.
data-sizeO tamanho resolvido.
data-shapeA forma resolvida.

getInitials(name, max = 2) retorna até max iniciais em maiúsculas: a da primeira palavra e a da última. Para um endereço de e-mail, usa a parte antes do @. Retorna uma string vazia quando o nome não tem letras, números nem emoji, então o fallback exibe o ícone de usuário.

Usado em blocos

Blocos que se baseiam em Avatar.