The only React image crop component with built-in upload, mobile touch gestures, and enterprise-grade accessibility
A modern, professional-grade React component for image cropping with drag-and-drop upload, advanced touch gestures (pinch-zoom & rotation), and full TypeScript support. Perfect for avatars, profile pictures, banner images, and any image editing needs.
Experience all features in action: drag-and-drop upload, circular crops, multiple themes, touch gestures, and more!
- 📤 Drag & Drop Upload - Intuitive file selection with
react-dropzoneintegration - ✂️ Easy Cropping - Smooth cropping powered by
react-easy-crop - 📱 Touch Gestures - Pinch-to-zoom and two-finger rotation on mobile devices
- 🔄 Rotation Controls - Rotate images with customizable step angles
- 🔍 Zoom Controls - Smooth zoom with customizable min/max range
- 📐 Aspect Ratios - Multiple presets (1:1, 16:9, 4:3, free) and custom options
- 👁️ Live Preview - Real-time crop preview as you adjust
- ⭕ Circular Crops - Perfect for avatar/profile picture uploads
- 🎨 Grid Overlay - Visual guides for better composition
- 📏 Multiple Output Formats - Export as base64, Blob, File, or all formats
- 🎯 Smart Resizing - Automatic output size constraints with aspect ratio preservation
- 🖼️ Image Validation - MIME type whitelist and content validation for security
- ♿ Accessible - WCAG 2.1 AA compliant with full keyboard support and ARIA labels
- 📘 TypeScript - Full type definitions and IntelliSense support
- 🪶 Lightweight - Optimized bundle size with all dependencies included
- 🎨 Themeable - Pre-defined themes (Light, Dark, Modern, Minimal) and custom theme support
- 🎨 Customizable - Extensive props for configuration and styling
- 🧪 Well Tested - Comprehensive test suite with high coverage
- 🔒 Secure - MIME type validation and content validation built-in
npm install react-image-crop-proyarn add react-image-crop-propnpm add react-image-crop-proImportant: Don't forget to import the CSS file:
import 'react-image-crop-pro/dist/react-image-crop-pro.css';
// or
import 'react-image-crop-pro/style.css';import { ImageCropUpload } from 'react-image-crop-pro';
// or use default import
// import ImageCropUpload from 'react-image-crop-pro';
import 'react-image-crop-pro/dist/react-image-crop-pro.css';
function App() {
const handleCropComplete = (result) => {
console.log('Cropped image:', result);
// result.base64 - Data URL
// result.blob - Blob object
// result.file - File object
// result.width, result.height - Dimensions
};
return (
<ImageCropUpload
onCropComplete={handleCropComplete}
/>
);
}Perfect for user profile pictures with circular cropping:
<ImageCropUpload
onCropComplete={handleCropComplete}
aspectRatio={1}
circularCrop
showPreview
maxFileSize={5 * 1024 * 1024} // 5MB
outputType="image/png"
/>Great for hero images, banners, and social media covers:
<ImageCropUpload
onCropComplete={handleCropComplete}
aspectRatio={16 / 9}
outputFormat="blob"
outputMaxWidth={1920}
outputMaxHeight={1080}
/>Multiple aspect ratio options for different platforms:
<ImageCropUpload
onCropComplete={handleCropComplete}
aspectRatioPresets={[
{ label: 'Square (1:1)', value: 1 },
{ label: 'Portrait (4:5)', value: 4 / 5 },
{ label: 'Story (9:16)', value: 9 / 16 },
{ label: 'Landscape (16:9)', value: 16 / 9 },
{ label: 'Free', value: 'free' }
]}
enableRotation
rotationStep={90}
/>Full control over all features:
<ImageCropUpload
// Callbacks
onCropComplete={handleCropComplete}
onError={handleError}
onChange={(state) => console.log('State:', state)}
// Upload constraints
maxFileSize={10 * 1024 * 1024} // 10MB
allowedFormats={['image/jpeg', 'image/png', 'image/webp', 'image/gif']}
// Crop settings
aspectRatio={1}
circularCrop
showGrid
// Zoom & rotation
minZoom={1}
maxZoom={5}
initialZoom={1}
enableRotation
rotationStep={90}
// Touch gestures (mobile)
enablePinchZoom
enableTouchRotation
// Output settings
outputFormat="all" // base64, blob, and file
outputQuality={0.95}
outputType="image/jpeg"
outputMaxWidth={2048}
outputMaxHeight={2048}
// UI customization
className="my-custom-class"
showPreview
previewSize={200}
/>| Prop | Type | Default | Description |
|---|---|---|---|
onCropComplete |
(result: CropResult) => void |
Required | Callback when crop is complete |
onError |
(error: ImageCropError) => void |
- | Error callback |
onChange |
(state: UploadState) => void |
- | State change callback |
aspectRatio |
number |
1 |
Fixed aspect ratio (width/height) |
aspectRatioPresets |
AspectRatioPreset[] |
Default presets | Available aspect ratio options |
circularCrop |
boolean |
false |
Enable circular crop |
showGrid |
boolean |
true |
Show crop grid overlay |
| Prop | Type | Default | Description |
|---|---|---|---|
maxFileSize |
number |
10485760 (10MB) |
Max file size in bytes |
allowedFormats |
string[] |
['image/jpeg', 'image/png', 'image/webp', 'image/gif'] |
Allowed MIME types |
| Prop | Type | Default | Description |
|---|---|---|---|
minZoom |
number |
1 |
Minimum zoom level |
maxZoom |
number |
3 |
Maximum zoom level |
initialZoom |
number |
1 |
Initial zoom level |
enableRotation |
boolean |
true |
Enable rotation control |
rotationStep |
number |
90 |
Rotation step in degrees |
| Prop | Type | Default | Description |
|---|---|---|---|
enablePinchZoom |
boolean |
true |
Enable pinch-to-zoom gesture |
enableTouchRotation |
boolean |
true |
Enable two-finger rotation |
| Prop | Type | Default | Description |
|---|---|---|---|
outputFormat |
'base64' | 'blob' | 'file' | 'all' |
'base64' |
Output format(s) |
outputQuality |
number |
0.95 |
JPEG quality (0-1) |
outputType |
'image/png' | 'image/jpeg' | 'image/webp' |
'image/jpeg' |
Output MIME type |
outputMaxWidth |
number |
- | Max output width |
outputMaxHeight |
number |
- | Max output height |
| Prop | Type | Default | Description |
|---|---|---|---|
theme |
ThemeName | ThemeConfig |
'light' |
Theme name or custom theme configuration |
className |
string |
- | Custom CSS class for container |
cropAreaClassName |
string |
- | Custom CSS class for crop area |
previewClassName |
string |
- | Custom CSS class for preview |
showPreview |
boolean |
true |
Show live preview |
previewSize |
number |
150 |
Preview size in pixels |
interface CropResult {
base64?: string; // Data URL (when outputFormat includes 'base64' or 'all')
blob?: Blob; // Blob object (when outputFormat includes 'blob' or 'all')
file?: File; // File object (when outputFormat includes 'file' or 'all')
width: number; // Final image width
height: number; // Final image height
}
type UploadState =
| 'idle' // Ready for file upload
| 'uploading' // Reading file
| 'cropping' // User is cropping
| 'processing' // Generating output
| 'complete' // Crop complete
| 'error'; // Error occurred
type ImageCropError =
| { type: 'FILE_TOO_LARGE'; maxSize: number }
| { type: 'INVALID_FILE_TYPE'; allowedTypes: string[] }
| { type: 'FILE_READ_ERROR'; message: string }
| { type: 'CROP_ERROR'; message: string }
| { type: 'CANVAS_ERROR'; message: string };
interface AspectRatioPreset {
label: string; // Display label
value: number | 'free'; // Aspect ratio or 'free'
icon?: ReactNode; // Optional icon
}Handle errors gracefully with the onError callback:
const handleError = (error: ImageCropError) => {
switch (error.type) {
case 'FILE_TOO_LARGE':
toast.error(`File too large. Max: ${error.maxSize / 1024 / 1024}MB`);
break;
case 'INVALID_FILE_TYPE':
toast.error(`Invalid type. Allowed: ${error.allowedTypes.join(', ')}`);
break;
case 'FILE_READ_ERROR':
case 'CROP_ERROR':
case 'CANVAS_ERROR':
toast.error(`Error: ${error.message}`);
break;
}
};
<ImageCropUpload
onCropComplete={handleCropComplete}
onError={handleError}
/>Customize all UI text for internationalization:
<ImageCropUpload
onCropComplete={handleCropComplete}
labels={{
dropzone: {
idle: 'Drag & drop your image here or click to browse',
active: 'Release to upload',
reject: 'Invalid file type'
},
buttons: {
cancel: 'Cancel',
crop: 'Apply Crop',
retry: 'Try Again'
},
controls: {
zoom: 'Zoom',
rotation: 'Rotation',
aspectRatio: 'Aspect Ratio'
}
}}
/>Apply your own styles:
<ImageCropUpload
className="my-cropper"
cropAreaClassName="my-crop-area"
previewClassName="my-preview"
onCropComplete={handleCropComplete}
/>Or override CSS variables:
.my-cropper {
--cropper-primary: #007bff;
--cropper-border: #dee2e6;
--cropper-radius: 8px;
--cropper-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}The component comes with a powerful theming system that allows you to customize colors, spacing, borders, typography, and other visual properties without writing CSS.
Choose from four professionally designed themes:
Clean, modern look with blue accents - perfect for standard web applications.
<ImageCropUpload theme="light" onCropComplete={handleCrop} />Features:
- Primary color: Blue (#4299e1)
- Background: White
- Best for: Light-themed applications
Dark mode with high contrast and vibrant accents - ideal for low-light environments.
<ImageCropUpload theme="dark" onCropComplete={handleCrop} />Features:
- Primary color: Light blue (#63b3ed)
- Background: Dark gray (#1a202c)
- Best for: Dark-mode applications, night usage
Vibrant purple and pink gradients with contemporary styling - great for creative applications.
<ImageCropUpload theme="modern" onCropComplete={handleCrop} />Features:
- Primary color: Purple (#8b5cf6)
- Secondary: Pink (#ec4899)
- Best for: Creative, trendy applications
Clean, subtle design with minimal colors and borders - suitable for professional interfaces.
<ImageCropUpload theme="minimal" onCropComplete={handleCrop} />Features:
- Primary color: Black (#171717)
- Background: White
- Best for: Minimalist, professional applications
Create custom themes by passing a configuration object:
<ImageCropUpload
theme={{
colors: {
primary: '#10b981',
primaryHover: '#059669',
text: '#1f2937',
}
}}
onCropComplete={handleCrop}
/><ImageCropUpload
theme={{
name: 'custom-brand',
colors: {
primary: '#10b981',
primaryHover: '#059669',
primaryActive: '#047857',
primaryLight: '#d1fae5',
background: '#ffffff',
text: '#1f2937',
border: '#e5e7eb',
},
spacing: {
sm: '0.5rem',
md: '1rem',
lg: '1.5rem',
},
borders: {
radius: {
md: '8px',
lg: '12px',
}
}
}}
onCropComplete={handleCrop}
/>import { createCustomTheme } from 'react-image-crop-pro';
const myTheme = createCustomTheme('dark', {
colors: {
primary: '#f59e0b', // Override primary color
primaryHover: '#d97706',
},
spacing: {
lg: '2rem', // Override large spacing
}
});
<ImageCropUpload theme={myTheme} onCropComplete={handleCrop} />import { useState } from 'react';
import { ImageCropUpload, ThemeName } from 'react-image-crop-pro';
function ThemeDemo() {
const [theme, setTheme] = useState<ThemeName>('light');
return (
<div>
<select value={theme} onChange={(e) => setTheme(e.target.value as ThemeName)}>
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="modern">Modern</option>
<option value="minimal">Minimal</option>
</select>
<ImageCropUpload
theme={theme}
onCropComplete={(result) => console.log(result)}
/>
</div>
);
}All available color properties:
interface ThemeColors {
// Primary colors
primary: string;
primaryHover: string;
primaryActive: string;
primaryLight: string;
// Secondary colors
secondary: string;
secondaryHover: string;
// State colors
success: string;
successLight: string;
error: string;
errorLight: string;
errorBorder: string;
// Neutral colors
background: string;
backgroundAlt: string;
surface: string;
border: string;
borderHover: string;
// Text colors
text: string;
textSecondary: string;
textOnPrimary: string;
// Component-specific
dropzoneBackground: string;
dropzoneBorder: string;
cropperBackground: string;
sliderThumb: string;
}interface ThemeSpacing {
xs: string; // Extra small (default: 0.25rem)
sm: string; // Small (default: 0.5rem)
md: string; // Medium (default: 1rem)
lg: string; // Large (default: 1.5rem)
xl: string; // Extra large (default: 2rem)
xxl: string; // 2X large (default: 3rem)
}interface ThemeBorders {
radius: {
sm: string; // Small radius (default: 4px)
md: string; // Medium radius (default: 6px)
lg: string; // Large radius (default: 8px)
full: string; // Full circle (default: 9999px)
};
width: {
thin: string; // Thin border (default: 1px)
normal: string; // Normal border (default: 2px)
thick: string; // Thick border (default: 4px)
};
style: string; // Border style (default: 'solid')
}The theming system uses CSS custom properties (CSS variables) which are applied at runtime. All variables use the --ricu- prefix.
--ricu-colors-primary
--ricu-colors-primaryHover
--ricu-spacing-md
--ricu-borders-radius-lg
--ricu-typography-fontSize-base/* Global override */
:root {
--ricu-colors-primary: #10b981;
--ricu-spacing-md: 1.25rem;
}
/* Scoped override */
.my-custom-cropper {
--ricu-colors-primary: #ef4444;
--ricu-borders-radius-lg: 16px;
}import {
resolveTheme,
mergeTheme,
applyTheme,
getThemeColor,
isDarkTheme,
themeToCSSVariables,
} from 'react-image-crop-pro';
// Resolve a theme prop to a full theme object
const theme = resolveTheme('dark');
// Merge two themes
const customTheme = mergeTheme(lightTheme, {
colors: { primary: '#10b981' }
});
// Check if a theme is dark
const isDark = isDarkTheme(theme); // true/false
// Get a specific color
const primaryColor = getThemeColor(theme, 'primary');
// Convert theme to CSS variables object
const cssVars = themeToCSSVariables(theme);<ImageCropUpload
theme={{
colors: {
primary: '#EA4335', // Google Red
primaryHover: '#D33B2C',
primaryLight: '#FDECEA',
text: '#202124',
border: '#DADCE0',
}
}}
onCropComplete={handleCrop}
/><ImageCropUpload
theme={{
colors: {
primary: '#000000',
primaryHover: '#333333',
background: '#FFFFFF',
text: '#000000',
border: '#000000',
},
borders: {
width: {
thin: '2px',
normal: '3px',
thick: '5px',
}
}
}}
onCropComplete={handleCrop}
/><ImageCropUpload
theme={{
spacing: {
xs: '0.125rem',
sm: '0.25rem',
md: '0.5rem',
lg: '0.75rem',
xl: '1rem',
},
borders: {
radius: {
sm: '2px',
md: '3px',
lg: '4px',
}
}
}}
onCropComplete={handleCrop}
/>The theming system is fully typed:
import {
Theme,
ThemeConfig,
ThemeName,
ThemeProp,
ThemeColors,
ThemeSpacing,
ThemeBorders,
} from 'react-image-crop-pro';
// Use a theme name
const themeName: ThemeName = 'dark';
// Create a custom theme configuration
const themeConfig: ThemeConfig = {
colors: {
primary: '#10b981',
}
};
// Full theme object
const theme: Theme = {
name: 'my-theme',
colors: { /* ... */ },
spacing: { /* ... */ },
// ... all required properties
};- Use Pre-defined Themes First: Start with a pre-defined theme and customize only what's needed
- Maintain Consistency: Keep color relationships consistent (e.g., hover should be darker than normal)
- Accessibility: Ensure sufficient color contrast for text and interactive elements
- Performance: Avoid creating new theme objects on every render; use
useMemoor define themes outside components - TypeScript: Leverage TypeScript types for autocomplete and type safety
Built with accessibility as a priority:
- ✅ Full keyboard navigation support
- ✅ ARIA labels and descriptions on all interactive elements
- ✅ Screen reader friendly
- ✅ Focus management
- ✅ High contrast mode support
- ✅ WCAG 2.1 AA compliant
- ✅ Automated accessibility tests
Tab/Shift+Tab- Navigate between controlsEnter/Space- Activate buttons and open file dialogArrow Keys- Adjust sliders (zoom, rotation)Escape- Cancel operation
- ✅ Chrome (latest)
- ✅ Firefox (latest)
- ✅ Safari (latest)
- ✅ Edge (latest)
- ✅ Mobile browsers (iOS Safari, Chrome Mobile)
- Lightweight and optimized for production
- Includes all dependencies (react-easy-crop, react-dropzone)
- Zero additional dependencies required
- Tree-shakeable exports
Special attention to mobile experience:
- Touch gestures: Pinch-to-zoom and two-finger rotation
- Responsive design: Works on all screen sizes
- Performance: Optimized for mobile devices
- Native feel: Smooth interactions like native apps
Built-in security features:
- ✅ MIME type whitelist validation
- ✅ File content validation (not just extension)
- ✅ Size validation before processing
- ✅ No external URL loading
- ✅ No eval() or dangerous APIs
Full TypeScript support with exported types:
import {
ImageCropUpload,
ImageCropUploadProps,
CropResult,
ImageCropError,
UploadState,
AspectRatioPreset,
// Theme types
Theme,
ThemeConfig,
ThemeName,
ThemeProp,
ThemeColors,
ThemeSpacing,
ThemeBorders,
ThemeTypography,
ThemeShadows,
ThemeTransitions,
ThemeZIndex,
} from 'react-image-crop-pro';Comprehensive test coverage:
- ✅ Unit tests for all utilities
- ✅ Component integration tests
- ✅ Accessibility tests (jest-axe)
- ✅ Performance tests
- ✅ Run tests with
npm test
Contributions are welcome! Please feel free to submit a Pull Request.
MIT © Mashrul Haque
Built on top of excellent open-source libraries:
- react-easy-crop - Smooth cropping functionality
- react-dropzone - File upload handling
- 📖 Documentation
- 🐛 GitHub Issues
- 💻 Examples
- ⭐ Star us on GitHub
Made with ❤️ for the React community
⭐ Star on GitHub • 📦 View on npm • 🐛 Report Bug • ✨ Request Feature