typescriptreact-nativeexpotailwind-cssnativewind

How to style child elements with nativewind


I'm trying to set the color of the text of all elements in a View to a specific color, this can be done with practicaly any class pretty easily in tailwind by simply giving the class you want to the parent element, like this:

import { Text, View } from 'react-native';

export default function MyComponent() {
  return (
    <View className='flex-1 items-center justify-center text-orange-600'>
      <Text className='font-bold text-4xl'>Some Text</Text>
    </View>
  );
}

This would make the text whithin the Text element orange and would do the same for any other element I add inside the Parent element. The problem is that it is not working with nativewind and thus I have to manually set the color on every Text element individually:

export default function MyComponent() {
  return (
    <View className='flex-1 items-center justify-center'>
      <Text className='font-bold text-4xl text-orange-600'>Some Text</Text>
      <Text className='font-bold text-4xl text-orange-600'>Some Other Text</Text>
    </View>
  );
}

I've seen this question How to access all the direct children of a div in tailwindcss? and the proposed solutions didn't work for me.

If anyone could tell me of a way to not have to manually set the class of each child element I would apreciate it.

Thanks!


Solution

  • TL;DR:

    CSS and RN behaves completely differently. To cascade styles using Nativewind use the styled higher-order component:

    import { styled } from 'nativewind';
    import { Text } from 'react-native';
    
    export const MyOrangeText = styled(Text, 'font-bold text-4xl text-orange-600');
    
    

    or define a custom component yourself:

    const MyOrangeText = ({children}) => {
      return <Text className='font-bold text-4xl text-orange-600'>{children}</Text>
    }
    

    To answer the question:

    This is a CSS behavior vs React Native behavior problem. React Native's styling only mimics CSS by having similar property names and trying to match as many properties as possible, but it does not have the same kind of property inheritance or "cascading" as CSS.

    1. CSS Behavior

    There are two kinds of properties in CSS:

    An inherited property get the value of its parent's property if not set. color is an inherited property, so if you set this property on the parent div but not set it on the div's children, all the children will get the same color.

    non-inherited properties are not inherited and they get a default value if not set (e.g. border will not be inherited if the parent has a border)

    That is the reason why in Tailwind CSS the inheritance works by default, because Tailwind is just CSS utility classes and has this same behavior.

    2. React Native Behavior

    In RN there is no inheritance of style properties from parents to children. In RN the style prop of every component is a plain old JS object. You can also pass an array of styles - the last style in the array has precedence, so you can also use this to merge styles.

    The kind of "cascading" you want to achieve would look like the following in RN:

    1. With exporting-importing style objects

    // somewhere like ./src/styles/text.js
    export const { orangeText, blueText } = StyleSheet.create({
      orangeText: {
        color: 'orange',
      },
      blueText: {
        color: 'blue',
      }
    )};
    
    // somewhere in a component
    import { orangeText } from '../styles/text.js'
    
    const MyComponent = () => {
      return (
        <View>
          <Text style={orangeText}>Orange</Text>
          <Text style={orangeText}>Also orange</Text>
        </View>
      );
    }
    

    2. Or creating custom component and passing down a style prop

    // The styles object created with StyleSheet.create
    const TextWrapper = () => {
      return (
        <View>
          <MyCustomText textStyle={styles.orangeText}>Orange</MyCustomText>
          <MyCustomText textStyle={styles.orangeText}>Also Orange</MyCustomText>
        </View>
      )
    }
    
    // Of course you could just use the build-in TextProps
    // but I wanted to show the type of the style property
    interface MyCustomTextProps {
      textStyle?: StyleProp<TextStyle>;
      children?: ReactNode;
    }
    
    const MyCustomText = ({ textStyle, children }: MyCustomTextProps): ReactElement => {
      return (
        <Text style={textStyle}>
          {children}
        </Text>
      );
    };
    

    3. How nativewind works

    nativewind uses the className extension on RN components to parse Tailwind CSS utility classes and translates them to RN style objects.

    In essence this:

    <Text className='font-bold text-4xl text-black'>Orange</Text>
    

    Gets translated to something like this:

    <Text style={[gen.fontBold, gen.text4xl, gen.textBlack]}>Orange</Text>;
    
    const gen = StyleSheet.create({
      fontBold: {
        fontWeight: '700',
      }
      text4xl: {
        fontSize: 36,
        lineHeight: 40,
      },
      textBlack: {
        color: 'rgba(0, 0, 0, 1)',
      }
    });
    

    Of course this is not exactly how it will look like. nativewind also does optimizations, flattening of the style objects, the names will be different etc. But it will generate style objects and applies them to the style prop.

    So if you set the color property on the parent View, it will be simply ignored as View does not have a color property.