react-native-inner-shadow gives your React Native apps beautiful inset shadows and highlight effects using React Native Skia. Create depth in your UI with both solid and gradient backgrounds, plus interactive shadows that respond to touches using Reanimated.
- Performance boost: Optimized rendering for smoother animations and less resource usage
- Reliable layouts: Fixed size calculations for consistent component dimensions
- Better border radius: Individual corner customization with proper shadow rendering
More details
- Added padding to prevent shadow clipping at edges
- Created
useShadowProperties
hook for cleaner, more consistent shadow handling - Fixed z-index layering for proper component stacking
- Removed unnecessary wrapper elements for better performance
- Improved shadow rendering across all components
- Enhanced gradient handling for smoother color transitions
- react-native-inner-shadow
# Using npm
npm install react-native-inner-shadow @shopify/react-native-skia@next react-native-reanimated
# Using Yarn
yarn add react-native-inner-shadow @shopify/react-native-skia@next react-native-reanimated
# Using Expo
npx expo install react-native-inner-shadow @shopify/react-native-skia@next react-native-reanimated
Add Reanimated to your Babel config:
// babel.config.js
module.exports = {
presets: [
// Your existing presets
],
plugins: [
// Your existing plugins
'react-native-reanimated/plugin',
],
};
For iOS, install pods:
cd ios && pod install && cd ..
- Inset shadows: Create depth effects not possible with React Native's standard shadows
- Reflected light: Add subtle highlights for a more realistic 3D appearance
- Linear gradients: Combine shadows with beautiful gradient backgrounds
- Interactive components:
- Pressable buttons with tactile shadow animations
- Toggle switches with state-dependent shadow effects
- Custom styling:
- Per-corner border radius control
- Precise control over shadow properties
- Animated transitions
- Performance optimized:
- Smart layout management
- Minimal re-renders
- Efficient canvas usage
The foundation component for creating shadows with solid backgrounds:
import React from 'react';
import { View, Text } from 'react-native';
import { ShadowView } from 'react-native-inner-shadow';
export default function Example() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ShadowView
inset
backgroundColor="#f0f0f0"
shadowColor="#00000066"
shadowOffset={{ width: 3, height: 3 }}
shadowBlur={5}
style={{
width: 150,
height: 100,
borderRadius: 12,
justifyContent: 'center',
alignItems: 'center',
}}
>
<Text>Inset Shadow</Text>
</ShadowView>
</View>
);
}
For gradient backgrounds with shadows:
import React from 'react';
import { View, Text } from 'react-native';
import { LinearShadowView } from 'react-native-inner-shadow';
export default function GradientExample() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<LinearShadowView
inset
from="top"
to="bottom"
colors={['#FF7A7A', '#FFE08C']}
shadowOffset={{ width: 4, height: 4 }}
shadowBlur={8}
style={{
width: 150,
height: 100,
borderRadius: 16,
justifyContent: 'center',
alignItems: 'center',
}}
>
<Text style={{ color: 'white' }}>Gradient Shadow</Text>
</LinearShadowView>
</View>
);
}
Create buttons with satisfying press animations:
import React from 'react';
import { View, Text } from 'react-native';
import { ShadowPressable } from 'react-native-inner-shadow';
export default function PressableExample() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ShadowPressable
shadowBlur={6}
duration={150}
damping={0.8}
style={{
width: 180,
height: 60,
backgroundColor: '#0081a7',
borderRadius: 12,
justifyContent: 'center',
alignItems: 'center',
}}
onPress={() => console.log('Pressed!')}
>
<Text style={{ color: 'white', fontWeight: 'bold' }}>Press Me</Text>
</ShadowPressable>
</View>
);
}
Toggle components with state-dependent shadows:
import React, { useState } from 'react';
import { View, Text } from 'react-native';
import { ShadowToggle } from 'react-native-inner-shadow';
export default function ToggleExample() {
const [isActive, setIsActive] = useState(false);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ShadowToggle
isActive={isActive}
activeColor="#E9C46A"
style={{
width: 120,
height: 60,
backgroundColor: '#fefae0',
borderRadius: 12,
justifyContent: 'center',
alignItems: 'center',
}}
onPress={() => setIsActive((prev) => !prev)}
>
<Text
style={{
color: isActive ? '#515050' : '#888',
fontWeight: 'bold',
}}
>
{isActive ? 'ON' : 'OFF'}
</Text>
</ShadowToggle>
</View>
);
}
The library provides powerful hooks for advanced customization:
Centralizes shadow configuration for consistent behavior:
import { useShadowProperties } from 'react-native-inner-shadow';
// Inside your component:
const { flatStyle, bgColor, shadowProps, layout, canRenderCanvas, onLayout } =
useShadowProperties({
propWidth,
propHeight,
style,
inset: true,
shadowOffset: { width: 3, height: 3 },
shadowBlur: 5,
propsOnLayout: customOnLayoutHandler,
});
Controls pressable animations with fine-grained control:
import { useAnimatedOffset } from 'react-native-inner-shadow';
// Inside your component:
const {
onPressIn,
onPressOut,
depth,
offset,
reflectedLightOffset,
inset,
blurRadius,
PressedAnimatedStyle,
} = useAnimatedOffset({
offset: shadowProps.shadowOffset,
reflectedLightOffset: shadowProps.reflectedLightOffset,
blurRadius: shadowProps.shadowBlur,
damping: 0.8,
duration: 150,
onPressIn: customPressInHandler,
onPressOut: customPressOutHandler,
});
Customize each corner individually:
<ShadowView
style={{
borderTopLeftRadius: 30,
borderTopRightRadius: 10,
borderBottomRightRadius: 30,
borderBottomLeftRadius: 10,
// Other styles
}}
// Other props
>
<Text>Custom Corners</Text>
</ShadowView>
For best performance:
- Set fixed dimensions whenever possible
- Memoize components using React.memo() to prevent unnecessary re-renders
- Use stable keys when rendering in lists
- Cache styles instead of generating them on each render
import React, { memo, useMemo } from 'react';
import { ShadowView } from 'react-native-inner-shadow';
const OptimizedShadowItem = memo(({ title, color }) => {
const styles = useMemo(
() => ({
container: {
width: 150,
height: 100,
borderRadius: 12,
justifyContent: 'center',
alignItems: 'center',
},
}),
[]
);
return (
<ShadowView backgroundColor={color} inset style={styles.container}>
<Text>{title}</Text>
</ShadowView>
);
});
The library provides default values in src/constants.ts
:
Constant | Value | Description |
---|---|---|
CANVAS_PADDING | 50 | Space to prevent shadow clipping |
BACKGROUND_COLOR | '#FFFFFF' | Default background color |
SHADOW_OFFSET_SCALE | 2.5 | Default shadow offset scale |
REFLECTED_LIGHT_OFFSET_SCALE | 2 | Default reflection offset scale |
SHADOW_BLUR | 2 | Default shadow blur radius |
REFLECTED_LIGHT_BLUR | 3 | Default reflection blur radius |
SHADOW_COLOR | '#2F2F2FBC' | Default shadow color |
REFLECTED_LIGHT_COLOR | '#FFFFFF4D' | Default reflection color |
DAMPING_DURATION | 150 | Animation duration (ms) |
DAMPING_RATIO | 0.8 | Animation damping ratio |
ShadowView Props
Prop | Type | Default | Description |
---|---|---|---|
inset | boolean | false | Makes shadow appear inside the component |
backgroundColor | string | '#FFFFFF' | Background color |
shadowColor | string | '#2F2F2FBC' | Shadow color |
shadowOffset | { width: number, height: number } | { width: 2.5, height: 2.5 } | Shadow position |
shadowBlur | number | 2 | Shadow blur radius |
reflectedLightColor | string | '#FFFFFF4D' | Highlight color |
reflectedLightOffset | { width: number, height: number } | Auto-calculated | Highlight position |
reflectedLightBlur | number | 3 | Highlight blur radius |
isReflectedLightEnabled | boolean | true | Whether to show highlights |
style | ViewStyle | - | React Native style object |
children | ReactNode | - | Component children |
LinearShadowView Props (extends ShadowView Props)
Prop | Type | Default | Description |
---|---|---|---|
from | 'top' | 'bottom' | 'left' | 'right' | 'top' | Gradient start direction |
to | 'top' | 'bottom' | 'left' | 'right' | 'bottom' | Gradient end direction |
colors | Color[] | - | Array of gradient colors |
ShadowPressable Props
Prop | Type | Default | Description |
---|---|---|---|
duration | number | 150 | Animation duration (ms) |
damping | number | 0.8 | How deeply shadows indent on press |
isReflectedLightEnabled | boolean | true | Whether to show highlights |
...ShadowView Props | - | - | All ShadowView props are supported |
...PressableProps | - | - | All React Native Pressable props |
ShadowToggle Props
Prop | Type | Default | Description |
---|---|---|---|
isActive | boolean | false | Current toggle state |
activeColor | string | - | Background color when active |
...ShadowPressable Props | - | - | All ShadowPressable props |
-
Shadows Not Showing
- Make sure width and height are defined (either in style or as props)
- Check border radius values are reasonable for your component size
- Verify shadow colors have opacity (e.g., '#00000066' not '#000000')
-
Dependency Errors
- Ensure all three dependencies are properly installed
- Check your babel.config.js includes 'react-native-reanimated/plugin'
- For iOS, run pod install after installation
- For Expo, make sure you're using compatible versions of all packages
-
Performance Problems
- Specify fixed dimensions when possible
- Use React.memo() for components in lists
- Check if you're creating new styles on each render
- For scrolling lists, consider virtualizing your list
-
Gradient Not Working
- Verify your colors array has at least 2 colors
- Check from/to directions are valid ('top', 'bottom', 'left', 'right')
Contributions welcome! Check out our Contributing Guide to get started.
This project is ISC licensed.
Built by ShinMini with β€οΈ