HextaUI

Avatar

Nutzerfotos mit Initialen als Fallback, Statusbadges und gestapelte Gruppen, die zu einer Zahl einklappen.

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

Fügt die Komponente, die HextaUI-Theme-Tokens und alle HextaUI-Komponenten hinzu, von denen sie abhängt.

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

Größen und Formen

Fünf Größen, als Kreise oder Quadrate. Initialen und das Nutzer-Icon skalieren mit der Box, und quadratische Ecken werden mit der Größe kleiner. Ein leeres <AvatarFallback /> zeigt das Nutzer-Icon.

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>
  )
}

Lädt

Initialen erscheinen, während das Foto lädt, dann blendet das Foto darüber ein. Ein defektes Foto behält den Fallback. Übergib delay, um vor dem Anzeigen der Initialen zu warten, damit schnelle Fotos sie nie aufblitzen lassen.

"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>
  )
}

Initialen

getInitials() wählt den ersten und den letzten Initialbuchstaben. Es verarbeitet E-Mail-Adressen, Emoji, CJK- und RTL-Namen, kombinierende Zeichen und Namen ganz ohne Buchstaben.

"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 /> sitzt bei jeder Größe und Form am Rand. Setze status für einen farbigen Punkt mit zugänglichem Label oder übergib ein Icon. Ein Statuswechsel spielt einen einzelnen Puls ab.

"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>
  )
}

Gruppe

<AvatarGroup /> überlappt seine Avatare und legt deren Größe und Form fest. max fasst den Rest zu einer Zahl zusammen.

"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>
  )
}

Verlinkte Gruppe

Rendere Avatare mit render als Links und gib jedem ein aria-label. Ein fokussierter Avatar hebt sich über seine Nachbarn, damit der Ring nie abgeschnitten wird. Füge <AvatarGroupCount /> selbst hinzu, wenn die Gesamtzahl aus deinen Daten stammt.

"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

Avatare schrumpfen in engen Reihen nie. Eine Größenklasse wie size-20 skaliert Initialen und Badge mit, und lange Initialen laufen nie über.

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>
  )
}

Rechts nach links

Das Badge bleibt an der Endecke, die in RTL links liegt, und Gruppen überlappen von rechts.

"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>
  )
}

Avatare sind für sich nicht fokussierbar. Als Link oder Button gerendert, erhalten sie die üblichen Tasten.

TasteAktion
TabSetzt den Fokus auf den nächsten verlinkten Avatar.
EnterFolgt dem fokussierten Link.
  • Verwende alt="", wenn der Name der Person bereits neben dem Avatar steht, und den Namen als Alt-Text, wenn nicht.
  • Badges mit einem status werden als „Online“, „Away“, „Busy“ oder „Offline“ angesagt. Offline wird als Ring gezeichnet, sodass der Status nie allein auf Farbe beruht.
  • Gruppen haben role="group". Die Zahl wird als „3 more“ vorgelesen, nicht als „+3“.
  • Bei reduzierter Bewegung erscheinen Fotos ohne Einblenden, und Statuswechsel pulsieren nicht.

Basiert auf dem Base UI Avatar. Jeder Teil akzeptiert die Attribute des Elements, das er rendert. Die Stile werden als avatarVariants und avatarBadgeVariants exportiert.

PropTypStandard
sizeWird von der Gruppe übernommen, wenn nicht angegeben.
"xs" | "sm" | "default" | "lg" | "xl""default"
shapeWird von der Gruppe übernommen, wenn nicht angegeben.
"circle" | "square""circle"
render
ReactElement | (props, state) => ReactElement<span>
AttributBeschreibung
data-slot="avatar"Avatare in CSS ansprechen.
data-sizeDie aufgelöste Größe.
data-shapeDie aufgelöste Form.
--avatar-radiusDer Eckenradius, den jede Ebene teilt.
PropTypStandard
src
string–
alt
string–
onLoadingStatusChange
(status: "idle" | "loading" | "loaded" | "error") => void–
keepMountedDas Bild direkt laden, statt es vorzuladen, für loading="lazy" oder next/image.
booleanfalse
render
ReactElement | (props, state) => ReactElement<img>
AttributBeschreibung
data-slot="avatar-image"Bilder in CSS ansprechen.
data-loadingVorhanden, während das Bild lädt.
data-errorVorhanden, wenn das Bild nicht geladen werden konnte.
data-starting-styleVorhanden, während das Bild einblendet.
data-ending-styleVorhanden, während das Bild ausblendet.
PropTypStandard
childrenLeer oder nur Leerraum zeigt das Nutzer-Icon.
ReactNode<IconUser />
delayMillisekunden, die vor dem Anzeigen gewartet wird.
number0
render
ReactElement | (props, state) => ReactElement<span>
AttributBeschreibung
data-slot="avatar-fallback"Fallbacks in CSS ansprechen.
data-readyfalse, bis die Verzögerung verstrichen ist.
PropTypStandard
statusFärbt den Punkt ein und benennt ihn für assistive Technologien. Ohne diese Angabe nutzt das Badge die Primärfarbe.
"online" | "away" | "busy" | "offline"–
childrenEin Icon im Badge. Bei den Größen xs und sm ausgeblendet.
ReactNode–
AttributBeschreibung
data-slot="avatar-badge"Badges in CSS ansprechen.
data-statusDer aktuelle Status.
data-slot="avatar-badge-pulse"Der Puls, der nach einem Statuswechsel abgespielt wird.
PropTypStandard
size
"xs" | "sm" | "default" | "lg" | "xl""default"
shape
"circle" | "square""circle"
maxWie viele Elemente angezeigt werden, einschließlich der Zahl. Werte unter 2 werden auf 2 angehoben.
number–
AttributBeschreibung
data-slot="avatar-group"Gruppen in CSS ansprechen.
data-sizeDie Größe der Gruppe.
PropTypStandard
countWird als +3 angezeigt, oder als 99+ über 99.
number–
childrenErsetzt die Zahl, zum Beispiel durch ein Icon.
ReactNode–
sizeWird von der Gruppe übernommen, wenn nicht angegeben.
"xs" | "sm" | "default" | "lg" | "xl"–
shapeWird von der Gruppe übernommen, wenn nicht angegeben.
"circle" | "square"–
AttributBeschreibung
data-slot="avatar-group-count"Den Zähler in CSS ansprechen.
data-sizeDie aufgelöste Größe.
data-shapeDie aufgelöste Form.

getInitials(name, max = 2) gibt bis zu max Initialen in Großbuchstaben zurück: die des ersten und die des letzten Worts. Bei einer E-Mail-Adresse wird der Teil vor dem @ verwendet. Hat der Name keine Buchstaben, Zahlen oder Emoji, wird ein leerer String zurückgegeben, sodass der Fallback das Nutzer-Icon zeigt.

In Blocks verwendet

Blocks, die auf Avatar aufbauen.