Building an Interactive Resizable Split View in React Native
In modern mobile UI, responsive layouts make an app feel alive. Static screens can feel rigid, but interactive layouts that adapt to user input create a sense of control and polish. This guide explains how to build an interactive, resizable split-screen view in React Native.
The component will have a draggable divider so users can resize two panes. As the panes resize, content inside adapts smoothly. Small, purposeful animations provide feedback. React Native Reanimated handles smooth animations and React Native Gesture Handler manages precise gesture control.
Why Micro-animations Matter
Micro-animations are small, functional animations that serve a purpose:
Provide Feedback – confirm a user’s action instantly.
Guide the User – direct attention and maintain spatial awareness.
Add Polish – make the UI feel crafted and deliberate.
They only shine when paired with a consistent, well-designed UI. Without a polished foundation, even the best animation will not elevate the experience.
Setting Up the Layout and State
The screen is split into a top section and a bottom section. The height of the top section changes dynamically as the divider is dragged, powered by a sharedValue
from Reanimated. Shared values update on the UI thread without triggering re-renders.

const { height: SCREEN_HEIGHT } = Dimensions.get('window');
const HEADER_HEIGHT = 60;
const MIN_SECTION_HEIGHT = 100;
const MAX_TOP_SECTION_HEIGHT = SCREEN_HEIGHT * 0.7;
const DEFAULT_TOP_SECTION_HEIGHT = SCREEN_HEIGHT * 0.45;
export default function ResizableSplitView() {
const topSectionHeight = useSharedValue(DEFAULT_TOP_SECTION_HEIGHT);
const topSectionAnimatedStyle = useAnimatedStyle(() => ({
height: topSectionHeight.value,
}));
const bottomSectionAnimatedStyle = useAnimatedStyle(() => ({
height: SCREEN_HEIGHT - HEADER_HEIGHT - topSectionHeight.value - 100,
}));
return (
<View style={styles.mainContainer}>
<Animated.View style={[styles.topSection, topSectionAnimatedStyle]} />
{}
<Animated.View style={[styles.bottomSection, bottomSectionAnimatedStyle]} />
</View>
);
}
Do: use constants for layout values like HEADER_HEIGHT
and MIN_SECTION_HEIGHT
.
Do not: scatter magic numbers directly in styles.
Making it Draggable with Gesture Handler
Gesture.Pan()
tracks drag movements:
onStart – record starting height.
onUpdate – update height while clamping between min and max.
onEnd – snap to the closest preset height (min, default, max).

const ANIMATION_CONFIG = { damping: 25, stiffness: 300, mass: 0.8 };
const VELOCITY_THRESHOLD = 800;
const startY = useSharedValue(0);
const isDragging = useSharedValue(false);
const panGesture = Gesture.Pan()
.onStart(() => {
startY.value = topSectionHeight.value;
isDragging.value = true;
})
.onUpdate((event) => {
const newHeight = startY.value + event.translationY;
topSectionHeight.value = Math.max(
MIN_SECTION_HEIGHT,
Math.min(newHeight, MAX_TOP_SECTION_HEIGHT)
);
})
.onEnd((event) => {
isDragging.value = false;
const velocity = event.velocityY;
let targetHeight;
if (Math.abs(velocity) > VELOCITY_THRESHOLD) {
targetHeight = velocity > 0 ? DEFAULT_TOP_SECTION_HEIGHT : MAX_TOP_SECTION_HEIGHT;
} else {
const distances = [
{ h: MIN_SECTION_HEIGHT, d: Math.abs(topSectionHeight.value - MIN_SECTION_HEIGHT) },
{ h: DEFAULT_TOP_SECTION_HEIGHT, d: Math.abs(topSectionHeight.value - DEFAULT_TOP_SECTION_HEIGHT) },
{ h: MAX_TOP_SECTION_HEIGHT, d: Math.abs(topSectionHeight.value - MAX_TOP_SECTION_HEIGHT) },
];
targetHeight = distances.sort((a, b) => a.d - b.d)[0].h;
}
topSectionHeight.value = withSpring(targetHeight, ANIMATION_CONFIG);
});
Subtle handle scaling during drag:

const dragIndicatorAnimatedStyle = useAnimatedStyle(() => ({
transform: [{
scale: withSpring(isDragging.value ? 1.1 : 1, ANIMATION_CONFIG),
}],
}));
Animating Content with interpolate
The top section shows different content based on size:
Compact View – small height, show a summary.
Expanded View – large height, show detailed content.

const compactCardsAnimatedStyle = useAnimatedStyle(() => {
const opacity = interpolate(
topSectionHeight.value,
[MIN_SECTION_HEIGHT, MIN_SECTION_HEIGHT + 100],
[1, 0],
'clamp'
);
return { opacity: withSpring(opacity) };
});
const expandedCardsAnimatedStyle = useAnimatedStyle(() => {
const opacity = interpolate(
topSectionHeight.value,
[MIN_SECTION_HEIGHT + 50, MIN_SECTION_HEIGHT + 150],
[0, 1],
'clamp'
);
return { opacity: withSpring(opacity) };
});
Both styles transition smoothly between states using the same driver value (topSectionHeight
).
Conclusion
A resizable split view with smooth, responsive animations can make a UI feel dynamic and engaging. Shared values improve performance, snap points create predictable behavior, and micro-animations enhance feedback. Polishing the core UI before adding animation ensures design and motion work together for an interface that feels alive.
Learn more and get full code -> landingcomponents