The Sorry State of States

Today’s Figma assets show design could align better with code

Nathan Curtis
12 min readDec 3, 2024

A few years back, I wrote Crafting component API together. Figma had become and remains the hegemonic design tool, and its capabilities for shaping components with a useful and usable API continue to strengthen. Libraries have taken deeper hold.

Yet, even the best system designers are missing opportunities to mimic how code actually works. No clearer is this than in the sorry state of the state prop of many interactive elements like text input, text area, dropdown, checkbox and radio button.

This post explores how design systems architect states in Figma assets inconsistent with how code works. The text input offers lessons about how to model props, from partial sets of option combinations and interdependent props to booleans versus enumerated options and just how far we should go — or not go — to purely model Figma assets.

The State of States Today

States is a generic term that designers and developers apply to a wide swath of situations. Eric Bailey’s lengthy list of all the user facing states highlights this well. There’s 38 in all, from rest, hover and active through disabled, readonly, selected, deselected to less common considerations like loading, ghost origin, and dirty.

Excerpt of example states from Eric Bailey’s All the user-facing states

Curious Chrome inspectors could select a text input, right click to select Inspect, and find a bevy of element states (:active, :hover, :focus, … ) including a robust set of HTML form validation hooks (:disabled, :valid, :invalid, … ) that front end developers can take advantage of with CSS combinations.

Chrome inspector’s states of a text input, including specific element states

Trouble is, the term states is exceedingly generic. All possible states aren’t alternatives. Instead, available states are a mishmash of interdependent concerns often with logical relationships and used in combination.

Yet when used in design critiques discussions, design specification section headers, and particularly in Figma asset properties: state becomes a glorious catch-all of insufficiently modeled ideas.

Visual designs of rest, hover and focus states of a text input

For a text input, of course we’ll show hover and active. Given that, we’ll pause and ponder whether to call the first one base (don’t), default (that’s ok), enabled or initial (better) or rest (best?). Nah, let’s go with resting I guess. Our impulse is to move on.

Visual designs of disabled, error and success states of a text input

Then complications arise. What about disabled? Of course, that’s needed. And readonly? Oh, I’d not thought of that. Obviously error state is a must. The states set grows gradually, stabilizing when we’re satisfied we’ve captured enough. Designers can be forgiven for not solving for every single “Eric Bailey State.” I never have. That list is crazy long!

States Today in Figma Assets

I examined the Text input / Input / etc Figma components published on the Figma community made by the following design systems:

Text input Figma components from Atlassian, Github and Shopify, normalized and simplified to include only properties relevant for states to enable comparison

state property patterns matched what I observe in teams I consult with:

  • Hover: Six of ten support a state:hover (or state:hovered) option.
  • Active: Two support a state:active option.
  • Focus: Eight of ten support state:focus and none offered focus as a separate boolean property.
  • Disabled: Nine of ten support state:disabled and only one (Atlassian) distinguished isDisabled as a separate boolean property.
  • Read only: Six of ten support state:readonly, while none offered readonly as a boolean property.
  • Error and Success: Eight of ten support an state:error (or similarly named validation) option, while one (Atlassian) offered error as a separate boolean property.

Components occasionally included other state options like warning, skeleton, and typing. One even included filled, which drifts towards the adjacent state challenge of value and placeholder properties not covered here.

No Figma assets supported plausible state combinations like readonly + focus or hover + error.

States Today in Code Libraries

A similar review of Text input component code from those libraries yields a different prevailing model.

  • Hover and Active: Most code libraries implement these implicitly as states that respond to user interaction.
  • Disabled and Read only: Nearly every code library implements these props, enough to consider both props as conventional.
  • Error and Success: Many code libraries implement an error (or inInvalid, hasError or similar) boolean prop to present an error display. GitHub Primer expands from a binary to enumerated validationStatus prop to distinguish error from success. Some code libraries implement an error prop for error text.

Shopify Polaris’ React component implement Typescript interfaces for disabled and readonly suggesting that an intentional convention across components. This library also implements a forceable focused.

Comparing how assets work today versus how they’d work if aligned with code

So design and code are different. So what?

The evidence is clear. Beyond interactive states like hover and active, Figma assets and code components offer distinct API signatures. Actually, let’s be more precise: design assets published for reuse by product designers are different from code assets that product developers use.

It could be that the design solution handed off by the system designer to a system developer is perfectly consistent with what happened in code. Nah. My experience suggests otherwise. Design iterations and specifications (often automated with the EightShapes Specs plugin) almost always reveal the same “States as kitchen sink” approach. Why should we care?

Corrosive impacts on efficiency, usability and satisfaction

At a minimum, most designers are aware of the common pitfalls of imprecise and/or incomplete design delivery. This leads to challenges like:

  1. Handoff friction as product designers deliver to product developers using published Figma assets that use different models from code.
  2. Poorly organized props in Figma assets that are harder to use, littering one or a few props with various choices that are actually independent.
  3. Continued distrust and disrespect of developers seeing designers as less or ill-equipped to model component props effectively.

That last one hurts, perpetuating an at times justified “don’t touch my stuff” attitude that some developers persist have when instead we could be working to craft component API, together. That’s a subject for another day.

Limiting impacts on automation

Less visible to some but far more important to many of my current collaborators are the negative impacts to taking advantage of Figma’s API to automate and cleanse increasing areas of a system’s surface. Poorly modeled properties can:

  1. Limit automation opportunities to audit and report on alignment across design and code libraries.
  2. Limit the ability to use assets as data to inform, serve as a source of truth, or even automate code development.
  3. Increase costs of mapping complicated (and brittle?) transformations in tools like Figma’s Code Connect that could be avoided.

My belief about API alignment across libraries including design has only hardened further since I wrote about it in 2021. When I see so many revered design systems unnecessarily diverge from code (just like those I work on too), I want us all to do better. This particular challenge isn’t hard.

Modeling Prop Combinations and Interactions

So let’s dig in, step by step, to think through stateful properties and how they work. We’ll start with the cleanly combined nature of selected and state found in radio buttons and checkboxes. From there, we’ll use Text input’s disabled, readonly, and validation states to learn how properties combine and interact with one another.

Complete sets of combination

Some types of states combine to form a complete set of relevant combinations. For example, Checkbox and Radio button combine interactive state (rest, hover, active and focus) and selected state (not selected and selected) into eight combinations.

The following table illustrates that for every combination of state and selected, the combination exists (depicted as ✅) and could warrant a distinct visual design.

Combination table of state versus selected

To Radio button states in a Figma component, one could include a state property with options for hover, active, focus and selected. However, that would omit possible combinations like selected and hover simultaneously. Those could be added as additional state options like hover selected, but risks sliding down the slippery slope of providing all combinations via compound option names. Better to separate concerns with distinct Figma variant properties for selected (even if it’s options are true and false, the visual design varies) and state.

Radio buttonwith selected as a separate prop versus as options of a state prop

Checkbox’s potential of a third checked state — indeterminate — results in 12 possible combinations, make the enumerated state list of compound terms less desirable. Additionally, the checked property would shift from a binary variant to one revealing enumerated options of not checked (default), indeterminate and checked.

Checkbox with selected as a separate prop versus as options of a state prop

To those brave enough to review Eric Bailey’s states list in detail, you may notice even more relevant considerations such as Deselected. For most teams, it’s impractical to include Deselected (having been changed from Selected) as distinct from Rest (neither selected nor interacted with).

Partial sets of combinations

The disabled property is different, as a variant set to true or (by default) false. Disabled controls are essentially at rest, visually distinct and hover, active and focus states are irrelevant.

Combination table of state versus disabled

As a result, the designer has a choice: separate disabled from interactive state or include disabled as an option of state. Either choice results in a partial set of five out of eight possible combinations, so long as the Figma asset doesn’t implement disabled states not at rest.

Text input with disabled as a separate prop versus as an option of a state prop

Would it be convenient to stuff a disabled option into the state dimension? Sure, it feels easy. Yet, that’s not how code works. Plus, collapsing disabled into state reduces the model’s clarity and meaning, and disables (pun intended) you from properly modeling other relationships, as we’ll see next.

Interdependent properties

The plot thickens with readonly, also conventionally a boolean property in code. Like disabled, readonly is false by default. Unlike disabled, readonly supports rest and focus but typically not hover and active.

Combination table of state versus disabled and readonly

Additionally, disabled and readonly never occur simultaneously. When both readonly and disabled are set to true, disabled takes precedence and readonly is ignored, making these properties interdependent.

If a designer implements all seven valid combinations, Figma handles this case well enough. Since it takes precedence, disabled should be ordered first, followed by readonly, followed by state. Should a component user set disabled to false, the component would revert any other state to rest.

Text input with readonly as a separate prop versus as an option of a state prop

Could both disabled and readonly be collapsed into options of a state property? Yes, but properties combining multiple dimensions into a one property with compound names doesn’t match code and doesn’t scale well.

Choosing enumerated or boolean props

Error and success validation states raise more questions. Both disabled and readonly are delightfully binary, easily represented with true and false values (even if built as Figma variant properties).

Some design systems only offer error states, which raises the already familiar choice (and recommendation) to offer an error variant property one can set to false (default) or true.

The combination table below does raise interesting questions: can an input be set to both error : true and either readonly : true or disabled : true? From a purist’s perspective, yes, probably.

Combination table of state versus error, disabled and readonly

But such combinations of error (in HTML, invalid?) withdisabled or readonly carries heavier considerations like:

  • Competing visual cues: what signals it’s in error versus disabled?
  • Conflicting resolution: if an input is in error, the user should be able to interact with it to resolve the issue. If it’s disabled, they cannot.

These are user experience design issues rather than API design issues. Guidance may suggest avoiding the combination unless it’s necessary and intuitive to users and encourage other UI patterns instead. For the Figma component and it’s attendant communications to implementing developers, however, at least a conversation if not explicit asset is warranted to clarify what happens if both are set to true.

Properties evolve further if an input also implements a success state (often combining a green color with a checkmark icon) that’s the opposite of an error state (that could use red color and an X icon).

In this case, error (or, invalid) is mutually exclusive from success (or, valid), and a visualized success state is different than showing neither an error's red or success's green. Therefore, consider an enumerated validation variant property with options for none (default), error and success.

Standalone prop versus or option of another prop

All this time, focus has been presumed a mutually exclusive option in a set that also includes rest, hover and active. In fact, an element can be both hover+focus or active+focus simultaneously.

Should focus (true,false) be a separate property from iteractive state (rest, hover, active)?

Combination table of state versus disabled, readonly and focus

A designer converted to be a purist by preceding sections may be incorrectly led to further separate concerns and add a focus variant property set to false by default. That’s likely going too far.

In a practical sense, separating a focus property from a state property isn’t practically necessary, because:

  • Focus is almost always triggered client-side, not a developer’s configuration: focus, like hover, is an interactive state based on user interaction. Although some libraries offer it, most implementing developers don’t find themselves forcing focus with a prop, unlike disabled, readonly or error.
  • Focus impacts distinct visual attributes, usually: focus typically impacts visual attributes (such as a container shadow or a showing a separate focus ring element) are distinct from those impacted by hover or active (usually, a container’s background or stroke color).
  • Focus overrides the same visual attributes, otherwise: Even if focus impacts container stroke weight and color as hover does, once the element has focus, the weaker visual cues relevant to hover or active matter far less if at all. focus takes precendence over hover as disabled did over readonly, and there’s no relevant third, distinct appearance for focus + hover.
Hover, active, and multiple focus states grouped as a set in Chrome’s inspector
  • Conventionally related as a set: The three are very closely related CSS pseudo classes, presented as and usually pursued as illustrated by their presence. If we’re going to distinguish focus, might as well distinguish hover from active. While we’re at it, is it time to further distinguish focus-within? I’ve never encountered a developer coding that yet, let alone a designer solving for it. We can all aspire for greatness, someday?

All but one code examples assessed lack a focused property. Only Shopify Polaris’ offered a focused property to force the focused state. I’m guessing at purpose: maybe interactions with elements slotted inside the input must force the focus state of the parent container? Additionally, Shopify Polaris’ focused property is additive to the focus interactive state triggered without applying the property.

I’m unconvinced that separating focus:true, false from an interactive state:rest, hover and active is necessary. That is, until collaboration fails (it won’t) or automation between libraries requires it (it could, someday).

Ending there is a useful illustration: the pursuit of perfect alignment — and a perfect model for configuring our design — loses steam before perfection. That three year old blog post was more than comfortable with diverging state across design and code assets.

Today, the threshold has moved towards deeper integration and stronger alignment. That’s the same journey many teams will be on over their years pursing systems. Maybe someday they’ll converge. Maybe soon.

--

--

Nathan Curtis
Nathan Curtis

Written by Nathan Curtis

Design systems consultant contributing to the field through the specs plugin, writing and workshops. VT & @uchicago grad.

Responses (14)