Designing the Empty State

Five different screens routinely get built as one. What each kind of empty has to accomplish, and the platform guidance that openly contradicts itself on whether to include an action.

Date

/

Category

UI/UX

UI/UX

/

Writer

Keon Hwui Kim

Keon Hwui Kim

Apple shipped a SwiftUI view whose entire job is to say that there is nothing to show. It is called ContentUnavailableView, and the availability table on Apple's own documentation page lists it from iOS 17.0, iPadOS 17.0, macCatalyst 17.0, macOS 14.0, tvOS 17.0, visionOS 1.0 and watchOS 10.0. The description is one sentence: "An interface, consisting of a label and additional content, that you display when the content of your app is unavailable to users."

What makes it worth reading closely is not the component. It is the overview line underneath, which quietly lists three different situations and treats them as one: "It is recommended to use ContentUnavailableView in situations where a view's content cannot be displayed. That could be caused by a network error, a list without items, a search that returns no results etc."

A network error, an empty list and a failed search are not the same screen. One is a fault, one is a beginning and one is a dead end in a task the person is in the middle of. Apple half-acknowledges this: the same page documents a dedicated built-in variant, ContentUnavailableView.search and search(text:), backed by a type called SearchUnavailableContent. So search gets its own treatment. The other two do not.

That is the shape of the problem across the whole industry. Everyone ships the screen. Almost nobody distinguishes the cases.

What the screen has to accomplish

An empty state is not decoration around an absence. It has three jobs, and they are in tension.

It has to explain the container. The person is looking at a region of the interface that has a name and a purpose, and right now nothing in it demonstrates either. This is Jakob Nielsen's first heuristic, published on 24 April 1994 and still the correct starting point: "The design should always keep users informed about what is going on, through appropriate feedback within a reasonable amount of time." His sixth, recognition rather than recall, is the other half: "Offer help in context, instead of giving users a long tutorial to memorize." An empty container is the single best piece of in-context real estate a product ever gets, because the person is looking directly at the thing they do not yet understand.

Kate Kaplan's analysis at Nielsen Norman Group, published 19 September 2021, defines empty states as "containers, screens, or panels for which content does not yet exist or otherwise cannot be displayed", and her first recommendation is about status rather than encouragement: give explicit feedback of the form "There are no records to display for the selected date range", so that nobody has to guess whether the thing is loading or genuinely bare.

It has to show the first action. IBM's Carbon Design System is blunt about this in its empty states pattern: an empty state's body should give a "clear explanation of next actions and reasoning", and its guidance includes "keep words to a minimum so they are fast to read and act upon" alongside a warning not to "lead users into dead ends".

And it has to avoid implying failure. This is the one that gets dropped. An empty list is the normal, expected, correct state of a brand-new account. A screen that apologises for it teaches the person that they have already done something wrong.

Three kinds of empty

The distinctions that matter are not about visual treatment. They are about who caused the emptiness and what they were trying to do at the moment it appeared.

Kind

Who caused it

What the person needs

What it must not do

First-run empty

Nobody. This is the shipped, correct initial state

An explanation of what the container is for, and one obvious way to put the first thing in it

Apologise, or present the state as a problem to be fixed

User-cleared empty

The person, deliberately. They archived, deleted or completed everything

Confirmation that the clearing worked, and nothing else. This is frequently a success state

Push a call to action. Inbox zero does not want a Create button

No-results empty

The person's query, against real content that exists elsewhere

The query echoed back, a reason, and a route out: broaden, clear a filter, try a suggested term

Offer a creation action that has nothing to do with the search they are in the middle of

Permission or tier empty

The account configuration, not the person

A statement of what is missing and who can grant it

Read as a fault in the product, or as an upsell dressed up as help

Error empty

The system. The data exists but could not be fetched

Acknowledgement that this is temporary, plus retry

Look identical to first-run empty, which teaches the person that the feature is useless

Two of the better-documented design systems agree on roughly this carve-up, and neither of them agrees with Apple's single component.

GitLab's design system distinguishes four states by name: blank content, "appears when no content exists within a configured feature"; configuration required, for features needing setup, which carries primary and secondary actions; higher tier feature, a placeholder for something the subscription does not include; and empty search results, which it specifies as being shown "without call-to-action elements".

Carbon uses three buckets, cut slightly differently: no-data empty states for initial experiences where the user has not yet added content; user action empty states, which cover empty search results and process completions; and error management empty states, for data that cannot surface because of permissions, system problems or missing configuration.

The disagreement nobody resolves

There is a genuine, unresolved conflict in the published guidance, and it is worth stating plainly because most articles on this subject quietly pick one side and pretend the other does not exist.

The standard advice is: every empty state needs a call to action. Carbon supports this, listing an optional primary action as "button or link for main call-to-action". So does Kaplan's NN/g piece, which recommends including actionable links or buttons so people can begin the relevant task immediately.

Google's original Material Design guidance says the opposite, and says it about the same screen. The Material 1 empty states pattern instructs that an empty state should consist of "a non-interactive image and a text tagline", that images should be "subtle and neutral with respect to the background", and, most directly, that you should avoid wording a tagline "like a call to action, which may encourage users to touch" it. Its recommendation for the user-cleared case is a "basic image and tagline conveying purpose without appearing actionable".

An empty state that begs is an empty state that has confused its own emptiness for the user's mistake.

Both sides are right about different kinds of empty, which is exactly the point. A first-run empty state has nothing but a call to action to offer, because the entire content of the screen is an invitation. A user-cleared empty state that offers one is nagging somebody who has just finished. And GitLab's rule for search, no call to action at all, is correct because the action a person wants after a failed search is a different query, not a new record.

Use one screen for all of them and you will necessarily be wrong in at least two of the three cases.

Empty is not loading, and the difference is visible

The most common failure I see in review is not a badly written empty state. It is an empty state that appears for four hundred milliseconds while a request is in flight, flashes the words "Nothing here yet", and then gets replaced by a full list. The person has now been told, in writing, that their data does not exist.

Samhita Tankala's NN/g article on skeleton screens, published 4 June 2023, sets the boundary: a skeleton screen "is used as a placeholder for a loading page", and "skeleton screens should be used with a wait time that's under 10 seconds", while pages that load in under a second do not need one at all. The reason to prefer a skeleton over blankness is precisely the misreading risk: they stop people "thinking that the site isn't working", and they reduce cognitive load by letting somebody anticipate the structure before the content arrives.

So the rule is mechanical. Until the request resolves, the state is unknown, and an unknown state is a skeleton, not an empty state. Only a resolved, successful, zero-length response earns the empty screen. A resolved failure earns the error variant. Three outcomes, three treatments, and the code should make it impossible to render the wrong one.

STATE MACHINE FOR ONE CONTAINER

request pending
  and elapsed < 1s   -> nothing (no flash)
  and elapsed >= 1s   -> skeleton, matching final layout

request resolved
  items.length > 0    -> content
  items.length == 0
    and no query and never populated
                      -> FIRST-RUN EMPTY
                         explain container + one action
    and no query and previously had
                      -> USER-CLEARED EMPTY
                         confirm, no action
    and query present -> NO-RESULTS EMPTY
                         echo query, offer broaden/clear

request failed
  403 / 401           -> PERMISSION EMPTY
                         name what is missing and who grants it
  5xx / network       -> ERROR EMPTY
                         temporary, plus retry

NOTE: "previously had" requires state the client usually does not
keep. If you cannot distinguish first-run from user-cleared,
say so in the copy rather than guessing. Ambiguous is better
than confidently wrong

STATE MACHINE FOR ONE CONTAINER

request pending
  and elapsed < 1s   -> nothing (no flash)
  and elapsed >= 1s   -> skeleton, matching final layout

request resolved
  items.length > 0    -> content
  items.length == 0
    and no query and never populated
                      -> FIRST-RUN EMPTY
                         explain container + one action
    and no query and previously had
                      -> USER-CLEARED EMPTY
                         confirm, no action
    and query present -> NO-RESULTS EMPTY
                         echo query, offer broaden/clear

request failed
  403 / 401           -> PERMISSION EMPTY
                         name what is missing and who grants it
  5xx / network       -> ERROR EMPTY
                         temporary, plus retry

NOTE: "previously had" requires state the client usually does not
keep. If you cannot distinguish first-run from user-cleared,
say so in the copy rather than guessing. Ambiguous is better
than confidently wrong

STATE MACHINE FOR ONE CONTAINER

request pending
  and elapsed < 1s   -> nothing (no flash)
  and elapsed >= 1s   -> skeleton, matching final layout

request resolved
  items.length > 0    -> content
  items.length == 0
    and no query and never populated
                      -> FIRST-RUN EMPTY
                         explain container + one action
    and no query and previously had
                      -> USER-CLEARED EMPTY
                         confirm, no action
    and query present -> NO-RESULTS EMPTY
                         echo query, offer broaden/clear

request failed
  403 / 401           -> PERMISSION EMPTY
                         name what is missing and who grants it
  5xx / network       -> ERROR EMPTY
                         temporary, plus retry

NOTE: "previously had" requires state the client usually does not
keep. If you cannot distinguish first-run from user-cleared,
say so in the copy rather than guessing. Ambiguous is better
than confidently wrong

That last note is the honest part. Most front ends genuinely cannot tell a first-run empty from a user-cleared one without a server flag, because the client has no memory of a list that has always been rendered as zero items. If the flag is not there, the copy has to work for both readings, which in practice means describing the container rather than commanding an action.

The no-results case is the one with actual research behind it

Of the three, only the failed search has been studied at any depth, and the findings are older and less flattering than the design-system guidance suggests.

Kathryn Whitenton's NN/g piece, published 5 January 2014, gives three guidelines, quoted here in full because they are short: "Clearly explain that there are no matching results." "Offer starting points for moving forward." "Don't mock the user."

The eye-tracking observation behind the first is the useful one. Her article documents that people frequently fail to notice the no-results message at all, particularly when it is set small and positioned between the logo and the main content area, because they are fixating on the region where results are normally found. In other words, the failure mode is not a badly worded message. It is a correctly worded message in a place nobody looks.

The second finding is about capability, and it is the reason the "try a different search" instruction is close to useless: the research indicates that typical users are not sophisticated at query refinement and struggle to invent alternative strategies unprompted. They need specific suggestions, not encouragement to think harder.

What the platforms do and do not say

I went looking for platform guidance on this and want to report what I actually found, including the gaps, because the gaps are the interesting part.

Apple's Human Interface Guidelines, as far as I can establish, has no page on empty states. A site-restricted search of developer.apple.com for the term returns nothing in the guidelines. What Apple provides instead is the API documentation quoted at the top of this piece, which is design guidance in the only form many engineers will ever read: a component, a default, and one worked example whose description text is "New mails you receive will appear here." That sentence is a good empty state. It names the container, states the mechanism by which it fills, and does not apologise. It is also buried in a reference page rather than in the design guidelines.

Material's position is stranger. The empty states pattern exists in the Material 1 documentation and in Material 2, and I could not find an equivalent page in Material 3. The guidance that does exist is the oldest and the most cautious: non-interactive image, positive tagline, no call-to-action phrasing. Material 1 also proposes three alternatives to showing an empty state at all, which almost no product implements: starter content, meaning pre-populated sample material with "broad appeal" that "demonstrates primary features" and which users can delete; educational content that shows what the feature will do once populated; and "best match", meaning showing close results when there is no exact match rather than showing nothing.

That third one is a small reframing with large consequences. A no-results page is only ever necessary when the search returns literally nothing. If you can return approximate matches with an honest label, the empty state stops being a design problem and becomes a ranking problem, which is a much better problem to have.

What I had wrong

For a long time I treated the empty state as a copy exercise: get a warm sentence, get an illustration, ship it. Two assumptions in that were wrong.

The first was that a friendly tone is the main variable. It is not. Whitenton's third guideline, "Don't mock the user", is a floor, not a strategy, and the eye-tracking finding says that placement decides whether the tone is read at all.

The second, and the more expensive one, was treating the empty state as a design deliverable rather than as a branch in a state machine. Designed screens do not flash for four hundred milliseconds. Branches do. Until the empty state is specified alongside the loading state and the error state, in the same document, with the conditions that select between them written down, it is not designed. It is just a picture of nothing.

Sources

  • Apple Inc., "ContentUnavailableView", SwiftUI developer documentation. Availability table (iOS 17.0, iPadOS 17.0, macCatalyst 17.0, macOS 14.0, tvOS 17.0, visionOS 1.0, watchOS 10.0), overview text, the search variant and SearchUnavailableContent. developer.apple.com/documentation/swiftui/contentunavailableview

  • Google, "Empty states", Material Design 1 patterns documentation. Definition, the non-interactive image and tagline construction, the instruction to avoid call-to-action phrasing, and the three alternatives: starter content, educational content and best match. m1.material.io/patterns/empty-states.html

  • IBM, "Empty states pattern", Carbon Design System. The three-type taxonomy (no data, user action, error management), the anatomy, and the dos and don'ts. carbondesignsystem.com/patterns/empty-states-pattern/

  • GitLab, "Empty states", GitLab Design System. The four named states including empty search results shown without call-to-action elements, plus anatomy and action guidance. design.gitlab.com/regions/empty-states

  • Kate Kaplan, "Empty States in Complex Applications", Nielsen Norman Group, 19 September 2021. Definition of empty states and the three recommendations on system status, learning cues and direct pathways. nngroup.com/articles/empty-state-interface-design/

  • Kathryn Whitenton, "3 Guidelines for Search Engine 'No Results' Pages", Nielsen Norman Group, 5 January 2014. The three guidelines and the eye-tracking finding that no-results messages are frequently not seen. nngroup.com/articles/search-no-results-serp/

  • Samhita Tankala, "Skeleton Screens 101", Nielsen Norman Group, 4 June 2023. Definition, the sub-10-second use window, and the contrast with blank screens. nngroup.com/articles/skeleton-screens/

  • Jakob Nielsen, "10 Usability Heuristics for User Interface Design", first published 24 April 1994, Nielsen Norman Group. Heuristic 1, visibility of system status, and heuristic 6, recognition rather than recall. nngroup.com/articles/ten-usability-heuristics/

Related reading

More from the Moc Mallen Studio journal