Closed Components
Learn how to create reusable components using the example of an avatar
Motivation
Writing a few lines of code every time you need a simple Avatar is tedious. Creating a dedicated component
encapsulates logic, simplifies the API, ensures consistent usage, and maintains clean code. This approach enhances
reusability, making the component easier to maintain and test.
Here's an example of an Avatar component that can be used consistently across your application:
import { Avatar as ArkAvatar } from '@ark-ui/react/avatar'
import { UserIcon } from 'lucide-react'
import { forwardRef } from 'react'
import styles from 'styles/avatar.module.css'
export interface AvatarProps extends ArkAvatar.RootProps {
name?: string | undefined
src?: string | undefined
}
export const Avatar = forwardRef<HTMLDivElement, AvatarProps>((props, ref) => {
const { name, src, ...rootProps } = props
return (
<ArkAvatar.Root ref={ref} className={styles.Root} {...rootProps}>
<ArkAvatar.Fallback className={styles.Fallback}>{getInitials(name) || <UserIcon />}</ArkAvatar.Fallback>
<ArkAvatar.Image className={styles.Image} src={src} alt={name} />
</ArkAvatar.Root>
)
})
const getInitials = (name = '') =>
name
.split(' ')
.map((part) => part[0])
.slice(0, 2)
.join('')
.toUpperCase()
import { Avatar as ArkAvatar } from '@ark-ui/solid/avatar'
import { UserIcon } from 'lucide-solid'
import { Show, splitProps } from 'solid-js'
import styles from 'styles/avatar.module.css'
export interface AvatarProps extends ArkAvatar.RootProps {
name?: string
src?: string
}
export const Avatar = (props: AvatarProps) => {
const [localProps, rootProps] = splitProps(props, ['name', 'src'])
return (
<ArkAvatar.Root class={styles.Root} {...rootProps}>
<ArkAvatar.Fallback class={styles.Fallback}>
<Show when={localProps.name} fallback={<UserIcon />}>
{getInitials(localProps.name)}
</Show>
</ArkAvatar.Fallback>
<ArkAvatar.Image class={styles.Image} src={localProps.src} alt={localProps.name} />
</ArkAvatar.Root>
)
}
const getInitials = (name = '') =>
name
.split(' ')
.map((part) => part[0])
.splice(0, 2)
.join('')
.toUpperCase()
<script setup lang="ts">
import { useForwardPropsEmits } from '@ark-ui/vue'
import { Avatar, type AvatarRootEmits, type AvatarRootProps } from '@ark-ui/vue/avatar'
import { computed } from 'vue'
import styles from 'styles/avatar.module.css'
export interface AvatarProps extends AvatarRootProps {
src?: string
name: string
}
const props = defineProps<AvatarProps>()
const emits = defineEmits<AvatarRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
const getInitials = computed(() =>
props.name
.split(' ')
.map((part) => part[0])
.slice(0, 2)
.join('')
.toUpperCase(),
)
</script>
<template>
<Avatar.Root :class="styles.Root" v-bind="forwarded">
<Avatar.Fallback :class="styles.Fallback">{{ getInitials }}</Avatar.Fallback>
<Avatar.Image :class="styles.Image" :src="props.src" :alt="props.name" />
</Avatar.Root>
</template>
<script lang="ts">
import { Avatar, type AvatarRootBaseProps } from '@ark-ui/svelte/avatar'
import { UserIcon } from 'lucide-svelte'
import styles from 'styles/avatar.module.css'
interface Props extends AvatarRootBaseProps {
name?: string | undefined
src?: string | undefined
}
let { name, src, ...rootProps }: Props = $props()
const getInitials = (name = '') =>
name
.split(' ')
.map((part) => part[0])
.slice(0, 2)
.join('')
.toUpperCase()
const initials = $derived(getInitials(name))
</script>
<Avatar.Root class={styles.Root} {...rootProps}>
<Avatar.Fallback class={styles.Fallback}>
{#if initials}
{initials}
{:else}
<UserIcon />
{/if}
</Avatar.Fallback>
<Avatar.Image class={styles.Image} {src} alt={name} />
</Avatar.Root>
Usage
To use the Avatar component, pass the name and src props as shown below:
<Avatar name="Christian" src="https://avatars.githubusercontent.com/u/1846056?v=4" />