React Native styling: Structure for style organization

React Native projects are flexible in how they can be organized and structured, especially when it comes to style implementations. We find a lot of variation between applications that we work on in how they setup and organize styles. This leads to extra overhead when developing new features for new projects and sometimes projects land on certain patterns that make it cumbersome to iterate on design.

We’ve learned a few simple strategies which have led us to a more enjoyable experience while working on React Native projects. Here are some of these concepts for applying styles to maximize ergonomics and readability. These allow us to develop and iterate on designs more quickly, easily, and consistently.

If you would rather learn by directly viewing some code, we’ve put together a minimal template application to demonstrate these concepts. You can find it here: RNStylingTemplate

React Native Template Screenshot

1: Styles are important: make them easy to find

Keep styles in the root source folder.

Styling is a first class concern, and as such, styles should be accessible from a top-level folder in the application code.

MyReactNativeApp
  - src
    - assets
    - compontents
      - MyComponent.js
    - styles
      - colors.js
      - index.js
      - typography.js
      - ...
  ...

We reference styles in almost every component and making them as accessible as possible will lead to cleaner code.

Another way to think about it is that we want to minimize the use of ../‘s in our relative paths. This not only reduces the amount of mental overhead in counting nested folders, but it allows for easier refactoring and understandability as the project evolves.

import { MyStyles } from '../styles'

is easier to work with than:

import { MyStyles } from '../../../../common/utils/styles/my_styles'

2. Get atomic!

Build complicated styles from simpler styles.

By using object destructuring in style declaration, we get really concise and readable styles which allows us to be declarative in our components.

// buttons.js

export const small = {
  paddingHorizontal: 10,
  paddingVertical: 12,
  width: 75
};

export const rounded = {
  borderRadius: 50
};

export const smallRounded = {
  ...base,
  ...small,
  ...rounded
};
// src/MyComponent/index.js

const styles = StyleSheet.create({
  button: {
    ...Buttons.smallRounded,
  },
})

is easier to understand the intent of and maintain than:

// src/MyComponent/index.js

const styles = StyleSheet.create({
  button: {
    paddingHorizontal: 10,
    paddingVertical: 12,
    width: 75,
    borderRadius: 50
  },
})

3: Styles are important: make them easy to use

Group similar variables in modules and bundle them up in an index.js file.

Style variables are easier to find and understand when they’re organized by function. Therefore, they should live in purposeful files.

If we place an index.js in this folder we can take advantage of JavaScript ES6 import syntax for importing all the styles at once.

- styles
  - colors.js
  - index.js
  - spacing.js
  - typography.js
  - buttons.js
// src/styles/index.js

import * as Buttons from './buttons'
import * as Colors from './colors'
import * as Spacing from './spacing'
import * as Typography from './typography'

export { Typography, Spacing, Colors, Buttons }

This allows us to:

  • import only what we need
  • import from the same file every time
  • give the variables descriptive and short names, contained in a descriptive object.
  • easily extend and modify the common styles
  • write more concise and expressive code.
// src/MyComponent/index.js

import { Typography, Colors, Spacing } from '../styles'

...

const styles = StyleSheet.create({
  container: {
    backgroundColor: Colors.background,
    alignItems: 'center',
    padding: Spacing.base,
  },
  header: {
    flex: 1,
    ...Typography.mainHeader,
  },
  section: {
    flex: 3,
    ...Typography.section,
  }
})

is better than:

// src/MyComponent/index.js

import {
  largePadding,
  smallest,
  small,
  large,
  base,
} from '../../../common/utils/styles/spacing'
import {
  largeRadius,
  baseTextColor,
  headerFontSize,
  smallFontSize,
} from '../../../common/utils/styles/common'
import { background, shuttleGray } from '../../../common/utils/styles/colors'

...

const styles = StyleSheet.create({
  container: {
    backgroundColor: background,
    padding: largePadding,
  },
  header: {
    flex: 1,
    alignItems: 'center',
    backgroundColor: '#b7bdc5',
    flexDirection: 'row',
    justifyContent: 'center',
    borderRadius: largeRadius,
    color: shuttleGray,
    fontSize: headerFontSize,
    paddingBottom: base,
  },
  section: {
    flex: 3,
    alignItems: 'center',
    backgroundColor: background,
    flexDirection: 'row',
    justifyContent: 'center',
    borderRadius: largeRadius,
    color: baseTextColor,
    fontSize: smallFontSize,
    lineHeight: 19,
  }
})

This conciseness and consistency of always pulling in styles from the same place adds up to significant savings in development time over the life of a project.

4. Keep styles close

Keep StyleSheets inline with components

Defining StyleSheets in the same file as your component can help make sure that:

  • styles for one component won’t be overwritten for another component in a future iteration
  • styles will be maintained as the component evolves
  • components can be flexible during design iteration
  • there is a minimum amount of mental overhead while implementing designs for components since there is one place to look for styles rather than many.

One reason you might want to not to use in-line stylesheets is to reduce the amount of duplication in the code, but now that you’re creating variables for global styles in functionally-named files, you can still be mindful of practicing D.R.Y. (Don’t Repeat Yourself) coding, without reusing stylesheets themselves.

// src/MyNewComponent/index.js

import { Typography } from '../styles'

const MyNewComponent = () => (
  <View style={styles.container}>
    <View style={styles.header}>
       <MyComponent />
    </View>
    <View style={styles.body}>
       <MyOtherComponent />
    <View>
  </View>
)

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  header: {
    ...Typography.header
  },
  body: {
    ...Typography.body
  },
})

is more self contained than:

// src/MyNewComponent/index.js

import { styleSheetA, styleSheetB, styleSheetC } from './stylesheets'

const MyNewComponent = () => (
  <View style={styleSheetC.container}>
    <View style={styleSheetA.header}>
       <MyComponent />
    </View>
    <View style={styleSheetB.body}>
       <MyOtherComponent />
    <View>
  </View>
)

The simplicity of only needing to update one file to update one stylistic change to a component is faster and leads to fewer bugs.

Caveats

What we’ve presented here works well as a starting point for us and our clients, but JavaScript and React Native are large and quickly-evolving ecosystems. There is no one-size-fits-all solution for all projects, so your mileage may vary.

Extra large projects with hundreds of components or projects with very specific business needs might benefit from different patterns. Themed components and non-inline stylesheets, for example, are other patterns that may work better for your specific situation.

Everything is a trade-off and here we are optimizing for speed and clarity of design implementation.

Ultimately you’ll need to iterate and find what works for your specific project and team.

Conclusion

  1. Styles are important: make them easy to find: Keep styles in the root application folder
  2. Get atomic!: Build complicated Styles from simpler Styles
  3. Styles are important: make them easy to use: Bundle like styles and expose via an index.js file
  4. Keep Styles close: Keep styles inline with components

All together, an application of styles to a component could look like this:

// src/MyOtherComponent/index.js

import React from 'react'
import { StyleSheet, View } from 'react-native'

import MyComponent from './MyComponent'

import { Colors } from './styles'

const MyOtherComponent = () => (
  <View style={styles.container}>
    <MyComponent />
  </View>
)

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: Colors.background,
  },
})

export default MyOtherComponent

By following the above conventions we can keep styles in our React Native projects cleaner, leaner, and meaner. This allows us to iterate faster, have a more enjoyable development experience, and ultimately build better products.

Again, here’s a simple template app to help demonstrate these practices for implementing styles in a React Native application: RNStylingTemplate

Want a great mobile experience?

thoughtbot uses a flexible, iterative approach in creating great mobile app experiences by incorporating designers and developers into the development process. Learn more about thoughtbot’s holistic approach to React Native projects.