Skip to main content

Overview

The Current User Avatar component displays the authenticated user’s profile image with:
  • Automatic image loading from user profile
  • Initials fallback when no image available
  • Loading skeleton states
  • Multiple size variants
  • Full TypeScript support

Installation

npx shadcn@latest add https://convex-ui.vercel.app/r/current-user-avatar-nextjs
This installs:
  • CurrentUserAvatar component
  • Custom hooks for user data
  • Convex backend queries

Usage

Basic Example

import { CurrentUserAvatar } from "@/components/current-user-avatar";

export function Header() {
  return (
    <header>
      <CurrentUserAvatar />
    </header>
  );
}

With Different Sizes

<CurrentUserAvatar size="sm" />  {/* 32x32px */}
<CurrentUserAvatar size="md" />  {/* 40x40px - default */}
<CurrentUserAvatar size="lg" />  {/* 48x48px */}

Custom Styling

<CurrentUserAvatar 
  size="lg"
  className="border-2 border-primary"
/>

Hide When Not Authenticated

<CurrentUserAvatar showFallback={false} />
This returns null if the user is not authenticated and has no image/name.

Component API

Props

size
'sm' | 'md' | 'lg'
default:"'md'"
Size of the avatar:
  • sm: 32x32px (h-8 w-8)
  • md: 40x40px (h-10 w-10)
  • lg: 48x48px (h-12 w-12)
className
string
Additional CSS classes to apply to the avatar
showFallback
boolean
default:"true"
Whether to show a fallback (”?”) when user is not authenticated. If false, returns null when not authenticated.

Component Source

The component uses custom hooks to fetch user data:
components/current-user-avatar.tsx
"use client";

import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
import { useCurrentUserImage } from "../hooks/use-current-user-image";
import { useCurrentUserName } from "../hooks/use-current-user-name";

interface CurrentUserAvatarProps {
  size?: "sm" | "md" | "lg";
  className?: string;
  showFallback?: boolean;
}

const sizeClasses = {
  sm: "h-8 w-8 text-xs",
  md: "h-10 w-10 text-sm",
  lg: "h-12 w-12 text-base",
};

function getInitials(name: string | null): string {
  if (!name) return "?";
  return name
    .split(" ")
    .map((n) => n[0])
    .join("")
    .toUpperCase()
    .slice(0, 2);
}

export function CurrentUserAvatar({
  size = "md",
  className,
  showFallback = true,
}: CurrentUserAvatarProps) {
  const image = useCurrentUserImage();
  const name = useCurrentUserName();

  // Loading state
  if (image === undefined || name === undefined) {
    return (
      <Skeleton className={cn("rounded-full", sizeClasses[size], className)} />
    );
  }

  // Not authenticated
  if (!showFallback && !image && !name) {
    return null;
  }

  return (
    <Avatar className={cn(sizeClasses[size], className)}>
      <AvatarImage src={image ?? undefined} alt={name ?? "User"} />
      <AvatarFallback>{getInitials(name)}</AvatarFallback>
    </Avatar>
  );
}

Custom Hooks

useCurrentUserImage

Fetch the current user’s profile image:
hooks/use-current-user-image.ts
"use client";

import { useQuery } from "convex/react";
import { api } from "@/convex/_generated/api";

export function useCurrentUserImage() {
  const user = useQuery(api.users.current);
  return user?.image ?? null;
}
Returns:
  • undefined - Loading
  • string - User’s image URL
  • null - No image or not authenticated

useCurrentUserName

Fetch the current user’s name:
hooks/use-current-user-name.ts
"use client";

import { useQuery } from "convex/react";
import { api } from "@/convex/_generated/api";

export function useCurrentUserName() {
  const user = useQuery(api.users.current);
  return user?.name ?? null;
}
Returns:
  • undefined - Loading
  • string - User’s name
  • null - No name or not authenticated

Backend Implementation

The component relies on the users.current query:
convex/users.ts
import { query } from "./_generated/server";
import { getAuthUserId } from "@convex-dev/auth/server";

export const current = query({
  args: {},
  handler: async (ctx) => {
    const userId = await getAuthUserId(ctx);
    if (userId === null) {
      return null;
    }
    return await ctx.db.get(userId);
  },
});

Loading States

The component handles three states:

1. Loading

Shows a skeleton while fetching user data:
<Skeleton className="h-10 w-10 rounded-full" />

2. Authenticated with Image

Displays the user’s profile image:
<Avatar>
  <AvatarImage src={user.image} alt={user.name} />
  <AvatarFallback>JD</AvatarFallback>
</Avatar>

3. Authenticated without Image

Shows initials as fallback:
<Avatar>
  <AvatarFallback>JD</AvatarFallback>
</Avatar>

Initials Logic

The getInitials function extracts up to 2 initials:
function getInitials(name: string | null): string {
  if (!name) return "?";
  return name
    .split(" ")
    .map((n) => n[0])
    .join("")
    .toUpperCase()
    .slice(0, 2);
}
Examples:
  • “John Doe” → “JD”
  • “Alice” → “AL” (first 2 chars)
  • "" → ”?”
  • null → ”?”

Common Patterns

In Navigation Bar

export function Navbar() {
  return (
    <nav className="flex items-center justify-between p-4">
      <Logo />
      <CurrentUserAvatar size="md" />
    </nav>
  );
}

With Dropdown Menu

import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";

export function UserMenu() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger>
        <CurrentUserAvatar />
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuItem>Profile</DropdownMenuItem>
        <DropdownMenuItem>Settings</DropdownMenuItem>
        <DropdownMenuItem>Logout</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

In User List

export function UserList({ users }) {
  return (
    <div className="space-y-2">
      {users.map((user) => (
        <div key={user._id} className="flex items-center gap-3">
          <CurrentUserAvatar size="sm" />
          <span>{user.name}</span>
        </div>
      ))}
    </div>
  );
}

Styling

The component uses Tailwind CSS and can be customized:

Size Classes

const sizeClasses = {
  sm: "h-8 w-8 text-xs",
  md: "h-10 w-10 text-sm",
  lg: "h-12 w-12 text-base",
};

Custom Borders

<CurrentUserAvatar className="border-2 border-primary ring-2 ring-offset-2" />

With Hover Effects

<CurrentUserAvatar className="cursor-pointer hover:opacity-80 transition-opacity" />

Authentication Required

This component requires authentication to be set up. See: If auth is not configured, the component will show the fallback (”?”) or null depending on showFallback prop.

Build docs developers (and LLMs) love