Primitives
Avatar
Profile image with initials fallback and composable overlapping groups.
Composition
Avatar group
- <AvatarGroup>
- <Avatar>
- <AvatarImage />
- <AvatarFallback />
- <AvatarBadge />
- <Avatar>
Installation
First install and activate one skin. Core deliberately contains no visual token defaults.
The Control UI source installs this primitive from src/registry/sources/control-ui/ui/avatar.tsx.
npx shadcn@latest add https://control-ui.dev/r/avatar.jsonRaw code
Primary installed source
"use client";import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";import type { ComponentProps, CSSProperties } from "react";import type { AvatarKnobStyle } from "@/components/control-ui/knob-contracts/avatar-knobs";import { cn } from "@/components/control-ui/lib/cn";export type AvatarProps = Omit<ComponentProps<"span">, "style"> & { style?: CSSProperties & AvatarKnobStyle };export type AvatarGroupProps = Omit<ComponentProps<"div">, "style"> & { style?: CSSProperties & AvatarKnobStyle };export type AvatarImageProps = ComponentProps<"img"> & { onLoadingStatusChange?: (status: "idle" | "loading" | "loaded" | "error") => void;} & { style?: CSSProperties & AvatarKnobStyle };export type AvatarFallbackProps = Omit<ComponentProps<"span">, "style"> & { delay?: number; style?: CSSProperties & AvatarKnobStyle;};export const avatarBadgeStatuses = ["online", "away", "busy", "offline"] as const;export type AvatarBadgeStatus = (typeof avatarBadgeStatuses)[number];export type AvatarBadgeProps = Omit<ComponentProps<"span">, "style"> & { status?: AvatarBadgeStatus; label?: string; style?: CSSProperties & AvatarKnobStyle;};export function Avatar({ className, ...props }: AvatarProps) { return ( <AvatarPrimitive.Root data-control-ui="avatar" data-control-family="avatar" data-slot="root" className={cn("relative inline-flex shrink-0 select-none items-center justify-center align-middle", className)} {...props} /> );}export function AvatarGroup({ className, ...props }: AvatarGroupProps) { return ( // biome-ignore lint/a11y/useSemanticElements: Profile imagery is not a form fieldset. <div role="group" data-control-ui="avatar" data-control-family="avatar" data-slot="group" className={cn("isolate inline-flex items-center -space-x-2", className)} {...props} /> );}export function AvatarImage({ className, ...props }: AvatarImageProps) { return ( <AvatarPrimitive.Image data-control-ui="avatar" data-control-family="avatar" data-slot="image" className={cn("size-full object-cover", className)} {...props} /> );}export function AvatarFallback({ className, ...props }: AvatarFallbackProps) { return ( <AvatarPrimitive.Fallback data-control-ui="avatar" data-control-family="avatar" data-slot="fallback" className={cn("flex size-full items-center justify-center", className)} {...props} /> );}export function AvatarBadge({ className, status, label, children, ...props }: AvatarBadgeProps) { return ( <span data-control-ui="avatar" data-control-family="avatar" data-slot="badge" data-status={status} className={cn("absolute inline-flex items-center justify-center", className)} {...props} > {children} {label ? <span className="sr-only">{label}</span> : null} </span> );}Knobs
Typed custom properties the recipe paints with. Set one on the root — style, a utility class, or a skin — and every slot inherits it.
--cui-avatar-* · 10 knobsHow the cascade resolves--cui-avatar-radius<length-percentage>9999px--cui-avatar-group-ring-color<color>var(--background)--cui-avatar-fallback-background<color>var(--muted)--cui-avatar-fallback-foreground<color>var(--muted-foreground)--cui-avatar-image-outline-color<color>var(--image-outline)--cui-avatar-badge-size<length-percentage>max(0.5rem, 30%)--cui-avatar-badge-ring-size<length-percentage>2px--cui-avatar-badge-ring-color<color>var(--background)--cui-avatar-badge-background<color>var(--primary)--cui-avatar-badge-foreground<color>var(--primary-foreground)