Design + Dev: building scalable user interface the painless way
By: Angie Sasmita
Tags: User Interface, Development, Design, Scalability, UX

Design + Dev: building scalable user interface the painless way

Reusable component is key in building a scalable digital product.

Article by Angie Sasmita and Vee Quek

In this complex world of multichannel interface experience and agile product development, applying traditional practices to design and front-end development is no longer sufficient. Scalability of user interface and thus scalable design systems are essential. The characteristics of a scalable user interface is that it is easy to maintain and flexible enough to be extensible as and when required. In order to achieve this, we need to break a couple of rules and come back to others.

We will talk about the principles of scalability of user interface, the benefits for businesses, design as well as development and how all of this translates into code. This article was written by a developer and designer to share with you real-life examples from our day-to-day work.

Developing a new feature — business as usual

Imagine you are working on a login page and a new ‘single-sign on’ (SSO) feature was added to your backlog. This means you now need to add a secondary button for the users to be able to access this feature.

At first, this can seem like a simple request. All the designer needs to do is add another button that looks secondary to the main, primary button. It is however important to pay attention to this seemingly straightforward solution as this can have a great impact on interface scalability later down the line. If designers don’t do their due diligence, you can quickly end up with a rather big list of secondary buttons (see image below).

While having multiple distinct types of buttons may not seem like a big deal, imagine if this happens to every single user interface element. Soon enough, the design system will be unmaintainable, thus the only way to add a new element is to reinvent them (even when they already exist in some form previously), bloating the system further. On big products, you might find this inevitable. The intention is to make a well-informed decision on how you add elements to the system.

The default mindset — speed first, quality later

When it comes to established products we often jump to the solution of ‘creating a button’ in order to move fast rather than take the time to investigate the variety of buttons already existing in the product’s ecosystem including mobile applications, campaign pages or internal systems. However these ecosystems are often complex and if you are trying to understand and figure out what button is best suited to align with the ecosystem, you will end up spending a significant amount of time investigating rather than building the solution, which is also not the most efficient use of your time.

This is why we often ‘don’t have time’ to focus on scalability as speed is the priority.

However if we were thinking of short-term tactics, it is perfectly acceptable to push a feature out as soon as possible to test it on the market. There is no point in trying to understand all of the possible future scenarios that may never happen.

If we acknowledge that products and their respective ecosystems grow and evolve (and it is ok to prioritize speed to get products out to be tested on the market), we also need to proactively include reviewing, testing and learning to maintain a healthy evolution of our interfaces.

Changes happen, even if you don’t like it

As a result of reviewing, testing and learning, changes will occur. For example our hypothesis was invalidated and we need to pivot or simply, the product grows and needs to evolve.

So how can we maintain the speed, embrace the change and build scalable interfaces?

The solution lies in components

With every new request you get, you will start spotting repetitive elements in an interface, let’s call these ‘components’. Components are elements that are designed, developed and used consistently across the interface of a product(s). These could be simple elements like dividers, buttons, and icons all the way to more complex ones like accordions, input fields, footer, and even a login page. They can be repeated not just within a product but also across the whole digital ecosystem.

The value of reusable components

Maintenance and speed of development (for both design and code)

When building for scalability, components encourage reusability. When it comes to making changes (for reasons outlined in the previous section), it can be implemented across all components without the hassle of doing it one by one. This means the design and the code for a component will be more maintainable as changes can be applied to one place and fixing bugs can happen faster without additional concerns around regression issues. Consistent user experience

Another great benefit is consistent user experience as the interface will become intuitive and predictable. As components are inheriting one another, they also inherit their behaviours, which allow designers to be consistent, developers to develop faster and end users to adopt behaviours quicker without even noticing the consistency.

Applying the concept of components correctly and consistently will increase the speed of development and decrease behavioural or stylistical defect in the long run, hence ensuring product scalability.

When should we start introducing components?

Similar to the treatment of technical debt, the longer we put off component reusability, the higher the cost becomes to do it in the future. Choosing not to prioritise component reusability can impact the speed of development in the future as well as lead to additional number of bugs or defects found.

Cost of development grows logarithmically when we use components and exponentially when we do not

The diagram above shows the intersection point where the initial benefit of using only custom elements disappears and the benefit of using components start to pay off. This point is usually reached quite quickly and the marginal cost increases even more rapidly after this point is reached. Symptoms of this pain point include recurring defects and slower UI development time as front-end code becomes more and more unmanageable.

There should, however, be a point where we need conscious thought and effort to gradually componentize the elements of both design and code to ensure that the project will be scalable.

We can avoid the exponential cost of development and bring it back to near the point of saturation by adopting components more strictly

The right time where such conscious effort needs to begin varies depending on the project scale and various other concerns. Making the call at the right time is where the judgment of experienced designers and developers are tested. We will discuss how team size affects how we build components in the next section.

How to build components

Evaluate the fit into the existing system

Determine if the new element you are about to implement (the single sign-on button) already exists in the ecosystem. This might take some investigation and conversations with designers, developers and other team members. If so, review if we can reuse the existing one or whether it needs to be enhanced based on the new requirements and/or research. If not, either develop existing buttons and replace the old ones or create new components. This way you can start developing your central repository of components.

If you are going to enhance existing elements, don’t forget to research if the component already has some usability issues. This could be a great opportunity to improve it. (Remember: “Products and their respective ecosystems grow and evolve, the key is to proactively include reviewing, testing and learning.”)

Build reusability incrementally

Not all components are created equal. To optimise the reusability of a component, one must look at the size and customizability of the component. The more customizable a component is, the more reusable they are. However, making all components extremely customizable risks the component in becoming a ‘god-like’ component that contains unnecessary properties or behaviour. The key to reusability, hence, lies in building smaller components. The smaller the component, the more likely they can be reused in many scenarios and be useful to build up a larger component.

Using known principles to guide transformation

There is no special methodology to build a robust, scalable interface that transforms custom elements into components. In fact, we should repurpose the good old fashioned software development and agile delivery principle to advance this noble cause. Some useful principles are:

Refactoring principle: the rule of three. If we see an element being repeated three times, we should work towards removing the duplication (in this case, by making them into a common component). In our experience, it is difficult to create the right generic component that would be reused from the beginning when the element is only used once in a specific use case. It is wise, therefore, to allow some duplication of elements to exist for some time until a pattern emerges and refactor them into components. Try not to predict the future (or YAGNI principle). Unless there is an obvious direction that a component will be reused or is already built, we should go with the custom element first. Putting aside resources for long-term gain. When we know beforehand that a particular element will be reusable, we should put in effort to make them customizable, not only from a development perspective, but also from planning the resources needed for it. This means that we should put in the effort estimation for the story/feature as well as communicate this importance to our clients/product owner.

Maintaining Component Library and Semantic Versioning

As more components are built, it might be worthwhile for developers to maintain a component library. An extensive component library will ensure that when there are new requirements coming in, components would be the natural choice in extending the code, thus the speed of development can be optimized even when the code base is extensive.

From developers perspective, building component libraries is similar to maintaining APIs, which means we should be thinking about how best we should model the concepts into components, what would be the logical inputs and outputs and how we could roll out contract changes so that our components do not break.

You could maintain semantic versioning for the whole library, groups of components or for each individual component. Initially, when the numbers of components you are maintaining are small (less than 10), maintaining individual versions for each component might cost significant operational overhead. However as the component library grows, logical grouping will start to emerge based on similar concepts of the components. For example radio buttons and checkboxes components may fall in the same category of “options” components which could be versioned together.

Be careful of reusability pitfalls

Although component reusability is mostly desirable in the design and development process, we also need to be mindful of the common pitfalls of over optimising for reusability and prematurely componentizing. The objective is not to make components to be as reusable as possible, but to really understand if they are scalable and resistant to near future changes.

Different scale, different strategy

Based on how large the scale of the project and team size, there are different strategies and investment on building (or not) components. Of course the scale of the project can change and therefore the strategy of componentization to support scalability should change accordingly.

Small scale project (5 people, 2 months)

Designers could sketch on paper 90% of the time

Developers could build a simple static page that had examples of the components

Designers and developers pair on Identifying components the first time they build something new

Medium scale project (3–4 teams, 1 year)

Reusable components are owned by a group of designers and developers from each stream or team

If everyone is working on a single code base and there may not be a need to take on the versioning overhead

The team can start maintaining simple component documentation library such as React StyleGuidist

Teams can form a Frontend guild which has some lightweight meetings to decide what to work on, what components they need, what they should look like etc. This can be done alongside product delivery.

The components are built as part of normal user stories by whoever needs it first. And then picked up by other teams and attached to appropriate stories.

Large Enterprise scale project (20+ applications, long-lived product)

Reusable components have a product owner. They need to do advocacy, support, align dependencies, be across new work, new teams, new products, rebranding.

Reusable components have a dedicated team. It could be 3 people, it could be 20. It probably changes based on the stage of the product and the type of work.

The dedicated team will maintain a shared component library with semantic versioning. One good tool to do this would be bit.dev

The team facilitates the collaboration. They do a fair amount of support, education, and consulting about how to adopt and use it. They take contributions from the rest of the organisation

The team’s product is the shared components library whose customers are designers and developers in other teams.

Building from the smallest blocks

To help with your understanding of how to build components, we will illustrate building a component with various structures.

Building Blocks

Building blocks are abstractions of commonly used properties that by themselves may not be visible. They usually abstract behaviours that we would like components who use them to inherit. Examples of this include whether they will occupy a specific space or behave in one way upon interaction.

A commonly used Building Block in our project. Flex will render a `div` with `flex` property to any component that uses it

In design language, these could be:

Design grid

Font family

Colour

Capitalization

Screen transition

Basic components

Basic components are the smallest visible component, usually containing only one concept and one behaviour. Examples include buttons, texts and fields. As a component, most of the properties inside the basic component should be customizable, with some that can be pre-determined or contain multiple customizable options.

A simple and extendable example of a basic component. `Button`’s `text` and `onSubmit` behaviour can be customized per use, while also providing discrete options for the `type` of the button.

In design language, these could be:

Buttons: consisting of text, colour, shapes, size, etc.

Fields: outline, background, font, colour, etc.

Composite components

Composite components are components that contain one or more basic components. The size of composite components can vary a lot, from a small promo code layout that contains a text field and a button to a complex form with multiple radio buttons and checkboxes.

An example of a composite component. `ListPicker` contains other basic components and building blocks such as `ScrollView`, `Box`, `Text`, `FlatList` and `ListItem`

Custom elements

Building blocks, basic components and composite components are built with reusability in mind. In contrast, custom elements are specifically built for a particular layout/page.They are not likely to be reusable.

The `TitleText` is built for a specific purpose, with predetermined position and size in mind. Thus its reusability is minimal

Transform custom elements to components

When we started on the project, the prioritization of project deliverables demand speed and thus many custom elements were inevitably developed. As the project progresses, developers and designers will naturally reuse similar elements and produce less new components, which result in an inverted pyramid of components.

The next step that must be done deliberately is to refactor down the custom elements to a lower tier, such that the number of custom elements would be the least and most elements will reuse components as appropriate.

Components: the foundation of scalable products

Scalable user interface is easy to maintain and flexible enough to be extensible as and when required. Components (elements that are designed, developed and used consistently across the interface) are essential for a scalable interface as they increase speed of development later down the line (and therefore decrease cost). To quote one client, “The only thing I regret about components is that we didn’t start using them earlier.”

The key is to start using them at the right time. If you start implementing them too early, you might be investing time on something that is not validated yet (risk of waste). If you start too late, you risk having to rework too much of already existing functionalities (risk of defects and cost of fixing broken interfaces).

In a mature product, we aim to have many reusable components and little custom-built elements. And most importantly, ensure you bake it into your stories and prioritize people’s effort on implementing it. All this requires a certain level of discipline and as Ajey Gore (CTO of Go-Jek) said “Discipline brings scale, scale disciplines.”