In this lesson, you will learn how to create highly modular UI components by using style composition to merge base layouts with flexible utility overrides.
The Problem: Rigid Components
When building UI, you often need the same button or card to look slightly different depending on where it’s used. A common beginner mistake is creating PrimaryButton, SecondaryButton, and SmallButton components, which leads to massive code duplication. Instead, we want a single Button component that accepts "style overrides."
The Pattern: Base + Utility
Style composition allows your component to define a "base" look while remaining open to modification. By utilizing the style prop’s ability to accept an array, React Native will merge multiple style objects, with the last item in the array taking precedence.
Here is how you implement it:
const Button = ({ style, children }) => (
<TouchableOpacity style={[styles.baseButton, style]}>
<Text>{children}</Text>
</TouchableOpacity>
);
const styles = StyleSheet.create({
baseButton: {
padding: 16,
borderRadius: 8,
backgroundColor: 'blue',
}
});
Applying the Pattern
Now, your component is infinitely reusable. If you need a specific version of this button for a screen, you don’t need a new component definition; you simply pass an override object:
// A "utility" override for a specific context
<Button style={{ backgroundColor: 'red', width: 200 }}>
Delete Item
</Button>
By passing { backgroundColor: 'red' } into the style array, React Native effectively performs a "shallow merge." The baseButton properties (padding, borderRadius) are preserved, but the backgroundColor is overridden by your utility style. This keeps your codebase clean, reduces the number of components you have to maintain, and ensures that your core design system remains consistent across your entire application.
Key Takeaway: By passing an array of styles [baseStyle, styleOverride] to your components, you create a flexible design system that combines shared structure with context-specific modifications.