All components
Avatar
User portraits with image and initials fallback, rounded or circular, from 2xs to 2xl.
EW
123456789101112import { Avatar } from "#components/ui/ark-ui/avatar.tsx";
export function Example() { return ( // Ark'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.Fallback>EW</Avatar.Fallback> <Avatar.Image src="/avatar.png" alt="Eelco Wiersma" /> </Avatar.Root> );}Variants
EW
subtleEW
solidEW
outlineSizes
EW
xsEW
smEW
mdEW
lgEW
xlInstall
1npx stylexblocks add avatarSource
components/ui/ark-ui/avatar.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168import * as stylex from "@stylexjs/stylex";import { Avatar as ArkAvatar } from "@ark-ui/react/avatar";import { createSlotRecipeContext } from "#lib/ark-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 Ark's real * avatar machine: the styled slots wrap `Avatar.Root/Image/Fallback` from * `@ark-ui/react/avatar` via `asChild`, 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: ({ children, ...props }: React.ComponentProps<typeof StyledRoot> & ArkAvatar.RootProps) => ( <StyledRoot asChild {...props}> <ArkAvatar.Root>{children}</ArkAvatar.Root> </StyledRoot> ), Image: ( props: React.ComponentProps<typeof StyledImage> & ArkAvatar.ImageProps, ) => ( <StyledImage asChild {...props}> <ArkAvatar.Image /> </StyledImage> ), Fallback: ({ children, ...props }: React.ComponentProps<typeof StyledFallback> & ArkAvatar.FallbackProps) => ( <StyledFallback asChild {...props}> <ArkAvatar.Fallback>{children}</ArkAvatar.Fallback> </StyledFallback> ),};