How user testing refines our pattern library development

Posted by Vincent Nalupta on

“We all do UX.”

Grubhub designer

Grubhub differs from other companies in that we all, from product designers to engineers, focus on delivering the best UX (user experience) to our diners, no matter the job title. We are diner-obsessed at the core of everything we do, and therefore we all do UX.

Most product delivery teams tend to think about UX as how it applies to end users. For those of us on the Pattern Library team, while we are always thoughtful of the diner, the main consumer of our work is the engineering team. How we take the same methodology and apply it to our internal teams has been a point of cross-team innovation. We’ve been leveraging classic UX user-testing techniques to guide our software development to produce the best possible product for our engineers to use. Specifically, task analysis, think-aloud, and feedback loops were leveraged.

This article covers how we created a new spacing system, tested it on our internal team, and incorporated the feedback into the final system.

The Need for a Common Language

Engineers need clear specs before the start of any task in order for it to be completed. Unfortunately for designers, specs are inherently time-consuming to create. Even small changes can result in hours of tedious pixel-pushing. We needed to get to a common set of talking points so that we can simply label patterns on a sketch file and deliver them to engineering to build. To do that, things had to change from both sides of the product delivery team.
Here is a sample of how design used to spec:

One could imagine how long it might take to create all the details after the actual design work was done. And now consider a case where something significant might change in the spec, and all of the lines might have be to be drawn all over again—not an optimal use of a designer’s time.

Conversely, here is the idea of an optimal spec: no tedious measurements, just pattern names. If any last minute changes occur, design can simply re-assign pattern names and communicate those to engineering. No more custom CSS code at all. The changes are made solely in the markup. Compared to our previous system, it’s easy to see that the time and effort savings are enormous.

Creating the System

Our design team got to work creating the rest of the system while we ported over the grid. The system they designed was largely based off of learnings from the work done at Eight Shapes. At this point we made a few very important decisions:

  • We removed automatic gutters from the grid, in favor of adding them manually, to give us more flexibility with our layouts
  • We re-baselined our spacing system on multiples of 4px and
  • Created an entire suite of utility classes to control the spacing in and around elements and rows. Previously, we had random helper classes that were multiples of 5px. Through research we found that 4px is becoming the industry standard because divides evenly into our breakpoints, such as 768px.

This new system was a little bit of work, and a lot of discussion. Here is the SCSS code to accomplish the spacing levels that we used.

// global spacing
$SPACING_LEVELS: (1, 2, 3, 4, 5, 6, 8, 9, 11, 12, 15, 16);

 * Initialize the SPACING_MAP from the SPACING_LEVELS
@function generate-map($map: $SPACING_MAP, $levels: $SPACING_LEVELS) {
    @for $i from 1 through length($levels) {
        $tempMap: (
            nth($levels, $i): nth($levels, $i) * $SPACING_BASE,

        // This is similar to pushing values onto the stack
        $map: map-merge($map, $tempMap);

    @return $map;

The output of this code is a SCSS map full of variables that we could then use with a simple getter, get-spacing-value(2):

 * get-spacing-value: Function
 * @param $level
 * This function takes a level from $SPACING_LEVELS
 * and returns the pixel value of that level accordingly
 * Examples:
 * get-spacing-value(2) => 8px
@function get-spacing-value($level) {
    @return map-get(generate-map($SPACING_MAP, $SPACING_LEVELS), $level);

Refining the System

With a basic foundation in place, we were ready to pressure test the grid. We began this process the way we normally would. We put the spacing system onto our apps and went through our testing routines. Everything worked fine, so we felt pretty confident to launch it. However, for a changeset this large, we decided to try something different to really make sure we had something perfect for the team. Our reasoning was that while we’ve fulfilled all of our requirements of what the system is supposed to do, we never took into account how easy it is to do it.

I devised a series of user tests that I affectionately called, “science fairs”. The science fair was a simple task analysis. We gave an engineer a sandbox with the new spacing system. Our design lead sketched out wireframes on a whiteboard with the new system. The engineer then had to implement the spec with the new system’s CSS utility classes while I took notes. We encouraged the engineer to think aloud as they coded. We then collected and documented their feedback, and used the input to refine our system.

It took three science fairs with three different engineers to finally nail down the terminology, but the final product was monumental: we produced a robust set of spacing and grid patterns, with common language shared by both design and engineering.

At times, compromises had to be made. Initially, full page rows were called “responsive insets”, because the padding scaled up or down at different media breakpoints. This led to a lot of confusion amongst the engineers – something we found out during user feedback with them. Internally, our engineering team was already calling them “sections” because of the HTML <section> tag. After some discussion, we decided to change the name of the pattern to simply “section” and drop the responsive name altogether.

In other cases, engineering had to compromise. We originally had a full set of padding and margin helper classes that we used to fine tune certain components. Design was firm that we should only include margin right and margin bottom spacings. Their reasoning was that if an element needed margin, should it go on the top or the bottom? While this might seem like a trivial thing to be decided by the engineer, it created a point of inconvenience to the design team. Ultimately we agreed that in cases like this, we want to limit the tools we give to engineers. We relented to design and created utility classes for right margins and bottom margins. We now term this pattern “stack” spacing. Stack-x spacing applies bottom margins and stack-y spacing applies right margins.
Eventually, all terminology was finalized and committed to the Pattern Library. This is a snapshot of the documentation in our living style guide.

Evaluating the Process

Looking back at this process, some might say, “Why even bother?” We could have just implemented the system and had a brief onboarding meeting to educate the team. It would have saved us an extra week of work and at first seemed silly to add so much overhead for internal software. But taking that approach, even for an internal tool, goes against all of the processes we have in place for modern software development. You could make the case that simply building and having a training meeting is akin to the old waterfall method of product delivery.

Taking a user-centered approach allowed us to get something in front of the user quickly, poke holes in the software, and refine the system as we went along. The end result of the extra time was a more robust system and a fully onboarded team. Testing users early, before going live, saves cost and development time beforehand, as opposed to patch fixing things after the fact.

While the Pattern Library is currently in a good place, as with all good UX, we will never be done. Continuing to listen to our users and adapting to their needs, will always guide the evolution of the pattern library. Because of this, we can confidently say that adopting a user-centric approach to development has made our Pattern Library the best it can be.