Shaping a Product’s Design Language Strategy as a Solo Developer

Lara Aigmüller

Posted in How We Work, Portfolio

I work on a web application, a human machine interface (HMI) to be precise, as the only frontend developer and UI/UX designer (part-time!). In this article I want to share my approach from defining design tokens to creating UI components and patterns for a user interface that is built and maintained by a small team of software developers who prefer writing backend code and struggle with CSS 😉.

This is the first of two articles in which I am going to describe how the project started and how we built the foundation of the ongoing UI and UX work. In a second post, I’ll dive into specific workflows and team communication that help us maintain and improve the product.

How it all started

I joined the team around three months before the initial release of the machine and its software. Many features have been built already using an existing component library but without any design guidance. For the team it was important to migrate features from an older product, make the PLC (programmable logic controller) communication work, add some buttons to the user interface that trigger the magic, and fill tables with data.

My onboarding and the task description was “the product is almost feature-complete, now we need to polish the UI.” My reaction was… well, let’s call it “astonished” 😅.

I started by investigating the UI library in use and found out that the only way to customize the visual appearance of components was to “simply” override the existing CSS. Not great, but I could work with that. I made a list of low-hanging fruits by evaluating the most used elements, which were (and still are) data grids (a.k.a. tables), buttons, dialogs, text fields, and other types of form fields.

Design tokens first

I wanted to start with a scalable plan right from the beginning and created design tokens as foundation:

  • font and typography
  • color palettes (based on a given primary color from the client’s corporate design): primary, secondary, success, warning, and neutral palettes
  • spacing tokens

The default theme changed from light to dark mode to match the machine’s style. By adjusting the typography, updating the neutral colors, and applying the primary color to buttons, progress bars, and form elements, the look and feel improved instantly. The user interface looked quite different from the well-known component library styles after adding just a few lines of CSS and felt unique.

Learning: In the beginning, small steps can make a huge difference already. The devil is always in the details. Striving for perfection right from the start is probably not worth the additional time to market and the impact on code review time; the last 20% can always be tackled later.

Style overrides for existing components

As mentioned above, the way to customize styles in our setup is overriding CSS. I won’t tell you the name of the component library we use, but the CSS selectors are at times very(!) specific and cumbersome to override 🫠. This was the first time I introduced cascade layers in a project, so I don’t need to start a specificity battle.

Step by step, I added new styles for the most important and heavily used components. I created CSS custom properties for all required colors, which was a huge advantage later when a light theme and new machine brandings came in. A lot of work in the beginning already paid off 😎.

As it is not possible to customize the UI library systematically, e.g., via a configuration file, I use a combination of CSS custom properties and SASS mixins to keep styles consistent across all UI components.

Learning: It took a long time to find a working CSS setup that is readable, robust, and easy to update. Even for tiny bits and pieces I created reusable color tokens and mixins instead of just repeating myself over and over again. Almost four years later, I can now reap the rewards of my work: adding a new theme with completely different color palettes or adjusting spacing values can be done easily without rewriting all existing files.

Custom components and patterns

Many parts of the HMI can be built using existing components from the UI library we use, but sometimes custom ones are required. These can be layout components for recurring micro-layouts, list items for product-specific entities, empty state or loading state visualizations, etc.

At the beginning of the collaboration, I took a look at all areas of the product and collected similar page structures and user flows. I could identify recurring patterns that were implemented a bit differently, like, for example, magic numbers for spacing between a headline and a data table, arbitrary usage of primary and secondary buttons, missing page hierarchy, etc. (Checking the git history revealed that they were built at different times by different people, which explained the randomness and inconsistency.)

I created basic layout components to help structure all pages in the same way. These were:

  • Application header
  • Page layout with and without a sidebar navigation
  • Cards to group section content on a page
  • Horizontal and vertical stack components for equal spacing between elements
  • Wrappers around titles with subtitles and actions
  • Callout/alert components to highlight important information

Document as we go

It’s great to have reusable UI components to work with, but it’s even better when somebody explains when to use which one and how! Early in the process we decided to collect all the design guidance in an existing Wiki the developers were already familiar with—our design system was born 🎉.

During the creation of new styles and components, I didn't wait for things to be finalized; I wrote down everything worth documenting the moment it landed in the codebase. Important is: to update stuff when it changes! (More about that in part two…)

Additionally, documentation about component properties (like component variants, sizing, spacing, etc.) was added directly to the code as comments, so developers can read the documentation while working on the product and don’t have to leave their code editor.

Recently, we introduced a dedicated page within the product (accessible to developers only) collecting all components used in the product in all their variants. This saves three purposes:

  • We always have a preview of components in their most up-to-date version (we don’t have the resources to maintain screenshots for the design system everytime a tiny thing changes)
  • Developers have a place where they can copy a basic version of a component to start working with
  • When design updates are being made, there is one place where errors could be spotted easily (instead of a visual regression testing setup)

Learning: Writing design documentation (a.k.a. maintaining a design system) is additional work for a solo UI developer and slows down progress when looking at the work achieved in one sprint. However, in the long run, it’s a great resource where all design decisions that have already been made can be looked up. This approach avoids unnecessary discussions and answers questions before they even arise. By following the established rules and patterns, new features can be introduced more easily into the existing product.

Work in progress

Everything described above—the design system, the documentation in code, the custom tokens and styles—is a “work in progress,” but so is the product itself. I don’t wait for things to be “final” to implement or document them, because in software development, “final” is a moving target anyway 😅.

As soon as small improvements to the current state come up, they get implemented and documented. The key is to keep everything up to date and ensure that major changes are always communicated clearly across teams!

My takeaway

Even as a small team, it is possible to maintain and improve an existing codebase while introducing new features. This requires people who value sustainability and not only care for development speed measured in new lines of code. I’m glad to be part of a project that allows me—as the solo UI/UX designer and frontend developer—to bridge the gap between maintenance and innovation… which brings me to part two of this series, so stay tuned for more!

Any thoughts or comments?

Say hello on Mastodon or contact us via email or contact form!