All components
Avatar
User portraits with image and initials fallback, rounded or circular, from 2xs to 2xl.
EW
123456789101112import { Avatar } from "#components/ui/base-ui/avatar.tsx";
export function Example() { return ( // Base UI's avatar machine wears the recipe: image load/error state // drives the fallback, while variant/size/shape resolve once on Root <Avatar.Root variant="subtle" size="md" shape="full"> <Avatar.Image src="/avatar.png" alt="Eelco Wiersma" /> <Avatar.Fallback>EW</Avatar.Fallback> </Avatar.Root> );}Variants
EWsubtle
EWsolid
EWoutline
Sizes
EWxs
EWsm
EWmd
EWlg
EWxl
Install
1npx stylexblocks add avatarSource
components/ui/base-ui/avatar.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155import * as stylex from "@stylexjs/stylex";import { Avatar as BaseAvatar } from "@base-ui-components/react/avatar";import { createSlotRecipeContext } from "#lib/base-ui/create-slot-recipe-context.tsx";import { defineSlotRecipe } from "#lib/recipe-props.ts";import { colorPalette } from '@stylexblocks/ui/color-palette.stylex.ts'import { fontWeights } from '@stylexblocks/ui/tokens/font-weights.stylex.ts'import { radii } from '@stylexblocks/ui/tokens/radii.stylex.ts'import { semanticRadii } from '@stylexblocks/ui/semantic-tokens/radii.stylex.ts'import { sizes } from '@stylexblocks/ui/tokens/sizes.stylex.ts'
const avatarRoot = stylex.create({ base: { '--avatar-font-size': 'calc(var(--avatar-size) / 2)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontWeight: fontWeights.medium, position: 'relative', verticalAlign: 'top', flexShrink: 0, userSelect: 'none', width: 'var(--avatar-size)', height: 'var(--avatar-size)', fontSize: 'var(--avatar-font-size)', borderRadius: 'var(--avatar-radius)', },})
const avatarRootSizes = stylex.create({ full: { '--avatar-size': '100%', '--avatar-font-size': 'calc(var(--tag-avatar-size) / 2)', }, '2xs': { '--avatar-size': sizes._4, }, xs: { '--avatar-size': sizes._5, }, sm: { '--avatar-size': sizes._6, }, md: { '--avatar-size': sizes._9, }, lg: { '--avatar-size': sizes._12, }, xl: { '--avatar-size': sizes._16, }, '2xl': { '--avatar-size': sizes._20, },})
const avatarRootVariants = stylex.create({ solid: { backgroundColor: colorPalette.solid, color: colorPalette.contrast, }, subtle: { backgroundColor: colorPalette.muted, color: colorPalette.fg, }, outline: { color: colorPalette.fg, borderWidth: '1px', borderColor: colorPalette.muted, },})
const avatarRootShapes = stylex.create({ rounded: { '--avatar-radius': semanticRadii.indicatorMd, }, full: { '--avatar-radius': radii.full, },})
const avatarRootBorderless = stylex.create({ true: {},})
const avatarImage = stylex.create({ base: { width: '100%', height: '100%', objectFit: 'cover', borderRadius: 'var(--avatar-radius)', },})
const avatarFallback = stylex.create({ base: { lineHeight: 1, textTransform: 'uppercase', fontWeight: fontWeights.medium, fontSize: 'var(--avatar-font-size)', borderRadius: 'var(--avatar-radius)', },})
export type AvatarSize = keyof typeof avatarRootSizes
export type AvatarVariant = keyof typeof avatarRootVariants
export type AvatarShape = keyof typeof avatarRootShapes
export type AvatarBorderless = keyof typeof avatarRootBorderless
// defineSlotRecipe validates the shape at compile time: default values must// exist in the axis maps, and slot keys must be `styles` or a declared axis// (axis maps are looked up by pluralized axis name: `shape` → `shapes`)const avatarSlotRecipe = defineSlotRecipe({ slots: { root: { styles: avatarRoot, sizes: avatarRootSizes, variants: avatarRootVariants, shapes: avatarRootShapes, }, image: { styles: avatarImage }, fallback: { styles: avatarFallback }, }, defaultVariants: { size: "md", shape: "full", variant: "solid" },});
/** * Avatar — recipe and component colocated in one file, worn by Base UI's * real avatar machine: the styled slots render `Avatar.Root/Image/Fallback` * from `@base-ui-components/react/avatar` through the `render` prop, so * image load/error state drives the fallback while `<Avatar.Root variant * size shape>` resolves the recipe once and every part inherits it through * context. */const { withProvider, withContext } = createSlotRecipeContext(avatarSlotRecipe);
const StyledRoot = withProvider("div", "root", "AvatarRoot");const StyledImage = withContext("img", "image", "AvatarImage");const StyledFallback = withContext("span", "fallback", "AvatarFallback");
export const Avatar = { Root: (props: React.ComponentProps<typeof StyledRoot>) => ( <StyledRoot {...props} render={<BaseAvatar.Root />} /> ), Image: (props: React.ComponentProps<typeof StyledImage>) => ( <StyledImage {...props} render={<BaseAvatar.Image />} /> ), Fallback: (props: React.ComponentProps<typeof StyledFallback>) => ( <StyledFallback {...props} render={<BaseAvatar.Fallback />} /> ),};