A modern, accessible React 19 component for image magnification with TypeScript support, built with Vite and tested with Vitest.
Note: The npm package is
@sandeepv68/react-magnifier
- Modern Stack: React 19 with hooks, TypeScript 5.3, Vite 5
- Styled Components: CSS-in-JS via styled-components β no external stylesheet required
- Small Bundle: 16.10 kB ESM (3.74 kB gzipped), 8.03 kB UMD (2.87 kB gzipped)
- Fully Typed: TypeScript with strict mode enabled,
ReactMagnifierPropstype exported - Accessible: WCAG 2.1 Level AA - keyboard navigation, ARIA attributes, screen reader support,
useId()for unique IDs - Zero Runtime Dependencies: React is a peer dependency only; styled-components is the sole runtime dependency
- Keyboard Navigation: Arrow keys to move magnifier (clamped to image bounds), Escape to close
- Touch Support: Works on mobile devices and touch screens
- Custom Events: Listen to magnifier state changes (initialized, moved, visible, invisible)
- Customizable Styling: Full CSS customization support via class names
- Forward Ref Support: Access the container DOM node directly via
ref - 100% Backward Compatible: Drop-in replacement for v0.x
- Fully Tested: 50 comprehensive test cases covering all functionality
- Performance Optimized: React.memo and useCallback for optimal rendering
- Husky Pre-commit Hooks: lint-staged runs on staged files
- Installation
- Quick Start
- Keyboard Navigation
- Accessibility
- API Documentation
- Examples
- Migration Guide
- Project Summary
- Contributing
- License
- Changelog
Install the package from NPM:
npm install @sandeepv68/react-magnifierOr with yarn:
yarn add @sandeepv68/react-magnifierimport ReactMagnifier from '@sandeepv68/react-magnifier';
export default function App() {
return (
<ReactMagnifier
imageUrl="https://example.com/image.jpg"
imageAltText="Product image"
zoomSize={2.5}
magnifierHeight={200}
magnifierWidth={200}
/>
);
}The magnifier supports full keyboard navigation for improved accessibility:
| Key | Action |
|---|---|
| Arrow Up | Move magnifier 10px up |
| Arrow Down | Move magnifier 10px down |
| Arrow Left | Move magnifier 10px left |
| Arrow Right | Move magnifier 10px right |
| Escape | Close magnifier |
Note: Keyboard navigation is active when the magnifier is visible (after hovering or focusing on the image).
ReactMagnifier is built with accessibility as a core feature, meeting WCAG 2.1 Level AA standards:
- β Keyboard Navigation - Full keyboard support with arrow keys and Escape
- β ARIA Labels - Proper ARIA attributes for screen readers
- β Screen Reader Support - Status updates announced to screen readers
- β Focus Management - Proper focus handling and tabindex
- β Semantic HTML - Proper semantic structure
- β Visual Focus Indicators - Clear focus indicators for keyboard users
<ReactMagnifier
imageUrl="image.jpg"
imageAltText="Product description for screen readers"
getMagnifier={(container) => {
console.log('Magnifier container:', container);
}}
/>| Prop | Type | Default | Description |
|---|---|---|---|
imageUrl |
string |
required | URL of the image to magnify |
imageAltText |
string |
"react-magnifier-image" |
Alt text for the image (accessibility) |
imageWidth |
number | string |
"auto" |
Width of the image element |
imageHeight |
number | string |
"auto" |
Height of the image element |
magnifierWidth |
number |
100 |
Width of magnifier glass in pixels |
magnifierHeight |
number |
100 |
Height of magnifier glass in pixels |
magnifierRadius |
number |
50 |
Border radius of magnifier glass (0-100 %) |
magnifierBorderColor |
string |
"#000" |
Border color of magnifier glass |
magnifierBorderStyle |
string |
"solid" |
Border style (solid, dashed, dotted, etc.) |
magnifierBorderWidth |
number |
3 |
Border width in pixels |
magnifierShadow |
boolean |
true |
Whether to show drop shadow |
cursor |
string |
"none" |
CSS cursor style |
zoomSize |
number |
2 |
Magnification zoom level |
ref |
React.Ref<HTMLDivElement> |
undefined |
Forwarded ref to the container div |
getMagnifier |
(container: HTMLDivElement | null) => void |
() => {} |
Callback when magnifier initializes |
customImgClass |
string |
"" |
Custom CSS class for image |
customContainerClass |
string |
"" |
Custom CSS class for container |
customImgStyles (deprecated) |
string |
"" |
customImgClass |
customContainerStyles (deprecated) |
string |
"" |
customContainerClass |
The component dispatches custom DOM events for magnifier state changes:
const containerRef = useRef(null);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const handleMagnifierInitialized = () => console.log('Magnifier initialized');
const handleMagnifierMoved = () => console.log('Magnifier moved');
const handleMagnifierVisible = () => console.log('Magnifier visible');
const handleMagnifierInvisible = () => console.log('Magnifier invisible');
container.addEventListener('magnifier-initialized', handleMagnifierInitialized);
container.addEventListener('magnifier-moved', handleMagnifierMoved);
container.addEventListener('magnifier-visible', handleMagnifierVisible);
container.addEventListener('magnifier-invisible', handleMagnifierInvisible);
return () => {
container.removeEventListener('magnifier-initialized', handleMagnifierInitialized);
container.removeEventListener('magnifier-moved', handleMagnifierMoved);
container.removeEventListener('magnifier-visible', handleMagnifierVisible);
container.removeEventListener('magnifier-invisible', handleMagnifierInvisible);
};
}, []);Event Names:
magnifier-initialized- Fired when magnifier is initializedmagnifier-moved- Fired when magnifier position changesmagnifier-visible- Fired when magnifier becomes visiblemagnifier-invisible- Fired when magnifier becomes hidden
<ReactMagnifier imageUrl="image.jpg" /><ReactMagnifier
imageUrl="product-image.jpg"
imageAltText="Blue wireless headphones"
imageWidth={500}
imageHeight={500}
magnifierWidth={200}
magnifierHeight={200}
magnifierRadius={100}
zoomSize={3}
magnifierShadow={true}
getMagnifier={(container) => {
console.log('Product magnifier loaded');
}}
/><ReactMagnifier
imageUrl="image.jpg"
magnifierBorderColor="#ff6b6b"
magnifierBorderWidth={2}
magnifierBorderStyle="dashed"
cursor="crosshair"
customContainerClass="product-magnifier"
customImgClass="product-image"
/>import { useRef, useEffect } from 'react';
import ReactMagnifier from '@sandeepv68/react-magnifier';
export default function ProductImage() {
const containerRef = useRef(null);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const handleMagnifierVisible = () => {
console.log('User started magnifying image');
};
container.addEventListener('magnifier-visible', handleMagnifierVisible);
return () => container.removeEventListener('magnifier-visible', handleMagnifierVisible);
}, []);
return (
<div ref={containerRef}>
<ReactMagnifier imageUrl="product.jpg" zoomSize={2.5} />
</div>
);
}ReactMagnifier v1.1.1 is 100% backward compatible with v0.0.4. No code changes are required, but you can take advantage of new features:
- React 19 Support - Now uses React 19 hooks internally
- Keyboard Navigation - Arrow keys and Escape key support
- Improved Accessibility - WCAG 2.1 Level AA compliant
- Smaller Bundle - 6.29 kB gzipped (vs 18 KB previously)
- Better Performance - React.memo and useCallback optimizations
- Full TypeScript - Strict mode enabled for type safety
- Improved Testing - 49 test cases with 100% coverage target
- Modern Build - Vite instead of Webpack for faster builds
Simply update your package:
npm update @sandeepv68/react-magnifierYour existing code will continue to work without any changes. To enable keyboard navigation, just press arrow keys or Escape when the magnifier is active.
ReactMagnifier v1.3.0 is a production-ready modernization that combines:
- React 19 functional component architecture
- Vite 5 build tooling with dual ESM and UMD bundles
- TypeScript 5.3 strict typing and declaration generation (via
vite-plugin-dts) - WCAG 2.1 Level AA accessibility with keyboard and screen reader support and
useId()for unique IDs - Vitest testing with a 100% coverage target
- Zero runtime dependencies bundled β React, ReactDOM, and styled-components are all peer dependencies
- React 19 ready with hooks and
React.memo React.forwardRefsupport for container DOM access- Keyboard navigation: arrow keys (clamped to image bounds) + Escape
- Custom DOM events for magnifier lifecycle states
- Build output:
dist/react-magnifier.jsanddist/react-magnifier.umd.cjs - Source maps and
.d.tsdeclarations included
- ESM bundle: 16.10 kB minified, 3.74 kB gzipped
- UMD bundle: 8.03 kB minified, 2.87 kB gzipped
- Build time: ~4s with Vite + vite-plugin-dts
- 100% test coverage target with 50 test cases
- Zero runtime dependencies (React, ReactDOM, styled-components are peer dependencies)
Recommended pre-publication checks:
npm run build
npm run type-check
npm run lint
npm test -- --runPublication steps:
git add .
git commit -m "feat: v1.0.0 - React 19 modernization"
git tag -a v1.0.0 -m "ReactMagnifier v1.0.0 release"
git push origin main
git push origin v1.0.0
npm publish --access publicContributions are welcome! Please read our contributing guidelines and submit pull requests to our GitHub repository.
MIT License - see LICENSE file for details
- React: 19.0.0
- TypeScript: 5.3.3
- Vite: 5.0.8
- Vitest: 1.1.0
- styled-components: 6.4.4 (peer dependency)
- @testing-library/react: 16.3.2
π New Features & Production Hardening
React.forwardRefβ container div ref forwarded for direct DOM access;ReactMagnifierPropstype exportedReact.useId()β uniquearia-describedbyIDs per instance (no duplicate ID violations)- Prop renames:
customImgStylesβcustomImgClass,customContainerStylesβcustomContainerClass(old names deprecated with backward compatibility)
- Keyboard bounds clamping β arrow keys stay within image boundaries
getCursorPosusesclientX/clientY(fixed double scroll compensation)PIXEL_PADDINGreplaced withmagnifierBorderWidthfor accurate glass positioning
- Removed redundant
useMemoon props, emptyscripts/directory, duplicatebuild:libscript,.npmrc - Updated
@testing-library/reactto16.3.2
- Husky + lint-staged pre-commit hooks configured
- Comprehensive TSDoc/JSDoc comments across all source files
π Production Readiness Fixes
Bundle Externalization
- Externalized
styled-componentsandreact/jsx-runtimefrom the Vite build β consumers no longer get duplicate copies - ESM bundle reduced from 25.25 kB to 13.92 kB; UMD from 12.08 kB to 7.51 kB
- UMD bundle no longer ships development-mode JSX runtime
Type Declarations
- Added
vite-plugin-dtsto generate.d.tsfiles indist/during build - TypeScript consumers can now import the package without errors
Console Warning Fix
- Changed
console.logtoconsole.warninlogMagnifierError()β errors now use proper severity level
- Moved
styled-componentsfromdependenciestopeerDependenciesβ consumers control the version - Removed
@types/styled-components(v6 ships its own types) - Removed unused
debounceutility function - Scoped global
* { box-sizing: border-box }reset to.react-magnifier-glassonly - Fixed
.gitignoreand.npmignoresource map patterns (*.map.jsβ*.map) - Removed invalid
./dist/style.cssexport entry - Added
"sideEffects": falsefor tree-shaking support - Fixed React peer dependency to support
^18.0.0 || ^19.0.0 - Fixed dev React dependency to stable
^19.0.0(was RC)
- Added
vite-plugin-dtsas a dev dependency for type declaration generation styled-componentsis now a peer dependency, not a runtime dependency
- Added demo GIF to README
- Updated bundle size stats throughout docs
π Bug Fixes & Documentation Cleanup
Keyboard Navigation Background Sync
- Fixed keyboard arrow key handlers (β β β β) to update
backgroundPositionalongside glass position - Previously, moving the magnifier with keyboard would shift the glass but the zoomed content would not follow
- Added
updateBackgroundPosition()helper that derives logical coordinates from the glass element's DOM position
Event Name Consistency
- Fixed misspelled custom event names (
magnfier-*βmagnifier-*) across all consumer code - The component dispatches correctly-spelled events, but stories, tests, and documentation referenced misspelled versions, meaning event listeners would never fire
- Fixed in:
ReactMagnifier.styled.ts,ReactMagnifier.stories.tsx,ReactMagnifier.memory.test.tsx,README.md,CHANGELOG.md,RELEASE_NOTES.md,TECHNICAL_DOCS.md
- Deleted
src/ReactMagnifier/style.cssβ redundant since v1.1.0 migrated to styled-components - Removed non-existent
index.cssimports fromsrc/index.tsxand stories
- Replaced fragile
await new Promise(resolve => setTimeout(resolve, N))withwaitForfrom@testing-library/reactinReactMagnifier.test.tsx - Fixed syntax error in
ReactMagnifier.memory.test.tsx:269(statement, eslint-disable, and expect were collapsed onto one line) - All 49 tests passing
- "Zero Dependencies" β "Minimal runtime dependencies (styled-components is the sole runtime dependency)"
- "17 comprehensive test cases" β "49 comprehensive test cases"
- Removed references to non-existent npm scripts (
build-tsc,build-dev,build-prod) - "Jest testing library" β "Vitest testing library"
- Updated test count from "50+" to accurate "49"
π¨ CSS-in-JS Migration via styled-components
styled-components Integration
- Component styles are now co-located with the component β no external stylesheet import required
- New
ReactMagnifier.styled.tsexports three styled primitives:ImageContainerβ styleddivreplacing.react-magnifier-image-containerSrOnlyβ styleddivfor screen-reader status announcementsMagnifierGlobalStylesβcreateGlobalStyleblock for the imperatively-created magnifier glass (.react-magnifier-glass,.show-magnifier,.hide-magnifier)
- The class name
react-magnifier-image-containeris still applied explicitly for full backward compatibility with external CSS overrides and existing tests
- Added
styled-componentsas a runtime dependency - Added
@types/styled-componentsas a dev dependency
All v1.0.0 props, events, and behaviors are fully supported. No breaking changes.
π Major Release - Complete Modernization & Accessibility Overhaul
React 19 & Modern Architecture
- Complete migration from React 16 class components to React 19 functional components with hooks
- React.memo, useCallback, useMemo, useRef, useEffect throughout
Keyboard Navigation β¨οΈ
- Arrow keys (β β β β) to move magnifier glass (10px per keypress)
- Escape key to close/hide magnifier
Accessibility Enhancements π―
- WCAG 2.1 Level AA compliance
- ARIA attributes:
role="group",aria-label,aria-describedby,aria-live - Screen reader support with dynamic status announcements
- Visual focus indicators via
:focus-visible
Custom Events System
magnifier-initialized,magnifier-moved,magnifier-visible,magnifier-invisible
Build System
- Vite 5.0.8 replacing Webpack 3 β 10x faster builds (~589ms)
- Dual ESM + UMD output with TypeScript declarations and source maps
Testing
- Vitest 1.1.0 with 49 tests across unit, performance, and memory-leak suites
| Metric | Before | After | Improvement |
|---|---|---|---|
| Bundle (gzipped) | ~18 KB | 3.74 KB | -79% |
| Build time | ~5000ms | ~4s | -92% |
| React version | 16.12 | 19.0 | Latest |
| Accessibility | Basic | WCAG AA + useId() | Full compliance |
| Runtime Dependencies | 0 | 0 (all peer) | Zero bundled |
100% backward compatible with v0.0.4 β no breaking changes.
v1.1.1 - Bug fixes, documentation cleanup, test improvements v1.1.0 - CSS-in-JS migration via styled-components v1.0.0 - Major modernization to React 19 v0.0.4 - Previous stable release
This modernized version builds upon the original React Magnifier concept, bringing it to 2026 standards with React 19, enhanced accessibility, improved performance, and comprehensive testing. Special thanks to all contributors and users who have supported this project.
Made with β€οΈ by Sandeep Vattapparambil and the React Community.
All suggestions and pull requests are welcome! Please read the CODE_OF_CONDUCT and CONTRIBUTING files before contributing.
Clone and contribute:
git clone https://github.com/SandeepVattapparambil/react-magnify.git
cd react-magnify
npm install
npm run dev # Start development server
npm test # Run tests
npm run build # Build for productionSee NPM_PUBLICATION_GUIDE.md for publishing instructions.
- Create production build for react source
npm run build- Run type checking
npm run type-check- Run linting
npm run lintYou need to have Nodejs ,npm in your system as development dependency.
This project includes unit tests written in Vitest testing library. Tests can be run by the npm script
npm run testMIT License
Copyright (c) 2020 Sandeep Vattapparambil http://www.sandeepv.in
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
This project is inspired from Blowup.js, but not copied or does not include any or part of it in this project.
Made with β€οΈ by Sandeep Vattapparambil.
All images used in demos and documentations are from Unsplash.com


