Vela UI Docsvenizia.ai

Avatar

An image element with a fallback for representing the user.

npx shadcn@latest add @velaui/avatar
npx shadcn@latest add @velaui/avatar

Examples

  • Photo
  • Text solid
  • Text subtle
  • Icon
  • Square
  • Status
  • Group circle
  • Group square

View code

Avatar · Photo

Installation

pnpm dlx velaui add @velaui/avatar
pnpm dlx velaui add @velaui/avatar

Code

avatar-photo.tsx
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";

export default function Example() {
  return (
    <Avatar>
      <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
      <AvatarFallback>CN</AvatarFallback>
    </Avatar>
  );
}

View code

Avatar · Text solid

Installation

pnpm dlx velaui add @velaui/avatar
pnpm dlx velaui add @velaui/avatar

Code

avatar-text-solid.tsx
import { Avatar, AvatarFallback } from "@/components/ui/avatar";

const colors = ["neutral", "red", "green", "blue", "yellow"] as const;

export default function Example() {
  return (
    <div className="flex items-center gap-3">
      {colors.map((color) => (
        <Avatar key={color}>
          <AvatarFallback background="solid" color={color}>
            AB
          </AvatarFallback>
        </Avatar>
      ))}
    </div>
  );
}

View code

Avatar · Text subtle

Installation

pnpm dlx velaui add @velaui/avatar
pnpm dlx velaui add @velaui/avatar

Code

avatar-text-subtle.tsx
import { Avatar, AvatarFallback } from "@/components/ui/avatar";

const colors = ["neutral", "red", "green", "blue", "yellow"] as const;

export default function Example() {
  return (
    <div className="flex items-center gap-3">
      {colors.map((color) => (
        <Avatar key={color}>
          <AvatarFallback background="subtle" color={color}>
            AB
          </AvatarFallback>
        </Avatar>
      ))}
    </div>
  );
}

View code

Avatar · Icon

Installation

pnpm dlx velaui add @velaui/avatar
pnpm dlx velaui add @velaui/avatar

Code

avatar-icon.tsx
import { UserProfile01 } from "@/components/icons/user-profile-01";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";

const colors = ["neutral", "red", "green", "blue", "yellow"] as const;

export default function Example() {
  return (
    <div className="flex items-center gap-3">
      {colors.map((color) => (
        <Avatar key={color}>
          <AvatarFallback background="subtle" color={color}>
            <UserProfile01 />
          </AvatarFallback>
        </Avatar>
      ))}
    </div>
  );
}

View code

Avatar · Square

Installation

pnpm dlx velaui add @velaui/avatar
pnpm dlx velaui add @velaui/avatar

Code

avatar-square.tsx
import { UserProfile01 } from "@/components/icons/user-profile-01";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";

export default function Example() {
  return (
    <div className="flex items-center gap-3">
      <Avatar shape="square">
        <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
        <AvatarFallback>CN</AvatarFallback>
      </Avatar>
      <Avatar shape="square">
        <AvatarFallback background="solid" color="blue">
          AB
        </AvatarFallback>
      </Avatar>
      <Avatar shape="square">
        <AvatarFallback background="subtle" color="green">
          <UserProfile01 />
        </AvatarFallback>
      </Avatar>
    </div>
  );
}

View code

Avatar · Status

Installation

pnpm dlx velaui add @velaui/avatar
pnpm dlx velaui add @velaui/avatar

Code

avatar-status.tsx
import { Avatar, AvatarBadge, AvatarFallback, AvatarImage } from "@/components/ui/avatar";

const statuses = ["online", "away", "busy", "offline"] as const;

export default function Example() {
  return (
    <div className="flex items-center gap-3">
      {statuses.map((status) => (
        <Avatar key={status}>
          <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
          <AvatarFallback>CN</AvatarFallback>
          <AvatarBadge status={status} />
        </Avatar>
      ))}
    </div>
  );
}

View code

Avatar · Group circle

Installation

pnpm dlx velaui add @velaui/avatar
pnpm dlx velaui add @velaui/avatar

Code

avatar-group-circle.tsx
import {
  Avatar,
  AvatarFallback,
  AvatarGroup,
  AvatarGroupCount,
  AvatarImage,
} from "@/components/ui/avatar";

export default function Example() {
  return (
    <AvatarGroup>
      <Avatar>
        <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
        <AvatarFallback>CN</AvatarFallback>
      </Avatar>
      <Avatar>
        <AvatarFallback background={"solid"} color="blue">
          AB
        </AvatarFallback>
      </Avatar>
      <Avatar>
        <AvatarFallback color="green">CD</AvatarFallback>
      </Avatar>
      <AvatarGroupCount>+5</AvatarGroupCount>
    </AvatarGroup>
  );
}

View code

Avatar · Group square

Installation

pnpm dlx velaui add @velaui/avatar
pnpm dlx velaui add @velaui/avatar

Code

avatar-group-square.tsx
import {
  AvatarGroup,
  Avatar,
  AvatarFallback,
  AvatarGroupCount,
  AvatarImage,
} from "@/components/ui/avatar";

export default function Example() {
  return (
    <AvatarGroup>
      <Avatar shape={"square"}>
        <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
        <AvatarFallback>CN</AvatarFallback>
      </Avatar>
      <Avatar shape={"square"}>
        <AvatarFallback background="solid" color="blue">
          AB
        </AvatarFallback>
      </Avatar>
      <Avatar shape={"square"}>
        <AvatarFallback background="solid" color="green">
          CD
        </AvatarFallback>
      </Avatar>
      <AvatarGroupCount shape={"square"} background={"solid"}>
        +5
      </AvatarGroupCount>
    </AvatarGroup>
  );
}

Sizes

Usage

import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";

export default function Example() {
  return (
    <Avatar>
      <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
      <AvatarFallback>CN</AvatarFallback>
    </Avatar>
  );
}

Props

Avatar

PropTypeDefault
shape"circle" | "square""circle"
size"sm" | "md" | "lg" | "2xs" | "xs" | "xl" | "2xl""md"
tabIndexnumber0

Takes these and every prop of AvatarProps.

AvatarImage

Takes every prop of AvatarImageProps.

AvatarFallback

PropTypeDefault
background"solid" | "subtle""subtle"
color"neutral" | "red" | "green" | "blue" | "yellow""neutral"

Takes these and every prop of AvatarFallbackProps.

AvatarBadge

PropTypeDefault
status"online" | "away" | "busy" | "offline""online"

Takes these and every prop of AvatarBadgeProps.

AvatarGroup

Takes every prop of AvatarGroupProps.

AvatarGroupCount

PropTypeDefault
background"solid" | "subtle""subtle"
shape"circle" | "square""circle"
size"sm" | "md" | "lg" | "2xs" | "xs" | "xl" | "2xl""md"

Takes these and every prop of AvatarGroupCountProps.

Dependencies

npm
class-variance-authority@0.7.1radix-ui@1.6.0
Installs with it
nothing else
npm
@base-ui/react@1.6.0class-variance-authority@0.7.1
Installs with it
nothing else

Source

components/ui/avatar.tsx
/* eslint-disable react-refresh/only-export-components */
import * as React from "react";
import { Avatar as AvatarPrimitive } from "radix-ui";
import { cva, type VariantProps } from "class-variance-authority";

import { cn } from "@/lib/utils";

const avatarVariants = cva(
  "group/avatar relative flex shrink-0 font-semibold select-none outline-none",
  {
    variants: {
      size: {
        "2xs": "size-5 text-[8px]",
        xs: "size-6 text-[10px]",
        sm: "size-8 text-[13px]",
        md: "size-10 text-[16px]",
        lg: "size-12 text-[19px]",
        xl: "size-14 text-[22px]",
        "2xl": "size-16 text-[26px]",
      },
      shape: {
        circle: "rounded-full",
        square: "rounded-[22.5%]",
      },
    },
    defaultVariants: {
      size: "md",
      shape: "circle",
    },
  },
);

export type AvatarProps = React.ComponentProps<typeof AvatarPrimitive.Root> &
  VariantProps<typeof avatarVariants>;

function Avatar({
  className,
  size = "md",
  shape = "circle",
  tabIndex = 0,
  ...props
}: AvatarProps) {
  return (
    <AvatarPrimitive.Root
      data-slot="avatar"
      data-size={size}
      data-shape={shape}
      tabIndex={tabIndex}
      className={cn(avatarVariants({ size, shape }), className)}
      {...props}
    />
  );
}

export type AvatarImageProps = React.ComponentProps<typeof AvatarPrimitive.Image>;

function AvatarImage({
  className,
  ...props
}: AvatarImageProps) {
  return (
    <AvatarPrimitive.Image
      data-slot="avatar-image"
      className={cn(
        "aspect-square size-full rounded-[inherit] border border-border-default object-cover",
        "group-focus-visible/avatar:outline-2 group-focus-visible/avatar:outline-offset-1 group-focus-visible/avatar:outline-neutral-500/75",
        className,
      )}
      {...props}
    />
  );
}

const avatarFallbackVariants = cva(
  "flex size-full items-center justify-center overflow-hidden rounded-[inherit] border border-border-default group-focus-visible/avatar:outline-2 group-focus-visible/avatar:outline-offset-1 [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[55%]",
  {
    variants: {
      background: {
        subtle: "",
        solid: "text-text-inverse",
      },
      color: {
        neutral: "group-focus-visible/avatar:outline-neutral-500/75",
        red: "group-focus-visible/avatar:outline-red-500/75",
        green: "group-focus-visible/avatar:outline-green-500/75",
        blue: "group-focus-visible/avatar:outline-blue-500/75",
        yellow: "group-focus-visible/avatar:outline-yellow-500/75",
      },
    },
    compoundVariants: [
      // Subtle — the design tints the glyph a step darker than the initials,
      // so the icon colour is not simply the text colour.
      {
        background: "subtle",
        color: "neutral",
        className: "bg-neutral-100 text-neutral-950 [&_svg]:text-neutral-700",
      },
      {
        background: "subtle",
        color: "red",
        className: "bg-negative text-negative-strong [&_svg]:text-red-700",
      },
      {
        background: "subtle",
        color: "green",
        className: "bg-success text-success-strong [&_svg]:text-green-700",
      },
      {
        background: "subtle",
        color: "blue",
        className: "bg-blue-100 text-blue-700",
      },
      {
        background: "subtle",
        color: "yellow",
        className: "bg-yellow-100 text-yellow-700",
      },
      {
        background: "solid",
        color: "neutral",
        className: "bg-primary-default",
      },
      { background: "solid", color: "red", className: "bg-negative-middle" },
      { background: "solid", color: "green", className: "bg-success-strong" },
      { background: "solid", color: "blue", className: "bg-blue-600" },
      { background: "solid", color: "yellow", className: "bg-yellow-700" },
    ],
    defaultVariants: {
      background: "subtle",
      color: "neutral",
    },
  },
);

export type AvatarFallbackProps = React.ComponentProps<typeof AvatarPrimitive.Fallback> &
  VariantProps<typeof avatarFallbackVariants>;

function AvatarFallback({
  className,
  background = "subtle",
  color = "neutral",
  ...props
}: AvatarFallbackProps) {
  return (
    <AvatarPrimitive.Fallback
      data-slot="avatar-fallback"
      className={cn(avatarFallbackVariants({ background, color }), className)}
      {...props}
    />
  );
}

const avatarBadgeVariants = cva(
  "absolute right-0 bottom-0 z-10 inline-flex size-[35%] items-center justify-center rounded-full border border-bg-main select-none",
  {
    variants: {
      status: {
        online: "bg-green-500",
        away: "bg-amber-500",
        busy: "bg-red-600",
        offline: "bg-neutral-600",
      },
    },
    defaultVariants: {
      status: "online",
    },
  },
);

export type AvatarBadgeProps = React.ComponentProps<"span"> & VariantProps<typeof avatarBadgeVariants>;

function AvatarBadge({
  className,
  status = "online",
  ...props
}: AvatarBadgeProps) {
  return (
    <span
      data-slot="avatar-badge"
      className={cn(
        avatarBadgeVariants({ status }),
        "[&>svg]:size-2/3",
        className,
      )}
      {...props}
    />
  );
}

export type AvatarGroupProps = React.ComponentProps<"div">;

function AvatarGroup({ className, ...props }: AvatarGroupProps) {
  return (
    <div
      data-slot="avatar-group"
      className={cn(
        // Overlap and the white separator ring both scale with the avatars the
        // group happens to contain, so neither needs a prop of its own.
        "group/avatar-group flex has-data-[size=2xs]:-space-x-2 has-data-[size=xs]:-space-x-2.5 has-data-[size=sm]:-space-x-[13px] has-data-[size=md]:-space-x-4 has-data-[size=lg]:-space-x-[19px] has-data-[size=xl]:-space-x-[22px] has-data-[size=2xl]:-space-x-[26px]",
        className,
      )}
      {...props}
    />
  );
}

const avatarGroupCountVariants = cva(
  "relative flex shrink-0 items-center justify-center border border-neutral-200 font-medium select-none",
  {
    variants: {
      size: {
        "2xs": "size-5 text-[8px]",
        xs: "size-6 text-[8px]",
        sm: "size-8 text-[11px]",
        md: "size-10 text-[14px]",
        lg: "size-12 text-[16px]",
        xl: "size-14 text-[19px]",
        "2xl": "size-16 text-[22px]",
      },
      background: {
        subtle: "bg-neutral-100 text-neutral-600",
        solid: "bg-primary-default text-text-inverse",
      },
      shape: {
        circle: "rounded-full",
        square: "rounded-[22.5%]",
      },
    },
    defaultVariants: {
      size: "md",
      background: "subtle",
      shape: "circle",
    },
  },
);

export type AvatarGroupCountProps = React.ComponentProps<"div"> &
  VariantProps<typeof avatarGroupCountVariants>;

function AvatarGroupCount({
  className,
  size = "md",
  shape = "circle",
  background = "subtle",
  ...props
}: AvatarGroupCountProps) {
  return (
    <div
      data-slot="avatar-group-count"
      data-size={size}
      className={cn(
        avatarGroupCountVariants({ size, background, shape }),
        className,
      )}
      {...props}
    />
  );
}

export {
  Avatar,
  AvatarImage,
  AvatarFallback,
  AvatarBadge,
  AvatarGroup,
  AvatarGroupCount,
  avatarVariants,
  avatarFallbackVariants,
  avatarBadgeVariants,
  avatarGroupCountVariants,
};
components/ui/avatar.tsx
/* eslint-disable react-refresh/only-export-components */
import * as React from "react";
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
import { cva, type VariantProps } from "class-variance-authority";

import { cn } from "@/lib/utils";

const avatarVariants = cva(
  "group/avatar relative flex shrink-0 font-semibold select-none outline-none",
  {
    variants: {
      size: {
        "2xs": "size-5 text-[8px]",
        xs: "size-6 text-[10px]",
        sm: "size-8 text-[13px]",
        md: "size-10 text-[16px]",
        lg: "size-12 text-[19px]",
        xl: "size-14 text-[22px]",
        "2xl": "size-16 text-[26px]",
      },
      shape: {
        circle: "rounded-full",
        square: "rounded-[22.5%]",
      },
    },
    defaultVariants: {
      size: "md",
      shape: "circle",
    },
  },
);

export type AvatarProps = AvatarPrimitive.Root.Props & VariantProps<typeof avatarVariants>;

function Avatar({
  className,
  size = "md",
  shape = "circle",
  tabIndex = 0,
  ...props
}: AvatarProps) {
  return (
    <AvatarPrimitive.Root
      data-slot="avatar"
      data-size={size}
      data-shape={shape}
      tabIndex={tabIndex}
      className={cn(avatarVariants({ size, shape }), className)}
      {...props}
    />
  );
}

export type AvatarImageProps = AvatarPrimitive.Image.Props;

function AvatarImage({
  className,
  ...props
}: AvatarImageProps) {
  return (
    <AvatarPrimitive.Image
      data-slot="avatar-image"
      className={cn(
        "aspect-square size-full rounded-[inherit] border border-border-default object-cover",
        "group-focus-visible/avatar:outline-2 group-focus-visible/avatar:outline-offset-1 group-focus-visible/avatar:outline-neutral-500/75",
        className,
      )}
      {...props}
    />
  );
}

const avatarFallbackVariants = cva(
  "flex size-full items-center justify-center overflow-hidden rounded-[inherit] border border-border-default group-focus-visible/avatar:outline-2 group-focus-visible/avatar:outline-offset-1 [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[55%]",
  {
    variants: {
      background: {
        subtle: "",
        solid: "text-text-inverse",
      },
      color: {
        neutral: "group-focus-visible/avatar:outline-neutral-500/75",
        red: "group-focus-visible/avatar:outline-red-500/75",
        green: "group-focus-visible/avatar:outline-green-500/75",
        blue: "group-focus-visible/avatar:outline-blue-500/75",
        yellow: "group-focus-visible/avatar:outline-yellow-500/75",
      },
    },
    compoundVariants: [
      // Subtle — the design tints the glyph a step darker than the initials,
      // so the icon colour is not simply the text colour.
      {
        background: "subtle",
        color: "neutral",
        className: "bg-neutral-100 text-neutral-950 [&_svg]:text-neutral-700",
      },
      {
        background: "subtle",
        color: "red",
        className: "bg-negative text-negative-strong [&_svg]:text-red-700",
      },
      {
        background: "subtle",
        color: "green",
        className: "bg-success text-success-strong [&_svg]:text-green-700",
      },
      {
        background: "subtle",
        color: "blue",
        className: "bg-blue-100 text-blue-700",
      },
      {
        background: "subtle",
        color: "yellow",
        className: "bg-yellow-100 text-yellow-700",
      },
      {
        background: "solid",
        color: "neutral",
        className: "bg-primary-default",
      },
      { background: "solid", color: "red", className: "bg-negative-middle" },
      { background: "solid", color: "green", className: "bg-success-strong" },
      { background: "solid", color: "blue", className: "bg-blue-600" },
      { background: "solid", color: "yellow", className: "bg-yellow-700" },
    ],
    defaultVariants: {
      background: "subtle",
      color: "neutral",
    },
  },
);

export type AvatarFallbackProps = AvatarPrimitive.Fallback.Props &
  VariantProps<typeof avatarFallbackVariants>;

function AvatarFallback({
  className,
  background = "subtle",
  color = "neutral",
  ...props
}: AvatarFallbackProps) {
  return (
    <AvatarPrimitive.Fallback
      data-slot="avatar-fallback"
      className={cn(avatarFallbackVariants({ background, color }), className)}
      {...props}
    />
  );
}

const avatarBadgeVariants = cva(
  "absolute right-0 bottom-0 z-10 inline-flex size-[35%] items-center justify-center rounded-full border border-bg-main select-none",
  {
    variants: {
      status: {
        online: "bg-green-500",
        away: "bg-amber-500",
        busy: "bg-red-600",
        offline: "bg-neutral-600",
      },
    },
    defaultVariants: {
      status: "online",
    },
  },
);

export type AvatarBadgeProps = React.ComponentProps<"span"> & VariantProps<typeof avatarBadgeVariants>;

function AvatarBadge({
  className,
  status = "online",
  ...props
}: AvatarBadgeProps) {
  return (
    <span
      data-slot="avatar-badge"
      className={cn(
        avatarBadgeVariants({ status }),
        "[&>svg]:size-2/3",
        className,
      )}
      {...props}
    />
  );
}

export type AvatarGroupProps = React.ComponentProps<"div">;

function AvatarGroup({ className, ...props }: AvatarGroupProps) {
  return (
    <div
      data-slot="avatar-group"
      className={cn(
        // Overlap and the white separator ring both scale with the avatars the
        // group happens to contain, so neither needs a prop of its own.
        "group/avatar-group flex has-data-[size=2xs]:-space-x-2 has-data-[size=xs]:-space-x-2.5 has-data-[size=sm]:-space-x-[13px] has-data-[size=md]:-space-x-4 has-data-[size=lg]:-space-x-[19px] has-data-[size=xl]:-space-x-[22px] has-data-[size=2xl]:-space-x-[26px]",
        className,
      )}
      {...props}
    />
  );
}

const avatarGroupCountVariants = cva(
  "relative flex shrink-0 items-center justify-center border border-neutral-200 font-medium select-none",
  {
    variants: {
      size: {
        "2xs": "size-5 text-[8px]",
        xs: "size-6 text-[8px]",
        sm: "size-8 text-[11px]",
        md: "size-10 text-[14px]",
        lg: "size-12 text-[16px]",
        xl: "size-14 text-[19px]",
        "2xl": "size-16 text-[22px]",
      },
      background: {
        subtle: "bg-neutral-100 text-neutral-600",
        solid: "bg-primary-default text-text-inverse",
      },
      shape: {
        circle: "rounded-full",
        square: "rounded-[22.5%]",
      },
    },
    defaultVariants: {
      size: "md",
      background: "subtle",
      shape: "circle",
    },
  },
);

export type AvatarGroupCountProps = React.ComponentProps<"div"> &
  VariantProps<typeof avatarGroupCountVariants>;

function AvatarGroupCount({
  className,
  size = "md",
  shape = "circle",
  background = "subtle",
  ...props
}: AvatarGroupCountProps) {
  return (
    <div
      data-slot="avatar-group-count"
      data-size={size}
      className={cn(
        avatarGroupCountVariants({ size, background, shape }),
        className,
      )}
      {...props}
    />
  );
}

export {
  Avatar,
  AvatarImage,
  AvatarFallback,
  AvatarBadge,
  AvatarGroup,
  AvatarGroupCount,
  avatarVariants,
  avatarFallbackVariants,
  avatarBadgeVariants,
  avatarGroupCountVariants,
};