Design System in Action at Myntra: Tips for Designers and Developers

Sunil Jhamnani
14 min readJan 27, 2023

--

In this article, I will delve into the evolution of Myntra’s Applique Design System, which has become much more than just a component library for product creation. With its combination of rich utility packages, the system promotes a generic design mentality and improves developer efficiency. Join me as we explore Myntra’s journey of developing, productizing, and open-sourcing their hero, Applique Design System.

Yeahhh!!. One more under the umbrella of 100’s. But stay with me here, I will definitely turn that face to a happy one because, apart from advertising and talking about Applique, I am going to share critical insights for the entire panel about our journey and help you become 101st

Beginnings

It all started in 2017 when two developers and a designer came together, the initial plan was to create some generic UI components like button, navigation, table, etc for our in-house Supply chain management applications to maintain a consistent design language(Better known as our MVP). The goal was to improve feature discovery and adoption by bringing all Myntra enterprise applications into a unified single platform, aka Unity Framework.

That👆🏼 was well said, right? But little did we know that our initial stronghold would crumble and all that would remain would be a desolate field of dead grass screaming…

Stay with me please

The team of designers, developers, and product managers then collaborated to create a Governance model which aimed to enhance our design system and add value to our products. In a short period of time, we were able to successfully implement the below💥💥💥💥💥

  • We went from 5 to 45 UI components. It will be 46 by the end of this article
  • 15 utility packages for token management, classnames, and CLI tools, to help developers expedite the Software Development Process
  • Theme provider(in progress) to enable customization.
  • A strong community for developers and designers to work together and contribute.

In this article, I will mention some of the pivotal changes which we incorporated in our design and development phase, what kind of code patterns to use for an initial approach, and a bunch of really-cool coding techniques.

Governance Model(Foundation)

You can have a comprehensive design system that contains a slew of well-structured components, thorough documentation, thoughtful guidelines, and a well-considered design language. But if a design system user can’t get done what they’re trying to get done, the whole system risks obsolescence.

Brad Frost

The reason behind our first version not working out was not that the baby lacked nourishment, rather that we missed the first rule from the parenting book(Design system 101), we didn’t define the end-to-end Governance Model.

Let me give you some insights into one of these cases. Under our Unity framework program, we created a set of UI components called Unity Components for teams to create their web application and maintain consistent design language. For a project, the primary concern is getting work out the door, not upholding the integrity of the design system. Now developers(enthusiasts and creative) always found ways to hack the component structure to get things done, creating a slew of one-off components or abandoning the design system altogether. Eg,

Usecase 1: Simple table component to render data under their respective columns

const [data] = useState([
{ id: 1, name: 'Jane Doe', status: 'single'},
{ id: 2, name: 'John Doe', status: 'single'}
]);
<Table data={data}>
<Column label="ID" key="id">
<Column label="Name" key="name"/>
<Column label="Status" key="status" />
</Table>

Usecase 2: Table component to fetch data using the endpoint provided, show data in paginated format with 10 rows per page and enable sorting

const config = {
dataUrl: "/app/api/mock/api/data",
currentPage: 1,
selectedRows: {},
onRowClick: this.onRowClick,
enableSorting: true,
showCheckbox: true,
resultsPerPage: 10,
columnMetadata: [
{"columnName": "id", "displayName": "ID","enableSorting":false},
{"columnName": "name", "displayName": "Name","enableSorting":true},
{"columnName": "status", "displayName": "Status"}
],
}
<DataTable tableConfig={config}/>

Now, what went wrong here? This 👇🏼

There is a Table component in the library but with the new requirement of creating a Table to support data fetching, sorting, pagination, etc, it was forked and a completely new component is created. To cater to this kind of product requirements, it’s important to establish a Governance Model for a healthy design system

Now getting back to what should one keep in mind while defining a Governance Model

Design system team key roles

Behind every success is a great team. Your team requires these 4 groups to work together as a foundation for your governance process.

  1. Owner/team lead: One who is both aligned with the product, tech, and design to ensure the components are built to target a priority business outcome.
  2. Design team: Evaluate and define design guidelines, token, and atom structure. Work with the product team to understand whether the requirement is a part of DS or a snowflake(use case for one specific product)
  3. Product team: Enforce the use of the design system to help design and build new work.
  4. Tech Team: Follow the design system’s code guidelines and will consider reuse, flexibility, composability, accessibility, performance, and other best practices.

Note: For future reference in this post I will be referencing the above mentioned team as the panel

UI Patterns(For developers and designers)

I heard an interesting thing at one of the online conferences I was attending a couple of years ago, if you Google search design system today, you will see that there are all styles of components and foundational elements like colors, and typography already present and if we curated them all into one universal design system then we may never have to design another Button again in our lifetime

Component manifestation is something that the panel is always spending more time on, the reason being they focus more on adding new components in the design library. But the parts add up to something more.

Components are the backbone of a design system and that’s even the first thing we start creating for a design system but it’s the patterns that actually saves a tremendous amount of time for the users and developers.

I dig that you’ve got a ton of questions and not enough answers, Hop on the back seat and hold on tight! I’ll deliver the answers as quickly as a motorcycle ride 🏍️!

Coding Patterns

Personally, my favorite part of the entire article, so buckle up peeps, get some coffee because this is going to be…….

I will not focus specifically on component development here because as I mentioned earlier Google search → design system → never have to create one again, rather, I will explain some of the coding patterns a developer can use to make the codebase more maintainable, flexible and scalable. Let’s start with some basics to make this article look familiar to you.

Tokens:

It’s the smallest unit you will use to make UI components in a design system

Tokens are basically a second language of communication between a developer and a designer. Suppose your designer asked you for a Red colored Solid Delete button with an error icon. Now questions you might come up with as a developer are:

  1. Which Red color? What’s the #hex?
  2. What is the error icon? Please provide an SVG.
  3. Do you need the same color border or a different one? If it’s different then what’s the color? What’s the #hex?

That’s frustrating, right? But not for much longer. With tokens, this can be easily solved.

Wouldn’t it be great if we just give some names to these values and not worry about the updates later? Check this out!

Let's recall the requirement that came in from the designer, Red colored Solid Delete button with an error icon. Here goes the code for that

<Button color="color-error" icon="icon-error">
Delete
</Button>

No one has to worry about the values flowing through the tokenized system. We just need to use them and the rest will be taken care of.

Our Applique design system comes with an inbuilt package @applique-ui/tokenizer to create tokens to be used in CSS, JS, and SCSS files.

Decorator pattern

The Decorator pattern is a structural design pattern used in software development to add new functionalities to objects without altering their existing structure

One word. Higher Order Components!!. 👈🏼 That's 3. Yes, but 1 function is called a higher-order component function.

Both HOCs and the Decorator Pattern involve a wrapper. In the case of HOCs, a component is wrapped to enhance or modify its behavior. Similarly, the Decorator Pattern wraps an object to add new functionalities or responsibilities dynamically.

I will show you an example of how we used the Decorator Pattern in our design system to solve our use case of creating multiple variations of the <Text /> component

const { tag } = props
const Tag = createComponent(tag)
return <Tag {...props} />
const mapTagToName = {
div: 'body',
span: 'caption',
}

function Text(props) {
const { tag } = props
const Tag = createComponent(
tag ? mapTagToName[tag] || tag : 'body',
tag || 'div'
)
return <Tag {...props} />
}

Text.Title = Text.title = createComponent('title', 'div')
Text.H1 = Text.h1 = createComponent('h1', 'h1')
Text.H2 = Text.h2 = createComponent('h2', 'h2')
Text.H3 = Text.h3 = createComponent('h3', 'h3')
Text.H4 = Text.h4 = createComponent('h4', 'h4')
Text.Body = Text.body = createComponent('body', 'div')
Text.P = Text.p = createComponent('p', 'p')
Text.Caption = Text.caption = createComponent('caption')

function createComponent(name, tag = 'span') {
return function({
className,
children,
abstract = false,
color = 'dark',
emphasis = 'high',
weight,
noGutter,
display,
...props
}) {
className = classnames(
name,
className,
color,
emphasis,
weight,
{
'no-gutter': noGutter,
},
`display-${display || 'initial'}`
)

if (abstract) {
children = React.Children.only(children)
if (React.isValidElement(children)) {
return React.cloneElement(children, {
className: classnames(className, (children.props as any).className),
} as any)
}
}
return React.createElement(tag, { ...props, className }, children)
}
}

Above is the code implementation on how we use the higher order function(createComponent) to create <Text /> component. The idea here is to simply pass the HTML tag names h1, h1, and h3 and create a component out of it with all the base-level implementation and design.

// Type 1
<PageHeader>heading 1</PageHeader>
<PageHeader2>heading 1</PageHeader2>
<PageHeader3>heading 1</PageHeader3>
<Paragraph>Paragraph</Paragraph>
<Caption>Caption goes here</Caption>
<PageTitle>Page Title</PageTitle>
// Type 2
<Text.h1>heading 1</Text.h1>
<Text.h2>heading 1</Text.h2>
<Text.h3>heading 1</Text.h3>
<Text.p>Paragraph</Text.p>
<Text.caption>Caption goes here</Text.caption>
<Text.title>Page Title</Text.title>

<Text /> is a very base-level atom for a design system. Using the Factory method, we were able to overcome the challenge of remembering the different names for the Text component. As you see in the above example. I am using actual HTML tags like h1, h2, and h3(Using the concept of static) to use the respective component rather than using names like PageHeader, PageTitle, etc. Do comment what are the other use cases where we can use the Factory Pattern

Observer Pattern

Let’s maintain the legacy of this article and bore you with the definition.

This pattern allows an object to be notified of changes to other objects, without tightly coupling the objects to one another.

Now think of the observer pattern as a subscription based model where you have a central subject(say observers) that generates information updates and will notify all the other subjects registered with it. It would look something like this👇🏼

With respect to react, the best tool could be the context API that lets you read and subscribe to the context from your component.

Let's try using the context API to create an <Accordion /> component:

Accordion base class component

// Importing the createContext function from react library
import React, { PureComponent, createContext } from 'react'
// Creating a new contect with null value as default
const AccordionContext = createContext(null);
class Accordion extends PureComponent {
constructor(props) {
super(props);
this.itemsCount = 0;
this.itemsToIndex = new WeakMap();
this.getItemIndex = (item) => {
if (this.itemsToIndex.has(item)) {
return this.itemsToIndex.get(item);
}
const index = this.itemsCount++;
this.itemsToIndex.set(item, index);
return index;
};
this.handleClick = (currentIndex, active) => {
if (this.props.onChange)
this.props.onChange(currentIndex, active);
};
this.state = {
active: props.active || 0,
};
}
get active() {
return typeof this.props.onChange === 'function'
? this.props.active
: this.state.active;
}
render() {
const { active, ...props } = this.props
this.itemsCount = 0
return (
// Assigning value to the provider so that it can be consumed by
// all the AccordionItem components
<AccordionContext.Provider
value={{
onActivation: this.handleClick,
getIndex: this.getItemIndex,
itemProps: props,
}}
>
{this.props.children}
</AccordionContext.Provider>
)
}
}
Accordion.Item = AccordionItem;
Accordion.defaultProps = {
active: 0
};

Accordion Item component

class AccordionItem extends Component {
constructor(props) {
super(props);
this.onTitleClick = (callback) => {
this.setState({
active: !this.state.active
}, () => {
callback && callback(this.index, this.state.active);
});
};
this.state = {
active: false,
};
}
render() {
const { title, children, className, ...props } = this.props
const { active } = this.state
return (
// Consuming the value provided by AccordionContext
<AccordionContext.Consumer>
{({ getIndex, onActivation, itemProps }) => {
const index = (this.index = getIndex(this))
<Layout
className={classnames('item-container', className, {
active,
})}
data-accordion-index={index}
style={style}
>
<Layout
onClick={() => this.onTitleClick(onActivation)}
{...props}
>
{typeof title === 'string' ? <div>{title}</div> : title}
</Layout>
<div className={classnames('item-content')}>
{children}
</div>
</Layout>
)
}}
</AccordionContext.Consumer>
)
}
}

Usage

import React from 'react'
import ReactDOM from 'react-dom'
function ToggleContent(props) {
return (
<Accordion>
<Accordion.Item title="One">
<div>First component</div>
</Accordion.Item>
<Accordion.Item title="Two">
<div>Second component</div>
</Accordion.Item>
<Accordion.Item title="Three">
<div>Third component</div>
</Accordion.Item>
</Accordion>
)
}
ReactDOM.render(<ToggleContent />, document.getElementById('root'))

Here, the <Accordion /> component is the central subject, i.e., the observer, and <AccordionItem /> are the subjects subscribing to that. For example, the onActivation function is an alias to handleClick in the Accordion component which is then called by the AccordionItem’s to execute the common onChange code. This onChange then with currentIndex and active as parameters execute some operation on that <AccordionItem />. Now scroll up and look back to the Observer-actions-subjects image again to relate the example.

Prop Validation

PropTypes.array,
PropTypes.bool,
PropTypes.func,
PropTypes.object,
PropTypes.string,
PropTypes.symbol

You might wonder why am I writing about a fairly simple topic. We all have used above mentioned code to add some strict typecheking using the prop-types package in our react component but the package provides more than just typechecking component props. 1 of them is custom validating your props.

As a design system component, it's important to have good designs for the component and write a good quality code, but it's also important to add proper validation. Here is an example code for that.

class InputDate extends PureComponent {
static propTypes = {
__$validation({ dateFormat }) {
if (isNullOrUndefined(dateFormat) || !dateFormat)
throw new Error(
`The props dateFormat should have a proper format. Refer this https://day.js.org/docs/en/display/format`
)
},
}
}

Let’s see what’s happening in the above code. Your custom validator is a function in your propTypes static variable which accepts 3 parameters(props, propName, componentName). In the above example, I have destructured my props variable and added validation for the dateFormat prop to not be null or undefined. This basically helps the developers mitigate the risk of breaking code on their application while using your component. More on prop-types here

There are a lot more tips/patterns to cover but in the interest of time and space I’ll be delving deeper into this subject in upcoming articles

Design Patterns

Design patterns are reusable solutions to common design problems. They can be used to create a design system, which is a set of guidelines and components that help to ensure consistency and efficiency in the design process. Some common design patterns used to create a design system include:

  1. Atomic Design: This pattern involves breaking down a design into its smallest components, such as atoms, molecules, and organisms. This allows for easy reuse and consistency across different parts of the design.
  2. Modularity: This pattern involves breaking a design down into modular components that can be easily reused in different parts of the design. This helps to ensure consistency and efficiency.
  3. Grid-based layout: This pattern involves using a grid system to structure a design. This helps to ensure consistency and alignment across different parts of the design.
  4. Typography: This pattern involves establishing a consistent typographic hierarchy and style across the design system. This helps to ensure readability and clarity.
  5. Color: This pattern involves establishing a consistent color palette and style across the design system. This helps ensure consistency and brand recognition.
  6. Icons: This pattern involves using a set of standardized icons throughout the design system. This helps ensure consistency and recognition.

Let’s understand more about some of these patterns with an example.

At Myntra, we redesigned our Partners/Seller Portal which yielded a major revamp to our design system. Once the direction was set, the team looked at individual components and broke them down to solve user problems.

Let’s understand more about some of these patterns with an example.

Navigation

  • A seller can have people assigned to handle/monitor specific part of the seller portal(say a finance professional)
  • In point 2 from the image above, we show the complete Level-1 side menu as a part of our navigation component.
  • Once the professional is under his/her territory, this sidebar improves discoverability and also reduces the user’s mental and physical effort to hover back to the top and navigate to any other screen in the same list

Button group

  • The primary action button allows the user to accomplish their most common or most important goal, so by default primary buttons are kept SOLID again to save the user time in finding them.
  • Things go wrong when we place two primary buttons together which then leads to user confusion and errors because it is not clear which button the user should select.
  • In the above case, we want our users to focus more on downloading the Payment Summary and less on Raise a ticket
  • It is important to clearly distinguish between different actions and provide clear and distinct choices for the user to make. It is best practice to provide a visual hierarchy on the buttons, with the most important button being the most prominent and easy to find.

Design systems can play a crucial role in supporting an organization’s growth by providing a consistent and cohesive user experience across all products, platforms, and digital experiences. For us, the use case was to give a consistent design language to all our Supply Chain Management applications to make it more convenient and fast to perform specific actions and get results faster.

Then comes the metrics, the design system is useful there as well. It provides Scalability, Flexibility, Collaboration between designers and developers, and much more.

With this, I want to announce the launch of Myntra’s Design system Appliqué, a UI component library that powers the web applications for the entire Supply Chain Management of Myntra. Check it out here.

Thank you for taking the time to read this. I hope I did turn those faces to….👇🏼

--

--