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

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

✨ 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 ? It will become hidden in your post, but will still be visible via the comment's permalink. as well , this person and/or CODE_BLOCK: yarn add react-native-customizable-image-crop-picker 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 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; 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 CODE_BLOCK: circularCrop: true CODE_BLOCK: circularCrop: true CODE_BLOCK: freeStyleCropEnabled: true CODE_BLOCK: freeStyleCropEnabled: true CODE_BLOCK: freeStyleCropEnabled: true CODE_BLOCK: showNativeCropControls: true controlsPlacement: 'bottom' CODE_BLOCK: showNativeCropControls: true controlsPlacement: 'bottom' CODE_BLOCK: showNativeCropControls: true controlsPlacement: 'bottom' CODE_BLOCK: { path: string, width: number, height: number, mime: string, base64?: string } 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