Turf- Designing the rules of play!
One of the most common traits of a successful application is the consistency in the use of colours, shapes, and patterns that leads to brand identification. At Dream11, we aim to standardise our entire design practices and design components. Turf, our design system, ensures that all our 120+ million users experience the same brand value, whichever platform they may be on. For example, the color combination used repetitively is intended to evoke a brand identification subconsciously regardless of where they see it. Using the exact shades and combination is of paramount importance, which we achieve through our design system. Such elements distinguish a great product from a good one.
Why should a design system be implemented?
A design system aspires to create a shared vocabulary between designers and developers. It allows for a centralized design source where all design-related attributes and components can be stored. This helps maintain consistency in designs and ensures efficient design changes across platforms. It not only helps in minimizing developer interaction time for such changes but also reduces the possibility of human errors.
How did Dream11 work prior to implementing a design system?
Prior to working with a design system, we did not have a single source of truth. Each platform maintained their own copy of design values and components. There was no standardisation among user interface (UI) components. This led our teams working on different features to create the same components with different names, which caused redundancy. Also, since each platform maintained its own design source of truth, any changes in the design required the active involvement of developers.
What are the objectives of using a design system?
Forming platform-agnostic design values
The first step towards consistency will be creating a central and designated place for design elements that can be referred to by all teams. Developers and designers can collaborate to define the common vocabulary which will be the basis for all components and tokens used across designs. The design elements and components defined, should remain platform agnostic and act as a database of raw values. These raw values would then have to be processed in multiple steps for consumption by different platforms.
Giving more control of the design changes to designers
Once the design system comes into the picture, all UI elements including colour, dimensions, fonts, etc., will be parameterized. This ensures that all parts of the platform that need a particular design element, use the same token. The value of the token should remain hidden from the developer. This will also ensure any changes made to the token value percolate into all parts of the platform without any extra effort. Moreover, since the source of truth is outside all platforms, it will enable designers to make design changes without any developer intervention.
Standardising design components
Once the design system is in place, it is expected to serve as a database for components. All components would be documented with code and property descriptions. This will help developers to know about existing components and thereby, minimise the possibility of duplication. Also, if any new components are required, they can make a component request which will then be created and documented for use by everyone. Moreover, these will be generalized enough to serve multiple use cases. The design elements within these components will be tokenised which will ensure all design changes to these components and other composite ones derived from these, will take place in a cascading manner.
To meet our expectations, we architectured a multi-level system with well-defined interaction zones for designers and developers. This system allows for a single source of design values to be referred to by designer and developer software, with clear guidelines and toolings around it, so logging and communicating changes to design values can be done.
The philosophy of tokens
In its essence, we have adopted Brad Frost’s Atomic Design Pattern for our design elements. The smallest units in a design are attribute values like color, dimension, font, etc. These are treated as atoms of the design system, which then combine to form molecules. These, in turn, combine to form organisms and so on. These attribute values or tokens are theoretically design decisions translated into code. To increase flexibility and allowance for easy and safe usage, we have defined three levels of tokens — global, alias and component. Each level of abstraction helps in various levels of mapping design values.
These are the primitive values in our design language, represented by context-agnostic names. They map directly to the absolute value of an attribute. The global tokens define the superset of values (colors, dimensions, fonts) that are permissible within the app. Global tokens follow one-on-one mapping with the attribute values, meaning, no two global tokens will point to the same attribute value.
For example, in the below diagram, a global token could be named color_global_group1__10 . This is context agnostic. Here, the palette is defined with a color holder, whose value is defined by the designer, which can change.
These are context-specific tokens that derive their values from global tokens. Unlike global, alias tokens have a many-to-one mapping, meaning multiple alias tokens might map to the same global token. These are generally consumed by different parts of the app.
For example: Alias tokens refer to a global token without knowing what value it holds. color_alias__group1Light is pointing to color_global_group1__10. Even if the value of the global token changes, there won’t be any changes needed in the alias tokens.
This is a special class of alias tokens that are specific to the components. Component tokens allow us to maintain a cleaner mapping for base components and higher-level composite components.
Tooling and processes
The main layers
The token management layer serves the purpose of storing all design values and allows designers to implement their design decisions. The attribute values, theme details, component code, and descriptions are maintained in this layer and it serves as the source of truth from which all platforms and design software derive their design values. One thing to note is that all design values which are consumed by developers are stored in a platform-agnostic manner.
This layer is responsible for consuming the platform-agnostic, raw design values from the token management layer, processing it, and generating platform-specific code. Special care is taken to ensure versioning of design values is done without any reprocessing of the raw values. The transformer layer is written to serve all platforms currently in use — Android, iOS, React Native, and Web.
Since raw design values are too unstructured for processing, we run a nightly process that converts these raw values into schema-based jsons. Separate jsons are generated on the basis of the category of attributes. This process happens inside the token management layer. The versioned jsons are stored separately for consumption by the transformer layer.
To ensure that changes made to designs are communicated instantly, we have set up a real-time update system that can alert the concerned developers and designers. The alert can be sent via multiple channels like slack, mail, etc., and carry information pertaining to the type of modification, user involved, time and date, etc.
After the platform-specific code is available to developers, there is no guarantee that the developers will start using them. This might be due to multiple reasons, such as lack of proper understanding of the design system, not being aware of tokens and components that are available, and so on. As such, it becomes necessary that we provide a nudge to the developers to adopt the design system. Each platform has its own way of handling this.
In Android, we piggyback on the existing linting framework. A major reason to adopt Android Lint is that it can run static analysis on the code. This means we can check our code for design violations as we code without having to wait for compilation. In addition to the existing standard rules provided by Lint, we have created our own custom Lint module with rules to enforce the usage of our design system elements and components while flagging the default Android components.
Apart from the static analysis, we run all the Lint rules on our project every time a pull request is raised. The benefit of this is two-fold:
- The development is not hindered by throwing lint errors during local compilations
- Any code being merged to master is fully compliant with the design system.
In iOS, we follow the same principle to avoid hindering the development by not running any compile-time checks. Instead, a script runs during every pull request which flags any violations to our design principles.
We hope this blog post offers you a detailed overview of the philosophy, toolings, and processes in place for Dream11’s design system. We have more to come in this series on how we built up the design system from scratch, detailing the technical challenges we faced and how we resolved them. So, stay tuned!
Keen to work with the Dream11 design team? Apply here to join us!