Avatar
A graphical representation of the user, often used in profile sections.
import { Avatar, type AvatarProps } from '~/components/ui/avatar'
export const Demo = (props: AvatarProps) => {
return <Avatar src="https://i.pravatar.cc/300" name="John Doe" {...props} />
}
import { Avatar, type AvatarProps } from '~/components/ui/avatar'
export const Demo = (props: AvatarProps) => {
return <Avatar src="https://i.pravatar.cc/300" name="John Doe" {...props} />
}
Usage
import { Avatar } from '~/components/ui/avatar'
Examples
Name Only
When only a name is supplied, the initials are displayed.
CS
<Avatar name="Christian Schröter" />
No User Data
Displays a default avatar in the absence of user data.
<Avatar />
Installation
npx @park-ui/cli components add avatar
1
Add Styled Primitive
Copy the code snippet below into ~/components/ui/styled/avatar.tsx
'use client'
import type { Assign } from '@ark-ui/react'
import { Avatar } from '@ark-ui/react/avatar'
import { type AvatarVariantProps, avatar } from 'styled-system/recipes'
import type { ComponentProps, HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from './utils/create-style-context'
const { withProvider, withContext } = createStyleContext(avatar)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
HTMLDivElement,
Assign<Assign<HTMLStyledProps<'div'>, Avatar.RootProviderBaseProps>, AvatarVariantProps>
>(Avatar.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
HTMLDivElement,
Assign<Assign<HTMLStyledProps<'div'>, Avatar.RootBaseProps>, AvatarVariantProps>
>(Avatar.Root, 'root')
export const Fallback = withContext<
HTMLSpanElement,
Assign<HTMLStyledProps<'span'>, Avatar.FallbackBaseProps>
>(Avatar.Fallback, 'fallback')
export const Image = withContext<
HTMLImageElement,
Assign<HTMLStyledProps<'img'>, Avatar.ImageBaseProps>
>(Avatar.Image, 'image')
export { AvatarContext as Context } from '@ark-ui/react/avatar'
export type { AvatarStatusChangeDetails as StatusChangeDetails } from '@ark-ui/react/avatar'
import { type Assign, Avatar } from '@ark-ui/solid'
import type { ComponentProps } from 'solid-js'
import { type AvatarVariantProps, avatar } from 'styled-system/recipes'
import type { HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from './utils/create-style-context'
const { withProvider, withContext } = createStyleContext(avatar)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
Assign<Assign<HTMLStyledProps<'div'>, Avatar.RootProviderBaseProps>, AvatarVariantProps>
>(Avatar.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
Assign<Assign<HTMLStyledProps<'div'>, Avatar.RootBaseProps>, AvatarVariantProps>
>(Avatar.Root, 'root')
export const Fallback = withContext<Assign<HTMLStyledProps<'span'>, Avatar.FallbackBaseProps>>(
Avatar.Fallback,
'fallback',
)
export const Image = withContext<Assign<HTMLStyledProps<'img'>, Avatar.ImageBaseProps>>(
Avatar.Image,
'image',
)
export { AvatarContext as Context } from '@ark-ui/solid'
export type { AvatarStatusChangeDetails as StatusChangeDetails } from '@ark-ui/solid'
No snippet found
2
Add Composition
Copy the composition snippet below into ~/components/ui/avatar.tsx
import { forwardRef } from 'react'
import * as StyledAvatar from './styled/avatar'
export interface AvatarProps extends StyledAvatar.RootProps {
name?: string
src?: string
}
export const Avatar = forwardRef<HTMLDivElement, AvatarProps>((props, ref) => {
const { name, src, ...rootProps } = props
return (
<StyledAvatar.Root ref={ref} {...rootProps}>
<StyledAvatar.Fallback>{getInitials(name) || <UserIcon />}</StyledAvatar.Fallback>
<StyledAvatar.Image src={src} alt={name} />
</StyledAvatar.Root>
)
})
Avatar.displayName = 'Avatar'
const UserIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<title>User Icon</title>
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" />
<circle cx="12" cy="7" r="4" />
</svg>
)
const getInitials = (name = '') =>
name
.split(' ')
.map((part) => part[0])
.splice(0, 2)
.join('')
.toUpperCase()
import { splitProps } from 'solid-js'
import * as StyledAvatar from './styled/avatar'
export interface AvatarProps extends StyledAvatar.RootProps {
name?: string
src?: string
}
export const Avatar = (props: AvatarProps) => {
const [localProps, rootProps] = splitProps(props, ['name', 'src'])
return (
<StyledAvatar.Root {...rootProps}>
<StyledAvatar.Fallback>{getInitials(localProps.name) || <UserIcon />}</StyledAvatar.Fallback>
<StyledAvatar.Image src={localProps.src} alt={localProps.name} />
</StyledAvatar.Root>
)
}
const UserIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<title>User Icon</title>
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" />
<circle cx="12" cy="7" r="4" />
</svg>
)
const getInitials = (name = '') =>
name
.split(' ')
.map((part) => part[0])
.splice(0, 2)
.join('')
.toUpperCase()
3
Integrate Recipe
If you're not using @park-ui/preset
, add the following recipe to yourpanda.config.ts
:
import { avatarAnatomy } from '@ark-ui/anatomy'
import { defineSlotRecipe } from '@pandacss/dev'
export const avatar = defineSlotRecipe({
className: 'avatar',
slots: avatarAnatomy.keys(),
base: {
root: {
borderRadius: 'full',
flexShrink: 0,
overflow: 'hidden',
},
fallback: {
alignItems: 'center',
background: 'bg.subtle',
borderRadius: 'full',
borderWidth: '1px',
color: 'fg.default',
display: 'flex',
fontWeight: 'semibold',
height: 'inherit',
justifyContent: 'center',
_hidden: {
display: 'none',
},
},
image: {
objectFit: 'cover',
},
},
defaultVariants: {
size: 'md',
},
variants: {
size: {
xs: {
root: {
height: '8',
width: '8',
},
image: {
height: '8',
width: '8',
},
fallback: {
textStyle: 'xs',
'& svg': {
width: '4',
height: '4',
},
},
},
sm: {
root: {
height: '9',
width: '9',
},
image: {
height: '9',
width: '9',
},
fallback: {
textStyle: 'sm',
'& svg': {
width: '4',
height: '4',
},
},
},
md: {
root: {
height: '10',
width: '10',
},
image: {
height: '10',
width: '10',
},
fallback: {
textStyle: 'md',
'& svg': {
width: '5',
height: '5',
},
},
},
lg: {
root: {
height: '11',
width: '11',
},
image: {
height: '11',
width: '11',
},
fallback: {
textStyle: 'lg',
'& svg': {
width: '6',
height: '6',
},
},
},
xl: {
root: {
height: '12',
width: '12',
},
image: {
height: '12',
width: '12',
},
fallback: {
textStyle: 'xl',
'& svg': {
width: '7',
height: '7',
},
},
},
'2xl': {
root: {
height: '16',
width: '16',
},
image: {
height: '16',
width: '16',
},
fallback: {
textStyle: '2xl',
'& svg': {
width: '8',
height: '8',
},
},
},
},
},
})