npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@concatstring/react-smart-image

v2.1.2

Published

A drop-in React <img> replacement with lazy loading, WebP, blur/LQIP placeholders, skeleton loaders, responsive srcSet, priority/LCP preloading, aspect-ratio to prevent layout shift (CLS), click-to-zoom lightbox & magnifier, and retry logic — zero depende

Readme

🚀 React Smart Image

A drop-in replacement for the native <img> element with lazy loading, responsive images, blur placeholders, skeleton loaders, AVIF/WebP detection, click-to-zoom, and automatic retry built in — delivering better performance, UX, and DX with zero runtime dependencies.

Lazy Loading • Responsive Images • Blur Placeholder • Skeleton Loader • Image Zoom • Retry Logic • AVIF/WebP • Core Web Vitals • TypeScript

npm version npm downloads License React TypeScript Zero Dependencies SSR Friendly Bundle Size

⚡ Built for Performance • Accessibility • Developer Experience

🌐 Live Demo: https://react-smart-image.netlify.app

🚀 Why React Smart Image?

Modern React applications often require multiple libraries just to build a complete image experience.

One library for lazy loading.

Another for blur placeholders.

Another for responsive images.

Another for zoom.

Another for retry logic.

Managing all of these increases dependencies, bundle size, and maintenance.

React Smart Image solves this by combining everything into one lightweight, production-ready component.

Simply install one package and start building.

Installation

npm install @concatstring/react-smart-image

or

yarn add @concatstring/react-smart-image

or

pnpm add @concatstring/react-smart-image

Peer Dependencies

react >= 18
react-dom >= 18

Quick Start

Using React Smart Image is as simple as replacing your existing <img>.

import { SmartImage } from "@concatstring/react-smart-image";

export default function App() {
  return (
    <SmartImage
      src="/photo.jpg"
      alt="Beautiful landscape"
      width={800}
      height={600}
    />
  );
}

That's it.

No configuration.

No provider required.

Works exactly like a normal image.

One Component. Everything Built In.

Enable multiple powerful features with simple props.

<SmartImage
  src="/product.jpg"
  alt="Product"
  lazy
  responsive
  skeleton
  placeholder="blur"
  format="auto"
  zoom
  retry={2}
  fallback="/images/placeholder.png"
/>

Instead of combining multiple image libraries, React Smart Image provides everything in one component.

✨ Features

🚀 Performance

Lazy Loading LCP Optimized Responsive Images CLS Prevention AVIF/WebP Auto

  • ✅ Lazy Loading
  • ✅ Priority Loading
  • ✅ Image Prefetch
  • ✅ Responsive Images
  • ✅ Aspect Ratio
  • ✅ Object Fit & Position
  • ✅ Image Preloading
  • ✅ AVIF & WebP Detection
  • ✅ Progressive Thumbnail Loading

🎨 User Experience

Blur Placeholder Skeleton Loader Image Zoom Magnifier Lens

  • ✅ Blur Placeholder
  • ✅ Skeleton Loader
  • ✅ Smooth Image Transitions
  • ✅ Click to Zoom
  • ✅ Magnifier Lens
  • ✅ Fullscreen Viewer

🛡 Reliability

Retry Automatic Fallback Supported Image Cache

  • ✅ Automatic Retry
  • ✅ Exponential Backoff
  • ✅ Error Fallback Images
  • ✅ Image Cache Utilities

⚛️ Developer Experience

TypeScript First SSR Friendly SmartImageProvider Built-In Image Presets Zero Dependencies

  • ✅ Drop-in <img> Replacement
  • ✅ Zero Runtime Dependencies
  • ✅ TypeScript First
  • ✅ SmartImageProvider
  • ✅ Image Presets
  • ✅ Responsive URL Builder
  • ✅ Analytics Callbacks
  • ✅ Download Progress Tracking
  • ✅ SSR Friendly

🏆 Feature Comparison

| Feature | HTML <img> | Next.js Image | React Smart Image | |----------|-------------|---------------|-------------------| | Lazy Loading | ✅ | ✅ | ✅ | | Responsive Images | ❌ | ✅ | ✅ | | Blur Placeholder | ❌ | ✅ | ✅ | | Skeleton Loader | ❌ | ❌ | ✅ | | Retry Failed Images | ❌ | ❌ | ✅ | | Error Fallback | ❌ | ❌ | ✅ | | Zoom Viewer | ❌ | ❌ | ✅ | | Magnifier Lens | ❌ | ❌ | ✅ | | Image Presets | ❌ | ❌ | ✅ | | Global Provider | ❌ | ❌ | ✅ | | AVIF/WebP Auto Detection | ❌ | ❌ | ✅ | | Progressive Thumbnail | ❌ | ❌ | ✅ | | TypeScript | ❌ | ✅ | ✅ | | Framework Independent | ✅ | ❌ | ✅ |

Choose the Right Feature

| Your Goal | Feature | |------------|----------| | Improve LCP | priority | | Warm the cache for a likely-next image | prefetch | | Improve CLS | aspectRatio | | Crop/fit an image into its box | objectFit | | Keep the important part of an image visible | objectPosition | | Faster Loading | responsive | | Better UX | placeholder="blur" | | Loading Animation | skeleton | | Product Gallery | zoom | | Magnifying Glass | zoomOptions={{ mode: "magnifier" }} | | Broken Image | fallback | | Retry Failed Images | retry | | Modern Formats | format="auto" | | Custom Progress Bar | onLoadProgress |

Table of Contents

Core Features

Lazy Loading

Load images only when they're about to enter the viewport, reducing initial page load time and bandwidth usage.

Perfect for:

  • Blog posts
  • Product listings
  • Galleries
  • Long pages
<SmartImage
  src="/gallery.jpg"
  alt="Gallery Image"
  width={800}
  height={500}
  lazy
/>

How it works

  • Uses Intersection Observer
  • Starts loading 100px before entering the viewport
  • Automatically disabled when priority is enabled

💡 Use lazy for every image that is below the fold.

Priority Loading (LCP)

Priority images are loaded immediately with the highest browser priority.

Ideal for:

  • Hero banners
  • Landing page images
  • First visible product image
  • Largest Contentful Paint (LCP)
<SmartImage
  src="/hero.jpg"
  alt="Hero"
  width={1600}
  height={700}
  priority
/>

When enabled, React Smart Image automatically:

  • disables lazy loading
  • sets loading="eager"
  • sets fetchpriority="high"
  • injects <link rel="preload">

Combine it with responsive — the preload link mirrors the rendered image via imagesrcset/imagesizes, so the browser preloads the exact candidate it will display (one request, not two):

<SmartImage
  src="/hero.jpg"
  alt="Hero"
  priority
  responsive
  sizes={{ mobile: 480, tablet: 768, desktop: 1200 }}
/>
<link
  rel="preload"
  as="image"
  href="/hero.jpg"
  imagesrcset="/hero.jpg?w=480 480w, /hero.jpg?w=768 768w, /hero.jpg?w=1200 1200w"
  imagesizes="(max-width:640px)480px,(max-width:1024px)768px,1200px"
  fetchpriority="high"
/>

⚠️ Only use priority for one or two images per page.

Image Prefetch

Fetch an image before the user actually needs it, so it's already cached and shows instantly once it's rendered or opened.

Ideal for:

  • The next image in a gallery/carousel
  • A card's image, ahead of a navigation you know is coming
  • The high-resolution source behind a zoom thumbnail
<SmartImage
  src="/product.jpg"
  alt="Product"
  prefetch
/>

Unlike priority, prefetch doesn't force this image to render eagerly or bump its loading/fetchpriority attributes — it only warms the shared cache in the background, at low fetch priority, so it never competes with images the user is actually looking at. It works fine on a lazy image that isn't visible yet.

<SmartImage
  src="/gallery/next.jpg"
  alt="Next photo"
  lazy
  prefetch
/>

Combine it with zoom + zoomSrc to also warm the high-res source, so opening the zoom view has no visible delay:

<SmartImage
  src="/thumb.jpg"
  alt="Product"
  zoom
  zoomSrc="/full-4000.jpg"
  prefetch
/>

Need to prefetch an image that isn't rendered as a SmartImage at all yet — e.g. the next product in a list you haven't scrolled to? Call prefetchImage directly:

import { prefetchImage } from "@concatstring/react-smart-image";

prefetchImage("/gallery/next.jpg");

It returns a Promise<void> that resolves once the fetch settles (success or failure), and shares the same cache SmartImage reads from — so a SmartImage rendered for that src afterwards loads instantly.

⚠️ Ignored when priority is also set — an above-the-fold image is already loading at the highest priority, so a background prefetch would be redundant.

Aspect Ratio (Prevent CLS)

Reserve image space before it loads to eliminate layout shifts and improve Core Web Vitals.

<SmartImage src="/banner.jpg" alt="Banner" aspectRatio={16 / 9} />

Or:

<SmartImage src="/banner.jpg" alt="Banner" width={1200} aspectRatio="16 / 9" />

Supported values:

aspectRatio={16 / 9}
aspectRatio={4 / 3}
aspectRatio={1}
aspectRatio="21 / 9"

aspectRatio accepts a number | string, passed straight to the CSS aspect-ratio property. {16 / 9} isn't special syntax — it's plain JS division evaluated before the prop reaches CSS (16 / 91.777…), just a readable way to write "1.777" while keeping the 16:9 intent obvious.

With the number form {16 / 9} JavaScript does the division; with the string form "16 / 9" the / stays literal inside the quotes. Both end up as valid CSS.

💡 Using aspectRatio means you usually don't need to specify height.

Object Fit & Position

Control how an image fills its box, and which part of it stays visible when it's cropped — without reaching for a style prop.

<SmartImage
  src="/product.jpg"
  width={400}
  height={300}
  objectFit="cover"
/>

objectFit accepts the same values as CSS object-fit: cover, contain, fill, none, scale-down.

Pair it with objectPosition to keep the important part of the image visible when it's cropped — a face in a profile photo, or an off-center product:

<SmartImage
  src="/profile.jpg"
  width={200}
  height={200}
  objectFit="cover"
  objectPosition="top"
/>

objectPosition accepts the same values as CSS object-position: keywords (center, top, bottom, left, right) or a coordinate pair ("50% 20%").

Both work with width/height, aspectRatio, responsive, skeleton, placeholder="blur", thumbnail, and zoom — set once on the SmartImage, and every mode respects it.

💡 In wrapper mode (skeleton, placeholder="blur", or thumbnail) objectFit already defaults to "cover" so the image fills its box — set objectFit explicitly to change that. On a plain <img> (none of those props set) it's left at the browser default ("fill") unless you set it.

⚠️ The full-resolution image shown in a zoom lightbox always uses object-fit: contain (so the whole image stays visible), independent of objectFit — override it via zoomOptions.style if needed.

Skeleton Loader

Display an animated loading placeholder while the image downloads.

ℹ️ Requires width and height (or aspectRatio) so the placeholder has the right dimensions.

<SmartImage src="/avatar.jpg" alt="Avatar" width={120} height={120} skeleton />

Customize the appearance:

<SmartImage
  src="/avatar.jpg"
  alt="Avatar"
  width={120}
  height={120}
  skeleton
  skeletonColor="#1F2937"
  skeletonHighlightColor="rgba(255,255,255,.12)"
/>

Perfect for:

  • User profiles
  • Product cards
  • Dashboards
  • Social feeds

Blur Placeholder (LQIP)

Display a tiny blurred preview until the full image loads.

ℹ️ Requires width and height (or aspectRatio) so the placeholder has the right dimensions.

<SmartImage
  src="/mountain.jpg"
  alt="Mountain"
  width={1200}
  height={700}
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,..."
/>

Benefits

  • Better perceived performance
  • Smooth image loading
  • Professional appearance

💡 skeleton and placeholder="blur" can be combined — both layers render, with the skeleton on top.

Auto Blur

Don't want to generate Base64 placeholders? Enable automatic blur generation.

<SmartImage
  src="/mountain.jpg"
  alt="Mountain"
  width={1200}
  height={700}
  placeholder="blur"
  autoBlur
/>

Customize preview size:

<SmartImage autoBlur blurWidth={32} />

ℹ️ Requires an image server or CDN capable of resizing images.

Examples:

  • Cloudinary
  • ImageKit
  • Imgix
  • CloudFront
  • Thumbor

Load Transitions

By default, skeleton/placeholder="blur" reveal the final image with a simple opacity crossfade — a plain <SmartImage> with neither set gets no fade at all unless you add transition or fade. thumbnail mode is different: the image swaps src in place with no fade to animate, so transition/fade are ignored there.

<SmartImage src="/photo.jpg" alt="Photo" skeleton transition="scale" />

Available animations:

  • fade
  • scale
  • grow
  • rotate
  • flip
  • slide-up
  • slide-left
  • reveal
  • none

Control animation duration:

<SmartImage transition="fade" transitionDuration={500} />

Simple shorthand:

<SmartImage src="/photo.jpg" fade />

⚠️ transition is ignored when thumbnail is set, since that mode swaps src in place rather than revealing over a hidden state.

Progressive Thumbnail Loading

Show a lightweight thumbnail instantly while the high-resolution image loads in the background.

<SmartImage
  src="/photo-large.jpg"
  thumbnail="/photo-thumb.jpg"
  width={1200}
  height={800}
/>

Perfect for:

  • Photography websites
  • Product galleries
  • Portfolio pages

Modern Image Formats

Automatically serve AVIF or WebP when available.

<SmartImage src="/banner.jpg" avif webp />

Or simply:

<SmartImage src="/banner.jpg" format="auto" />

Loading order:

banner.avif → banner.webp → banner.jpg

Supported source formats:

  • JPG
  • JPEG
  • PNG
  • GIF
  • BMP
  • TIFF

Retry Failed Images

Automatically retry failed image requests.

<SmartImage src="/cdn-image.jpg" retry={3} retryDelay={500} />

Retry timing:

Attempt 1 → 500ms
Attempt 2 → 1000ms
Attempt 3 → 2000ms

Uses exponential backoff to reduce unnecessary network traffic.

Error Fallback

Display a replacement image when all retries fail.

<SmartImage src="/missing-image.jpg" fallback="/images/no-image.png" />

Combine with retry:

<SmartImage
  src="/cdn-image.jpg"
  retry={2}
  retryDelay={1000}
  fallback="/images/no-image.png"
/>

This provides the best user experience for unreliable networks.

Combine Multiple Features

React Smart Image is designed so features work together seamlessly.

<SmartImage
  src="/product.jpg"
  alt="Product"
  width={600}
  height={600}
  lazy
  responsive
  placeholder="blur"
  skeleton
  transition="fade"
  format="auto"
  retry={2}
  fallback="/images/placeholder.png"
/>

One component. Everything built in. No additional image libraries required.

Responsive Images

Serve the right image for every screen size to improve loading performance and reduce bandwidth usage.

React Smart Image supports two responsive strategies:

  • srcSet (default)
  • viewport

Strategy 1 — srcSet (Recommended)

Let the browser automatically choose the best image based on:

  • Screen size
  • Device Pixel Ratio (DPR)
  • Browser capabilities
<SmartImage
  src="/banner.jpg"
  alt="Banner"
  responsive
  sizes={{ mobile: 480, tablet: 768, desktop: 1400 }}
/>

Generated HTML:

<img
  srcset="/banner.jpg?w=480 480w, /banner.jpg?w=768 768w, /banner.jpg?w=1400 1400w"
  sizes="(max-width:640px)480px,(max-width:1024px)768px,1400px"
/>

Best For

  • Marketing websites
  • Blogs
  • Landing pages
  • Ecommerce
  • Most applications

Strategy 2 — Viewport

Always load the image matching the viewport width.

<SmartImage
  src="/banner.jpg"
  responsive
  strategy="viewport"
  sizes={{ mobile: 480, tablet: 768, desktop: 1400 }}
/>

Unlike srcSet, viewport ignores DPR. A mobile device always receives the mobile image.

Breakpoints: mobile ≤ 640px, tablet ≤ 1024px, desktop above. Updates live on resize.

Best For

  • Saving bandwidth
  • Internal dashboards
  • Admin panels
  • Mobile-first applications

Strategy Comparison

| Feature | srcSet | viewport | |----------|---------|----------| | Browser chooses image | ✅ | ❌ | | DPR Aware | ✅ | ❌ | | Lowest bandwidth | ❌ | ✅ | | Highest image quality | ✅ | ✅ | | Recommended | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |

Custom Image URLs

Different CDNs generate resized images differently. By default React Smart Image generates:

/photo.jpg?w=480

If your CDN uses another format, provide a custom URL builder.

<SmartImage
  src="/photo.jpg"
  responsive
  sizes={{ mobile: 480, tablet: 768, desktop: 1200 }}
  srcSetBuilder={(src, width) => src.replace(/(\.\w+)$/, `-${width}$1`)}
/>

Generated:

/photo-480.jpg
/photo-768.jpg
/photo-1200.jpg

⚠️ srcSetBuilder should be a pure function — it's called once per breakpoint width (and again at blurWidth when autoBlur is on), and may re-run on every resize.

CDN Examples

Cloudinary

srcSetBuilder={(src, width) => `https://res.cloudinary.com/demo/image/upload/w_${width}${src}`}

ImageKit

srcSetBuilder={(src, width) => `${src}?tr=w-${width}`}

Imgix

srcSetBuilder={(src, width) => `${src}?w=${width}`}

AWS CloudFront

srcSetBuilder={(src, width) => `${src}?width=${width}`}

Bunny CDN

srcSetBuilder={(src, width) => `${src}?width=${width}`}

Image Zoom

Enable beautiful image zoom with a single prop.

<SmartImage src="/shoe.jpg" alt="Running Shoe" zoom />

By default, clicking the image opens a fullscreen lightbox.

Zoom Modes

React Smart Image supports four zoom experiences.

| Mode | Description | |------|-------------| | lightbox | Fullscreen modal | | inline | Zoom inside image | | magnifier | Magnifying lens | | fullscreen | Browser Fullscreen API |

Lightbox

Perfect for galleries.

<SmartImage src="/product.jpg" zoom />

Inline Zoom

Magnify the image without opening a popup.

<SmartImage
  src="/product.jpg"
  zoom
  zoomOptions={{ mode: "inline", scale: 2 }}
/>

Magnifier Lens

Ideal for ecommerce product images.

<SmartImage
  src="/product.jpg"
  zoom
  zoomOptions={{ mode: "magnifier", scale: 3, magnifierSize: 180 }}
/>

Fullscreen

Use the browser Fullscreen API.

<SmartImage src="/photo.jpg" zoom zoomOptions={{ mode: "fullscreen" }} />

⚠️ iOS Safari doesn't support the Fullscreen API on non-<video> elements. There, the lightbox still opens normally, but it silently stays windowed instead of entering true fullscreen.

Custom High Resolution Image

Display a different image when zooming.

<SmartImage src="/thumb.jpg" zoom zoomSrc="/original-4000px.jpg" />

Perfect for ecommerce product images.

Zoom Toolbar

Enable built-in controls.

<SmartImage
  zoom
  zoomOptions={{
    showToolbar: true,
    toolbarItems: ["zoomIn", "zoomOut", "reset", "download", "fullscreen"],
  }}
/>

⚠️ The download button uses the download attribute, which forces a save only for same-origin images (or cross-origin images served with the right CORS headers). Otherwise the browser may just open the image in a new tab instead.

Zoom Animation

Choose the animation style.

zoomOptions={{ animation: "scale" }}

Available:

  • fade
  • scale
  • zoom
  • slide
  • none

Control animation duration:

zoomOptions={{ animation: "scale", animationDuration: 500 }}

Zoom Caption

Show a caption beneath the zoomed image.

<SmartImage src="/product.jpg" zoom zoomOptions={{ caption: true }} />

caption:true uses the image's alt text automatically, or pass a custom string:

zoomOptions={{ caption: "Available in 4 colors" }}

Zoom Backdrop & Close Behavior

Customize the lightbox backdrop color and how it can be closed.

zoomOptions={{
  backdropColor: "rgba(0,0,0,0.95)",
  closeOnBackdropClick: false,
  closeOnEsc: false,
  showCloseButton: false,
}}

| Option | Default | |--------|---------| | backdropColor | rgba(0,0,0,0.85) | | closeOnBackdropClick | true | | closeOnEsc | true | | showCloseButton | true |

Customizing the Zoomed Image

Apply a class name or inline style to the zoomed <img> inside the lightbox.

zoomOptions={{ className: "my-zoomed-image", style: { borderRadius: 8 } }}

Zoom Accessibility

The lightbox is fully keyboard accessible.

  • Open — click the image, or focus it and press Enter / Space
  • Close — press Escape, click the × button, or click the backdrop (clicking the image itself keeps it open)

It renders through a React portal into <body> (so it escapes any overflow: hidden / transform ancestors), locks background scroll while open, moves focus to the close button, and exposes role="dialog" + aria-modal.

SmartImageProvider

Avoid repeating the same props everywhere. Wrap your application once.

import { SmartImageProvider } from "@concatstring/react-smart-image";

function App() {
  return (
    <SmartImageProvider
      defaults={{
        responsive: true,
        retry: 2,
        placeholder: "blur",
        transition: "fade",
      }}
    >
      <AppRoutes />
    </SmartImageProvider>
  );
}

Every SmartImage automatically inherits these defaults. Individual component props always override provider defaults.

The merge is shallow and applies at the top level only — an object prop like zoomOptions set on the image replaces the provider's zoomOptions entirely rather than merging field-by-field. Nesting SmartImageProviders is supported, but the closest provider's defaults (and presets) replace the outer one's rather than merging with it.

You can also set a default preset for every image, so preset doesn't need to be repeated on each one:

<SmartImageProvider defaults={{ preset: "product" }} presets={presets}>
  <AppRoutes />
</SmartImageProvider>

An explicit preset prop on a SmartImage always overrides this default.

SmartImageProvider Props

| Prop | Type | Default | Description | |------|------|----------|-------------| | defaults | SmartImageDefaults | {} | Default SmartImage props; may include preset for an app-wide default | | presets | SmartImagePresetMap | {} | Image preset collection | | children | ReactNode | — | React children |

Image Presets

Presets let you define reusable image configurations. Instead of repeating props across your application, define them once.

import { createImagePresets } from "@concatstring/react-smart-image";

const presets = createImagePresets({
  hero: { priority: true, responsive: true, transition: "fade" },
  product: { zoom: true, responsive: true, placeholder: "blur" },
  avatar: { width: 60, height: 60, skeleton: true },
});

Use a preset:

<SmartImage preset="hero" src="/hero.jpg" alt="Hero" />
<SmartImage preset="product" src="/shoe.jpg" alt="Running Shoe" />
<SmartImage preset="avatar" src="/user.jpg" alt="User" />

Presets dramatically reduce repetitive code.

💡 keyof typeof presets gives you a typed union of preset names (e.g. 'hero' | 'product' | 'avatar') — handy for typing a preset prop on your own wrapper component.

⚠️ An unrecognized preset name logs a console warning and falls back to just the provider defaults + component props, rather than throwing.

Provider + Presets

They work together.

<SmartImageProvider defaults={{ retry: 2 }} presets={presets}>
  <App />
</SmartImageProvider>

Resolution order:

Component Props → Preset → Provider Defaults → Library Defaults

The closest value always wins.

Common Recipes

Hero Banner

<SmartImage preset="hero" src="/hero.jpg" alt="Hero" />

Product Card

<SmartImage preset="product" src="/shoe.jpg" alt="Running Shoe" />

User Avatar

<SmartImage preset="avatar" src="/avatar.jpg" alt="User" />

Blog Thumbnail

<SmartImage src="/blog.jpg" responsive lazy placeholder="blur" />

Gallery Image

<SmartImage src="/gallery.jpg" zoom responsive skeleton transition="fade" />

Which Features Should I Use?

| Scenario | Recommended Features | |-----------|----------------------| | Hero Banner | priority + responsive | | Product Gallery | zoom + responsive + placeholder | | User Avatar | skeleton | | Dashboard | lazy | | Ecommerce | responsive + zoom + retry | | Blog | lazy + placeholder="blur" | | Portfolio | thumbnail + zoom | | Marketing Page | priority + aspectRatio | | Slow Network | autoBlur + retry | | CDN Images | responsive + srcSetBuilder |

Performance Tips

✅ Use priority only for above-the-fold images.

✅ Use lazy for all remaining images.

✅ Enable responsive whenever possible.

✅ Use aspectRatio to prevent layout shift.

✅ Prefer format="auto" for modern image formats.

✅ Combine placeholder="blur" with responsive for the best perceived performance.

✅ Use presets to keep image configuration consistent across your application.

Callbacks

React Smart Image provides lifecycle callbacks for analytics, monitoring, and custom business logic.

onVisible

Triggered once when the image enters the viewport.

Perfect for:

  • Impression tracking
  • Analytics
  • Marketing events
  • Lazy business logic
<SmartImage
  src="/banner.jpg"
  alt="Summer Sale"
  lazy
  onVisible={() => analytics.track("banner_impression")}
/>

Notes

  • Fires only once.
  • Works with or without lazy.
  • Uses the same Intersection Observer as lazy loading.

onLoadInfo

Get detailed information after an image successfully loads.

<SmartImage
  src="/photo.jpg"
  onLoadInfo={(info) => {
    console.log(info.loadTime, info.width, info.height, info.fromCache);
  }}
/>

LoadInfo

interface LoadInfo {
  loadTime: number;
  width: number;
  height: number;
  fromCache: boolean;
}

Useful for:

  • Performance monitoring
  • Logging
  • Analytics
  • Debugging

onLoadProgress

Track byte-level download progress while an image loads — build a custom progress bar or percentage indicator for large images.

<SmartImage
  src="/large-image.jpg"
  onLoadProgress={(progress) => {
    console.log(progress); // { loaded: 450000, total: 1000000, progress: 45 }
  }}
/>

LoadProgressInfo

interface LoadProgressInfo {
  loaded: number;
  total: number | undefined;
  progress: number | undefined;
}

Fired repeatedly as bytes arrive. total and progress are undefined when the server doesn't send a Content-Length header — treat that as indeterminate progress (show a spinner, not a percentage) rather than assuming 0%.

Useful for:

  • Custom progress bars / percentage indicators
  • Galleries and dashboards with large or high-resolution images
  • Image editors
  • Performance monitoring and debugging

How it works — and its limits

A native <img> doesn't expose byte-level progress, so onLoadProgress fetches the image itself with fetch/ReadableStream, reports progress as chunks arrive, then hands the assembled bytes to an <img> via an object URL. This only works when:

  • The browser supports fetch and ReadableStream (all modern browsers).
  • src is same-origin, or cross-origin with CORS headers that allow reading the response body.

When either isn't true — e.g. a cross-origin image without CORS headers — React Smart Image silently falls back to a normal load with no progress events. The image still loads correctly; you just won't get progress callbacks for it. This is a deliberate choice: rather than fabricate a fake progress curve, onLoadProgress simply doesn't fire when byte-level progress isn't reliably available.

⚠️ Not supported together with responsive — the browser (not this library) chooses which srcSet candidate to fetch, so there's no single request to attribute progress to. onLoadProgress is ignored when responsive/srcSet is active.

⚠️ Never fires for a cache hit — there's no network transfer to report progress on.

onZoomChange

Know when the zoom viewer opens or closes.

⚠️ Only fires for lightbox/fullscreen modes. Hovering to trigger inline/magnifier zoom doesn't call onZoomChange.

<SmartImage zoom onZoomChange={(open) => console.log(open)} />

Perfect for:

  • Analytics
  • Pause videos
  • Stop autoplay
  • Track product interactions

Cache Utilities

React Smart Image includes an in-memory cache to avoid unnecessary image processing.

Clear Cache

import { clearImageCache } from "@concatstring/react-smart-image";

clearImageCache();

Removes every cached image.

Invalidate One Image

import { invalidateImageCache } from "@concatstring/react-smart-image";

invalidateImageCache("/uploads/avatar.jpg");

Useful after:

  • Uploading a new profile picture
  • Replacing an image
  • CDN cache refresh

Prefetch an Image Imperatively

import { prefetchImage } from "@concatstring/react-smart-image";

prefetchImage("/gallery/next.jpg");

Fetches the image in the background at low priority and marks it loaded in the same cache SmartImage reads from — without needing to render a SmartImage for it first. See Image Prefetch for the declarative prefetch prop.

TypeScript

All public types are exported.

import type {
  SmartImageProps,
  LoadInfo,
  LoadProgressInfo,
  ResponsiveSizes,
  ZoomOptions,
  ZoomMode,
  ZoomAnimation,
  ZoomToolbarButton,
  TransitionKind,
  SmartImageDefaults,
  SmartImageProviderProps,
  SmartImagePresetConfig,
  SmartImagePresetMap,
  PrefetchOptions,
} from "@concatstring/react-smart-image";

No additional packages required.

How Wrapper Rendering Works

SmartImage renders a bare <img> whenever possible.

A <span> wrapper is added — for the component's entire lifetime, not just while loading — whenever one of these props is set:

  • skeleton
  • placeholder="blur"
  • thumbnail

The wrapper stays mounted after the image finishes loading, so the same <img> node persists across the loading → loaded transition. This is required for transition (and the default crossfade) to animate correctly.

⚠️ In wrapper mode, className is applied to the <span>, not the inner <img>.

API Reference

Core Props

| Prop | Type | Default | Description | |------|------|----------|-------------| | src | string | — | Image source URL | | alt | string | "" | Accessible alternative text | | width | number | string | — | Width | | height | number | string | — | Height | | preset | string | — | Apply preset configuration |

All standard <img> HTML attributes (className, style, onClick, onLoad, onError, loading, etc.) are forwarded to the underlying <img> element. ref is also forwarded via forwardRef.

Loading

| Prop | Type | Default | Description | |------|------|----------|-------------| | lazy | boolean | false | Delay loading until the image enters the viewport. Ignored when priority is set | | priority | boolean | false | Load with maximum priority — disables lazy, sets loading="eager" + fetchpriority="high", injects <link rel="preload"> | | prefetch | boolean | false | Fetch this image in the background ahead of it being needed, warming the cache at low fetch priority. Doesn't force eager rendering. Also warms a distinct zoomSrc when zoom is set. Ignored when priority is set | | aspectRatio | number | string | — | Reserve layout space before load via CSS aspect-ratio. Prevents layout shift (improves CLS) | | objectFit | cover, contain, fill, none, scale-down | cover in wrapper mode (skeleton/placeholder/thumbnail), browser default otherwise | How the image fills its width/height/aspectRatio box | | objectPosition | string | — | Which part of the image stays visible when objectFit crops it (e.g. "top", "50% 20%") |

Placeholder

| Prop | Type | Default | Description | |------|------|----------|-------------| | skeleton | boolean | false | Requires width and height | | placeholder | "blur" | — | Requires width and height | | blurDataURL | string | — | Base64 LQIP source | | autoBlur | boolean | false | Derives the blur preview from a tiny version of src | | blurWidth | number | 24 | Width (px) of the auto-blur preview | | thumbnail | string | — | Low-quality image shown while the full image loads |

Transition

| Prop | Type | Default | Description | |------|------|----------|-------------| | transition | fade, scale, grow, rotate, flip, slide-up, slide-left, reveal, none | opacity crossfade in skeleton/blur mode, otherwise none | Ignored when thumbnail is set | | transitionDuration | number | 300 | Duration (ms) of the transition | | fade | boolean | false | Shorthand for transition="fade" |

Responsive

| Prop | Type | Default | Description | |------|------|----------|-------------| | responsive | boolean | false | Enable auto-generated srcSet/sizes | | strategy | srcset | viewport | srcset | srcset lets the browser pick (DPR-aware); viewport picks by media query only and ignores DPR | | sizes | ResponsiveSizes | string | — | Breakpoint widths, or a raw sizes string passed through as-is | | srcSetBuilder | Function | `${src}?w=${width}` | Maps src + a breakpoint width to a URL. Reused by autoBlur |

interface ResponsiveSizes {
  mobile?: number;   // used for max-width: 640px
  tablet?: number;   // used for max-width: 1024px
  desktop?: number;  // default (no media query)
}

Modern Formats

| Prop | Type | Default | Description | |------|------|----------|-------------| | avif | boolean | false | Try a .avif version of src first (checked before webp) | | webp | boolean | false | Try a .webp version of src; falls back to the original on failure | | format | auto | — | Shorthand for enabling both avif and webp |

Retry

| Prop | Type | Default | Description | |------|------|----------|-------------| | retry | number | 0 | Number of times to retry a failed load | | retryDelay | number | 1000 | Base delay (ms) between retries. Each retry doubles the delay (exponential backoff) | | fallback | string | — | URL to display when the image fails to load after all retries |

Zoom

| Prop | Type | Default | Description | |------|------|----------|-------------| | zoom | boolean | false | Enable zoom. Clicking opens a lightbox by default; configure via zoomOptions | | zoomSrc | string | src | Separate (usually higher-res) image shown when zoomed | | zoomOptions | ZoomOptions | — | Zoom configuration — see below | | onZoomChange | Function | — | Called when the zoom lightbox opens (true) or closes (false). Only fires for lightbox/fullscreen modes |

ZoomOptions

| Prop | Type | Default | Description | |------|------|----------|-------------| | mode | lightbox, inline, magnifier, fullscreen | lightbox | Trigger/presentation — lightbox/fullscreen open a popup; inline/magnifier magnify in place | | animation | fade, scale, zoom, slide, none | fade | Lightbox open/close animation | | animationDuration | number | 300 | Animation duration in ms | | showToolbar | boolean | false | Show the lightbox toolbar | | toolbarItems | ZoomToolbarButton[] | zoomIn, zoomOut, reset, download, fullscreen | Buttons to show, in order | | scale | number | 2 | Magnification for inline/magnifier and toolbar zoom steps | | magnifierSize | number | 160 | Lens diameter (px) in magnifier mode | | backdropColor | string | rgba(0,0,0,0.85) | Lightbox backdrop color | | caption | boolean | string | — | Caption under the zoomed image (true = use alt) | | closeOnBackdropClick | boolean | true | Close on backdrop click | | closeOnEsc | boolean | true | Close on Escape | | showCloseButton | boolean | true | Show the × close button | | className | string | — | Applied to the zoomed <img> | | style | CSSProperties | — | Applied to the zoomed <img> |

Callbacks

| Prop | Type | Default | Description | |------|------|----------|-------------| | onVisible | Function | — | Fires once when image enters viewport | | onLoadInfo | Function | — | Returns load statistics | | onLoadProgress | Function | — | Reports byte-level download progress ({ loaded, total, progress }). Requires fetch/CORS; ignored with responsive | | onZoomChange | Function | — | Zoom open / close callback. Only fires for lightbox/fullscreen modes |

SmartImageProvider (Props Reference)

Full usage and prop table: see SmartImageProvider above.

createImagePresets

Create reusable image configurations.

const presets = createImagePresets({
  hero: { priority: true },
});

Returns a fully typed preset object.

Browser Support

| Browser | Supported | |----------|-----------| | Chrome | ✅ | | Edge | ✅ | | Firefox | ✅ | | Safari | ✅ | | Mobile Chrome | ✅ | | Mobile Safari | ✅* |

* zoomOptions={{ mode: "fullscreen" }} doesn't enter true fullscreen on iOS Safari — see Fullscreen.

FAQ

  • Next.js
  • Remix
  • Gatsby
  • Astro
  • React Router SSR
transition="none"
<SmartImage
  responsive
  zoom
  lazy
  retry={2}
  placeholder="blur"
  format="auto"
/>

Contributing

Contributions are welcome!

If you find a bug, have a feature request, or would like to improve the documentation, please open an issue or submit a pull request.

GitHub Issues

https://github.com/concatstring-account/react-smart-image/issues

Changelog

See GitHub Releases for the latest updates.

License

MIT © Concatstring Labs

Links

🌐 Live Demo

https://react-smart-image.netlify.app

📖 Learn more

https://labs.concatstring.com/products/react-smart-image

📦 npm

https://www.npmjs.com/package/@concatstring/react-smart-image

⭐ GitHub

https://github.com/concatstring-account/react-smart-image

Thank You ❤️

If React Smart Image helps your project, please consider:

⭐ Starring the GitHub repository

📦 Sharing the package

🐛 Reporting bugs

💡 Suggesting new features

Your support helps make the library even better for the React community.