Hey guys, I have been working on a library for a while now. It's a Design System around React Native and this blog answers some of the common questions that you might stumble while going through the repo. So let's start ๐.
What is RNDS ๐ง?
RNDS
in short for React Native Design System
. It's a set of component library and design rules that lets you build React Native apps faster. It's documented, tested in production and works on Android
, iOS
and Web
. Visit this link for documentation.
Why bother?
This title seems a little better than a generic title Motivation. So Why bother to create a library?
I was working on different projects and copy-pasting components I built over this past year.
I thought (like most devs) that it will be a good idea to make a package out of it and import it in any project.
I did exactly that! I wanted something to prototype faster and it was a perfect solution.
At first, I decided to add a bunch of props which is common across all the components to have a low API surface area because seriously Who wants to remember a lot of props to use a component library?
I tried making it close to React Native itself so that I don't have to learn anything new. My goal was to create something that lets you get started in 10 minutes or less. (which is not that easy, I realized later ๐
). I'm gonna go through my approach in the next section as Failures
because that's where the magic happens.
Failures and Learnings
I will discuss my failures and what I learned while making this. Most of the things are pretty common if you're familiar with design systems but I had to learn the hard way. I was solving my own problems.
API
I wanted flexibility, freedom to create any component real quick so I added a bunch of props like size
, color
initially.Size
prop was supposed to take an integer value like size={17}
. There was some calculation involved to create a component of an appropriate size but possibilities were endless. I can basically create a component from size say 10
to say 100
. I just had to try a bunch of numbers and see what fits.color
was supposed to take a hex code
of color and it will be the color of a component say Button
. When you're working on a project, all you get is a color and it's easy to pass like that. So what failed?
If you're experienced dev, you might have already guessed what an idiot I am but hear me out. The problem was With too much freedom comes too much inconsistency.
I provided size
and color
to each usage of say Button
in a project and it's difficult to maintain consistency when there are so many options available. size={16}
and size={17}
looks almost similar but are not. This was a big failure in terms of pixel-perfect design.
The second problem was passing the hex code to color
prop. There aren't a lot of buttons when it comes to a real app. It means I was passing one single color to each and every button for no reason ๐.
The solution was using a tokenized system of configuration and restricting the possibilities to a bare minimum which covers most of the cases.
The key is to find the balance between Freedom and Consistency.
Customization and Consistency
So as we talked above, the solution for inconsistency was defining all the component-specific configurations in a single file. It solved one more problem than it was supposed. Now, with everything in one place, it was much easier to customize components. Suppose, I got 7 sizes of buttons as follows:
theme = {
buttonSize: {
'xxsmall': 8,
'xsmall': 10,
'small': 12,
'medium': 14,
'large': 16,
'xlarge': 18,
'xxlarge': 20,
},
...
}
It means I can simply do this to assign a different size according to my requirements:
theme.buttonSize.medium = 15;
This also helps in making things consistent. There can be 7 different sizes of components and I feel that's enough. Any more than that will just introduce ambiguity. You can always modify things (or add more) in the theme file. This also works well for colors. Suppose, we have initially defined colors in theme file is as follows:
theme = {
brandColor: {
'primary': '#1e88e5',
'secondary': '#f9a825',
'tertiary': '#e53935',
'background': '#f8f8f8',
...
},
}
You can easily change primary (or any) brand color as:
theme.brandColor.primary = '#aeea00';
Sweet! Let's move on.
Design Language
Everything is interlinked with one another. Solving the first and second problems solved a third problem which I had no idea.
Communication was a problem with the earlier approach of size
and color
. You need to tell the exact (number of) sizes and (string) Hex code if you want someone to create a button for you. Token-based theme and restricting sizes eliminated this issue as well.
You just have to say that "Hey, can you create a medium size button with a primary color?" That's it. It favors better communication and avoids confusion.
Design Rules
Prototyping an app is much more than just dropping components on a screen. Space matters a lot more than actual components when you're working on the frontend. Managing space on each component was my first approach but it was difficult.
I added a few separate components which take care of space only. These are Layout components
like Stack
, Inline
, Box
and Card
. Their sole purpose is to provide spacing to their children. You can go to the playground of Stack in the documentation for a live demo.
All the other components like Button
, Badge
, etc have 0 margins
.
This separates the concerns in two:
- A normal component should worry about only one thing: rendering a component of appropriate size.
- A layout component should worry only about providing space to the component.
There are a bunch of other rules that will be discussed later in these articles (Also available on docs).
Layout components are inspired/stolen from Braid Design System ๐
Documentation
What good is a library if you don't know how to use it. A design system at its minimum is the sum of Design Library + Design Rules + Documentation and it is supposed to boost your productivity. Which is certainly not possible if you waste your time figuring out how to use a component.
After creating the whole library, I instantly googled "How to document your library like a pro" and decided to use React DocGen. I wrote half the documentation but it wasn't working out well so I decided to go with Docz. It's really good and I re-wrote the whole documentation for Docz but...
Then I went to a Design System Meetup organized by JSLovers and someone suggested to use StoryBook instead. It was perfect and exactly what I needed. It's an interactive playground not only helps you explore the components but also enables to create them in isolation a lot faster. With StoryBook
, you don't even have to have to read the whole documentation. Just playing with the component in the playground is enough.
So, for the third, I decided to rewrite the whole documentation with StoryBook
in MDX
and that's the final version that you will see at rnds.netlify.com.
There were other complications/hurdles on using MDX
with React Native but I won't go in that much detail.
Logo
After working so hard, all I wanted was it to be perfect and look at what I created at first ๐ ๐
First thought, definitely not good ๐ฃ. So, after much thinking I created these, thanks to my photoshop skills:
Looks good but I can do better. So this is the final version after giving a hell lot of thoughts ๐.
This looks pretty satisfying and states my thoughts on why I created this too.
So these were a few of my failures while working on this. I learned a lot of things from webpack configs to compound components and a lot that I can't cover in this article. I know there are other great libraries available like NativeBase and React Native Element but I was just trying to solve my own problems and this is the result of going through a lot of iteration.
Sanket Sahu and Nader Debit have done a great job on the libraries mentioned above. The name
React Native Design System
was suggested by Nader himself and I'm thankful to him for helping me in building this.
Design Rules
This section is the core of this library and I have spent most of my time iterating this to make it better. It's not much but it's honest work.
It is based on the four key aspects that I considered while creating this.
Feel free to send a PR for corrections, these are personal opinion and I'm here to learn. ๐
1. Low surface area
Most of the props are common across all components or they are the same as their parent component imported from React Native. This makes sure that you do not have to learn a whole set of extra props or just use what you already know.
For example:
- If it's a Button, it receives all the props of Touchable component of React Native.
- If it's a Modal, it receives all the props of Modal.
- Each component receives a
size
prop whose value can be one of these:xxsmall
,xsmall
,small
,medium
,large
,xlarge
&xxlarge
. You just have to passsize
prop with one of these value and you're done. - Another example could be
color
prop. You can pass one of the colors defined inbrandColor
of the theme to any component. - If it's a layout based component like
Box
,Stack
,Inline
or our best friendCard
then it receives aspace
prop. This also takes one of the values betweenxxsmall
andxxlarge
(with the addition ofspace="none"
) and provide appropriate spacing between components. - Each component receives a
style
and atextStyle
prop (if there is a text involved). This is for some rare cases when you have to override the default styling. It's preferable to tweak thetheme
instead to maintain consistency and avoid adding thatstyle
again and again.
These props are common to all the components. Other component-specific props are pretty straight forward too.
2. Speed
For most of the cases, default styles like size={medium}
or space={medium}
will be enough. In other cases, it's just two to three props max to achieve any desired result. This makes it faster to prototype. The layout components make it easier to achieve the desired screen layout with space
props. See the playground in Stack
documentation.
One of the key aspects of pixel-perfect design is the spacing between components. This design system proposes two things:
- Every UI component has a margin of 0.
- The spacing of any component will be determined by its parent Layout component.
The separation of concerns makes the job easier. Layout component should take care of space
only and UI component should worry about UI only i.e., color
or size
.
3. Consistency
The correct balance of freedom and consistency is hard to achieve.
- For freedom, you get straight forward props like
color
andsize
. - For consistency, you need to define these configurations inside theme file i.e Single source of truth.
It helps in maintaining uniformity across all the usage. It also allows you to have several choices and use them as needed. Check out the theme section of this guide for the default configuration.
4. Connection
Big problems are always more manageable when broken into smaller pieces. The design language of the system is broken down into color, typography, size, and space. These API is followed by each component.
Color
There are basically two types of colors:
- brandColor: "primary", "secondary", "tertiary", "background", "disabled", "semitransparent".
- textColor: "default", "heading", "grey", "subtle", "disabled", and "white"
Typography
To keep things simple and consistent. There are two choices for fonts to use:
- heading
- text
Size
- The available sizes you can use is
"xxsmall"
,"xsmall"
,"small"
,"medium"
,"large"
,"xlarge"
and"xxlarge"
. The default is"medium"
and it will be applied if are not passing anything. - Font size is also similar and it ranges from "xxsmall" to "xxlarge".
Space
Space also shares similar API as size but with one addition. You can also pass "none"
with the range of "xxsmall"
to "xxlarge"
. Space is a dedicated prop for layout components like Stack
, Inline
, Box
and Card
.
You can contribute to the theme. Please share what are the common color tokens you use, what are the common font sizes that you use in any app, etc. Let's make the theme config more generic together.
Getting Started
Install
Simply go to the command line and run this command.
yarn add react-native-design-system
You can use yarn
or npm
as per your choice.
npm install react-native-design-system
This library needs react-native-vector-icons
so go on and install that too to get all the cool icons. Check out Install guide.
Usage
Step 1. Import ThemeProvider
and theme
then wrap your root component.
This step is important. We are passing theme
as context value that each component will access.
//your root component
import { ThemeProvider, theme } from 'react-native-design-system';
function App(){
return (
<ThemeProvider value={theme}>
<Root />
</ThemeProvider>
);
}
Step 2. Use component.
//inside any file
import { Button } from 'react-native-design-system';
function HomeScreen(){
return (
<Button>
Press Me
</Button>
);
}
Visit this for more info.
Reference:
Thanks to each person who helped me feedbacks. Other than that, these were talks and article which helped:
- ReactLive 2019 - What goes into building a design system by Siddharth Kshetrapal
- ReactiveConf 2019 - Rethinking Design Practices by Mark Dalgleish
- ReactConf 2019 - Building (And Re-Building) the Airbnb Design System | Maja Wichrowska & Tae Kim
- React Advanced 2019 - Design Systems Design System by Siddharth Kshetrapal
- Distilling How We Think About Design Systems by Sarah Federman
- Everything you need to know about Design Systems by Audrey Hacq
If this feels interesting, please drop a star on the repo. Click on this link.
Contributions are most welcome, you can reach here to read contribution guidelines.