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 Feature | v3 Equivalent | Notes |
|---|---|---|
name prop | Text element | Render name as text or heading |
description prop | Text element | Render description as text |
avatarProps prop | Avatar component | Use v3 Avatar component directly |
isFocusable prop | Manual focus handling | Add tabIndex and focus styles if needed |
classNames prop | Tailwind classes | Apply 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>With Link Description
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>Creating a Reusable User Component (Recommended)
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
- Component Removed:
Usercomponent no longer exists in v3 - Import Change: Remove
import { User } from "@heroui/react" - Manual Composition: Compose using Avatar + text elements
- Avatar Changes: Use v3 Avatar compound component pattern
- Styling: Apply Tailwind CSS classes directly
- Focus Handling: Implement focus styles manually if needed
Migration Steps
- Remove Import: Remove
Userfrom@heroui/reactimports - Replace Component: Replace all
<User>instances with manual composition - Use Avatar: Use v3 Avatar component with compound pattern
- Add Text Elements: Add name and description as text elements
- Apply Styling: Use Tailwind CSS classes for layout and styling
- Handle Focus: Add focus styles if
isFocusablewas used - 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>