Skip to content
edmi-ui

Avatar

Image with an initials fallback. Sizes sm, default, lg; badge, group and count.

StackBlitz ↗StackBlitz ↗StackBlitz ↗
DLCNSC
JB
NGERSC
+9
DLCNSC
JB
NGERSC
+9
DL
CN
SC
JB
NG
ER
SC
+9
Terminal window
npx shadcn@latest add @edmi-ui/avatar
Terminal window
pnpm dlx shadcn@latest add @edmi-ui/avatar
Terminal window
yarn dlx shadcn@latest add @edmi-ui/avatar
Terminal window
bunx shadcn@latest add @edmi-ui/avatar

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-vue@latest add @edmi-ui/avatar
Terminal window
pnpm dlx shadcn-vue@latest add @edmi-ui/avatar
Terminal window
yarn dlx shadcn-vue@latest add @edmi-ui/avatar
Terminal window
bunx shadcn-vue@latest add @edmi-ui/avatar

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/avatar.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/avatar.json
Terminal window
yarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/avatar.json
Terminal window
bunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/avatar.json

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

import {
Avatar,
AvatarBadge,
AvatarFallback,
AvatarImage,
} from "@/components/ui/avatar";
<div className="flex flex-wrap items-center gap-8">
<div className="flex items-center gap-3">
<Avatar size="sm">
<AvatarFallback>DL</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarFallback>SC</AvatarFallback>
</Avatar>
</div>
<Avatar size="lg">
<AvatarFallback>JB</AvatarFallback>
<AvatarBadge />
</Avatar>
{/* … */}
</div>
<script setup lang="ts">
import {
Avatar,
AvatarBadge,
AvatarFallback,
AvatarGroup,
AvatarGroupCount,
AvatarImage,
} from "@/components/ui/avatar";
</script>
<template>
<div class="flex flex-wrap items-center gap-8">
<div class="flex items-center gap-3">
<Avatar size="sm">
<AvatarFallback>DL</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarFallback>SC</AvatarFallback>
</Avatar>
</div>
<Avatar size="lg">
<AvatarFallback>JB</AvatarFallback>
<AvatarBadge />
</Avatar>
<AvatarGroup>
<Avatar><AvatarFallback>NG</AvatarFallback></Avatar>
<Avatar><AvatarFallback>ER</AvatarFallback></Avatar>
<Avatar><AvatarFallback>SC</AvatarFallback></Avatar>
<AvatarGroupCount>+9</AvatarGroupCount>
</AvatarGroup>
</div>
</template>
<script lang="ts">
import * as Avatar from "$lib/components/ui/avatar";
</script>
<div class="flex flex-wrap items-center gap-8">
<div class="flex items-center gap-3">
<Avatar.Root size="sm">
<Avatar.Fallback>DL</Avatar.Fallback>
</Avatar.Root>
<Avatar.Root>
<Avatar.Image src="https://github.com/shadcn.png" alt="shadcn" />
<Avatar.Fallback>CN</Avatar.Fallback>
</Avatar.Root>
<Avatar.Root size="lg">
<Avatar.Fallback>SC</Avatar.Fallback>
</Avatar.Root>
</div>
<Avatar.Root size="lg">
<Avatar.Fallback>JB</Avatar.Fallback>
<Avatar.Badge />
</Avatar.Root>
<Avatar.Group>
<Avatar.Root><Avatar.Fallback>NG</Avatar.Fallback></Avatar.Root>
<Avatar.Root><Avatar.Fallback>ER</Avatar.Fallback></Avatar.Root>
<Avatar.Root><Avatar.Fallback>SC</Avatar.Fallback></Avatar.Root>
<Avatar.GroupCount>+9</Avatar.GroupCount>
</Avatar.Group>
</div>
registry/ui/avatar.tsx
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
import { cn } from "cn";
import type * as React from "react";
function Avatar({
className,
size = "default",
...props
}: AvatarPrimitive.Root.Props & {
size?: "default" | "sm" | "lg";
}) {
return (
<AvatarPrimitive.Root
data-slot="avatar"
data-size={size}
className={cn(
"group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border data-[size=lg]:size-10 data-[size=sm]:size-6",
className,
)}
{...props}
/>
);
}
function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
return (
<AvatarPrimitive.Image
data-slot="avatar-image"
className={cn(
"aspect-square size-full rounded-full object-cover",
className,
)}
{...props}
/>
);
}
// Initials are mono (DESIGN §1 type): every short code, number and address uses JetBrains Mono.
function AvatarFallback({
className,
...props
}: AvatarPrimitive.Fallback.Props) {
return (
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"
className={cn(
"flex size-full items-center justify-center rounded-full bg-muted font-mono text-[13px] font-medium text-muted-foreground group-data-[size=sm]/avatar:text-[10px] group-data-[size=lg]/avatar:text-sm",
className,
)}
{...props}
/>
);
}
function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="avatar-badge"
className={cn(
"absolute -right-px -bottom-px z-10 inline-flex items-center justify-center rounded-full bg-brand text-brand-foreground ring-2 ring-background select-none",
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
"group-data-[size=default]/avatar:size-[11px] group-data-[size=default]/avatar:[&>svg]:size-2",
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
className,
)}
{...props}
/>
);
}
function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="avatar-group"
className={cn(
"group/avatar-group flex -space-x-2.5 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
className,
)}
{...props}
/>
);
}
function AvatarGroupCount({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="avatar-group-count"
className={cn(
"relative flex size-8 shrink-0 items-center justify-center rounded-full border border-border bg-muted font-mono text-xs text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
className,
)}
{...props}
/>
);
}
export {
Avatar,
AvatarBadge,
AvatarFallback,
AvatarGroup,
AvatarGroupCount,
AvatarImage,
};
registry/ui/avatar/Avatar.vue
<script setup lang="ts">
import type { AvatarRootProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { AvatarRoot } from 'reka-ui'
import { cn } from '@/registry/edmi/lib/utils'
const props = withDefaults(defineProps<AvatarRootProps & {
class?: HTMLAttributes['class']
size?: 'default' | 'sm' | 'lg'
}>(), {
size: 'default',
})
const delegatedProps = reactiveOmit(props, 'class', 'size')
</script>
<template>
<AvatarRoot
data-slot="avatar"
:data-size="size"
v-bind="delegatedProps"
:class="cn('group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border data-[size=lg]:size-10 data-[size=sm]:size-6', props.class)"
>
<slot />
</AvatarRoot>
</template>
registry/ui/avatar/AvatarBadge.vue
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<span
data-slot="avatar-badge"
:class="cn('absolute -right-px -bottom-px z-10 inline-flex items-center justify-center rounded-full bg-brand text-brand-foreground ring-2 ring-background select-none group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden group-data-[size=default]/avatar:size-[11px] group-data-[size=default]/avatar:[&>svg]:size-2 group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2', props.class)"
>
<slot />
</span>
</template>
registry/ui/avatar/AvatarFallback.vue
<script setup lang="ts">
import type { AvatarFallbackProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { AvatarFallback } from 'reka-ui'
import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<AvatarFallbackProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, 'class')
</script>
<template>
<!-- Initials are mono (DESIGN §1 type): every short code, number and address uses JetBrains Mono. -->
<AvatarFallback
data-slot="avatar-fallback"
v-bind="delegatedProps"
:class="cn('flex size-full items-center justify-center rounded-full bg-muted font-mono text-[13px] font-medium text-muted-foreground group-data-[size=sm]/avatar:text-[10px] group-data-[size=lg]/avatar:text-sm', props.class)"
>
<slot />
</AvatarFallback>
</template>
registry/ui/avatar/AvatarGroup.vue
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div
data-slot="avatar-group"
:class="cn('group/avatar-group flex -space-x-2.5 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background', props.class)"
>
<slot />
</div>
</template>
registry/ui/avatar/AvatarGroupCount.vue
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div
data-slot="avatar-group-count"
:class="cn('relative flex size-8 shrink-0 items-center justify-center rounded-full border border-border bg-muted font-mono text-xs text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3', props.class)"
>
<slot />
</div>
</template>
registry/ui/avatar/AvatarImage.vue
<script setup lang="ts">
import type { AvatarImageProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { AvatarImage } from 'reka-ui'
import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<AvatarImageProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, 'class')
</script>
<template>
<AvatarImage
data-slot="avatar-image"
v-bind="delegatedProps"
:class="cn('aspect-square size-full rounded-full object-cover', props.class)"
>
<slot />
</AvatarImage>
</template>
registry/ui/avatar/index.ts
export { default as Avatar } from "./Avatar.vue";
export { default as AvatarBadge } from "./AvatarBadge.vue";
export { default as AvatarFallback } from "./AvatarFallback.vue";
export { default as AvatarGroup } from "./AvatarGroup.vue";
export { default as AvatarGroupCount } from "./AvatarGroupCount.vue";
export { default as AvatarImage } from "./AvatarImage.vue";
src/lib/registry/ui/avatar/avatar-badge.svelte
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
</script>
<span
bind:this={ref}
data-slot="avatar-badge"
class={cn(
"absolute -right-px -bottom-px z-10 inline-flex items-center justify-center rounded-full bg-brand text-brand-foreground ring-2 ring-background select-none group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden group-data-[size=default]/avatar:size-[11px] group-data-[size=default]/avatar:[&>svg]:size-2 group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
className
)}
{...restProps}
>
{@render children?.()}
</span>
src/lib/registry/ui/avatar/avatar-fallback.svelte
<script lang="ts">
import { Avatar as AvatarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: AvatarPrimitive.FallbackProps = $props();
</script>
<!-- Initials are mono (DESIGN §1 type): every short code, number and address uses JetBrains Mono. -->
<AvatarPrimitive.Fallback
bind:ref
data-slot="avatar-fallback"
class={cn(
"flex size-full items-center justify-center rounded-full bg-muted font-mono text-[13px] font-medium text-muted-foreground group-data-[size=sm]/avatar:text-[10px] group-data-[size=lg]/avatar:text-sm",
className
)}
{...restProps}
/>
src/lib/registry/ui/avatar/avatar-group-count.svelte
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="avatar-group-count"
class={cn(
"relative flex size-8 shrink-0 items-center justify-center rounded-full border border-border bg-muted font-mono text-xs text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
className
)}
{...restProps}
>
{@render children?.()}
</div>
src/lib/registry/ui/avatar/avatar-group.svelte
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="avatar-group"
class={cn(
"group/avatar-group flex -space-x-2.5 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
className
)}
{...restProps}
>
{@render children?.()}
</div>
src/lib/registry/ui/avatar/avatar-image.svelte
<script lang="ts">
import { Avatar as AvatarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: AvatarPrimitive.ImageProps = $props();
</script>
<AvatarPrimitive.Image
bind:ref
data-slot="avatar-image"
class={cn("aspect-square size-full rounded-full object-cover", className)}
{...restProps}
/>
src/lib/registry/ui/avatar/avatar.svelte
<script lang="ts">
import { Avatar as AvatarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
loadingStatus = $bindable("loading"),
size = "default",
class: className,
...restProps
}: AvatarPrimitive.RootProps & {
size?: "default" | "sm" | "lg";
} = $props();
</script>
<AvatarPrimitive.Root
bind:ref
bind:loadingStatus
data-slot="avatar"
data-size={size}
class={cn(
"group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border data-[size=lg]:size-10 data-[size=sm]:size-6",
className
)}
{...restProps}
/>
src/lib/registry/ui/avatar/index.ts
import Root from "./avatar.svelte";
import Badge from "./avatar-badge.svelte";
import Fallback from "./avatar-fallback.svelte";
import Group from "./avatar-group.svelte";
import GroupCount from "./avatar-group-count.svelte";
import Image from "./avatar-image.svelte";
export {
Badge,
Badge as AvatarBadge,
Fallback,
Fallback as AvatarFallback,
Group,
Group as AvatarGroup,
GroupCount,
GroupCount as AvatarGroupCount,
Image,
Image as AvatarImage,
Root,
//
Root as Avatar,
};
Prop Type Default
size sm | default | lg default

Parts: Avatar, AvatarImage, AvatarFallback, AvatarBadge, AvatarGroup, AvatarGroupCount.

  • Initials render in JetBrains Mono; the badge uses --brand with a background ring.