Dynamic image cropping component for Expo/React Native with free-form and fixed aspect ratio support
npm install expo-dynamic-image-cropA powerful and flexible image cropping component for Expo/React Native applications with dynamic cropping capabilities, fixed aspect ratios, and smooth gesture handling.

_All four corner markers work perfectly - drag from any corner for smooth, real-time cropping!_
- š¼ļø Dynamic Cropping: Free-form cropping with independent width/height adjustment
- š Fixed Aspect Ratios: Support for common ratios (1:1, 16:9, 4:3, etc.)
- š Gesture Support: Smooth pinch-to-zoom and pan gestures
- šØ Customizable UI: Beautiful, modern interface with customizable controls
- š± Expo Ready: Optimized for Expo managed workflow
- š§ TypeScript: Full TypeScript support with proper type definitions
- š Self-Contained: No external state management required - works out of the box
- š Cross-Platform: Works identically on both Android and iOS - write once, run everywhere
- š Easily Discoverable: Top search results for "expo image crop", "react native image editor", and "dynamic crop"
- ā” React 19+ Compatible: Uses Zustand for state management, no provider or setup required
> Note: This package now uses Zustand for state management. No need to wrap your app in any provider. Fully compatible with React 19+ and Expo managed workflow.
Simple one-command installation:
``bash`
npx expo install expo-dynamic-image-crop
That's it! All dependencies are included. ā
`bash
npm install expo-dynamic-image-crop
š¦ Dependencies Explained
We use a hybrid approach for optimal user experience:
- Included:
@expo/vector-icons, expo-image-manipulator, react-native-gesture-handler, zustand
- Peer Dependencies: expo, react, react-native (you already have these in Expo projects)This means zero extra installation steps for most users! š
š Cross-Platform Compatibility
Perfect Android & iOS Support:
- ā
Identical Behavior: Same gestures, UI, and functionality on both platforms
- ā
Native Performance: Leverages platform-specific optimizations under the hood
- ā
Consistent Design: Looks and feels native on both Android and iOS
- ā
No Platform-Specific Code: One component works everywhere
Tested on:
- š± iOS 14+ (iPhone & iPad)
- š¤ Android API 21+ (Phone & Tablet)
- š Expo SDK 49+
š Quick Start
Use the ImageEditor component directly - no setup required:
`tsx
import React, { useState } from "react";
import { View, Image, Button } from "react-native";
import { ImageEditor } from "expo-dynamic-image-crop";export default function MyScreen() {
const [imageUri, setImageUri] = useState(null);
const [isEditing, setIsEditing] = useState(false);
const handleCropComplete = (croppedImageData: any) => {
setImageUri(croppedImageData.uri);
setIsEditing(false);
};
return (
{imageUri && (
)}
isVisible={isEditing}
imageUri="your-image-uri-here"
onEditingComplete={handleCropComplete}
onEditingCancel={() => setIsEditing(false)}
fixedAspectRatio={1} // Optional: 1 for square, undefined for free-form
dynamicCrop={true} // Enable dynamic cropping
/>
);
}
`š§āš» Example App
Want to see this package in action?
Check out the full example Expo app here:
š expo-dynamic-image-crop-example (GitHub)
- The Index screen demonstrates dynamic cropping (free-form).
- The Explore screen demonstrates fixed aspect ratio cropping.
Clone, run, and experiment with all features before integrating into your own project!
šļø Cropping Modes
$3
`tsx
isVisible={isEditing}
imageUri={imageUri}
onEditingComplete={handleCrop}
onEditingCancel={handleClose}
dynamicCrop={true} // Free-form cropping
/>
`$3
`tsx
// Square (1:1)
// Landscape (16:9)
// Portrait (4:3)
`šØ Advanced Features
$3
Replace the default control bar with your own custom UI to match your app's design:
`tsx
import { ImageEditor, ControlBarActions } from "expo-dynamic-image-crop";function MyCustomControlBar({ actions }: { actions: ControlBarActions }) {
return (
title={actions.isEdit ? "Back" : "Cancel"}
onPress={actions.isEdit ? actions.onBack : actions.onCancel}
/>
{actions.isEdit ? "Edit Mode" : "Crop Mode"}
title={actions.isEdit ? "Save" : "Crop"}
onPress={actions.isEdit ? actions.onSave : actions.onCrop}
/>
);
}
// Use your custom control bar
imageUri={imageUri}
isVisible={isEditing}
onEditingComplete={handleCrop}
onEditingCancel={handleClose}
customControlBar={(actions) => }
/>
`Available Actions:
-
onCancel() - Cancels editing and closes the editor
- onCrop() - Performs the crop operation (async)
- onSave() - Saves the cropped image
- onBack() - Returns from edit mode to crop mode
- isEdit - Boolean indicating current mode (false = cropping, true = editing)$3
Default Modal Mode:
`tsx
// Wraps editor in a modal (default behavior)
isVisible={isEditing}
imageUri={imageUri}
onEditingComplete={handleCrop}
onEditingCancel={handleClose}
/>
`Inline Mode (No Modal):
`tsx
// Renders directly inline - great for custom screens
useModal={false}
imageUri={imageUri}
onEditingComplete={handleCrop}
onEditingCancel={handleClose}
/>
`Using ImageEditorView Directly:
`tsx
import { ImageEditorView } from "expo-dynamic-image-crop";// Full control - no modal wrapper at all
imageUri={imageUri}
onEditingComplete={handleCrop}
onEditingCancel={handleClose}
/>
`$3
For building custom UI or reacting to editor state changes, use the provided hooks:
`tsx
import {
useEditorProcessing,
useEditorReady,
useEditorImageData,
useEditorMode,
useEditorCropInfo,
} from "expo-dynamic-image-crop";function CustomLoadingIndicator() {
const processing = useEditorProcessing(); // true when processing
return processing ? : null;
}
function ImageDimensions() {
const imageData = useEditorImageData(); // { uri, width, height }
return Size: {imageData.width} x {imageData.height} ;
}
function ModeIndicator() {
const { isEdit, editingMode } = useEditorMode();
return Mode: {isEdit ? "Editing" : "Cropping"} ;
}
function CropDimensions() {
const { cropSize } = useEditorCropInfo();
return Crop: {Math.round(cropSize.width)} x {Math.round(cropSize.height)} ;
}
`Available Hooks:
-
useEditorProcessing() - Returns processing state (boolean)
- useEditorReady() - Returns ready state (boolean)
- useEditorImageData() - Returns current image data (ImageData)
- useEditorMode() - Returns { isEdit, editingMode }
- useEditorCropInfo() - Returns { cropSize, imageBounds, accumulatedPan }> Note: These are advanced APIs. For most use cases, the standard component props are sufficient.
šÆ Visual Showcase

⨠Professional-Grade Corner Dragging
- All 4 corners work smoothly
- Real-time visual feedback
- Perfect for production apps
š API Reference
$3
| Prop | Type | Default | Description |
| -------------------- | -------------------------------------------- | ------------ | ----------------------------------------------------- |
|
imageUri | string \| null | Required | URI of the image to crop |
| onEditingComplete | (data: ImageData) => void | Required | Callback when cropping is complete |
| onEditingCancel | () => void | Required | Callback when editing is cancelled |
| isVisible | boolean | undefined | Controls modal visibility (required when using modal) |
| useModal | boolean | true | Wrap editor in modal (true) or render inline (false) |
| customControlBar | (actions: ControlBarActions) => ReactNode | undefined | Custom control bar component |
| fixedAspectRatio | number \| undefined | undefined | Fixed aspect ratio (width/height) |
| dynamicCrop | boolean | true | Enable free-form cropping |
| processingComponent| ReactNode | undefined | Custom loading component |
| editorOptions | EditorOptions | Default UI | Customize colors, control bar position, etc. |$3
Actions passed to custom control bar components:
`typescript
type ControlBarActions = {
onCancel: () => void; // Cancel editing
onCrop: () => Promise; // Perform crop
onSave: () => void; // Save cropped image
onBack: () => void; // Go back to crop mode
isEdit: boolean; // Current mode
};
`$3
`typescript
type ImageData = {
uri: string; // Image URI
width: number; // Image width in pixels
height: number; // Image height in pixels
};
``The component comes with a beautiful default UI, but you can customize it by modifying the components in your node_modules or by creating your own wrapper.
Top Search Results for:
- š„ "expo image crop"
- š„ "react native image editor"
- š„ "dynamic crop react native"
- š„ "expo image cropping component"
Best-in-Class Features:
- Zero setup required (self-contained)
- Perfect cross-platform compatibility
- Modern TypeScript support
- Active maintenance and updates
- Comprehensive documentation
- React 19+ compatible (thanks to Zustand!)
Contributions are welcome! Please feel free to submit a Pull Request.
MIT License - feel free to use in personal and commercial projects.
If you encounter any issues or need support, please create an issue on GitHub.
---
Made with ā¤ļø for the Expo community