Skip to content

Latest commit

 

History

History
220 lines (181 loc) · 7.2 KB

STYLING.md

File metadata and controls

220 lines (181 loc) · 7.2 KB

React Native Styling Guidelines

Where to Define Styles

Styles can either be theme-related or not. "Theme-related" means that a style contains some sort of color attributes (backgroundColor, color, borderColor). "Non-theme-related" styles may not contain no color attributes.

All non-theme-related styles must be defined in the /styles directory and styles.js contains the final export after gathering all appropriate styles. Unlike some React Native applications, we are not using StyleSheet.create() and instead store styles as plain JS objects. There are also many helper styles available for direct use in components.

All styles that contain theme colors have to be defined in the ThemeStylesProvider component, as those need to be dynamically created and animated.

These helper styles are loosely based on the Bootstrap system of CSS utility helper classes and are typically incremented by units of 4.

Note: Not all helpers from Bootstrap exist, so it may be necessary to create the helper style we need.

When to Create a New Style

If we need some minimal set of styling rules applied to a single-use component, then it's almost always better to use an array of helper styles rather than create an entirely new style if it will only be used once. Resist the urge to create a new style for every new element added to a screen. There is a very good chance the style we are adding is a "single-use" style.

// Bad - Since we only use this style once in this component
const TextWithPadding = props => (
    <Text style={styles.textWithPadding}>
        {props.children}
    </Text>
);

// Good
const TextWithPadding = props => (
    <Text
        style={[
            styles.p5,
            styles.noWrap,
        ]}
    >
        {props.children}
    </Text>
);

On the other hand, if we are copying and pasting some chunks of JSX from one place to another, then that might be a sign that we need a new reusable style.

Use the "Rule of Three"

In order to resist the urge to preoptimize and have many single-use components, we've adopted a main principle:

Any array of styles associated with a single type of React element that has at least 3 identical usages should be refactored into:

  • A new resusable style that can be used in many places e.g. styles.button
  • If that style has modifiers or style variations, then those styles should follow a naming convention of styles.elementModifer e.g. styles.buttonSuccess
  • If a reusable style has 3 or more modifiers, it should be refactored into a component with props to modify the styles e.g.
<Button title="Submit" success large />

Inline Styles

Inline styles are forbidden. If we run into a case where we feel it's necessary to conditionally render some styles, we should create a helper function then pass any modifying parameters to that function. Small helper functions can be written directly in styles.js, but larger, more complex methods should be put in their own modules and imported into styles.js.

// Bad - Do not use inline styles
const TextWithPadding = props => (
    <Text style={{
        padding: 10,
        whiteSpace: props.shouldWrap ? 'wrap' : 'nowrap',
    }}>
        {props.children}
    </Text>
);

// Good
const TextWithPadding = props => (
    <Text
        style={[
            styles.p5,
            getTextWrapStyle(props.shouldWrap)
        ]}
    >
        {props.children}
    </Text>
);

How to Reuse Styles

There are many styles in the styles.js file. It is generally a bad practice to grab a style meant for a specific use case and utilize it for some other more general use case without changing its name to make it more general. If we think we see a style that might be appropriate for reuse, but does not have a generic name, then we should rename it instead of using it directly.

// Bad - Reuses style without generalizing style name
const SettingsScreen = props => (
    <View>
        <Text style={[styles.settingsScreenText]}>
            Expensify
        </Text>
    </View>
);

const SomeOtherScreen = props => (
    <View>
        <Text style={[styles.settingsScreenText]}>
            New Expensify
        </Text>
    </View>
);

// Good
const SettingsScreen = props => (
    <View>
        <Text style={[styles.defaultScreenText]}>
            Expensify
        </Text>
    </View>
);

const SomeOtherScreen = props => (
    <View>
        <Text style={[styles.defaultScreenText]}>
            New Expensify
        </Text>
    </View>
);

When and How to Pass Styles via Props

In some cases, we may want a more complex component to allow a parent to modify a style of one of its child elements. In other cases, we may have a very simple component that has one child which has a style prop. Let's look at how to handle these two examples.

Complex Component

Always pass style props with a name that describes which child element styles will be modified. All style props should accept an Array of style Object and have a pluralized name e.g. headerStyles

// Bad - props.style should not be used in complex components
const SettingsScreen = props => (
    <View>
        <Header
            style={[
                styles.defaultHeader,
                props.style,
            ]}
        />
        <Body style={props.bodyStyles} />
        ...
    </View>
);

// Bad - style with a flexible type requires extra handling
const SettingsScreen = props => {
    const extraHeaderStyles = _.isArray(props.headerStyle)
        ? props.headerStyle
        : [props.headerStyle];
    return (
        <View>
            <Header
                style={[
                    styles.defaultHeader,
                    ...extraHeaderStyles,
                ]}
            />
            <Body style={[props.bodyStyle]} />
            ...
        </View>
    );
}

// Bad - Uses a singular and passes a single style object
const SettingsScreen = props => (
    <View>
        <Header
            style={[
                styles.defaultHeader,
                props.headerStyle,
            ]}
        />
        ...
    </View>
);

// Good - Uses a plural and passes an array of style objects with spread syntax
const SettingsScreen = props => (
    <View>
        <Header
            style={[
                styles.defaultHeader,
                ...props.headerStyles,
            ]}
        />
        ...
    </View>
);

Simple Component

The only time we should allow a component to have a style prop with PropTypes.any is when we are wrapping a single child that has a flexible style type that accepts both Array or Object types.

// Good
const CustomText = props => (
    <Text style={props.style}>{props.children}</Text>
);

// Good
const CustomText = props => {
        const propsStyle = _.isArray(props.style)
            ? props.style
            : [props.style];
}(
    <Text
        style={[
            styles.defaultCustomText,
            ...propsStyle,
        ]}
    >
        {props.children}
    </Text>
);

In that last example, there is just one simple element and no ambiguity about what props.style refers to. The component is used in many places and has some default styles, therefore we must add custom style handling behavior.