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.
Overview
The User component was a convenience component that combined an Avatar with user name and optional description text. In v3, you should compose this manually using the Avatar component and text elements with Tailwind CSS classes for layout and styling.
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 |
Migration Examples
Basic Usage
import { User } from "@heroui/react";
export default function App() {
return (
<User
name="Junior Garcia"
avatarProps={{
src: "https://example.com/avatar.jpg",
}}
/>
);
}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>
);
}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>Focusable User (Clickable)
import { User } from "@heroui/react";
<User
name="Junior Garcia"
isFocusable
avatarProps={{
src: "https://example.com/avatar.jpg",
}}
/>import { Avatar } from "@heroui/react";
<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 Element
import { User } from "@heroui/react";
<User
as="button"
name="Junior Garcia"
avatarProps={{
src: "https://example.com/avatar.jpg",
}}
/>import { Avatar } from "@heroui/react";
<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"
/>Complete Example
import { User, Link } from "@heroui/react";
export default function App() {
return (
<div className="space-y-4">
<User
name="Junior Garcia"
avatarProps={{
src: "https://example.com/avatar1.jpg",
}}
/>
<User
name="Jane Doe"
description="Product Designer"
avatarProps={{
src: "https://example.com/avatar2.jpg",
}}
/>
<User
name="John Smith"
description={
<Link href="https://x.com/johnsmith" size="sm">
@johnsmith
</Link>
}
avatarProps={{
name: "John Smith",
getInitials: (name) =>
name
.split(" ")
.map((n) => n[0])
.join(""),
}}
/>
</div>
);
}import { Avatar, Link } from "@heroui/react";
function getInitials(name: string) {
return name
.split(" ")
.map((n) => n[0])
.join("")
.toUpperCase()
.slice(0, 2);
}
export default function App() {
return (
<div className="space-y-4">
<div className="inline-flex items-center gap-2">
<Avatar>
<Avatar.Image
src="https://example.com/avatar1.jpg"
alt="Junior Garcia"
/>
<Avatar.Fallback>JG</Avatar.Fallback>
</Avatar>
<span className="text-sm">Junior Garcia</span>
</div>
<div className="inline-flex items-center gap-2">
<Avatar>
<Avatar.Image
src="https://example.com/avatar2.jpg"
alt="Jane Doe"
/>
<Avatar.Fallback>JD</Avatar.Fallback>
</Avatar>
<div className="flex flex-col items-start">
<span className="text-sm">Jane Doe</span>
<span className="text-xs text-muted">Product Designer</span>
</div>
</div>
<div className="inline-flex items-center gap-2">
<Avatar>
<Avatar.Fallback>{getInitials("John Smith")}</Avatar.Fallback>
</Avatar>
<div className="flex flex-col items-start">
<span className="text-sm">John Smith</span>
<Link href="https://x.com/johnsmith" className="text-xs">
@johnsmith
</Link>
</div>
</div>
</div>
);
}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)
Breaking Changes 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
Tips for Migration
- Create Reusable Component: Since user displays are common, create a reusable component
- Use Avatar Compound Pattern: Follow v3 Avatar structure with
Avatar.ImageandAvatar.Fallback - Generate Initials: Create a helper function to generate initials from names
- Consistent Styling: Use consistent text sizes (
text-smfor name,text-xsfor description) - Accessibility: Include
alttext for avatar images - Focus States: Add focus-visible styles for interactive user elements
- Flexbox Layout: Use
flex items-center gap-2for horizontal layout
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>Need Help?
For Avatar component:
- See the Avatar documentation
- Check Avatar migration guide
For styling guidance:
- See the Styling documentation
- Check Tailwind CSS documentation for flexbox utilities
For community support: