MIX: A tool for building design systems in Flutter
Mix helps you to build design systems in Flutter expressively and effortlessly. It offers primitive building blocks to help developers and designers create beautiful and consistent user interfaces for their applications. This makes even complex design systems easily maintainable and scalable.
What is a design system?
A design system is a set of standards to be followed throughout the user interface (UI) of a certain brand to maintain consistency and make it easily scalable. All the components that are created for that brand follow these design guidelines throughout their entire ecosystem, whether it be a mobile application or a web application (though the design guidelines vary a bit according to the different screen sizes).
Typically, most of the applications build for Android follow the Material Design guidelines for designing their user interface, iOS follow Human Interface Guidelines, and Windows follow Fluent Design System.
But certain brands created their own design system to make their applications stand out from the apps following the traditional design systems, and can be easily distinguished from them. Some of the bands following their own design guidelines includes Uber, Shopify, Atlassian, IBM, Airbnb, and many more.
Know more about the other design systems that companies follow from here.
Why are there so limited design systems?
Now that you know what’s the importance of having a proprietary design system, you would assume that most popular brands should have their own design system. But in reality, it’s not the case because creating a design system from scratch is a time-consuming task and would also require a lot of creative people to work in sync to give shape to an entirely new design language.
You can take a look at the latest Design Systems Survey conducted by Sparkbox.
Also keeping in mind, once the design system is ready, that the design language has to be converted into code to build components for a mobile or web app which is more difficult, time-consuming, and would require teams to work in proper sync.
Once you have a design system in place, the hardest part is maintaining the design system overtime keeping the consistency.
Today, we’ll mostly focus on the part of transferring a design language to code for creating components that can be incorporated within an app. So, we are assuming that after a lot of iterations you finally have a proper design system, now you need to build components for your app that are future-proof and easily scalable.
In this article, we have chosen the Flutter framework, as it is currently the fastest and easiest framework to iterate upon UI designs, to be used for building the user interface by following those design guidelines.
How Mix helps?
Though we have said Flutter to be the best to work on user interfaces of any application, once you start working and maintaining a design system in a large-scale app that is growing over time, things might start getting a bit messy and hard to manage.
Using Theme in Flutter helps to keep a bit of consistency across user interfaces but it doesn’t extend across all visual attributes. So, even if you are using themes, most of the time you need to define attributes inside the widgets which are really hard to maintain over time.
The Flutter widgets favor composition (meaning wrapping one widget with another) over inheritance, which is a nice decision as it makes it easy to interact with the API. But to maintain a consistent design language, inheritance also plays an important role as it helps in automatically passing attributes down the widget tree.
This is where Mix comes into the picture which takes the help of composition style of Flutter with the added advantage of inheritance for all attributes, Mix acts as a helper for maintaining a design language easily.
Following is a code snippet for building a simple decorated Container
widget using the basic in-built Flutter composition:
Notice in the above code snippet, the attributes and widgets are very tightly integrated so it’s really hard to separate out the attributes.
Using Mix a similar component can be designed using the following:
Here, you can see that the attributes are defined completely out of the widgets, this makes it a lot easier to maintain the design system over time. Also, if you notice closely, we have defined the style
Mix inside the Box
widget but the text color is automatically set to white as the TextMix
inherits the design attributes from the Box
.
Don’t worry too much about how a Mix is defined as you’ll get a deeper dive into the core concepts of Mix and how it works.
Implementing Basic Mix
The best way to understand how you can use Mix is by creating a new Flutter project and integrating Mix with it.
Run the following command to create a new Flutter project:
flutter create mix_design_samples
The project is created using Flutter version 2.8 with null safety support.
To add Mix, run the following command:
flutter pub add mix
This will automatically add the latest version of Mix present on pub.dev to your project’s pubspec.yaml
file.
You can also add it to your pubspec.yaml
file manually:
dependencies:
mix: <version>
Run flutter pub get
after adding the Mix dependency.
Now, go to the main.dart
file inside your project’s lib
folder and replace with the following code:
This is just a sample Flutter app template. Let’s define a simple Mix with attributes for building a Box
with red as the background color.
Here, a Mix is defined with some attributes: height
, width
, rounded
, elevation
and bgColor
(background color), and stored inside a final variable. Then, that mix
can be used inside some widgets provided by Mix, in this code snippet a Box
widget is used.
The widget will look like this:
Notice here we have defined the mix
inside the build method. To keep your code clean, you should keep the mixes in a separate file. But if you define the mix
as a final variable outside of the build method, you will lose one of the most advantageous Flutter features, hot reload.
To solve this issue, you can define the same mix
that we have created for the Box
widget in a completely separate file, like this:
Mix attribute hot reload in action:
The Box
widget that has been used here is similar to the Container
widget of Flutter. And, you may refer to the Mix attributes as the properties that you usually use inside a Container
.
Fundamentals of Mix
There are some core concepts that you should know about before starting to use Mix. Let’s go through them one by one by taking the Basic Mix as a reference.
Attributes
Attributes in Mix are basically the widget properties that you usually specify while defining a widget. But doing the same thing in the Mix way, allows you to take advantage of the in-built inheritance of Mix and also keeps your code clean. So, an attribute for a Box
widget can be defined like this:
Mix(BoxAttribute(height: 100));
Here, 100
is the height attribute. But there’s an even better and recommended way of defining attributes using Mix.
Utilities
Utilities are functions that help you to easily compose an attribute, you can define the same Mix like this:
Mix(height(100));
This helps in defining the attributes with an even lesser and cleaner code.
Decorators
In Mix, a Box
widget is equivalent to a Container
but it doesn’t have any scale
property. Using Box
we can still apply properties that are not a part of the widget directly, so the scale
attribute can be used with Box
, and these are called decorators.
So, the following in Flutter:
Transform.scale(
scale: 0.5,
child: Container(),
);
Is equivalent to this in Mix:
Box(mix: Mix(scale(0.5)));
Directives
To modify a widget’s properties directives can be used, they can also be passed similar to attributes. The best example of this would be text directives.
It can be defined as follows:
This will display the text in Title Case:
Design Tokens
Mix provides design tokens for certain attributes like colors, text styles, and spacing. The advantage of using design tokens is that you can use the context
reference values.
An example is as follows:
Here, $primary
is a design token for using the primary color present in the colorScheme
of ThemeData
.
If the primary colorScheme
inside the MaterialApp
widget is set to orange
:
return MaterialApp(
theme: ThemeData(
colorScheme: ThemeData.light().colorScheme.copyWith(
primary: Colors.orange,
),
),
);
Then, using the above Mix will generate the following widget:
Variants
One of the most powerful features of Mix is variant. They help in making the design system more flexible and reusable. A variant can be used for overriding certain properties of a Mix and it only applies to the widget(s) implementing the variant.
There are some in-built variants that some of the Mix widgets can take advantage of. One example is below:
Within a single Mix, you can define multiple variants.
In the above code snippet, hover
& press
are two in-built variants that are automatically applied if the Mix is used inside any Pressable
widget. A Pressable
widget is similar to a button (ElevatedButton
, TextButton
, etc.) or GestureDetector
widget in Flutter. Here, the attributes that are defined inside the hover
variant will be automatically applied to the Pressable
widget when it is in the hovering state, and similarly, the attributes under press
variant will be applied when the widget is in the pressed state.
Pressable
widget with variants in action:
You can also define your own (custom) variants. For example, if you are using the same Mix inside multiple TextMix
widgets but you want to add or override certain properties for a particular TextMix
then you can do it in the following way:
Here, myStyle
is a custom variant and certain attributes are defined inside it, on applying the variant to a TextMix
it will look like this:
The first TextMix
is without the variant and the second one is with the variant applied.
There are some advanced features of Mix that allows you to combine the attributes of two Mix, inherit the attributes of another Mix, or override them. More information about these are present in the documentation.
Overview of Mix Widgets
While explaining the core concepts, a number of Mix Widgets are used. To get you more comfortable with some of the basic widgets present inside Mix, here’s an overview of the widgets.
Box
This widget is similar to the Container
widget of Flutter but has some additional advantages of using it.
You can also use the
Box
widget in place of theCard
widget of Flutter.
FlexBox
You can think of the FlexBox
widget to be similar to the flexible widgets of Flutter like Column
, Row
, Flex
. Following is an example of placing widgets in a column using FlexBox
:
TextMix
This widget is equivalent to the Text
widget of Flutter, a basic example of it is given below:
IconMix
Equivalent to the Icon
widget of Flutter but supports inheritance and is easily customizable using variants. Following is an example of defining IconMix
widget:
Pressable
This is equivalent to the button widgets (ElevatedButton
, TextButton
, etc.) or GestureDetector
widget of Flutter. It can be defined as follows.
Dynamic user interface
You can easily create dynamic user interfaces (dark mode and light mode) using Mix.
For implementing a dynamic theme, it is recommended to use a state management library so that it’s easier to apply changes across your whole app as the theme mode changes. But today for the sake of showing how Mix helps in applying a dynamic theme, we’ll use a simple StatefulWidget
with setState()
to update the theme mode.
- In the above code snippet, a
MaterialApp
is defined withtheme
set to just the defaultThemeData()
anddarkTheme
set toThemeData.dark()
, it is important to set this otherwise dark theme won’t apply. - A global boolean variable,
isDarkMode
is defined that stores the dark theme mode state, and thethemeMode
is set based on that variable. - Now, a simple
IconButton
is used to toggle the state of the dark mode variable.
The code for the DynamicBoxSample
widget is as follows:
Here, we have defined a Mix called dynamicMix
, notice that a dark
variant (in-built) is used here, this is what does the trick of applying the theme mode. The attributes that are specified inside dark
gets used only when the theme mode is set to dark.
The app in action:
Adding subtle animations
Mix also helps you to incorporate animations in your Flutter app easily. The following is an example of simple animation of a Pressable
widget:
Inside the animPressableMix
, notice that an attribute called animation
is defined, and scale
is set to 1. And, inside the press
variant scale
is set to 0.9. So, when the button is pressed it will animate between scale 1 and 0.9.
NOTE: By default the scale is
null
, so if you don’t define the scale to be1
initially, the animation won’t work properly.
Building your own design system
At this point, you should be able to build your own design system using Mix. You can take the Recipe App UI template as a reference, it is built completely using attributes and widgets provided by Mix.
The codebase of this UI template is divided into the following directories:
screens
: contains the Flutter app screenswidgets
: contains various widgets which are used inside the screensres
: contains the color palette, Mix(s), image paths & text strings used in the app
One more thing to keep in mind, Mix is created to be used alongside Material or Cupertino widgets.
Wrapping up
Mix is created to improve the developer experience while building design systems in Flutter. The design systems created using Mix are easily maintainable as the attributes can be defined separately and inheritance makes the code concise. Explore more about Mix from the links present below in the references.
Mix is created by Leo Farias.
Mix is still in the experimental phase, and major APIs might change until the 1.0 release of the package.
References
- Mix Documentation (source code present here)
- Recipe App UI template
- Mix Design Samples (the samples used in this articles)
You can follow me on Twitter and find some of my projects on GitHub. Also, don’t forget to checkout my Website. Thank you for reading, if you enjoyed the article make sure to show me some love by hitting that clap (👏) button!
Happy coding…
Souvik Biswas
Follow Flutter Community on Twitter: https://twitter.com/FlutterComm