Use it for
Use BbAvatar for a person's or entity's visual identity in member lists, activity feeds, and assignee cells. It is display-only.
Use something else when
BbIndicator— status or a count belongs on the avatarBbDropdown— the avatar opens an account menuBbIcon— the mark represents no person or entity
Pass Through
Hover or tap a part to outline it. Toggles flip loading, errors and warnings when the component has them — only parts highlight.
Show a member
Pass the photo and a meaningful alt; keep initials in the default slot so the same row survives a missing or failed image.
<template>
<div class="flex items-center gap-3">
<BbAvatar :alt="user.fullName" size="xl" :src="photo" :timeout="4000" />
<div class="flex flex-col text-sm">
<span class="font-medium">{{ user.fullName }}</span>
<span class="text-xs text-[color:var(--bb-text-muted)]">
{{ user.jobTitle }}
</span>
</div>
</div>
</template>
<script setup lang="ts">
import { BbAvatar } from 'bitboss-ui';
import { usersWithPhoto } from '~/demo-data';
const user = usersWithPhoto[0]!;
/**
* A real portrait from a remote origin. Every photo demo on this page raises
* `timeout` past its 400ms default for that reason — see "Slow images".
*/
const photo = user.photo ?? undefined;
</script>
The fallback order is image, default slot, then the built-in person icon. On a fallback, alt becomes the accessible name. Leave it empty only when the avatar is decorative.
<template>
<div class="flex flex-wrap items-start gap-6 text-center text-xs">
<!-- 1. A source that loads. Nothing else is consulted. -->
<figure class="flex flex-col items-center gap-1">
<BbAvatar :alt="user.fullName" size="2xl" :src="photo" :timeout="4000" />
<figcaption class="opacity-70">photo</figcaption>
</figure>
<!-- 2. No source: the default slot is what shows. -->
<figure class="flex flex-col items-center gap-1">
<BbAvatar :alt="colleague.fullName" size="2xl">
{{ colleague.initials }}
</BbAvatar>
<figcaption class="opacity-70">slot</figcaption>
</figure>
<!-- 3. No source and no slot: the built-in person glyph. -->
<figure class="flex flex-col items-center gap-1">
<BbAvatar alt="Unassigned" size="2xl" />
<figcaption class="opacity-70">built-in</figcaption>
</figure>
<!-- 4. A source that fails falls through to the slot, exactly as if it
had never been passed. -->
<figure class="flex flex-col items-center gap-1">
<BbAvatar :alt="colleague.fullName" size="2xl" :src="brokenPhoto">
{{ colleague.initials }}
</BbAvatar>
<figcaption class="opacity-70">failed photo</figcaption>
</figure>
</div>
</template>
<script setup lang="ts">
import { BbAvatar } from 'bitboss-ui';
import { users, usersWithPhoto } from '~/demo-data';
const user = usersWithPhoto[0]!;
const photo = user.photo ?? undefined;
/** One of the many people in the directory who never uploaded a picture. */
const colleague = users.find((person) => person.photo === null)!;
/**
* Stands in for a broken URL. It is a data URI that decodes to nothing, so the
* browser fires `error` straight away and the page makes no request — a 404 in
* a demo would be a network dependency.
*/
const brokenPhoto = 'data:image/png;base64,QUJD';
</script>
size accepts the shared xs–2xl scale, pixels, or a CSS length. The image is always center-cropped into a circle; there is no square variant.
Presence and groups
Compose status and overflow around the avatar instead of turning the avatar into an interactive control.
Marta VilloresiHead of Product · Online
Lukas BrandtEngineering Manager · Away
Sofia MarchettiDesign Lead · Online
Amara OkonkwoStaff Engineer · Away
<template>
<ul class="flex max-w-xs flex-col gap-2 text-sm">
<li v-for="member in roster" :key="member.id" class="flex items-center gap-3">
<!--
BbIndicator positions and clips the dot. Never hand-place an
absolutely positioned marker over the avatar.
-->
<BbIndicator
bottom
dot
:variant="member.online ? 'success' : 'default'"
:style="member.online ? undefined : { '--bg': 'var(--bb-text-muted)' }"
>
<BbAvatar :alt="member.fullName" size="40" :src="member.avatar" />
</BbIndicator>
<span class="flex min-w-0 flex-col">
<span class="truncate font-medium">{{ member.fullName }}</span>
<!-- The dot is decorative and announced to nobody, so the state
is written out here as well. -->
<span class="truncate text-xs text-[color:var(--bb-text-muted)]">
{{ member.jobTitle }} · {{ member.online ? 'Online' : 'Away' }}
</span>
</span>
</li>
</ul>
</template>
<script setup lang="ts">
import { BbAvatar, BbIndicator } from 'bitboss-ui';
import { users } from '~/demo-data';
/** Presence is a fixed property of the fixture, never a random value. */
const roster = users
.slice(0, 4)
.map((user) => ({ ...user, online: user.id % 2 === 1 }));
</script>
<template>
<div class="flex max-w-xs flex-col gap-2 text-sm">
<span class="font-medium">Reviewers</span>
<div class="avatar-stack">
<BbAvatar
v-for="reviewer in visible"
:key="reviewer.id"
:alt="reviewer.fullName"
size="40"
:src="reviewer.avatar"
/>
<!-- The overflow marker is just another avatar: same size, same
ring, the count in the fallback slot. -->
<BbAvatar v-if="hidden > 0" :alt="`${hidden} more reviewers`" size="40">
+{{ hidden }}
</BbAvatar>
</div>
</div>
</template>
<script setup lang="ts">
import { BbAvatar } from 'bitboss-ui';
import { users } from '~/demo-data';
const reviewers = users.slice(0, 6);
const maxVisible = 4;
const visible = reviewers.slice(0, maxVisible);
const hidden = reviewers.length - maxVisible;
</script>
<style scoped>
/*
* Stacking is the one case where reaching into the avatar root is the intended
* answer: the negative margin makes the overlap and the panel-colored ring
* keeps the overlapped edges readable. Keep it in one reusable class rather
* than as inline styles on each avatar.
*/
.avatar-stack {
display: flex;
}
.avatar-stack :deep(.bb-avatar) {
box-shadow: 0 0 0 2px var(--bb-panel);
}
.avatar-stack :deep(.bb-avatar + .bb-avatar) {
margin-left: -12px;
}
</style>
A presence dot is silent, so repeat the status in text. Wrap a clickable avatar in a real button, link, or dropdown trigger; BbAvatar emits no events.
Slow images and SSR
The component checks the image timeout milliseconds after mount (400 by default) to recover events missed before hydration. A late image can still replace the fallback.
<BbAvatar :src="member.photo" :alt="member.name" :timeout="4000" />
Raise the deadline for a slow origin. Use srcset with sizes for responsive sources; both pass through to the underlying image.
Interaction and color
For a bot or service account, place an unlabeled BbIcon in the fallback slot and keep the actor's name in alt.
The fallback uses --bb-primary and --bb-primary-fg. For a per-tenant color, set both local values on the avatar:
<BbAvatar alt="Acme Bot" :style="{ '--bg': tenant.color, '--fg': tenant.foreground }">
AB
</BbAvatar>
Coming from v2color → tokens
The color prop was removed. Retheme the primary pair for product-wide color, or use --bg and --fg for one instance.