We've been spending more and more time lately working on the "in between." No, I'm not making some reference to the upside down or other convoluted movie plot lines. I'm referring to the gap between UI design and software component development. I'm being very specific here about the challenge. It's the point where the most fundamental elements of a design system—components and patterns—get built using code and become real functioning widgets. Having worked in agencies, startups and large enterprises for the last twenty years and seeing tech evolve, I hoped by now that this gap no longer existed. Unfortunately, designers and developers still feel the friction when trying to create and implement core concepts in software. Design system management and the tools both designers and devs use is an emerging discipline called DesignOps. DesignOps experts specialize in this "in between" space to help make designers, engineers and businesses more efficient and scalable.
What we'll cover:
- Understanding Components and Variants
- Figma Auto Layout vs CSS
- Building Components
- Organizing Your Components
Designers' tools improved dramatically over the past 5-10 years. Way back in the day we utilized Adobe InDesign to create reusable page templates to save time and avoid costly copy/paste errors. A bunch of wireframing tools came along, but nothing could fully supplant Photoshop in the world of design (with a little dash of Illustrator from time to time).
Until Sketch. Sketch proposed to be the first design software to bundle together vector graphic design capabilities, basic raster imaging and the oh so powerful Symbols. A collective sigh of relief echoed through office hallways as the design community finally achieved truly reusable elements. Granted, documentation, collaboration and transfer of assets still relied upon other tools, but at least we had a great new solution for working on our UIs. Well, at least on a Mac.
Then Figma. Figma stepped into the role of cross-platform and collaborative design software. It took many of the great things from Sketch and improved them by putting it all in the browser. Now devs on PCs and Linux could review the designs without the need of a separate documentation layer. All of the files were stored "in the cloud" which meant no more Dropbox Sketch file sharing. Sketch did improve and brought about near-feature parity over time. Figma, for the developer-minded designer, was already the winner. The migration needed to occur.
We were very early switching to Figma and honestly it probably made things easier. Without so many assets in Sketch, there was less to transition. Instead, we experienced teething moments as Figma dramatically changed (for the better) the way Components worked. There was much rework.
That's the end of the history lesson and subtle difference explanation between Sketch and Figma. There are plenty of articles out there to help you and your organization make a decision if you should switch or not. We're big Figma fans and have learned a ton building and enhancing design systems over the years.
This article is for you to learn some tips and tricks to migrate to Figma. And if you have questions or get stuck, send us an email. We'd be happy to chat.
Let's get this out of the way. You must rebuild your components in Figma.
Annnnnd that's where a bunch of you leave the article and say things like, "Nope."
"Why?" you bleat and plead for your sanity. Simply because the way quality Figma structures empower your designs means there is no way to migrate. Yes, Figma can import Sketch files, but this will not maintain your components and set them up the way you need. It's a quick and dirty way to get files transferred, but it's certainly not going to help you get your design system up and running. With that said, there's really not too much value in referring to Sketch Symbols when creating our new Figma design library.
Therefore, the best practices will really try to reframe how you approach components to work best with code.
Understanding Components and Variants
Not too long ago, Figma introduced the concept of variants. Prior to variants, each component would be exchanged with another component to represent state changes. An input field might have three different components: default (empty), placeholder text, and valued. The designer would swap out the component lock, stock and barrel. If you wanted disabled versions of each, they became components as well. This worked just fine and enabled the UI designer to quickly change between components. Navigating the complex tree menu within the small side panel was quite annoying, but doable.
"An ounce of prevention is worth a pound of cure" —Ben Franklin
The best way to build components is with other components. A key to scalable and reusable software is creating atomic components which themselves are consumed by other components. Inheritance in Figma, unfortunately, is limited to a single other component (as of the publish date of this article, but hoping for a change). It's not possible to have two different states and merge them together to create a new combined state. Using our input example it would make sense that all disabled inputs appear the same regardless of the type of value/content within it. It would also be ideal if the value (text) used the same font and color regardless of its focus, active and hover states. Figma does not allow the user to define "rules" which may be composited together to create a state, a very common practice in CSS. This was true before variants and this is true today. Instead the user must manage these states manually.
Variants helped this a bit by combining all the related components into a single component. Instead of navigating through a deeply nested naming convention to find the swappable state, variants empower the designer to swap states with a toggle or prop selection. Ultimately this didn't change the way designers create and manage components. It did at least make Figma's UI a little easier to use. The lack of "rule" support still plagues Figma making it impossible (without a plugin like Recursica) to cascade rule changes down to all the various elements. It means that creating every possible combination of state for something as simple as a button is a lot of work to build manually.
Left icon, right icon, loading, disabled, spinner as well as size and color instantiates a lot of combinations. Prior to variants, the same number of components would exist. At least now they are easier to use. No more hunting through a list of tree branches to find the right option.
What does all of this have to do with best practices of migrating to Figma? Knowing what it will take to build and maintain proper components is critical to building a usable design system. We designers often leave a vast majority of state combinations unstyled and hope for the best. The truth is these variations exist in the code so it means they might exist in the software. By properly building our design system to express all of the conditions, there won't be any surprises when we start validating the implemented UI.
Figma Auto Layout vs CSS
Note: Here's a bit of a translation between Figma and CSS. It's more background to help the terms and concepts of Figma Auto Layout make sense to someone already familiar with HTML/CSS. If you want to skip to the next section and start building the components, that's fine by me.
If you're a pure designer and haven't dabbled in HTML/CSS, you might want to go read up on flexbox. Figma is a lot more centered around the concepts of creating basic styling based on the rules and concepts in CSS. This moved front and center with the introduction of Auto Layout. Auto Layout is essentially flexbox, but using weird terms that are unnecessarily confusing. It's almost as if the design team at Figma intentionally avoided using CSS terms as, in their own way, flexbox terms are confusing. Unfortunately this meant introducing even more bewildering terms like "Packed" which we now need to memorize.
This is actually a great lesson to keep in mind for UI design in general. Learned patterns, terms and behaviors that are wrong, but widespread in their adoption, should not be replaced simply because they are wrong. This knowledge must be leveraged. Don't fix something only because it's broken if everyone knows how to use the broken system already. Do that and you better be ready to tackle the education that comes along with your righteousness.
Add Auto Layout to a frame
CSS: display: [flex] and flex-direction: [row | column]
The concepts are identical between Auto Layout and CSS, except the default direction for flexbox is horizontal while Figma is vertical. Flexbox has the ability to reverse the sequence which Figma lacks, but this isn't really something commonly used in code anyway.
Auto Layout "Alignment and padding" control (alignment only + packed/space between)
CSS: A combination of:
(a) justify-content: [flex-start | flex-end | center | space-between | space-around | space-evenly]
(b) align-items: [flex-start | flex-end | center]
This is so unnecessarily complicated. In a lot of ways, the Figma approach makes more sense. It visually displays where the content anchors within the container. However, due to Auto Layout combining two flexbox styles and two different "justify-content" properties, it takes a bit of thinking based on the Auto Layout "direction" to code properly. Auto Layout presents a dropdown with the cryptic "Packed" label as the default. This is the same as flexbox "flex-start, flex-end and center." Switching Auto Layout to "Space between" gives you access to flexbox "space-between." There is no direct equivalent of flexbox "space-around" or "space-evenly," but these are rarely used in CSS as they are a bit unpredictable.
Auto Layout "Spacing between items"
CSS: No true equivalent (except padding on contained items)
Figma makes it easy to space items by a set amount which causes the container to grow/shrink with the content. Flexbox doesn't really make this possible and adding "display: flex" might actually cause you problems if you're trying to space things precisely. You'll need to resort to setting padding/margin on the inner items. The closest equivalent would be using CSS "grid" and creating a gutter.
Auto Layout "Padding around items"
CSS: padding: <number>
Flexbox isn't really involved here as this is simply a padding set on the container. It's annoying that Figma requires Auto Layout to be turned on to set padding for a container. Without Auto Layout, Figma treats the spacing as "margin" and requires a very different setup.
Auto Layout "Alignment and padding" control (padding only)
CSS: padding-left: <number>, padding-right: <number>, padding-top: <number>, and padding-bottom: <number>
A totally separate control within Figma if you want to set different padding for each side of the container. Figma does a great job here of utilizing the shorthand version of "padding" in CSS or setting independent padding by side. This matches well and is an easy translation.
CSS: align-items: [stretch]
It's critical to know that Figma has a separate control for determining how the container will respond to items within it. When Auto Layout is turned on, there are new options within the "Resizing" section such as "Hug contents" and "Fill container" depending on how you have your Auto Layout settings configured. The "Hug contents" will shrink the container around the content while "Fill container" will expand the container to fill the parent regardless of the children sizes. Very confusing. And it often requires fiddling.
Components within components. It's a simple concept that will give great benefit when built properly. To demonstrate how we might construct a scalable design system, we're going to go through building a few basic components and turn them into a form component.
- Label - a text description associated with an field that has two states: optional (default) and required
- Input - a form field with several possible conditions: placeholder, valued and error
- Error - a flag indicating a problem with a field
- Form - a combination of the Label and Input (which in turn is a combination of the field and Error flag)
Building the Label
Assume that all components will gain complexity over time. While the Label may seem simple, let's go through the proper steps of building a full component. As new requirements modify the component over time, we'll be able to scale it without rebuilding it. Remember, all component changes will cause ripples throughout the designs. We want to make sure these ripples are not tsunamis.
The first thing we do is build a container. Let's throw a frame on there and do the natural steps of turning on Auto Layout.
We're not too worried about many styles here as it's a simple example, but we do want to make sure that we have some basic settings on the container. Note that the container layer name is "Label / Default." Even for the most basic component we should use proper slash notation as it will simplify the process of combining the components into variants later.
The text inside the container isn't using Auto Layout. It is set to "fill container" (a value that only exists because the parent is an Auto Layout) with the text left-aligned.
Now that we have this basic component we're going to make a new version for the required field and inherit this basic component.
There are two steps to making the required version of the Label. We've created an instance of the first component and then wrapped that into a new component called "Label / Required." Adding a red asterisk and doing some minor resizing of the Label instance will make sure they are the same width. Changes made to the font, size, etc of the "Default" component will automatically affect the "Required" text.
Next we simply highlight both items and click "Combine as Variants" and now we have our two states. We're going to change a few more things in the naming of the properties so that the variant will show a toggle.
You might be wondering why we made a static width for the Label. Once we combine this component with our input field inside a frame with Auto Layout itself, the spacing will be maintained for the width of the Label keeping our form fields nicely aligned.
Building the Input
The Input follows many of the same principals as the Label:
- Make sure to create a base component that is inherited into other states
- Combine the related components into a variant
Let's just jump ahead to show what our new Input looks like before we turn the components into variants:
There are four new components here. Three are variants of the Input representing the empty (default), valued and error states. Note that both the "valued" and "error" components inherit the base default component. Just like the Label, this allows the designer to change the base element styles like width and height and the other states will automatically adopt these changes. Overrides like the border color, font style and font color are exactly that: styles added on top of the base component styles. You can think of it like CSS declarations further down the stylesheet that control the appearance.
Very important note: Use the containing "frame" itself to set borders and background colors. Auto Layout will not work properly if you're trying to put backgrounds in with other rectangles. Also, styling the container is aligned to HTML/CSS so get used to this technique. It might feel a bit foreign at first if you're used to drawing all the visible elements, but it will make your design much simpler to implement in code.
The fourth component is an Error flag that appears next to the field within an Input. The container uses Auto Layout to stretch the flag based on the length of the content.
Combine the three fields as variants and do a bit of prop renaming and we're all set with the Input field.
Remember, the first prop value in the list of variants (e.g. "Default" in the "State" prop for our Input) is going to be the default state when you instantiate a new component. Otherwise, it will pick the top-left variant in the group. A bit odd and it sometimes does whatever it wants so be aware.
Combining the Label and Input into a Form Component
Now that we have our two components Label and Input we're going to combine them together into a new Form component. Typically forms are designed to a specific pattern, in this case with the label to the left and the field on the right.
What are the benefits with this approach?
- The form can now be replicated into a design with global governance down to a field level.
- The UI designer no longer needs to worry about "how" to represent basic states as they are all inherited.
- Changes to either the atomic Label or Input will automatically ripple through the components.
- This structure mimics closely how the components will be built in code.
Building the Form Starter
I often build what I call Starters which you can think of like the shortcuts to building more complex pages. Anything with repeating rows or elements are great candidates to be turned into a Starter. When pulling the Starter into a page/view, I often "detach" the Starter from the component. This enables me to move things around, but the basic structure is there and easy to copy. All of the sub-components maintain their connection with the atomic components which is great.
If you do detach the form fields from the Starter, you'll be free to change the sequence of fields using the up/down arrow keys. This is great when you're trying to refine the sequence of fields to make it as usable as possible. No more cut/paste.
It's not always necessary to "detach" the Starter from the component. Even with the above example, double-clicking down into the elements (or selecting them from the layers inspector), it's simple to toggle the variants and change the text.
As a general rule, try to avoid detaching the instances whenever possible. Once they are detached, you gain the freedom to manipulate them willy nilly, but you also lose the inheritance and control provided by the design system. You've been warned.
Organizing Your Components
I've tried a few different ways of organizing components into a Figma library. I've put each major component in a separate file and then each file may be imported independently. The best, though, seems to be putting all the library components into one Figma file and adding shared/related components together on a page.
The primary benefit of one file is that all the components come in together. Instead of having to pull them in one-by-one and possibly miss a library file, the library is imported as one giant set of components. It also makes it a lot harder to get "out of sync" situations where a component in one file is used in a different library component. In these cases the file might be out of sync multiple levels deep which is annoying to diagnose which component/file is causing the problem.
Everything is in one file which makes it way more complicated to keep organized. It will take a lot of diligence to make sure the design system is organized Day One. Making changes after large-scale rollout will cause possible headaches. The good news is that using a single file system isn't really a problem as the component navigator in Figma doesn't care about structure. Most large design systems rely heavily on search so be sure you name your components, variants and layers well.
Taking it Further
You're now excited about how Figma can work for you, right? But it's a daunting task to build all the components. That's where we can help. At Border, we specialize in helping companies and agencies build better design systems using Figma. We've even built a new free tool called Recursica to help jump start a new project. Drop me a note and let's figure out how to tame the beast that is your design system.
If you delve into the world of Figma design systems and convert your Sketch library, godspeed and know you are not the first to bravely ford the river.