SECRET OF CSS

Build a Custom Design System Using Jetpack Compose | by Semyon Zadoroznyi | Sep, 2022


Let’s replace Android’s material package with our own implementation

In this article you will find out:

  • What a Design system is
  • What ways to implement your Design system there are in Jetpack Compose
  • How to implement a Design system based on the foundation package and completely exclude the Material library from your project.

UI design system is a collection of standards for designing and coding, which includes a set of components created according to certain rules which create a single UI concept of the product. A design system can be seen as instruction and a set of components which can be used to build the final version of the design.

Quite often a design system is divided into a few layers:

  • Atomic layer: it is a set of atomic elements, for example, a set of colors, typography rules, component corner shapes, elevation, and padding.
  • Molecular layer: it is a basic set of UI widgets with minimal functionality using molecules for styling. An example of molecules can be Text, Icon, Avatar, Border, Separator components.
  • Organism layer: it is a component made of one or several molecular components.
  • Screens layer: it is a set of components from organisms and molecules.

A design system solves the problems of:

  • management (design and development team finds a common language)
  • “the only source of truth” in the context of design
  • changeability of atomic layer (by changing the values of colors, typography and corner shapes, you can get a modified UI style)
  • extensibility (large components are built on top of smaller/pre-build ones)

At the moment, there are quite a few implementations of design systems of big companies. The two most known are:

These design systems are used not only within companies but are also recommended for implementation in applications which are written for both Android and iOS devices. The companies use the fact that a user is familiar with such a design format to encourage their use, and their approaches to design these design systems are scientifically and experimentally substantiated.

Examples of other design systems:

For the purpose of this article, I chose a rather raw yet well-structured design system from the Figma Community portal. You can follow the link to watch it and simultaneously follow its implementation in the project.

After seeing the cover of this design system, I decided to name it SpaceTheme.

To begin with, it is worth mentioning that Google has already made sure that its design system is implemented in the code. So developers are able to take ready-to-use components and configure them at their discretion.

androidx.compose.material package is responsible for this in the code — in which you can find many ready-to-use components. Their description and specifications can be found in the official Material Design documentation.

Despite of the presence of ready-to-use components, Google provides space for customization and implementation of your version of a design system in the project. Here is what Google says about it:

There are several approaches you might take:
Extending MaterialTheme with additional theming values
Replacing one or more Material systemsColors, Typography, or Shapes — with custom implementations, while maintaining the others
Implementing a fully-custom design system to replace MaterialTheme

Based on my experience of working with Jetpack Compose and opinions of some of my acquaintances, who write projects on it, the first two approaches are the most used ones by developers.

In a new article from Tinder, they shared their progress in Compose integration and said that their theme is also built on top of Material Design, and a few components are written on the basis of the foundation package. You can find more information in their article.

So, what does ‘based on the foundation package’ mean?

Jetpack Compose is architecturally built in layers:

1*ZA6tLcT8bsPJ3RnfhJ1 fA

At the top of the graph, there is Material package that implements Material Design. Jetpack Compose developers imply that the Material package is fully optional and can be replaced with your custom design system.

The final result of our work will contain the following layers:

1*bPnGSC8Tb19Q79F2BmblHg

And this is what we are going to do: create a new Compose project and make the first commit by getting rid of the material package.

1*mH631G6ogCyhoIwLVSTmTg

Let’s begin with simple things. We need our own alternative of MaterialTheme in which we will lay out our screens. Let’s call it SpaceTheme, and here is what its implementation will look like:

It is implied that in the design system we have color typography and corner smoothing as atoms.

1*gJD9A2DKEQk9jwHl5y6Egw
https://www.figma.com/file/z2BtuZqLwoXw7gnXu8AZAL/Design-System-Foundation-(Community)?node-id=2%3A121

Despite the fact that corner values are not described anywhere, you can see it clearly in the implementation of components in the design system. It is a minor mistake of the design system’s author but not a critical one.

Let’s start with SpaceShapes. We are going to have three types of corners in total – 8, 16 and 45 dp. There is also LocalShapes object for static composition, so that the corners can be used in the code.

The next step is typography and there are only 9 types and 2 font-weight — regular and bold. BM Plex Sans is used as a font. It can be downloaded from Google Fonts. In the constructor, we set default values for our fields and at the end of the file we create a field for the static composition LocalTypography

Colors are the most routine part of writing this design system, since, unlike Material where with 12 primary colors defined, our system has 32 of them! As well as lightSpaceColors/darkSpaceColors for setting default color values for light and dark themes.

Additionally, we will create a contentColorFor method to determine what color the content should be on a particular background color (mapping of these colors is calculated from the analysis of the design system).

In the end, we will create compositions for working with LocalColors colors and the content color LocalContentColor

All these classes and full implementation you can find in the project on GitHub in the package com.compose.designsystem.space.theme

After implementing a custom theme, we can proceed to the implementation of our components based on it. Let’s first analyze our abilities after deciding not to use Material Design.

During the implementation of this design system, after I finished working with atoms, I proceeded to small components straightaway, and this is where I got a big surprise.

Let’s take a closer look at what the Link component in the design system consists of.

1*arUbfQAL36h69QpvTQvU9g
https://www.figma.com/file/z2BtuZqLwoXw7gnXu8AZAL/Design-System-Foundation-(Community)?node-id=3%3A253

Link is a set of several components. The text is at the top, the link has an icon before or after the text, small Border can be displayed below the text, and the background of the link can change.

If we were to do it in Compose, like we are used to, we would use Surface for background, Text, Icon and Border for the rest of the components. However, there is a little problem. All these components are part of the material package, so we have to implement them by ourselves.

androidx.compose.material.Text(text = "Link")
androidx.compose.material.Divider()
androidx.compose.material.Icon(painter = .., contentDescription = null)
androidx.compose.material.Surface()

It is fine though since the key @Composable functions are still at our disposal, such as Row, Column, Box, Layout, BasicText, Spacer, Image and all sorts of Modifiers

A small disclaimer: we have already covered everything that was necessary in order to create your own theme. The implementation of the components from the design system will be described below. There can be a lot of variations for this implementation. I focused on specific and not the most complex ones. I will not fully describe all the nuances but highlight interesting points in the implementation of several components.

1*l81NUrAPcxvdWFp0 VMXOA

This one requires such components as Text, Border, Icon, Tabs, Tab, NavBar and NavTab

Let’s start with the Text component:

A noteworthy thing here is the sequence of calls to the takeOrElse functions, which are used to determine the color our text will have. We prioritize the color the developer set. If it is Undefined, we take the color from the provided text style. If the color from the text style is also Undefined, then we take the value from the static composition, it is always not Undefined and can be default. In my project it is black.

LocalContentColor.current also allows us to change the color if, for instance, we have a button and it overrides the color value for its content, such an example can be found below.

To display text, we take BasicText from the foundation package and pass information to it.

We will not cover Divider here, it is a complete copy of Divider from the Material package.

We also won’t use anything extra for Icon. A simple Box and a combination of toolingGraphicsLayer and paint modifiers will be enough.

The Tabs and NavBar organisms contain Row of smaller components. Below there is an implementation of Tabs. NavBar is made in a similar way.

In this part the Surface component is worth paying attention to. It is not in the foundation package and, essentially, it is a convenient wrapper over Box that encapsulates the settings for colors, shapes and semantics for the accessibility of the framework. You can also find an example of redefining the content color here. Placed on such a Surface, the Text component will use LocalContentColor.current to set the text color

Tab and NavTab components a little bit more complicated, you can find their implementation here.

What is left to do now is to put all components on one screen. The screen itself can be found on GitHub.

1*CgIEiI0nlZn f 5DWMRsmQ

Here, we add the Button component and the ability to open the BottomSheet on part of the screen

Let’s start with the button:

It can be seen in the design that we have to configure the type of button — the color depends on this. You can add an icon to the button before and after the text, as well as configure the shape of the button.

I used the slot-based API, in case we want to insert something other than text. Slot-based is about passing one or more @Composable functions so as not to heavily restrict the implementation of the component.

The button uses SpaceTheme.typography.h4 typography by default and just lays out the components on Row with little padding.

After that, you can write a simple screen implementation that can show the BottomSheet:

BottomSheetScreenStack in this example is responsible for the way content of different levels will be drawn on the screen.

This is where we create Layout for the component, the content takes up the entire screen and BottomSheet is nailed to the bottom of the screen.

1*b5OPKZOS0QfHKeQ5FEgJFw

The greatest challenge here is to show the dialogue to the user. In material, it was handled with AlertDialog. The foundation package provides us with @Composable Dialog which we just need to wrap in our Prompt and define its style and content:

1*yVKHsPVsS0ECkLkEi0FCbg

The Form/Card organism is a noteworthy thing here, which essentially contains content and a confirmation button

InputGroup and SelectGroup are small organisms that include a title, a field for entering or selecting an option, and a subtitle.

1*pDVhG8Giz6D7PtWO18iLHw

There is nothing interesting in these components, but input field itself is worth paying attention to. The foundation package provides BasicTextField that processes user input and displays it on the screen. For the visual component, the decorationBox field is used

decorationBox: @Composable (innerTextField: @Composable () -> Unit) -> Unit =
@Composable { innerTextField -> innerTextField() }

It’s a @Composable component that you define yourself and put an innerTextField in it.

1*QrCaDMIbbR254onCnZ2oTg

This screen is based on already existing components, the only added widget is Avatar. Its implementation is easily done with Box and there are no unique approaches. So as not to make it too long, I will not describe its implementation.



News Credit

%d bloggers like this: