In React Native, how do you apply a conditional style such as a disabled coupon card, and which style wins when entries conflict?
answer
- the style prop takes arrays
- falsy entries are skipped
- last entry wins, key by key
- nested values replaced, not merged
- no selectors, no cascade
basics
~20 sReact Native's style prop accepts an array, so a disabled card uses [styles.card, disabled && styles.cardDisabled]. Falsy entries are skipped, and for each property the last entry that sets it wins; there are no selectors or cascade.
solid answer
~40 sEvery core component's `style` prop takes an object **or an array** of style objects, nested arrays included. For a coupon card that can be expired, write `style={[styles.card, isDisabled && styles.cardDisabled]}`: when `isDisabled` is false the entry is `false` and is skipped. React Native resolves the array **key by key, left to right**, so the last entry that sets `opacity` or `borderColor` wins - the docs put it as "the last style in the array has precedence". The merge is shallow: a later `transform` or `shadowOffset` replaces the earlier value whole. There are **no selectors** - no `.card.disabled`, no `:disabled` - and no cascade from a `View` to its children, so each child that changes when disabled gets its own conditional entry. Put a caller's `style` prop last so it can override the defaults.
code
tsx · 23 linesimport { StyleSheet, Text, View } from 'react-native';
import type { StyleProp, ViewStyle } from 'react-native';
type Props = {
title: string;
isDisabled: boolean;
style?: StyleProp<ViewStyle>;
};
export function CouponCard({ title, isDisabled, style }: Props) {
return (
<View style={[styles.card, isDisabled && styles.cardDisabled, style]}>
<Text style={[styles.title, isDisabled && styles.titleDisabled]}>{title}</Text>
</View>
);
}
const styles = StyleSheet.create({
card: { padding: 16, borderRadius: 12, borderWidth: 1, borderColor: '#2f6f4f', opacity: 1 },
cardDisabled: { borderColor: '#9a9a9a', opacity: 0.5 },
title: { fontSize: 18, fontWeight: '600', color: '#1d3b2a' },
titleDisabled: { color: '#6b6b6b', textDecorationLine: 'line-through' },
});go deeper
Know that style takes an array, that false entries are skipped, and that the last entry setting a property wins.
Explain the key-by-key, shallow resolution, why unset is not reset, and why numbers break the falsy-entry pattern in TypeScript.
Design variant styling without selectors: one variant flag driving every affected element, a documented override order, and callers' styles last.
Decide how variants are expressed across a component library, as props mapped to style entries rather than ad hoc overrides, so screens cannot drift from the design.
## Styles are values, not rules On the web, CSS rules live in stylesheets and reach elements through selectors and the cascade. React Native has neither. A **style** is a plain JavaScript object of camel-cased properties, passed straight to one component through its `style` prop. The only way a style reaches an element is that you put it on that element. The `style` prop accepts: - a single style object; - an **array** of style objects, which may itself contain arrays; - **falsy entries** - `false`, `null`, `undefined` (and `''` in the TypeScript type) - which are skipped. ## Conditional styles in a coupon wallet A coupon wallet renders each coupon as a card; expired or used coupons are shown disabled. With `StyleSheet.create` declaring `card`, `cardDisabled`, `title` and `titleDisabled`: 1. The container gets `style={[styles.card, isDisabled && styles.cardDisabled]}`. 2. The title gets `style={[styles.title, isDisabled && styles.titleDisabled]}`. 3. A caller-supplied `style` prop goes **last**: `[styles.card, isDisabled && styles.cardDisabled, style]`. When `isDisabled` is false, `isDisabled && styles.cardDisabled` evaluates to `false`, which the style system skips. A ternary - `isDisabled ? styles.cardDisabled : styles.cardActive` - is the alternative when both branches carry styles. ## Which entry wins React Native resolves the array into one set of properties by walking it **left to right** and assigning each property in turn, so for any given property the **last entry that sets it wins**. That is exactly what `StyleSheet.flatten` does, and the docs state it plainly: the last style in the array has precedence. | Array | Resolved `opacity` | Resolved `borderColor` | |---|---|---| | `[{ opacity: 1, borderColor: 'gray' }, { opacity: 0.5 }]` | `0.5` | `'gray'` | | `[{ opacity: 0.5 }, { opacity: 1, borderColor: 'gray' }]` | `1` | `'gray'` | | `[{ opacity: 1 }, false, null]` | `1` | not set | Three consequences are easy to miss: - **Order is the only priority.** There is no specificity and no `!important`. A disabled style placed before the base style loses. - **The merge is shallow.** Each property is replaced as a whole. A later `transform: [{ scale: 0.98 }]` does not add to an earlier `transform: [{ rotate: '-2deg' }]`; it replaces it. The same holds for `shadowOffset` and other object-valued properties. - **Unset is not reset.** A later entry cannot remove a property by omitting it; to undo an earlier `borderWidth: 1`, the later entry must set `borderWidth: 0`. ## No selectors, no cascade Because there are no selectors, the disabled state cannot be expressed once on the card and picked up by descendants. Every element that looks different when disabled - title, discount badge, expiry line - needs its own conditional entry, driven by the same `isDisabled` value passed down as a prop. A style set on a `View` does not reach its children at all. Text inheriting styles from an enclosing `Text` is a separate mechanism with its own rules. ## Nested arrays Arrays can nest to any depth, and resolution recurses into them in order. That is what lets a component forward whatever its caller passed - an object, an array or `undefined` - as a single entry: `[styles.card, isDisabled && styles.cardDisabled, style]` works even when `style` is itself `[styles.wide, isFeatured && styles.featured]`. The effective order is simply the order you would read the flattened list in, left to right. ## Numbers are not falsy enough A common slip is `count && styles.badgeActive` where `count` is a number. When `count` is `0`, the entry is the number `0`. `StyleSheet.flatten` ignores anything that is not an object, but React Native's `StyleProp` TypeScript type allows only `false`, `null`, `undefined` and `''` as falsy entries, so the expression is a type error. Write `count > 0 && styles.badgeActive`. ## Where the array is built Building the array inline in render is idiomatic and cheap for most components. When a memoized child should skip re-rendering, `StyleSheet.compose` returns one of its inputs unchanged when the other is `null` or `undefined`, preserving the reference; that and deeper render-cost questions belong to performance work rather than to how precedence works.
- Why can't a later style entry remove a border that an earlier entry set?Because resolution only assigns properties; it never deletes them. Walking the array left to right, an entry that does not mention `borderWidth` leaves the earlier value in place. To undo it, the later entry must set the property explicitly, for example `borderWidth: 0`, which then wins because it comes last.
- If a title inside the disabled card should turn grey, why isn't setting color on the card's View enough?Because React Native has no cascade from a `View` to its children. A style applies to the component that receives it, and `color` is not even a `View` style. The title's `Text` needs its own conditional entry, such as `[styles.title, isDisabled && styles.titleDisabled]`, driven by the same `isDisabled` value.
saying these in an interview costs you the question
- The first style in a React Native style array has precedence.
- A later style entry deep-merges object values like transform.
- A disabled style set on the card View cascades to its Text children.
- React Native supports a :disabled-style selector in StyleSheet.create.
- count && styles.badge is safe in a style array when count is a number.