The Sorry State of States
Today’s Figma assets show design could align better with code
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
.
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.
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.
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.
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:
- Material 3 Design Kit
- Material UI for Figma (and MUI X)
- Salesforce Components for Web | Lightning Design System v1
- Github Primer Web
- IBM Carbon Design System
- Atlassian ADS Components
- Oracle Redwood
- Newskit Component Library
- Shopify Polaris Components
state
property patterns matched what I observe in teams I consult with:
- Hover: Six of ten support a
state
:hover
(orstate
:hovered
) option. - Active: Two support a
state
:active
option. - Focus: Eight of ten support
state
:focus
and none offeredfocus
as a separate boolean property. - Disabled: Nine of ten support
state
:disabled
and only one (Atlassian) distinguishedisDisabled
as a separate boolean property. - Read only: Six of ten support
state
:readonly
, while none offeredreadonly
as a boolean property. - Error and Success: Eight of ten support an
state
:error
(or similarly named validation) option, while one (Atlassian) offerederror
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
(orinInvalid
,hasError
or similar) boolean prop to present an error display. GitHub Primer expands from a binary to enumeratedvalidationStatus
prop to distinguisherror
fromsuccess
. Some code libraries implement anerror
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
.
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:
- Handoff friction as product designers deliver to product developers using published Figma assets that use different models from code.
- Poorly organized props in Figma assets that are harder to use, littering one or a few props with various choices that are actually independent.
- 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:
- Limit automation opportunities to audit and report on alignment across design and code libraries.
- Limit the ability to use assets as data to inform, serve as a source of truth, or even automate code development.
- 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.
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
.
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
.
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.
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.
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
.
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
.
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.
But such combinations of error
(in HTML, invalid
?) withdisabled
or readonly
carries heavier considerations like:
- Competing visual cues: what signals it’s in
error
versusdisabled
? - Conflicting resolution: if an input is in
error
, the user should be able to interact with it to resolve the issue. If it’sdisabled
, 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
)?
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
, likehover
, is an interactive state based on user interaction. Although some libraries offer it, most implementing developers don’t find themselves forcingfocus
with a prop, unlikedisabled
,readonly
orerror
. - 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 byhover
oractive
(usually, a container’s background or stroke color). - Focus overrides the same visual attributes, otherwise: Even if
focus
impacts container stroke weight and color ashover
does, once the element hasfocus
, the weaker visual cues relevant tohover
oractive
matter far less if at all.focus
takes precendence overhover
asdisabled
did overreadonly
, and there’s no relevant third, distinct appearance forfocus
+hover
.
- 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 distinguishhover
fromactive
. While we’re at it, is it time to further distinguishfocus-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.