Tools: πŸš€ React Native: Fully Customizable Image Crop Picker (Android + iOS)

Tools: πŸš€ React Native: Fully Customizable Image Crop Picker (Android + iOS)

Source: Dev.to

✨ Why Another Crop Library? ## Android Screenshot ## iOS Screenshot ## πŸ“¦ Installation ## πŸ›  Complete Working Example ## 🎯 Key Features ## βœ‚οΈ Free Style Cropping ## 🎨 Fully Custom Header ## πŸ”˜ Fully Custom Footer Buttons ## πŸŒ“ Native Controls (Optional) ## πŸ“¦ Output Format ## πŸ“± Real World Use Cases ## πŸš€ What Makes This Different? ## πŸ“Œ Final Thoughts While building a feature recently, I hit a limitation. 🎨 Fully customizable header & footer πŸ”΅ Circular crop (for profile images) βœ‚οΈ Free-style cropping πŸ“¦ Base64 + file output πŸŒ™ Theme control πŸ“± Consistent behavior on Android & iOS Most crop libraries either: react-native-customizable-image-crop-picker Typical problems I faced: ❌ No custom header design ❌ No footer button layout control ❌ Hard to match app branding ❌ Limited icon customization ❌ Poor theme control I wanted something production-ready with deep customization β€” so I created this package. Here’s a clean implementation: πŸ”΅ Circular Crop (Perfect for Profile Pictures) Let users resize crop area freely. You are not locked into default native UI anymore. If you prefer native system crop toolbar: You get structured output: Which works perfectly for: Instead of forcing native UI, this gives you: βœ” UI-level customization βœ” Native performance βœ” Circular + freestyle crop βœ” Header/Footer full control βœ” Clean base64 output βœ” Android + iOS support If you're building a production React Native app and want: This package is built for that exact use case. If this helped you, consider: πŸ’¬ Sharing feedback πŸ› Reporting improvements Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK: yarn add react-native-customizable-image-crop-picker Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: yarn add react-native-customizable-image-crop-picker CODE_BLOCK: yarn add react-native-customizable-image-crop-picker CODE_BLOCK: cd ios && pod install Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: cd ios && pod install CODE_BLOCK: cd ios && pod install COMMAND_BLOCK: import React, { useState } from 'react'; import { Image, Pressable, StatusBar, StyleSheet, Text, View, } from 'react-native'; import { openImageCropPicker } from 'react-native-customizable-image-crop-picker'; const App = () => { const uploadIconUri = require('../../../upload.jpg'); const [image, setImage] = useState(''); const [base64Image, setBase64Image] = useState(''); const commonOptions = { cropWidth: 1, cropHeight: 1, includeBase64: true, compressQuality: 0.8, compressFormat: 'jpeg', circularCrop: true, freeStyleCropEnabled: true, cropGridEnabled: true, showNativeCropControls: false, headerTitle: 'Preview', footerButtonLayout: 'vertical', footerButtonOrder: 'uploadFirst', topLeftControl: 'upload', topRightControl: 'cancel', cancelText: 'Cancel', uploadText: 'Upload', uploadButtonIconUri: uploadIconUri, }; const open = async (source) => { try { const res = await openImageCropPicker({ source, ...commonOptions, }); const uri = res?.path ? `file://${res.path}` : ''; setImage(uri); setBase64Image(res?.base64); } catch (e) { console.log('Crop failed', e); } }; return ( <View style={styles.container}> <StatusBar barStyle="dark-content" /> <Pressable onPress={() => open('camera')}> <Text>Camera</Text> </Pressable> <Pressable onPress={() => open('gallery')}> <Text>Gallery</Text> </Pressable> <Image source={{ uri: image }} style={{ width: 150, height: 150 }} /> <Image source={{ uri: `data:image/jpeg;base64,${base64Image}` }} style={{ width: 150, height: 150 }} /> </View> ); }; export default App; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: import React, { useState } from 'react'; import { Image, Pressable, StatusBar, StyleSheet, Text, View, } from 'react-native'; import { openImageCropPicker } from 'react-native-customizable-image-crop-picker'; const App = () => { const uploadIconUri = require('../../../upload.jpg'); const [image, setImage] = useState(''); const [base64Image, setBase64Image] = useState(''); const commonOptions = { cropWidth: 1, cropHeight: 1, includeBase64: true, compressQuality: 0.8, compressFormat: 'jpeg', circularCrop: true, freeStyleCropEnabled: true, cropGridEnabled: true, showNativeCropControls: false, headerTitle: 'Preview', footerButtonLayout: 'vertical', footerButtonOrder: 'uploadFirst', topLeftControl: 'upload', topRightControl: 'cancel', cancelText: 'Cancel', uploadText: 'Upload', uploadButtonIconUri: uploadIconUri, }; const open = async (source) => { try { const res = await openImageCropPicker({ source, ...commonOptions, }); const uri = res?.path ? `file://${res.path}` : ''; setImage(uri); setBase64Image(res?.base64); } catch (e) { console.log('Crop failed', e); } }; return ( <View style={styles.container}> <StatusBar barStyle="dark-content" /> <Pressable onPress={() => open('camera')}> <Text>Camera</Text> </Pressable> <Pressable onPress={() => open('gallery')}> <Text>Gallery</Text> </Pressable> <Image source={{ uri: image }} style={{ width: 150, height: 150 }} /> <Image source={{ uri: `data:image/jpeg;base64,${base64Image}` }} style={{ width: 150, height: 150 }} /> </View> ); }; export default App; COMMAND_BLOCK: import React, { useState } from 'react'; import { Image, Pressable, StatusBar, StyleSheet, Text, View, } from 'react-native'; import { openImageCropPicker } from 'react-native-customizable-image-crop-picker'; const App = () => { const uploadIconUri = require('../../../upload.jpg'); const [image, setImage] = useState(''); const [base64Image, setBase64Image] = useState(''); const commonOptions = { cropWidth: 1, cropHeight: 1, includeBase64: true, compressQuality: 0.8, compressFormat: 'jpeg', circularCrop: true, freeStyleCropEnabled: true, cropGridEnabled: true, showNativeCropControls: false, headerTitle: 'Preview', footerButtonLayout: 'vertical', footerButtonOrder: 'uploadFirst', topLeftControl: 'upload', topRightControl: 'cancel', cancelText: 'Cancel', uploadText: 'Upload', uploadButtonIconUri: uploadIconUri, }; const open = async (source) => { try { const res = await openImageCropPicker({ source, ...commonOptions, }); const uri = res?.path ? `file://${res.path}` : ''; setImage(uri); setBase64Image(res?.base64); } catch (e) { console.log('Crop failed', e); } }; return ( <View style={styles.container}> <StatusBar barStyle="dark-content" /> <Pressable onPress={() => open('camera')}> <Text>Camera</Text> </Pressable> <Pressable onPress={() => open('gallery')}> <Text>Gallery</Text> </Pressable> <Image source={{ uri: image }} style={{ width: 150, height: 150 }} /> <Image source={{ uri: `data:image/jpeg;base64,${base64Image}` }} style={{ width: 150, height: 150 }} /> </View> ); }; export default App; CODE_BLOCK: circularCrop: true Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: circularCrop: true CODE_BLOCK: circularCrop: true CODE_BLOCK: freeStyleCropEnabled: true Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: freeStyleCropEnabled: true CODE_BLOCK: freeStyleCropEnabled: true CODE_BLOCK: showNativeCropControls: true controlsPlacement: 'bottom' Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: showNativeCropControls: true controlsPlacement: 'bottom' CODE_BLOCK: showNativeCropControls: true controlsPlacement: 'bottom' CODE_BLOCK: { path: string, width: number, height: number, mime: string, base64?: string } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: { path: string, width: number, height: number, mime: string, base64?: string } CODE_BLOCK: { path: string, width: number, height: number, mime: string, base64?: string } - Don’t allow UI customization - Lock you into native toolbar - Or don’t give proper base64 support - Background color - Font styling - Layout (horizontal / vertical) - Button order - Icons (local or remote) - Border radius - API uploads - Firebase Storage - Multipart forms - Instant preview - Profile image upload - KYC document capture - E-commerce product listing - Social media post creation - Avatar builders - Custom design tools - Full branding control - No native UI limitations - Proper base64 support