UI State
Concept
A named set of the different ways one screen element, a button or a list, can look depending on what the user does and what the data says.
Why it exists
For a long time a screen mockup was a single picture. A list with twelve items in it, a button in blue, no messages anywhere on the page — the version where everything went right. In a real product, though, the moment when that list holds twelve items is only one of several. It is the state the screen reaches once someone has used the product for a while and everything on the way there has worked. Someone who signed up a minute ago has zero. Someone on a slow connection has nothing for a few seconds. Someone whose request never came back has nothing at all. One mockup drew one of those situations and left the rest undrawn.
The rest got filled in by whoever built the screen, and usually without anyone noticing that a decision had been made. Code that draws a list draws nothing when the list has zero items — that's the default behavior, so an empty screen is what appears unless someone decides otherwise. If nobody decides what to show while data is being fetched, those seconds are an empty screen too. If nobody decides what to show when the request fails, that's an empty screen as well. Three completely different situations arrive at the user as the same blank rectangle. Nobody wrote a line of code to produce that rectangle, which is why nobody remembers choosing it.
At Pagedeck this surfaced when three list screens were put side by side. The document list showed nothing at all when it was empty. The comment list showed a line of text saying there were no comments. The share-target list kept five gray boxes blinking forever. Theo built all three, and none of the three existed in any mockup. The support ticket that reached Sofia said documents had disappeared, and the account in question turned out to have had zero documents from the start. The person had signed up, opened a screen with nothing on it, and concluded that something they had written was gone. Two people spent two days looking for documents that had never gone missing. What was broken wasn't saving or loading. It was that nobody had decided what a zero-document list looks like.
UI state is the idea that turns those gaps into a list. Take one element, count the forms it can take, give each one a name, and the mockup now carries that many pictures while the people building it can see which ones are still undecided. Making it visible that a button has five of these and a list has four or five is most of what the concept does. The count itself is the work: once the number is written down, an undrawn state reads as a missing picture rather than as nothing at all. If interaction design is the area that pairs a user's action with the product's response, UI state is that response sorted into the forms it actually takes on screen.
How this field breaks down
States split by what causes the change. If a person touches the element and it changes, that's the interaction branch. If the change comes from data being slow, absent, or broken, that's the data branch. The first attaches to things you can operate — buttons, input fields, menus. The second attaches to things that carry content — lists, tables, cards. Most screens contain both at once. A single list screen carries rows that respond to a pointer and content that has to arrive before there are any rows to respond to.
The interaction branch has five, one for each moment a pointer, a key, or a finger meets the element.
- Default state — how the element looks when nobody has touched it. The other four are all defined as departures from this one, so until the default is settled none of the rest can be. This is the picture that already exists in the mockup.
- Hover — how it looks while the pointer sits on top of it. The background darkens a step, or the cursor turns into a hand, which tells you this thing is pressable before you press it. It only exists where there's a mouse, so a delete button that appears only on hover is a button that doesn't exist on a phone.
- Focus — how the element looks when the tab key or an assistive device has it selected. For anyone not using a mouse this outline is the only indication of where they are on the page. Remove it because it looks untidy and those users press enter without knowing what they are about to trigger.
- Active — the brief form the element takes while it is being held down, before the finger lifts. It's a tenth of a second of the button sinking or its color deepening, and without it you learn whether your press registered only from the result. The slower the result arrives, the more work this short response does. On a screen that answers instantly it is barely noticed, and on a slow one it is the only thing telling you the press landed at all.
- Disabled — how an element looks when it can't be used right now. It's dimmed to gray and ignores presses. Dimming it is the easy part; saying why it can't be used is the part that gets skipped, which leaves users guessing whether the checkout button is gray because no card is on file or because the amount is zero.
The data branch has six. These exist because the screen has to fetch its content from somewhere else.
- Loading state — the form that says content is on its way, usually a spinner or a progress bar. Leave this slot empty and users can't tell waiting from broken, so most of them press again. That second press is where duplicate saves come from. Both requests reach the server, both succeed, and the account ends up holding two copies of the same document.
- Empty state — the form for when the fetch succeeded and there's nothing to show. Two different situations hide in here and they need separating. A new user who hasn't created anything needs to be told what to create; a search or filter that returned zero results needs to be told how to change the query. Use one message for both and one of the two will always be wrong — telling someone with no documents to try different search terms, or telling someone with zero search results to create their first document.
- Error state — the form for when the fetch failed. It carries what went wrong and what to do about it. The same failure produces a different number of support tickets depending on whether there's a retry button on the screen.
- Success state — the form that says the requested work is finished. A saved marker or a completion message lives here, along with the decision about whether it fades after a few seconds or stays. If the action can be reversed, this is where undo goes.
- Skeleton UI — one way of expressing the loading state. Instead of a spinner, the shapes of the content that's coming are drawn in gray first. Nothing actually gets faster, but the structure of the page is visible in advance, so the same two seconds feel shorter and the layout shifts less when real content lands.
- Optimistic UI — updating the screen as if the request succeeded, without waiting for the server. Tap a like and the heart fills immediately while the request runs behind it. Reverting the screen on failure has to be built alongside it, so this is reserved for actions that rarely fail and cost little when they roll back.
The two branches run into each other inside a single element. Look at one share button: before it's pressed the interaction branch applies and it cycles through default, hover, focus and active; after it's pressed the data branch takes over and it becomes loading, then success or error. That's seven pictures for one button, and nobody draws seven pictures every time. Instead the rules for how each state differs get written down once — hover darkens the background one step, disabled drops opacity to 40 percent — and then twenty buttons on a screen need exactly one set of mockups instead of a hundred and forty separate pictures.
How it sounds at work
"There's no empty-state mockup for this list. What does someone see on their first day?"
Only the version with content arrived, and the zero-item version is being requested. Shipped as is, new users get a blank area.
"Do we have all five button states? Include the copy for the disabled one."
The mockup shows the default form only, and the other four plus an explanation of why the button can't be pressed are being asked for.
"Let's not wait for the server here. Update first, roll back on failure, show a message."
An agreement to go optimistic, with the recovery path counted as part of the same piece of work rather than a follow-up.
At Pagedeck this comes up most in the handoff meeting where Noa passes mockups to Theo. At some point Noa started attaching a table next to the screens: element names down the side, state names across the top, each cell describing what that combination looks like. A row for the list and a row for the share button, a column for loading and a column for empty, and the gaps visible at a glance. Theo's questions now follow the table. What sits in the list area when the account has zero documents. What is visible during the five seconds a fetch takes. What the button looks like while a share link is being generated. Before the table existed, those questions arrived the day after release, in the support inbox.
Blank cells get decided in the meeting. When they can't be decided, at least the fact that they're blank is written down somewhere. The old pattern — nobody knowing, and the default behavior of the code answering instead — has mostly stopped.
What people get wrong
The most common mistake is filing state work under polish. States are expressed through color and shadow and opacity, so it's natural to read the whole thing as a visual pass at the end. But which states exist isn't a visual question at all; it's a list of the situations the product can end up in. Saying an empty state is needed means there really are users with zero items. Saying an error state is needed means there's a path where the request fails. Classify that as polish and it becomes the first thing cut when the schedule slips, and the cut is filled by whatever the code does by default. That's how one product ends up looking like three different products screen to screen, which is exactly what those three Pagedeck lists were.
Reading skeleton UI as a performance improvement is the other one. After the change users genuinely say the page got faster, which makes the conclusion easy to believe. The server, however, is not a millisecond quicker. What dropped is the uncertainty in the wait: the structure appears first, so people wait knowing what's coming, and the same two seconds feel shorter. Miss that distinction and a three-second request stays three seconds while only its costume changes. The gray shapes are worth drawing, but they answer how the wait feels, not how long it is. Past about eight seconds no amount of careful gray shapes holds anyone, and at that point the request itself is the thing to fix.
Where to go next
- Interaction design
see which design area these states sit inside
- Default state
every other state is defined against this one, so start here
- State management
how the screen remembers and switches states, from the building side
Sub-terms
Last updated 2026-09-16