Many teams use both React and React Native to drive their web and mobile development, offering a unique way to have UI/UX consistency across two very different platforms. One of the problems that developer teams encounter in synchronizing between these platforms is how to share code between them easily.
What sounds like a trivial problem can become a hustle when scaling up and adding more elements to the ecosystem. More elements usually mean more maintenance.
It is natural to ask ourselves how to achieve this by using a scalable and maintainable architecture while also being flexible enough to allow variations within the ecosystem, especially when styling.
This guide will teach us how to build a consistent Design System with React and React Native. We will show how to share design tokens, logic, and types while making a design system that aims to provide consistency between the two platforms.
In case you prefer to watch rather than read, you can see the whole process here:
Building platform-specific Accordions
To show how we can achieve it, we will build two accordions, one for React and another for React Native. To ensure consistent UI, we'll create a set of objects that hold design tokens and compose them into themes.
With Bit, we can manage shared code between React and React Native as components and use it as dependencies for both platforms' respective design system elements. In our example, we are building two accordions, one for React and another for React Native. We want each accordion to contain the minimum amount of platform-specific code (since their APIs are different) and will outsource all the common parts.
Looking at the dependencies graph, we can see how the base-tokens are consumed by other components and provided to different themes. Both Accordions will share a Types component and the internal state management (hooks).
React Native Dependencies:
As mentioned, we want to have as much code outside the platform-specific implementations as possible, meaning that hooks, types, themes, and design tokens will be part of different components.
That means that we will need to build the following components:
api/accordion: To share types. For example, the props of these accordions.
api/accordion-items: To share the items the accordions will need to render
design-tokens/base-tokens: A basic design-token object.
design-tokens/react-tokens: An extension of design-tokens/base-tokens with platform specific styles for React.
design-tokens/rnative-tokens: An extension of design-tokens/base-tokens with platform specific styles for React Native.
base-ui/hooks/use-open: A hook that will control the open / close state of the accordion.
base-ui/hooks/use-select: A hook that will control the selected item of the accordion.
theme/web: A component that will share the react-tokens through the Context Api, and provide a hook to consume it.
theme/mobile: A component that will share the rnative-tokens through the Context Api, and provide a hook to consume it.
base-ui/react/accordion: A React Accordion.
base-ui/react-native/accordion: A React Native Accordion.
We will apply the same pattern of creating a context and a hook to inject our tokens into our components.
The trick to making it work with React and React Native is that we are going to have three kinds of tokens:
A base Token, common to both
A Token extended from the base one to use with React.
A Token extended from the base one to use with React Native.
Providing it to two different Themes, we will have two sets of platform-specific tokens that share a common ground.
React and React Native don't share the same styling properties and types. While styles in React are defined by CSS.Properties, in React Native it is defined by ViewStyle | TextStyle | ImageStyle. In practice, that means that React Native has a more restricted type resolution. For example, while in React, many properties comply with the Globals type, allowing others to inherit, in React Native, there isn't such a possibility. Another subject to mention is units. React Native units are unitless, as specified in the documentation:
All dimensions in React Native are unitless and represent density-independent pixels.
In React, there is more freedom: you can use px, rem, em.
Back to our tokens, that means that we can't have a property like margin: 10px or backgroundColor: "inherit" because it won't work in React Native. To overcome this issue, we will have a base object that shares the most common values and other Platform specific that adds more properties.
Providing the Design Tokens to a Theme
Now that we have set the ground and architecture for this system let's look at the code, starting with the three tokens objects.
The base one would look something like this:
We need to have an object with the properties of our theme and call the createTheme function, providing it as an argument. The result will be an object with a hook to use those values and a component that injects it using the React Context.
Providing the react-tokens to a React Theme provider
In this example, we will show how to create a Theme for React:
Now that the Themes are ready let's provide them to the Accordions. All we need to do is to import those components that we created. Both of our Accordions will have the same skeleton, where we consume the hooks and return a mapped list, represented and abstracted by a fragment in the following snippet.
By using design tokens and Theme Providers, we were able to create platform-specific themes for our applications. Using Bit, we split elements into different components, writing them once but consuming them on both accordions, maintaining a unique source of truth and the code maintenance to a minimum. We also documented and showed usage examples of each component, scaling our apps further and quicker, even across multiple platforms like React and React Native. Lastly, we were also able to create production ready applications.