The Right Way to Implement Component-based Design Systems
Implementing a component-based system for your web application starts long before developers open a code editor. By pairing designers and developers at each stage of the creation, build-out, and testing of a design system, you will help ensure the end result matches the initial vision.
In a component system, code is written in self-contained blocks and connected to create an application (or multiple applications). The latter type of software development entails additional considerations: the potential of multiple frameworks, housing the component system separately from the applications, and greater naming concerns. This process guide is a good start for building any component system but focuses on those destined for a single application.
Components and web apps: the perfect pair
Component-centric design systems give teams greater consistency, maintainability, and efficiency in the process of building web applications.
Large applications and sizable teams necessitate intentional structures and processes to maintain consistency. Otherwise there will inevitably be unnecessary duplications, difficulty in tracking variations, and out-of-date functionality or styling.
Managing every instance of a component through a single definition alleviates these issues, and a system of components across disciplines keeps project teams on the same page. Everyone references the same style, function, and naming of the application’s building blocks. Changes to this system trickle down across design and development.
Component systems also provide sane management of design and code. When an application is composed only of components, the mountainous job of maintaining the entire app becomes a matter of maintaining its individual components.
With a single source, small edits or sweeping changes to components can be made from a single file rather than multiple. Work is done in one place and easily shared throughout the app. Since a single component can be flexible enough for many different instances, most instances of duplicate code are no longer necessary.
With a design system in use, efficiency in implementation skyrockets. Each component and its variations are considered before building begins. You reuse components instead of reimplementing them.
This efficiency applies to scaling the app as well. When there is a well-defined component system in place, the path to building on top of existing work is clear: Extend existing components or build new ones.
Design systems provide a layer of abstraction to the work of building web applications. Since the application is an implementation of a broader design system, you can evaluate and build upon the system, then carry changes downstream into the application. There shouldn’t be a need to first inventory the application or pore through dense business logic to get a good grasp on where to begin.
Design systems are better when all disciplines have a voice in their creation
The advantages of a componentized application are clear. The next question: How do you ensure the design system behind it is set up for success during implementation? No matter what final form the design system takes, the process needs to include each discipline involved with its implementation.
While designers certainly lead the design system process, it’s important to get developers involved early on. Developers are already accustomed to component-like structures (design systems borrow from programming concepts after all) and can bring a lot of insight into the process that might be missed without them.
When combining these disciplines, it’s easier to explore new and daring ideas that solve design problems specific to the project. Developers can share which solutions are possible and estimate the time, budget, and effort required to implement them. This adds a layer of context that might otherwise be unavailable to designers.
The earlier you have this information, the sooner you can plan around potential obstacles. Design decisions tend to have a snowball effect (each decision affects the next in some way), so creating feasible designs up front bypasses a trip back to the drawing board should an initial idea not work.
Aside from the primary dialogue around component details, it is helpful for designers and developers to discuss:
- Which proposed design solutions are most feasible given project constraints?
- Which design concepts require mockups? Sometimes documentation will suffice for details that are ancillary or are better implemented in code.
- Beyond the essentials, what content should be provided in the design system? Some examples are guidelines around responsiveness, animation, and state transitions, or oft-forgotten elements like empty states, loading states, and loading mechanisms.
How to build out components
Once the design system is formed, it’s tempting to start building its corresponding component system right away. Jumping code-first is usually a mistake (unless you have unlimited time and budget). A little planning beforehand goes a long way.
Step 1: Estimate the build
First, the implementers will want to familiarize themselves with the design system. It’s time to read through it, ask questions, and have any conversations necessary to gain a thorough understanding. The design system isn’t just for reference; it’s a foundational, evolving document that is worth getting to know from the start.
Rather than picking a few components to start tackling one by one, it’s first necessary to seek greater context on the relative importance and complexity of each component as it relates to the overall project. Gathering this information at the onset will prevent unnecessary rewrites later on.
This information should be used to estimate the time needed to implement each component (or groups of similar components), providing a helpful outline of what’s on your plate. An easy mistake is to base estimates on the visuals alone. However, it’s crucial to consider where these components will be used, the possible states they will be in, and the functionality tied into them. These factors add complexity to what might seem straightforward when looking at isolated components.
Step 2: Prioritize components
The next step is prioritization. Generally a best practice is to build the most basic components (think text, colors, buttons, icons) that are most commonly used before moving on to more involved components. You should also consider tackling certain global elements like navigation and page layouts early on. This is a good approach if you want to get page structures set before filling them in.
For the rest of the components, prioritize based on importance and urgency. This productivity strategy is known as the Eisenhower Box and is applicable at the level of features, as well as to the pieces that form them. Which components are most important? Which components are required early in the development timeline? Ones that check both criteria are likely the best next step.
Keep in mind that most components have dependencies (they are composed by other components) and differing levels of complexity. These factors will help set planning the development timeline. Also consider: What is the relationship between front end and back end development in creating these components? Does one hand off to the other? Who starts first?
Step 3: Write in a component-driven development environment
After the design system is in place and you know the order you want to start building it in, you can now make it come to life.
One of the recent advancements in creating components has been the advent of component-driven development environments. The leading player in this arena is Storybook, self-described as “an open source tool for developing UI components in isolation”. Storybook lets you preview components as you build them, see every component you’ve built, and view the possible states of these components — helping you stay organized and providing a high-level overview.
With each new component, start out by building its default state. Find the most essential aspects of the component and write a generalized, default version of it. Each component should usable in this existence.
For example, any given Button component will have a label and action to perform as well as a certain size, color, font style, etc. A default configuration of these characteristics will be the base usage.
Next, you modify the component to include other states it requires alongside the default. In the case of Button, it might have an error state, a state where it displays an icon with text, a loading state, etc. In most cases (especially for basic components), these states should be independent of each other.
It’s worth taking the time to set up easy access to each of these states, both to preview during build-out and for later reference. With Storybook, you create a “story” for each component state. So for the Button example, you might have the following stories:
- Button in default state
- Button in error state
- Button with icon
- Button in loading state
Here’s what the Storybook markup might look like for the Button component stories (built in React and passing props to Button to set its internal state):
Pretty simple, right? Besides stories for each component state, it’s sometimes useful to have a story showing the component in common context patterns. For example, if you commonly nest buttons inside a card component, you may want to show this pattern as a story of its own.
Component naming and folder structure
Naming is important when building a design system. You want to choose short, obvious names. Ideally these will correspond with the names of each component in the design system, but there may be reasons to name them differently in code.
Naming also denotes structure. For example, NavigationMenu is the name of an icon component that exists solely inside a Navigation component. NavigationMenuItem would be a component that is further nested inside NavigationMenu and is limited in scope compared to its immediate parent and even further limited than its grandparent component.
Folder structure is also important in designating hierarchy among components. There’s no single perfect approach, but you’ll likely want to distinguish among:
- Base components: the simplest type used all over the app
- Examples include buttons, headings, icons, inputs
- Page-specific components: used only on a single page (these might live in the same file as the page or as their own files in that page’s folder)
- Other components: everything else
Testing components and iterating based on feedback
Testing and feedback aren’t just for evaluating a final product. As in the creation of a design system, early communication among disciplines is helpful during implementation.
You want to get the basic components right from the start. Since they are used throughout, you’ll want to bring core components to final form before instances of them appear everywhere. Changes to these late in the game can require editing many files and result in more work to avoid unintentionally affecting other component instances.
Once you’ve made some headway in building out the design system, give designers access to test components and provide feedback. Design tools are becoming more dynamic, but there’s nothing like interacting with components in the browser rather than static designs. Ideas for adjustments often arise during testing, and these tweaks are ripe for pairing opportunities with developers.
If you’ve utilized Storybook to set up components and their states, you already have a great tool for designers to complete visual reviews. For those who don’t have local access to the project code, it’s easy to deploy Storybook for easy sharing. This guide walks you through continuous deployment of Storybook to Netlify (free) and setup is about as easy as running it locally.
With a Storybook testing setup, it’s easy to go the extra mile with the Storybook Knobs add-on. This handy tool makes it simple to test changes to text, numbers, booleans, and other objects that affect or control the component’s state. This allows designers to put a component through its paces without touching code. While stories are still the go-to for viewing a component in all its states, Knobs adds an interactive layer that is especially helpful for catching edge cases.
Another pairing opportunity at this stage is to collaborate on scenarios not accounted for in the design system when they arise. Usually these tend to be smaller in scope and complexity, and designers and developers can work together to find a solution that makes sense at this stage in the project. Developers may also have new suggestions at this point now that they’ve begun implementation.
Design systems are living documents, so be open to extending or revising them during testing. It might feel like on-the-fly changes are haphazard, but when thought through by the team, they can bring real benefits since the implementation phase provides insight previously unavailable.
The real-world advantages of design systems
The benefits of a well-built design system were pivotal to Envy Labs’ development of the Moonllight fantasy soccer web app. With an information-dense UI, we wanted our layout to be set up for easy reading and interaction. Mobile design was ongoing with implementation, which had the positive side effect of showing which desktop-sized components were naturally responsive.
While thinking through responsiveness, our lead designer removed padding around containers, making them full-width on narrow screens. The entire mobile layout was improved by tweaking a single line of CSS. It would have taken at least a couple hours to make this same change throughout each design file or to draw mockups to compare the two options.
Not all changes are as easy to implement, but the principle behind disseminating design adjustments remains. At this point in the project it was both faster and more helpful to document these changes in the design system than to apply it to previous mockups. The relevant layout examples were updated, and the design direction was clear going forward.
Don’t leave this process of testing, feedback, and discussion until after implementation. Having these conversations early will elevate the design system, ensure the vision is carried through implementation, and that it is iterated upon as better ideas come forward.
There are a great number of advantages to using design systems for your next application. Do it right by establishing opportunities for communication and collaboration amongst the team at each stage of creation and implementation. Lastly, tools like Storybook are helpful for aiding the implementation process, so be sure to find ones that work well for your team and use them to their full potential.
When turning a software idea into an actionable plan, you must ask the right questions during discovery.