Avatar
avataruiRound or rounded-square user image with a fallback that shows initials or an icon when the image is missing or fails to load. Sizes from xs to 2xl, optional status dot, and a group composition for stacked avatar lists.
Installation
$pnpm dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/avatar.json$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/avatar.json$yarn dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/avatar.json$bunx shadcn-svelte@latest add https://uipkge.dev/r/svelte/avatar.jsonManifest:
https://uipkge.dev/r/svelte/avatar.jsonInstalls to:src/lib/components/ui/avatar/Examples
Loading interactive previews…
Loading interactive previews…
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
size | 'xs''sm''default''lg''xl''2xl' | default | optional |
rounded | 'none''sm''default''md''lg''xl''2xl''3xl''full' | default | optional |
color | 'default''primary''secondary''destructive''success''warning''info''error''muted' | default | optional |
variant | 'default''outlined''soft' | — | optional |
tile | boolean | false | optional |
disabled | boolean | false | optional |
loading | boolean | false | optional |
ref | HTMLSpanElement | null | — | optional |
npm dependencies
Used by
activity-feedchat-threadchat-two-panecustomer-reviewsdata-table-pageenterprise-workspace-switcherevent-calendar-dashboardherohero-01inboxkanban-task-boardorder-management-pageprofile-menurecord-detail-pagesidebar-01sidebar-02sidebar-05sidebar-06sidebar-07team-sectiontestimonialtestimonial-marquee-scrolluser-profile-page
Files installed (7)
src/lib/components/ui/avatar/Avatar.svelte1.7 kB
<script lang="ts" module> import type { HTMLAttributes } from 'svelte/elements' export interface AvatarProps extends HTMLAttributes<HTMLSpanElement> { size?: 'xs' | 'sm' | 'default' | 'lg' | 'xl' | '2xl' rounded?: 'none' | 'sm' | 'default' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full' color?: 'default' | 'primary' | 'secondary' | 'destructive' | 'success' | 'warning' | 'info' | 'error' | 'muted' variant?: 'default' | 'outlined' | 'soft' tile?: boolean disabled?: boolean loading?: boolean ref?: HTMLSpanElement | null } </script> <script lang="ts"> import type { MouseEventHandler } from 'svelte/elements' import { setContext } from 'svelte' import { cn } from '$lib/utils' import { avatarVariants } from './avatar.variants' import { AVATAR_CONTEXT_KEY, AvatarContextState } from './context.svelte' let { class: className, size, rounded, color, variant, tile = false, disabled = false, loading = false, children, ref = $bindable(null), onclick, ...restProps }: AvatarProps = $props() // Sibling AvatarFallback shows until a sibling AvatarImage reports loaded. setContext(AVATAR_CONTEXT_KEY, new AvatarContextState()) const handleClick: MouseEventHandler<HTMLSpanElement> = (event) => { if (!disabled) { onclick?.(event) } } const rootClasses = $derived( cn( avatarVariants({ size, rounded, color, variant }), tile ? 'rounded-none' : '', disabled ? 'cursor-not-allowed opacity-50' : '', loading ? 'animate-pulse' : '', className, ), ) </script> <span bind:this={ref} class={rootClasses} data-uipkge="" data-slot="avatar" onclick={handleClick} {...restProps}> {@render children?.()} </span>src/lib/components/ui/avatar/AvatarFallback.svelte1.4 kB
<script lang="ts" module> import type { HTMLAttributes } from 'svelte/elements' export interface AvatarFallbackProps extends HTMLAttributes<HTMLSpanElement> { size?: 'xs' | 'sm' | 'default' | 'lg' | 'xl' | '2xl' color?: 'default' | 'primary' | 'secondary' | 'destructive' | 'success' | 'warning' | 'info' | 'error' | 'muted' text?: string ref?: HTMLSpanElement | null } </script> <script lang="ts"> import { getContext, hasContext } from 'svelte' import { cn } from '$lib/utils' import { avatarFallbackVariants } from './avatar.variants' import { AVATAR_CONTEXT_KEY, type AvatarContextState } from './context.svelte' let { class: className, size = 'default', color = 'default', text, children, ref = $bindable(null), ...restProps }: AvatarFallbackProps = $props() // Show fallback until a sibling AvatarImage reports loaded (Radix composition). const ctx = hasContext(AVATAR_CONTEXT_KEY) ? getContext<AvatarContextState>(AVATAR_CONTEXT_KEY) : undefined const visible = $derived(!ctx || ctx.status !== 'loaded') const rootClasses = $derived(cn(avatarFallbackVariants({ size, color }), className)) </script> {#if visible} <span bind:this={ref} class={rootClasses} data-uipkge="" data-slot="avatar-fallback" {...restProps}> {#if text} {text} {:else} {@render children?.()} {/if} </span> {/if}src/lib/components/ui/avatar/AvatarGroup.svelte2.1 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' export interface AvatarGroupProps extends HTMLAttributes<HTMLDivElement> { /** * Max visible slots including the +N badge. Svelte can't slice snippet * children like Vue can slice VNodes, so when overflowing render at most * `max - 1` avatars and let `total` drive the badge count. */ max?: number overlap?: boolean size?: 'xs' | 'sm' | 'default' | 'lg' | 'xl' | '2xl' /** Override the total used for +N when only a subset of avatars is rendered. */ total?: number overflow?: Snippet<[{ count: number }]> ref?: HTMLDivElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, max, overlap = true, size = 'default', total, children, overflow, ref = $bindable(null), ...restProps }: AvatarGroupProps = $props() // When overflowing, reserve one slot for the +N chip so max includes the badge. const isOverflowing = $derived(max != null && total != null && total > max) const overflowCount = $derived(isOverflowing ? total! - (max! - 1) : 0) const overflowSizeClass = $derived.by(() => { switch (size) { case 'xs': return 'size-4 text-xs' case 'sm': return 'size-6 text-xs' case 'lg': return 'size-12 text-base' case 'xl': return 'size-16 text-lg' case '2xl': return 'size-20 text-xl' default: return 'size-8 text-sm' } }) </script> <div bind:this={ref} class={cn('flex items-center', overlap ? '-space-x-2' : 'gap-1', className)} data-uipkge="" data-slot="avatar-group" {...restProps} > {@render children?.()} {#if isOverflowing} <div class={cn('bg-muted ring-background relative flex shrink-0 overflow-hidden rounded-full ring-2', overflowSizeClass)}> {#if overflow} {@render overflow({ count: overflowCount })} {:else} <span class="flex size-full items-center justify-center font-medium">+{overflowCount}</span> {/if} </div> {/if} </div>src/lib/components/ui/avatar/AvatarImage.svelte2.1 kB
<script lang="ts" module> import type { HTMLImgAttributes } from 'svelte/elements' export interface AvatarImageProps extends HTMLImgAttributes { ref?: HTMLImageElement | null } </script> <script lang="ts"> import type { EventHandler } from 'svelte/elements' import { getContext, hasContext, onMount } from 'svelte' import { cn } from '$lib/utils' import { AVATAR_CONTEXT_KEY, type AvatarContextState } from './context.svelte' let { class: className, src, alt, // Radix's React AvatarImage has no lazy default; lazily-loaded avatars below // the fold would sit on their fallback initials and diverge from the React // mirror, so eager is the parity-correct default. loading = 'eager', ref = $bindable(null), onload, onerror, ...restProps }: AvatarImageProps = $props() const ctx = hasContext(AVATAR_CONTEXT_KEY) ? getContext<AvatarContextState>(AVATAR_CONTEXT_KEY) : undefined const status = $derived(ctx?.status ?? 'idle') // Runs before mount (like Vue's immediate watcher): mark loading as soon as // a src exists so the fallback shows while the image fetches. $effect.pre(() => { ctx?.setStatus(src ? 'loading' : 'idle') }) // An image that finished before this component attached its listener never // fires `load`/`error`, so reconcile from the element state on mount: a // complete image with pixels is loaded; a complete image without them failed. // (`bind:this` below has already assigned `ref` when onMount runs.) onMount(() => { const el = ref if (!el?.complete) return ctx?.setStatus(el.naturalWidth > 0 ? 'loaded' : 'error') }) const handleError: EventHandler<Event, Element> = (event) => { ctx?.setStatus('error') onerror?.(event) } const handleLoad: EventHandler<Event, Element> = (event) => { ctx?.setStatus('loaded') onload?.(event) } </script> {#if src && status !== 'error'} <img bind:this={ref} {src} {alt} {loading} class={cn('aspect-square size-full object-cover', className)} data-uipkge="" data-slot="avatar-image" onload={handleLoad} onerror={handleError} {...restProps} /> {/if}src/lib/components/ui/avatar/avatar.variants.ts3.3 kB
import type { VariantProps } from 'class-variance-authority' import { cva } from 'class-variance-authority' /** * Variant definitions live in their own file (rather than the package * `index.ts` or inline in the SFC) so `Avatar.vue` / `AvatarFallback.vue` * can `import { avatarVariants } from './avatar.variants'` without * creating a circular dependency back through the index. The circular * form caused intermittent `$setup.avatarVariants is not a function` * errors during dev SSR. */ export const avatarVariants = cva('relative flex shrink-0 overflow-hidden', { variants: { size: { xs: 'size-4', sm: 'size-6', default: 'size-8', lg: 'size-12', xl: 'size-16', '2xl': 'size-20', }, rounded: { none: 'rounded-none', sm: 'rounded-sm', default: 'rounded-full', md: 'rounded-md', lg: 'rounded-lg', xl: 'rounded-xl', '2xl': 'rounded-2xl', '3xl': 'rounded-3xl', full: 'rounded-full', }, color: { default: '', primary: 'bg-primary text-primary-foreground', secondary: 'bg-secondary text-secondary-foreground', destructive: 'bg-destructive text-destructive-foreground', success: 'bg-success text-white dark:text-black', warning: 'bg-warning text-black', info: 'bg-info text-white dark:text-black', error: 'bg-destructive text-white dark:text-black', muted: 'bg-muted text-muted-foreground', }, variant: { default: '', outlined: 'border-2 border-current', soft: 'bg-opacity-20', }, }, compoundVariants: [ { color: 'primary', variant: 'soft', class: 'bg-primary/20 text-primary' }, { color: 'secondary', variant: 'soft', class: 'bg-secondary/20 text-secondary-foreground' }, { color: 'destructive', variant: 'soft', class: 'bg-destructive/20 text-destructive' }, { color: 'success', variant: 'soft', class: 'bg-success/20 text-success' }, { color: 'warning', variant: 'soft', class: 'bg-warning/20 text-warning' }, { color: 'info', variant: 'soft', class: 'bg-info/20 text-info' }, { color: 'error', variant: 'soft', class: 'bg-destructive/20 text-destructive' }, ], defaultVariants: { size: 'default', rounded: 'default', color: 'default', }, }) export type AvatarVariants = VariantProps<typeof avatarVariants> export const avatarFallbackVariants = cva( 'flex size-full items-center justify-center rounded-full bg-muted font-medium', { variants: { size: { xs: 'text-xs', sm: 'text-xs', default: 'text-sm', lg: 'text-base', xl: 'text-lg', '2xl': 'text-xl', }, color: { default: '', primary: 'bg-primary text-primary-foreground', secondary: 'bg-secondary text-secondary-foreground', destructive: 'bg-destructive text-destructive-foreground', success: 'bg-success text-white dark:text-black', warning: 'bg-warning text-black', info: 'bg-info text-white dark:text-black', error: 'bg-destructive text-white dark:text-black', muted: 'bg-muted text-muted-foreground', }, }, defaultVariants: { size: 'default', color: 'default', }, }, ) export type AvatarFallbackVariants = VariantProps<typeof avatarFallbackVariants>src/lib/components/ui/avatar/context.svelte.ts0.5 kB
export type AvatarImageStatus = 'idle' | 'loading' | 'loaded' | 'error' /** * Shared mutable state between `Avatar` and its `AvatarImage` / * `AvatarFallback` children. Created in `Avatar.svelte` and shared via * `setContext` / `getContext` (Svelte counterpart of Vue's provide/inject). */ export class AvatarContextState { status = $state<AvatarImageStatus>('idle') setStatus(status: AvatarImageStatus) { this.status = status } } export const AVATAR_CONTEXT_KEY = Symbol('uipkge-avatar')src/lib/components/ui/avatar/index.ts0.6 kB
export { default as Avatar, type AvatarProps } from './Avatar.svelte' export { default as AvatarFallback, type AvatarFallbackProps } from './AvatarFallback.svelte' export { default as AvatarImage, type AvatarImageProps } from './AvatarImage.svelte' export { default as AvatarGroup, type AvatarGroupProps } from './AvatarGroup.svelte' // Re-export variant API from the sibling file (kept separate to avoid the // Avatar.svelte <-> index.ts circular import that broke dev SSR). export { avatarVariants, avatarFallbackVariants, type AvatarVariants, type AvatarFallbackVariants, } from './avatar.variants'
Raw manifest:https://uipkge.dev/r/svelte/avatar.json