Contextual Typography Styling in React Native

Lewis Barnes
4 min readFeb 11, 2019

--

Although I’ve been using React Native for quite some time, I’ve never really been happy with approaches I’ve seen when it came to theming Text components. I wanted a solution that felt more like CSS. While React Native tries to emulate styling like the Web through a library called Yoga, it doesn’t provide cascading functionality.

All styling in React Native is effectively inline and therefore shares similar problems the Web ecosystem has churned through ever since the CSS in JS movement:

  • Media queries
  • Global styles (fonts, default styles)
  • Pseudo styles

The Problem

I want the ability to apply a base style to all Text components within the application. This includes styles such as:

  • Font family
  • Font size
  • Font style
  • Colour

I would also like to override styles, e.g. parts of the application that might want a different font size or to be italics.

Solution One: Style Module

A simple approach would be to declare the base styling as a module and import it when using Text components:

An Expo Snack demo can be found here: https://snack.expo.io/@lewie9021/contextual-typography-styling-in-react-native---style-module.
Screenshot of solution one.

Although simple, there are couple of problems with this approach:

  • Views become littered with style props.
  • Easily prone to cases where style props are missed.

Solution Two: Custom Component

As detailed in the official documentation, another approach is to encapsulate the base styling in a custom Text component with the ability to override specific styles:

An Expo Snack demo can be found here: https://snack.expo.io/@lewie9021/contextual-typography-styling-in-react-native---custom-component.
Screenshot of solution two.

The documentation also explains that Text components have a concept of style inheritance. However, nesting MyAppText results in cases where some styles are ignored due to the base style overwriting:

// Nesting with MyAppText overwrites fontStyle with textStyle.fontStyle.
<MyAppText style={{fontStyle: "italic"}}>
<MyAppText>Hello World</MyAppText>
</MyAppText>
// Nesting with Text allows style inheritance to work as expected.
<MyAppText style={{fontStyle: "italic"}}>
<Text>Hello World</Text>
</MyAppText>

Overall, there are a couple of problems with this approach:

  • The composition issue described above makes it confusing to require parts of the application to use both Text and MyAppText components to have the desired effect.
  • Style inheritance only works with Text-only component trees.

Solution Three: Context API

Note: To keep this article reasonably short, I’d recommend reading the React documentation if you aren’t already familiar with the new Context API.

The defaultValue parameter used to create Context Consumer and Provider components is the value received by Consumer components if no ancestor Provider is found in the component tree. Setting this parameter to the base style will yield a similar effect to the previous solutions.

To get the cascading effect, the Provider component will be used to alter the value descendant Consumer components receive. Its value will be the result of merging the Consumer value with the style prop.

An Expo Snack demo can be found here: https://snack.expo.io/@lewie9021/contextual-typography-styling-in-react-native---context-api.

Unfortunately, there are a couple of problems with this approach:

  • Just like the previous solution, contextual styling is limited to Text-only component trees.
  • Some styles shouldn’t cascade such as padding and margin, and instead directly apply to the Text component.

Addressing the second problem would mean MyAppText mimics the functionality Text provides out of the box. However, there’s now no longer a need to use Text when nesting like in the previous solution:

// Renders in italics as expected.
<MyAppText style={{fontStyle: "italic"}}>
<MyAppText>Hello World</MyAppText>
</MyAppText>

Solution Four: Context API (Rev. 2)

This approach builds on top of the previous solution but instead extracts the two main concerns of MyAppText into separate components:

  • StyleText — Alters the contextual style value.
  • MyAppText — Consumes the contextual style value and style prop.
An Expo Snack demo can be found here: https://snack.expo.io/@lewie9021/contextual-typography-styling-in-react-native---context-api-(rev.-2).
Screenshot of solution four.

MyAppText is drastically simplified and no longer requires logic to determine which styles should/shouldn’t cascade. This is because cascading styles should be passed to StyleText, while component specific styles should be passed directly to MyAppText.

Additionally, since StyleText doesn’t render anything, its no longer constrained to Text-only component trees either. Styling can be altered for entire sub trees and descendant MyAppText components will receive the altered contextual style.

As with most things in software development, there are trade-offs to consider:

  • MyAppText is less deterministic as its styling is mostly reliant on where it is in the component tree. Having StyleText as an explicit way to alter the contextual style value does however help to reduce this issue.
  • In complex views, it can be difficult to debug what styling particular MyAppText components ultimately apply. One way to solve this is to alter StyleText to take a function-as-children to allow console logging. Another way is to add a ‘logStyle’ prop to MyAppText that when present console logs the Text style labelled with the string passed via ‘logStyle’.
  • Components that use Context become trickier to test. Unlike the first two solutions where shallow rendering was sufficient, here I had to mount the component in order to inspect the Text style prop. There are ways to avoid this by mocking the Context components, but I personally try to avoid mocking if possible as it leads to tighter coupling with the implementation.
  • I’m still speculative of potential performance issues, though I’m yet to see this in real projects I’m working on that use this pattern.

Thanks for reading! :)

--

--