27.5k

Image

Migration guide for Image from HeroUI v2 to v3

The Image component has been removed in HeroUI v3. Use native HTML <img> element or Next.js Image component with Tailwind CSS classes instead.

Overview

The Image component was a feature-rich wrapper around the native HTML <img> element with built-in support for blur effects, zoom animations, loading skeletons, and fallback images. In v3, you should use native <img> elements (or Next.js Image for optimization) and implement these features manually with Tailwind CSS and React hooks.

Key Changes

1. Component Removal

v2: <Image> component from @heroui/react
v3: Native HTML <img> element or Next.js Image component

2. Features Mapping

The v2 Image component had several features that need to be replaced:

v2 Featurev3 EquivalentNotes
radius proprounded-* Tailwind classesUse rounded-sm, rounded-md, rounded-lg, rounded-full
shadow propshadow-* Tailwind classesUse shadow-sm, shadow-md, shadow-lg
isBlurred propManual blur implementationUse CSS filter: blur() or Tailwind blur-* utilities
isZoomed propManual hover zoomUse hover:scale-* Tailwind classes
fallbackSrc propManual error handlingUse onError handler with state
disableSkeleton / Loading skeletonManual loading stateUse React state + conditional rendering
removeWrapper propDirect renderingNo wrapper needed, render <img> directly

Migration Examples

Basic Usage

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

export default function App() {
  return (
    <Image
      src="https://example.com/image.jpg"
      alt="Example image"
      width={300}
      height={200}
    />
  );
}
export default function App() {
  return (
    <img
      src="https://example.com/image.jpg"
      alt="Example image"
      width={300}
      height={200}
      className="rounded-lg"
    />
  );
}

If you're using Next.js, use the optimized Image component:

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

<Image
  src="/image.jpg"
  alt="Example"
  width={300}
  height={200}
/>
import Image from "next/image";

<Image
  src="/image.jpg"
  alt="Example"
  width={300}
  height={200}
  className="rounded-lg"
/>

With Radius Variants

<Image radius="sm" src="..." alt="..." />
<Image radius="md" src="..." alt="..." />
<Image radius="lg" src="..." alt="..." />
<Image radius="full" src="..." alt="..." />
<img src="..." alt="..." className="rounded-sm" />
<img src="..." alt="..." className="rounded-md" />
<img src="..." alt="..." className="rounded-lg" />
<img src="..." alt="..." className="rounded-full" />

With Shadow Variants

<Image shadow="sm" src="..." alt="..." />
<Image shadow="md" src="..." alt="..." />
<Image shadow="lg" src="..." alt="..." />
<img src="..." alt="..." className="shadow-sm" />
<img src="..." alt="..." className="shadow-md" />
<img src="..." alt="..." className="shadow-lg" />

With Zoom Effect

<Image isZoomed src="..." alt="..." />
<div className="relative overflow-hidden rounded-lg">
  <img
    src="..."
    alt="..."
    className="object-cover transition-transform duration-300 hover:scale-125"
  />
</div>

With Blur Effect

<Image isBlurred src="..." alt="..." />
<div className="relative">
  <img
    src="..."
    alt="..."
    className="relative z-10"
  />
  <img
    src="..."
    alt=""
    aria-hidden="true"
    className="absolute inset-0 z-0 h-full w-full scale-105 object-cover blur-lg opacity-30 saturate-150"
  />
</div>

With Fallback Image

<Image
  src="https://example.com/image.jpg"
  fallbackSrc="/fallback.jpg"
  alt="Example"
/>
import { useState } from "react";

function ImageWithFallback({ src, fallbackSrc, alt, ...props }) {
  const [imgSrc, setImgSrc] = useState(src);

  return (
    <img
      src={imgSrc}
      alt={alt}
      onError={() => setImgSrc(fallbackSrc)}
      {...props}
    />
  );
}

<ImageWithFallback
  src="https://example.com/image.jpg"
  fallbackSrc="/fallback.jpg"
  alt="Example"
/>

With Loading Skeleton

<Image
  src="https://example.com/image.jpg"
  alt="Example"
  disableSkeleton={false}
/>
import { useState } from "react";

function ImageWithSkeleton({ src, alt, ...props }) {
  const [isLoading, setIsLoading] = useState(true);
  const [hasError, setHasError] = useState(false);

  return (
    <div className="relative overflow-hidden rounded-lg bg-default-200">
      {isLoading && (
        <div className="absolute inset-0 animate-pulse bg-gradient-to-r from-transparent via-default-300 to-transparent" />
      )}
      <img
        src={src}
        alt={alt}
        className={`transition-opacity duration-300 ${
          isLoading ? "opacity-0" : "opacity-100"
        }`}
        onLoad={() => setIsLoading(false)}
        onError={() => {
          setIsLoading(false);
          setHasError(true);
        }}
        {...props}
      />
    </div>
  );
}

<ImageWithSkeleton src="https://example.com/image.jpg" alt="Example" />

Combined Features

<Image
  src="https://example.com/image.jpg"
  alt="Example"
  radius="lg"
  shadow="md"
  isZoomed
  isBlurred
  width={400}
  height={300}
/>
<div className="relative overflow-hidden rounded-lg shadow-md">
  <img
    src="https://example.com/image.jpg"
    alt="Example"
    width={400}
    height={300}
    className="relative z-10 object-cover transition-transform duration-300 hover:scale-125"
  />
  <img
    src="https://example.com/image.jpg"
    alt=""
    aria-hidden="true"
    className="absolute inset-0 z-0 h-full w-full scale-105 object-cover blur-lg opacity-30 saturate-150"
  />
</div>

Creating a Reusable Image Component (Optional)

If you frequently use images with similar features, you can create a reusable component:

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

<Image
  src="..."
  radius="lg"
  shadow="md"
  isZoomed
/>
import { useState } from "react";
import { cn } from "@/lib/utils"; // or your cn utility

interface CustomImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
  radius?: "none" | "sm" | "md" | "lg" | "full";
  shadow?: "none" | "sm" | "md" | "lg";
  isZoomed?: boolean;
  isBlurred?: boolean;
  fallbackSrc?: string;
}

const radiusClasses = {
  none: "rounded-none",
  sm: "rounded-sm",
  md: "rounded-md",
  lg: "rounded-lg",
  full: "rounded-full",
};

const shadowClasses = {
  none: "shadow-none",
  sm: "shadow-sm",
  md: "shadow-md",
  lg: "shadow-lg",
};

export function CustomImage({
  src,
  alt,
  className,
  radius = "lg",
  shadow = "none",
  isZoomed = false,
  isBlurred = false,
  fallbackSrc,
  onError,
  ...props
}: CustomImageProps) {
  const [imgSrc, setImgSrc] = useState(src);
  const [isLoading, setIsLoading] = useState(true);

  const handleError = (e: React.SyntheticEvent<HTMLImageElement, Event>) => {
    if (fallbackSrc && imgSrc !== fallbackSrc) {
      setImgSrc(fallbackSrc);
    }
    onError?.(e);
  };

  const imageElement = (
    <img
      src={imgSrc}
      alt={alt}
      className={cn(
        radiusClasses[radius],
        shadowClasses[shadow],
        isZoomed && "object-cover transition-transform duration-300 hover:scale-125",
        isLoading && "opacity-0",
        "transition-opacity duration-300",
        className
      )}
      onLoad={() => setIsLoading(false)}
      onError={handleError}
      {...props}
    />
  );

  if (isBlurred) {
    return (
      <div className={cn("relative", radiusClasses[radius], shadowClasses[shadow])}>
        {imageElement}
        <img
          src={imgSrc}
          alt=""
          aria-hidden="true"
          className={cn(
            "absolute inset-0 z-0 h-full w-full scale-105 object-cover blur-lg opacity-30 saturate-150",
            radiusClasses[radius]
          )}
        />
      </div>
    );
  }

  if (isZoomed || isLoading) {
    return (
      <div className={cn("relative overflow-hidden", radiusClasses[radius], shadowClasses[shadow])}>
        {isLoading && (
          <div className="absolute inset-0 animate-pulse bg-gradient-to-r from-transparent via-default-300 to-transparent" />
        )}
        {imageElement}
      </div>
    );
  }

  return imageElement;
}

// Usage
<CustomImage
  src="..."
  alt="Example"
  radius="lg"
  shadow="md"
  isZoomed
  isBlurred
  fallbackSrc="/fallback.jpg"
/>

Complete Example

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

export default function App() {
  return (
    <div className="space-y-4">
      <Image
        src="https://example.com/image1.jpg"
        alt="Image 1"
        width={300}
        height={200}
        radius="lg"
        shadow="md"
      />
      <Image
        src="https://example.com/image2.jpg"
        alt="Image 2"
        width={300}
        height={200}
        isZoomed
        radius="lg"
      />
      <Image
        src="https://example.com/image3.jpg"
        alt="Image 3"
        width={300}
        height={200}
        isBlurred
        radius="full"
      />
    </div>
  );
}
export default function App() {
  return (
    <div className="space-y-4">
      <img
        src="https://example.com/image1.jpg"
        alt="Image 1"
        width={300}
        height={200}
        className="rounded-lg shadow-md"
      />
      <div className="relative overflow-hidden rounded-lg">
        <img
          src="https://example.com/image2.jpg"
          alt="Image 2"
          width={300}
          height={200}
          className="object-cover transition-transform duration-300 hover:scale-125"
        />
      </div>
      <div className="relative rounded-full">
        <img
          src="https://example.com/image3.jpg"
          alt="Image 3"
          width={300}
          height={300}
          className="relative z-10 rounded-full"
        />
        <img
          src="https://example.com/image3.jpg"
          alt=""
          aria-hidden="true"
          className="absolute inset-0 z-0 h-full w-full scale-105 object-cover rounded-full blur-lg opacity-30 saturate-150"
        />
      </div>
    </div>
  );
}

Breaking Changes Summary

  1. Component Removed: Image component no longer exists in v3
  2. Import Change: Remove import { Image } from "@heroui/react"
  3. Use Native Element: Replace with native <img> HTML element or Next.js Image
  4. Features: Implement blur, zoom, skeleton, and fallback manually
  5. Styling: Apply Tailwind CSS classes directly for radius, shadow, and effects

Migration Steps

  1. Remove Import: Remove Image from @heroui/react imports
  2. Replace Component: Replace all <Image> instances with <img> elements
  3. Add Tailwind Classes: Apply equivalent Tailwind classes for styling (radius, shadow)
  4. Implement Features: Add manual implementations for blur, zoom, skeleton, and fallback if needed
  5. Use Next.js Image: If using Next.js, consider using next/image for optimization
  6. Optional: Create reusable wrapper components for frequently used patterns

Tips for Migration

  1. Next.js Users: Use next/image for automatic optimization, lazy loading, and responsive images
  2. Simple Images: For basic images, native <img> with Tailwind classes is sufficient
  3. Complex Features: Create custom hooks or components for blur, zoom, and loading states
  4. Performance: Use loading="lazy" attribute for images below the fold
  5. Accessibility: Always include alt text and consider aria-hidden="true" for decorative blurred backgrounds
  6. Error Handling: Implement onError handlers for fallback images
  7. Loading States: Use React state to manage loading and error states

Next.js Image Component

If you're using Next.js, the Image component from next/image provides:

  • Automatic image optimization
  • Lazy loading by default
  • Responsive images with srcSet
  • Placeholder blur support
  • Built-in loading states
import Image from "next/image";

<Image
  src="/image.jpg"
  alt="Example"
  width={300}
  height={200}
  className="rounded-lg shadow-md"
  placeholder="blur" // Optional: blur placeholder
  blurDataURL="data:image/..." // Optional: base64 blur data
/>

Need Help?

For styling guidance:

For Next.js Image:

For community support: