@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
Maintainers
Keywords
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
⚡ 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-imageor
yarn add @concatstring/react-smart-imageor
pnpm add @concatstring/react-smart-imagePeer Dependencies
react >= 18
react-dom >= 18Quick 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
- ✅ 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
- ✅ Smooth Image Transitions
- ✅ Click to Zoom
- ✅ Magnifier Lens
- ✅ Fullscreen Viewer
🛡 Reliability
- ✅ Automatic Retry
- ✅ Exponential Backoff
- ✅ Error Fallback Images
- ✅ Image Cache Utilities
⚛️ Developer Experience
- ✅ 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
- Installation
- Quick Start
- Lazy Loading
- Priority Loading
- Image Prefetch
- Aspect Ratio
- Object Fit & Position
- Skeleton Loader
- Blur Placeholder
- Image Transition
- Responsive Images
- AVIF & WebP
- Thumbnail Loading
- Zoom
- Retry & Fallback
- SmartImageProvider
- Image Presets
- Performance Tips
- TypeScript
- API Reference
- FAQ
- License
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
priorityis enabled
💡 Use
lazyfor 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
priorityfor 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
zoomthumbnail
<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
priorityis 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 / 9 → 1.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
aspectRatiomeans you usually don't need to specifyheight.
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", orthumbnail)objectFitalready defaults to"cover"so the image fills its box — setobjectFitexplicitly 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
zoomlightbox always usesobject-fit: contain(so the whole image stays visible), independent ofobjectFit— override it viazoomOptions.styleif needed.
Skeleton Loader
Display an animated loading placeholder while the image downloads.
ℹ️ Requires
widthandheight(oraspectRatio) 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
widthandheight(oraspectRatio) 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
💡
skeletonandplaceholder="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 />⚠️
transitionis ignored whenthumbnailis set, since that mode swapssrcin 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.jpgSupported 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 → 2000msUses 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=480If 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⚠️
srcSetBuildershould be a pure function — it's called once per breakpoint width (and again atblurWidthwhenautoBluris 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
downloadbutton uses thedownloadattribute, 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 presetsgives you a typed union of preset names (e.g.'hero' | 'product' | 'avatar') — handy for typing apresetprop on your own wrapper component.
⚠️ An unrecognized
presetname 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 DefaultsThe 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
fetchandReadableStream(all modern browsers). srcis 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 whichsrcSetcandidate to fetch, so there's no single request to attribute progress to.onLoadProgressis ignored whenresponsive/srcSetis 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/fullscreenmodes. Hovering to triggerinline/magnifierzoom doesn't callonZoomChange.
<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:
skeletonplaceholder="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,
classNameis 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.
