Avatar
Overview
The Avatar represents a user or entity with an image, falling back to initials when no image is available or the image fails to load. The useAvatarRecipe() composable creates a fully configured recipe with color, variant, shape, and size options, while useAvatarBadgeRecipe() adds a small status dot for presence indicators. For stacked, overlapping avatars, see the Avatar Group recipe.
The Avatar recipe integrates directly with the default design tokens preset and generates type-safe utility classes at build time with zero runtime CSS.
Why use the Avatar recipe?
The Avatar recipe helps you:
- Ship faster with sensible defaults: A neutral, soft, circular avatar out of the box from a single composable call.
- Fail gracefully: The image clips to the avatar shape, and a styled initials fallback covers missing or broken images.
- Express identity and status: Colored fallbacks plus an optional status badge cover presence states without extra components.
- Customize without forking: Override base styles, default variants, or filter out options you don't need — all through the options API.
- Stay type-safe: Full TypeScript support means your editor catches invalid color, variant, shape, or size values at compile time.
Usage
Register the recipes
Add the Avatar recipes to a local Styleframe instance. The global styleframe.config.ts provides design tokens and utilities, while the component-level file registers the recipes themselves:
import { styleframe } from 'virtual:styleframe';
import { useAvatarRecipe, useAvatarBadgeRecipe } from '@styleframe/theme';
const s = styleframe();
const avatar = useAvatarRecipe(s);
const avatarBadge = useAvatarBadgeRecipe(s);
export default s;
Build the component
Import the avatar runtime function from the virtual module, pass variant props to compute class names, and render an <img> with an initials fallback:
import { useState } from "react";
import { avatar, type AvatarProps } from "virtual:styleframe";
interface AvatarComponentProps extends AvatarProps {
src?: string;
alt?: string;
label?: string;
}
export function Avatar({ src, alt, label, ...variants }: AvatarComponentProps) {
const [failed, setFailed] = useState(false);
return (
<span className={avatar(variants)}>
{src && !failed ? (
<img src={src} alt={alt} onError={() => setFailed(true)} />
) : (
label
)}
</span>
);
}
See it in action
Colors
The Avatar fallback surface supports 4 color options following the Minimal color pattern. The default neutral color renders a muted gray surface that adapts to light and dark mode.
Color Reference
| Color | Behavior | Use Case |
|---|---|---|
primary | Brand color, adapts to dark mode | Highlighted or "you" avatars |
light | Always light, fixed across modes | Avatars on dark backgrounds |
dark | Always dark, fixed across modes | Avatars on light backgrounds |
neutral | Adaptive (light ↔ dark) | Default, general-purpose fallback |
neutral for general-purpose initials fallbacks that adapt to both light and dark mode automatically.Variants
Two fill styles control how the fallback color is applied. solid fills the avatar with the color and uses light text; soft uses a tinted background with darker text.
| Variant | Description |
|---|---|
solid | Filled background with contrasting text |
soft | Tinted background with colored text (default) |
Shapes
The Avatar can be rendered as a circle (default) or a square with rounded corners. The image clips to the chosen shape automatically.
| Shape | Border Radius |
|---|---|
circle | @border-radius.full |
square | @border-radius.md |
Sizes
The Avatar comes in 5 sizes that control both the dimensions and the fallback initials font size.
| Size | Dimensions | Font Size |
|---|---|---|
xs | @spacing * 1.5 × @spacing * 1.5 | @font-size.2xs |
sm | @spacing * 2 × @spacing * 2 | @font-size.xs |
md | @spacing * 2.5 × @spacing * 2.5 | @font-size.sm |
lg | @spacing * 3 × @spacing * 3 | @font-size.md |
xl | @spacing * 4 × @spacing * 4 | @font-size.lg |
Image and fallback
Place an <img> inside the avatar — the recipe's > img selector sizes it to cover the box and clips it to the avatar shape via border-radius: inherit. Render the initials fallback when no image is set or when the image fails to load.
position: relative (rather than overflow: hidden), a status badge can sit at the edge without being clipped — see below.Status indicator
Add useAvatarBadgeRecipe() to render a small status dot anchored to the bottom-right of an avatar. The badge uses the Full color pattern so it can express presence states.
| Status color | Typical meaning |
|---|---|
success | Online (default) |
warning | Away |
error | Busy / do not disturb |
neutral | Offline |
<span :class="avatar({ size: 'lg' })">
AL
<span :class="avatarBadge({ color: 'success', size: 'lg' })" />
</span>
Accessibility
- Provide an
alton the image that names the person or entity (e.g.alt="Ada Lovelace"). Use an emptyalt=""only when the avatar is purely decorative and the name appears adjacent. - The initials fallback is text and is announced by screen readers; keep it meaningful (the person's initials).
- Communicate status in text, not color alone: pair the status badge with a visible or visually-hidden label (e.g. "Online") so presence isn't conveyed by color only.
Customization
Overriding Defaults
const avatar = useAvatarRecipe(s, {
defaultVariants: { color: 'primary', variant: 'solid', size: 'lg', shape: 'square' },
});
Filtering Variants
const avatar = useAvatarRecipe(s, {
filter: {
color: ['neutral', 'primary'],
shape: ['circle'],
},
});
API Reference
useAvatarRecipe(s, options?)
Creates the avatar recipe with color, variant, shape, and size variants. Registers a > img selector that clips the image to the avatar shape.
Parameters:
| Parameter | Type | Description |
|---|---|---|
s | Styleframe | The Styleframe instance |
options | DeepPartial<RecipeConfig> | Optional overrides for the recipe configuration |
options.base | VariantDeclarationsBlock | Custom base styles |
options.variants | Variants | Custom variant definitions |
options.defaultVariants | Record<keyof Variants, string> | Default variant values |
options.compoundVariants | CompoundVariant[] | Custom compound variant definitions |
options.filter | Record<string, string[]> | Limit which variant values are generated |
Variants:
| Variant | Options | Default |
|---|---|---|
color | primary, light, dark, neutral | neutral |
variant | solid, soft | soft |
shape | circle, square | circle |
size | xs, sm, md, lg, xl | md |
useAvatarBadgeRecipe(s, options?)
Creates the status badge recipe, positioned absolutely at the bottom-right of an avatar.
Variants:
| Variant | Options | Default |
|---|---|---|
color | primary, secondary, success, info, warning, error, light, dark, neutral | success |
size | xs, sm, md, lg, xl | md |
position | top-left, top-right, bottom-left, bottom-right | bottom-right |
Best Practices
- Always set
alton avatar images that represent a person or entity. - Match the badge size to the avatar size so the status dot scales proportionally.
- Use
neutral+softfor general-purpose initials; reserveprimary/solidfor emphasis. - Keep initials short (1–2 characters) so they stay legible at small sizes.
- Filter what you don't need: Pass a
filteroption to reduce generated CSS.
FAQ
<img> inside it, the recipe's > img selector covers the box and clips to the avatar shape. If the image is missing or errors, render the initials instead — they sit on the fallback surface. The example components wire an @error/onError handler to switch to the fallback automatically.border-radius: inherit rather than overflow: hidden on the root. This keeps the root position: relative without clipping, so a status badge (or any edge-anchored element) can sit at the avatar's corner without being cut off.color per user (e.g. derive it from a hash of their name) and pass it to avatar({ color }). The solid and soft variants give filled or tinted surfaces respectively.