HextaUI

Avatar

イニシャルのフォールバック、ステータスバッジ、件数にまとめられる積み重ねグループを備えたユーザー写真です。

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

コンポーネント、HextaUIのテーマトークン、依存するHextaUIコンポーネントを追加します。

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

サイズと形

円形または正方形の 5 つのサイズ。イニシャルとユーザーアイコンはボックスに合わせて拡大縮小し、正方形の角丸はサイズに応じて小さくなります。空の <AvatarFallback /> はユーザーアイコンを表示します。

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

読み込み中

写真の読み込み中はイニシャルが表示され、その上に写真がフェードインします。写真が壊れている場合は、フォールバックが維持されます。delay を渡すとイニシャルの表示を待つため、速く読み込まれた写真でイニシャルが一瞬見えることはありません。

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

イニシャル

getInitials() は最初と最後のイニシャルを選びます。メールアドレス、絵文字、CJK や RTL の名前、結合文字、文字をまったく含まない名前にも対応します。

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

ステータス

<AvatarBadge /> は、どのサイズや形でも縁に配置されます。status を設定すると、アクセシブルなラベル付きの色付きドットが表示され、アイコンを渡すこともできます。ステータスを変更すると、パルスが 1 回再生されます。

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

グループ

<AvatarGroup /> はアバターを重ねて、サイズと形を設定します。max を指定すると、残りは件数にまとめられます。

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

リンク付きグループ

render でアバターをリンクとしてレンダリングし、それぞれに aria-label を付けます。フォーカス中のアバターは隣より前面に出るため、リングが切れることはありません。合計がデータから来る場合は、自分で <AvatarGroupCount /> を追加してください。

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

レイアウト

窮屈な行でも、アバターが縮むことはありません。size-20 のようなサイズクラスを指定すると、イニシャルとバッジも一緒に拡大縮小し、長いイニシャルもはみ出しません。

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

右から左

バッジは終了側の角に留まり、RTL では左側になります。グループは右から重なります。

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

アバター自体はフォーカスできません。リンクやボタンとしてレンダリングすると、通常のキー操作が使えます。

キーアクション
Tabフォーカスを次のリンク付きアバターに移動します。
Enterフォーカス中のリンクに追従します。
  • アバターの隣にすでに本人の名前がある場合は alt="" を使い、ない場合は本人の名前を alt テキストにします。
  • status を持つバッジは、「Online」「Away」「Busy」「Offline」と通知されます。Offline はリングで描画されるため、ステータスが色だけに頼ることはありません。
  • グループには role="group" が付きます。件数は「+3」ではなく「3 more」と読み上げられます。
  • モーションの低減が有効な場合、写真はフェードせずに表示され、ステータスの変更でパルスは再生されません。

Base UI の avatar をベースにしています。各パーツは、レンダリングする要素の属性をすべて受け付けます。スタイルは avatarVariants と avatarBadgeVariants としてエクスポートされています。

プロパティ型デフォルト
size省略すると、グループから継承されます。
"xs" | "sm" | "default" | "lg" | "xl""default"
shape省略すると、グループから継承されます。
"circle" | "square""circle"
render
ReactElement | (props, state) => ReactElement<span>
属性説明
data-slot="avatar"CSS でアバターを指定します。
data-size解決されたサイズ。
data-shape解決された形。
--avatar-radiusすべてのレイヤーで共有される角丸の半径。
プロパティ型デフォルト
src
string–
alt
string–
onLoadingStatusChange
(status: "idle" | "loading" | "loaded" | "error") => void–
keepMounted画像をプリロードせず、その場で読み込みます。loading="lazy" や next/image 向けです。
booleanfalse
render
ReactElement | (props, state) => ReactElement<img>
属性説明
data-slot="avatar-image"CSS で画像を指定します。
data-loading画像の読み込み中、付与されます。
data-error画像の読み込みに失敗したときに付与されます。
data-starting-style画像がフェードインしている間、付与されます。
data-ending-style画像がフェードアウトしている間、付与されます。
プロパティ型デフォルト
children空文字または空白のみの場合は、ユーザーアイコンが表示されます。
ReactNode<IconUser />
delay表示するまでの待機時間(ミリ秒)。
number0
render
ReactElement | (props, state) => ReactElement<span>
属性説明
data-slot="avatar-fallback"CSS でフォールバックを指定します。
data-ready遅延時間が経過するまでは false です。
プロパティ型デフォルト
statusドットに色を付け、支援技術向けにラベルを付けます。指定しない場合、バッジは primary カラーを使います。
"online" | "away" | "busy" | "offline"–
childrenバッジ内のアイコン。xs と sm のサイズでは非表示になります。
ReactNode–
属性説明
data-slot="avatar-badge"CSS でバッジを指定します。
data-status現在のステータス。
data-slot="avatar-badge-pulse"ステータスの変更後に再生されるパルス。
プロパティ型デフォルト
size
"xs" | "sm" | "default" | "lg" | "xl""default"
shape
"circle" | "square""circle"
max件数を含めて表示する項目数。2 未満の値は 2 に引き上げられます。
number–
属性説明
data-slot="avatar-group"CSSでグループを指定します。
data-sizeグループのサイズ。
プロパティ型デフォルト
count+3 のように表示され、99 を超えると 99+ になります。
number–
children件数の代わりに、たとえばアイコンを表示します。
ReactNode–
size省略すると、グループから継承されます。
"xs" | "sm" | "default" | "lg" | "xl"–
shape省略すると、グループから継承されます。
"circle" | "square"–
属性説明
data-slot="avatar-group-count"CSSでカウントを指定します。
data-size解決されたサイズ。
data-shape解決された形。

getInitials(name, max = 2) は、最大 max 個の大文字のイニシャルを返します。最初の単語と最後の単語のイニシャルです。メールアドレスの場合は @ より前の部分を使います。名前に文字、数字、絵文字がない場合は空文字列を返すため、フォールバックにはユーザーアイコンが表示されます。

使用しているブロック

Avatar の上に構築されるブロック。