skip to content

StyleSheet Objects

StyleSheet.create declares styles as objects composed through arrays where the last entry wins, with no selectors or cascade. Interviewers ask how conditional styles compose and what flatten returns.

part ofReact Nativeoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In React Native, how do you apply a conditional style such as a disabled coupon card, and which style wins when entries conflict?

level: juniorimportance: must knowfreq 68%

answer

  1. the style prop takes arrays
  2. falsy entries are skipped
  3. last entry wins, key by key
  4. nested values replaced, not merged
  5. no selectors, no cascade

basics

~20 s

React 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 s

Every 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 lines
tsx
import { 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

for a junior

Know that style takes an array, that false entries are skipped, and that the last entry setting a property wins.

for a middle

Explain the key-by-key, shallow resolution, why unset is not reset, and why numbers break the falsy-entry pattern in TypeScript.

for a senior

Design variant styling without selectors: one variant flag driving every affected element, a documented override order, and callers' styles last.

for a principal

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.
open as a page

What does StyleSheet.create do in React Native 0.87, and how does it differ from passing an inline style object?

level: juniorimportance: must knowfreq 64%

basics

~20 s

In React Native 0.87, StyleSheet.create returns the object it is given, freezing each style in development; its practical benefit is TypeScript checking. An inline style object works the same but is recreated on every render.

open as a page

In React Native, what do StyleSheet.flatten and StyleSheet.compose return, and when would you use each instead of a plain style array?

level: middleimportance: should knowfreq 34%

basics

~20 s

StyleSheet.flatten resolves a style or nested array into one object where later entries win, returning a lone object unchanged. StyleSheet.compose(a, b) returns [a, b], or just the other style when one is null or undefined.

open as a page

You are building a reusable React Native CouponCard with a disabled variant; how should it type, order and merge a caller's style prop, given there are no selectors?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Type the prop as StyleProp<ViewStyle> from react-native, merge it as the last array entry after the base and disabled styles, expose extra props for inner elements since selectors do not exist, and never spread a style prop into an object.

open as a page

In React Native 0.87, what do StyleSheet.absoluteFill and StyleSheet.hairlineWidth provide, and what replaced StyleSheet.absoluteFillObject?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

StyleSheet.absoluteFill is a ready-made style that pins a view to all four edges of its parent; absoluteFillObject was removed in 0.85 in its favour. StyleSheet.hairlineWidth is the platform's thin-line width, rounded to whole physical pixels.

open as a page