Pro--% off in--d : --h : --m : --s
HeroUI

User

Migration guide for User from HeroUI v2 to v3

The User component has been removed in HeroUI v3. Compose user displays manually using Avatar and text elements with Tailwind CSS classes.

Key Changes

1. Component Removal

v2: <User> component from @heroui/react
v3: Manual composition using Avatar + text elements

2. Features Mapping

The v2 User component had the following features that need to be replaced:

v2 Featurev3 EquivalentNotes
name propText elementRender name as text or heading
description propText elementRender description as text
avatarProps propAvatar componentUse v3 Avatar component directly
isFocusable propManual focus handlingAdd tabIndex and focus styles if needed
classNames propTailwind classesApply classes directly to elements

Structure Changes

v2: User Component

In v2, User was a convenience component combining Avatar with name:

import { User } from "@heroui/react";

export default function App() {
  return (
    <User
      name="Junior Garcia"
      avatarProps={{
        src: "https://example.com/avatar.jpg",
      }}
    />
  );
}

v3: Manual Composition

In v3, compose user displays manually using Avatar and text elements:

import { Avatar } from "@heroui/react";

export default function App() {
  return (
    <div className="inline-flex items-center gap-2">
      <Avatar>
        <Avatar.Image
          src="https://example.com/avatar.jpg"
          alt="Junior Garcia"
        />
        <Avatar.Fallback>JG</Avatar.Fallback>
      </Avatar>
      <span className="text-sm">Junior Garcia</span>
    </div>
  );
}

Migration Examples

With Description

import { User } from "@heroui/react";

<User
  name="Junior Garcia"
  description="Software Engineer"
  avatarProps={{
    src: "https://example.com/avatar.jpg",
  }}
/>
import { Avatar } from "@heroui/react";

<div className="inline-flex items-center gap-2">
  <Avatar>
    <Avatar.Image
      src="https://example.com/avatar.jpg"
      alt="Junior Garcia"
    />
    <Avatar.Fallback>JG</Avatar.Fallback>
  </Avatar>
  <div className="flex flex-col items-start">
    <span className="text-sm">Junior Garcia</span>
    <span className="text-xs text-muted">Software Engineer</span>
  </div>
</div>

With Default Avatar (Initials)

import { User } from "@heroui/react";

<User
  name="Junior Garcia"
  avatarProps={{
    name: "Junior Garcia",
    getInitials: (name) =>
      name
        .split(" ")
        .map((n) => n[0])
        .join(""),
  }}
/>
import { Avatar } from "@heroui/react";

function getInitials(name: string) {
  return name
    .split(" ")
    .map((n) => n[0])
    .join("");
}

<div className="inline-flex items-center gap-2">
  <Avatar>
    <Avatar.Fallback>{getInitials("Junior Garcia")}</Avatar.Fallback>
  </Avatar>
  <span className="text-sm">Junior Garcia</span>
</div>
import { User, Link } from "@heroui/react";

<User
  name="Junior Garcia"
  description={
    <Link href="https://x.com/jrgarciadev" size="sm">
      @jrgarciadev
    </Link>
  }
  avatarProps={{
    src: "https://example.com/avatar.jpg",
  }}
/>
import { Avatar, Link } from "@heroui/react";

<div className="inline-flex items-center gap-2">
  <Avatar>
    <Avatar.Image
      src="https://example.com/avatar.jpg"
      alt="Junior Garcia"
    />
    <Avatar.Fallback>JG</Avatar.Fallback>
  </Avatar>
  <div className="flex flex-col items-start">
    <span className="text-sm">Junior Garcia</span>
    <Link href="https://x.com/jrgarciadev" className="text-xs">
      @jrgarciadev
    </Link>
  </div>
</div>

Clickable User

import { User } from "@heroui/react";

{/* Focusable */}
<User
  name="Junior Garcia"
  isFocusable
  avatarProps={{
    src: "https://example.com/avatar.jpg",
  }}
/>

{/* As button */}
<User
  as="button"
  name="Junior Garcia"
  avatarProps={{
    src: "https://example.com/avatar.jpg",
  }}
/>
import { Avatar } from "@heroui/react";

{/* Focusable */}
<button
  className="inline-flex items-center gap-2 rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-focus"
  tabIndex={0}
>
  <Avatar>
    <Avatar.Image
      src="https://example.com/avatar.jpg"
      alt="Junior Garcia"
    />
    <Avatar.Fallback>JG</Avatar.Fallback>
  </Avatar>
  <span className="text-sm">Junior Garcia</span>
</button>

{/* As button */}
<button
  className="inline-flex items-center gap-2 rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-focus"
>
  <Avatar>
    <Avatar.Image
      src="https://example.com/avatar.jpg"
      alt="Junior Garcia"
    />
    <Avatar.Fallback>JG</Avatar.Fallback>
  </Avatar>
  <span className="text-sm">Junior Garcia</span>
</button>

Since User displays are commonly needed, here's a reusable component:

import { User } from "@heroui/react";

<User
  name="Junior Garcia"
  description="Software Engineer"
  avatarProps={{
    src: "https://example.com/avatar.jpg",
  }}
/>
import { Avatar, Link } from "@heroui/react";
import { ReactNode } from "react";
import { cn } from "@/lib/utils"; // or your cn utility

interface UserProps {
  name: string | ReactNode;
  description?: string | ReactNode;
  avatarSrc?: string;
  avatarAlt?: string;
  avatarFallback?: string;
  className?: string;
  isFocusable?: boolean;
  as?: "div" | "button" | "a";
  href?: string;
  onClick?: () => void;
}

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

export function User({
  name,
  description,
  avatarSrc,
  avatarAlt,
  avatarFallback,
  className,
  isFocusable = false,
  as = "div",
  href,
  onClick,
}: UserProps) {
  const Component = as === "a" ? "a" : as === "button" ? "button" : "div";
  const fallback = avatarFallback || (typeof name === "string" ? getInitials(name) : "?");

  const content = (
    <>
      <Avatar>
        {avatarSrc && (
          <Avatar.Image
            src={avatarSrc}
            alt={avatarAlt || (typeof name === "string" ? name : "")}
          />
        )}
        <Avatar.Fallback>{fallback}</Avatar.Fallback>
      </Avatar>
      <div className="flex flex-col items-start">
        <span className="text-sm">{name}</span>
        {description && (
          <span className="text-xs text-muted">{description}</span>
        )}
      </div>
    </>
  );

  const baseClasses = cn(
    "inline-flex items-center gap-2 rounded-sm outline-none",
    isFocusable && "focus-visible:ring-2 focus-visible:ring-focus",
    className
  );

  if (Component === "button") {
    return (
      <button className={baseClasses} onClick={onClick} tabIndex={isFocusable ? 0 : -1}>
        {content}
      </button>
    );
  }

  if (Component === "a") {
    return (
      <a href={href} className={baseClasses} tabIndex={isFocusable ? 0 : -1}>
        {content}
      </a>
    );
  }

  return (
    <div className={baseClasses} tabIndex={isFocusable ? 0 : -1}>
      {content}
    </div>
  );
}

// Usage
<User
  name="Junior Garcia"
  description="Software Engineer"
  avatarSrc="https://example.com/avatar.jpg"
  avatarAlt="Junior Garcia"
/>

Styling Reference

The v2 User component used these base styles that you should replicate:

  • Base container: inline-flex items-center gap-2 rounded-sm
  • Wrapper (for name/description): inline-flex flex-col items-start
  • Name: text-sm (text-small)
  • Description: text-xs text-muted (text-tiny text-foreground-400)

Summary

  1. Component Removed: User component no longer exists in v3
  2. Import Change: Remove import { User } from "@heroui/react"
  3. Manual Composition: Compose using Avatar + text elements
  4. Avatar Changes: Use v3 Avatar compound component pattern
  5. Styling: Apply Tailwind CSS classes directly
  6. Focus Handling: Implement focus styles manually if needed

Migration Steps

  1. Remove Import: Remove User from @heroui/react imports
  2. Replace Component: Replace all <User> instances with manual composition
  3. Use Avatar: Use v3 Avatar component with compound pattern
  4. Add Text Elements: Add name and description as text elements
  5. Apply Styling: Use Tailwind CSS classes for layout and styling
  6. Handle Focus: Add focus styles if isFocusable was used
  7. Optional: Create reusable User component for your application

Common Patterns

User List

<div className="space-y-2">
  {users.map((user) => (
    <div key={user.id} className="inline-flex items-center gap-2">
      <Avatar>
        <Avatar.Image src={user.avatar} alt={user.name} />
        <Avatar.Fallback>{getInitials(user.name)}</Avatar.Fallback>
      </Avatar>
      <div className="flex flex-col items-start">
        <span className="text-sm">{user.name}</span>
        {user.role && (
          <span className="text-xs text-muted">{user.role}</span>
        )}
      </div>
    </div>
  ))}
</div>

Clickable User

<button
  className="inline-flex items-center gap-2 rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-focus hover:bg-default-100"
  onClick={() => handleUserClick(user)}
>
  <Avatar>
    <Avatar.Image src={user.avatar} alt={user.name} />
    <Avatar.Fallback>{getInitials(user.name)}</Avatar.Fallback>
  </Avatar>
  <div className="flex flex-col items-start">
    <span className="text-sm">{user.name}</span>
    <span className="text-xs text-muted">{user.email}</span>
  </div>
</button>

On this page