Building a native design system for iOS

By: Kevin Beaulieu

Image for post
Image for post
Photo by William Iven on Unsplash

What is a “native design system”?

Why now?

By putting an emphasis on accessibility and detail, we end up improving product quality by adopting a design system as well. Building a polished, accessible app can be extremely difficult if every page uses its own custom components, since the work to make high quality views must be duplicated across every page. By standardizing around a single set of components, we can put in the work once and reap the benefits across the entire app.

Architecting a new component

Parity should be maintained across platforms where possible.

Name parity: Platforms often refer to components differently. For instance, what iOS engineers call a text field, web engineers call an input. When everyone is using different names to talk about the same component, discussion can quickly get confusing. Therefore when building new components, we standardize on a single name to use across all platforms.

Design parity: Consistency in the appearance of components is likewise important for ensuring that users experience the Thumbtack brand in a consistent, recognizable way regardless of the device they are using. There are some exceptions to this guideline, such as when different platforms have different design patterns themselves, but for the most part we try to maintain a consistent experience across our iOS, Android, and web products.

Standard usage should be easy. Customization should be possible.

To decide where to land on this spectrum, consider that a design system has two primary goals:

  1. Increase product quality
  2. Increase developer productivity

If an engineer is implementing a design that fits squarely within the bounds of the design system, we should make that as easy for them as possible. If a design is breaking some of the design system guidelines, we should make that harder, to serve as a cautionary reminder that what the engineer is trying to do is not standard. That said, if the design is only slightly beyond the guidelines, we shouldn’t require the engineer to build everything from scratch — the amount of friction introduced should be proportional to the rules being bent/broken.

As an example of how we optimized for this, we can take a look at our Button component. Buttons in Thumbprint can have one of five themes: primary, secondary, tertiary, caution, or solid. The button theme defines things like the button’s title color, its background and border colors, if any, and whether it supports a loading state. These five themes serve 99% of button needs within our apps, but there’s always that 1% of pages that need a button which doesn’t quite fit into one of these themes. To enable engineers to create such custom buttons, we implemented button themes as a struct with a public constructor.

public struct Theme {
public let titleColor: UIColor?
public let activeTitleColor: UIColor?
public let disabledTitleColor: UIColor?
public let backgroundColor: UIColor?
...
}

Defining the five standard themes was then just a matter of creating constants with specific constructor arguments:

public struct Theme {
...
public static let primary = Theme(titleColor: Color.white, ...)
public static let secondary = Theme(titleColor: Color.black, ...)
...
}

So for the 99% of cases where a standard button theme is sufficient, engineers can construct a button like Button(theme: .secondary). Then for one-offs, all they need to do is call the Theme constructor directly with whatever properties they want their custom theme to have, and then pass that into the Button constructor instead. If a custom button needs to deviate even further from the design system, such that customizing the Theme properties is insufficient, then the engineer is forced to fall back on using vanilla UIKit to implement their custom button from scratch. While this requires additional work, the friction serves as a reminder to the engineer & designer that they are not following the guidelines of the design system. We have found this pattern of defining components' properties as Swift structs to work quite well.

Using Thumbprint should feel familiar to engineers who have experience with UIKit.

What’s next?

Originally published at https://engineering.thumbtack.com on February 28, 2020.

Thumbtack Engineering

From the Engineering team at Thumbtack

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store