Skills/Frontend design/Interface state audit

Interface state audit: the eleven screens you did not build

Enumerates every state a screen can actually be in, decides which are reachable, and specifies what each must show. The taxonomy is the asset.

skill 2,543 words MIT by Locul Verified safe · 0 secrets Tested 2026-08-18
No account, no install, nothing to sign up for. Copy it or download it and it works in Claude Code today. One-click import lands here shortly.
Our verdict

The clearest result of the six. It found the four states that matter most and are missed most.

This was the clearest result of the six skills we tested, and the only one where the gap was large enough to be obvious without counting carefully.

Both runs built a competent team-members screen. The run without the skill covered 14 of the 18 states we scored: loading, error and retry, search with no matches, seat limits, pending invites, suspended members, long names, avatar fallbacks, in-flight guards, optimistic updates, a live region, and a responsive breakpoint. That is a good screen and better than most shipped code.

The four it missed are the four that hurt. It never handled the first-run empty state, so a brand new workspace with one person in it falls through to the same component as a search that returned nothing. It treated inviting five people as one operation that either succeeds or fails, with no per-invite status, so one bad address discards four good ones. It offered no last-administrator guard, so the interface happily lets you demote the only admin and orphan the workspace. And it left keyboard focus on the document body after removing a row, which strands keyboard users completely.

The run with the skill covered all 18, and its deliverable was three times longer, most of that being the reachability pass: which states are possible here, which are not and by what mechanism, and which are deliberately out of scope. That written-down reasoning is the part worth having in a pull request.

The honest caveat: the skill makes the work longer. On a screen with two states, that is overhead you do not want.

When to reach for it

  • Building any screen that loads a list, submits a form, or shows data that can fail to arrive.
  • A design review where the mockup shows one happy path and you suspect that is the only path anyone considered.
  • After a support ticket that turned out to be an unhandled state rather than a bug.
  • Before a first-run experience ships, because the empty state is the screen every single user sees first.
  • When a screen already exists and you want an inventory of what is missing rather than a rewrite.

The test we ran

We gave both runs the same task, a team-members admin screen with invite, role change and remove, and an API shape with status values of active, invited and suspended. One run got the skill. The other got the identical prompt with no skill. Neither could see the other, and both worked in a directory with no clue to the method in its path or filenames.

We scored 18 states drawn from real failures, not from the skill's own taxonomy, so the skill was not marking its own homework.

Result: 14 of 18 without, 18 of 18 with. Missed without the skill: first-run empty, per-invite partial failure, the last-administrator guard, and focus placement after a row is removed.

What it does not do

Stated plainly, because a skill that claims everything is useful for nothing.

  • It does not implement anything. It produces an enumeration and a specification, and writing the code is a separate step.
  • It is not an accessibility audit. It covers announcement and focus for the states it enumerates and stops there.
  • It does not verify that already-implemented states are correct. It says what should exist, not what does.
  • On a trivial screen it will over-generate. The reachability pass is what keeps it honest, and it has to be used honestly.
  • It has no opinion about which states matter most for your specific product. A read-heavy dashboard and a data-entry tool have different answers and it gives you the general priority order, not yours.

Install it

  1. Open Locul, go to Library, and choose Import. One-click import from this page lands shortly.
  2. Locul writes the file to the right folder for every assistant you have connected, so you do not have to know where each one keeps its skills.
  3. Environment variables and headers in any shared config are replaced with a placeholder before they reach you, so importing a stranger's setup cannot hand you their credentials or take yours.
  4. Locul is free to start, on Mac and Windows. Get it here.
  1. Download SKILL.md using the button above, or copy the file.
  2. Save it at .claude/skills/interface-state-audit/SKILL.md in your project, or under ~/.claude/skills/interface-state-audit/SKILL.md on Mac and Linux, or %USERPROFILE%\.claude\skills\interface-state-audit\SKILL.md on Windows, to make it available everywhere.
  3. Start a new session. Claude Code picks up the skill from the name and description in the file's frontmatter, so you can also invoke it by name.
  1. Download or copy the file.
  2. For Claude Desktop, add it through the skills panel in settings, or drop the folder into your skills directory.
  3. For Cursor and other assistants that read plain instruction files, paste the body into your project rules file. The skill is plain markdown with no tool bindings, so it carries across.

What else does this job

Nothing we found does this specific job. The state lists that circulate publicly are usually four items long, empty, loading, error, ideal, which is the list this skill exists to replace. The closest adjacent tools are accessibility linters, which catch focus and announcement problems but have nothing to say about a missing empty state or an unguarded last admin, because those are product decisions rather than markup defects.

We also wrote and tested a design-system conformance skill intended to sit alongside this one. It did not beat the control and is not published, so this is currently the only frontend skill here.

Read the full source
---
name: interface-state-audit
description: Enumerates every state a screen can actually be in, decides which ones are reachable, and specifies what each must show. Covers the three different empty states, loading and slow and stale, partial and permanent failure, permission and quota limits, hostile content shapes, optimistic and conflicting mutations, and identity edge cases like the last admin. This skill should be used when building or reviewing any screen that loads data, submits data, or renders a list.
---

# Interface state audit

## The claim this skill is built on

Almost every interface is built for one state: a moderate amount of well-formed data, loaded
successfully, by a user with full permissions, on a wide screen.

Users spend a startling proportion of their time in the other states. The first five minutes of every
account are the first-run empty state. Every slow network is the loading state. Every typo in a search
box is the no-results state. Every expired session is the permission state. The states that feel like
edge cases are, in aggregate, most of the experience, and they are disproportionately concentrated in
the moments that decide whether someone keeps using the product.

They are also where implementations quietly break rather than loudly fail. A list that renders nothing
when the array is empty does not throw. A name that overflows its container does not error. A second
click that fires a duplicate request returns 200. None of this shows up in a demo, because a demo has
three well-named rows loaded instantly by an admin.

This skill is a systematic enumeration so that the states are chosen rather than discovered.

## How to use it

There are three moves, in order:

1. **Enumerate** every state on the eight axes below.
2. **Decide** which are reachable for this screen. Most are not, and pretending otherwise produces
   bloated components nobody maintains.
3. **Specify** what each reachable state shows, and how the user gets out of it.

The third move is the one people skip. A state that has been identified but not designed becomes a
spinner or a blank area, which is the same as not handling it.

## The eight axes

### Axis 1. Data cardinality

The most common source of missed states, and the one where the distinctions matter most.

| State | Why it is different |
|---|---|
| **Empty, never had data** | First run. This is onboarding, not an error. It should teach and offer the primary action. |
| **Empty, had data and now does not** | The user deleted everything, or completed everything. Often a success, sometimes a mistake. Offer undo if a deletion caused it. |
| **Empty, because of a filter or search** | Nothing is wrong with the account. The user needs the query echoed back and a way to clear it. |
| **Exactly one item** | Breaks layouts that assume a grid. Also breaks copy that says "items". |
| **A handful** | The state everyone builds. |
| **Many** | Needs pagination, virtualisation, or a cap, and a decision about which. |
| **Far too many** | Ten thousand rows. Does the page still render? Does the filter still respond? |
| **One item that is enormous** | A single record with a 40,000 character field. Different failure than many small ones. |

**These three empty states must have different copy, and this is the most valuable single rule in this
skill.** Showing "No results found" to a brand new user who has never added anything is a small
disaster: it reads as a failure, gives no next action, and makes the product look broken at the exact
moment the user is deciding whether it works. Showing "Invite your first teammate" to a user who just
searched for a name that does not exist is equally wrong in the other direction. If a codebase has one
empty state component with one string, that is a finding on its own.

### Axis 2. Time

| State | What it needs |
|---|---|
| **Not started** | Deliberate: is this lazy, or does it fetch on mount? |
| **Loading, first time** | Skeleton matching the real layout, or a spinner. Skeletons only if they match; a skeleton whose shape differs from the loaded content causes a visible jump. |
| **Loading more** | The existing content must stay visible and stable. Never replace a loaded list with a spinner. |
| **Refreshing already-visible data** | Subtle indicator, no layout shift, no scroll jump. |
| **Slow** | Past roughly ten seconds, a spinner stops reassuring and starts looking broken. Say what is happening, or offer a cancel. |
| **Timed out** | Distinct from failure. Retry is usually the right primary action. |

### Axis 3. Outcome

- **Success.**
- **Partial success.** Three of five items saved. The single most under-built state in this axis, and
  the most damaging, because reporting it as a flat failure makes the user redo work that succeeded.
  It needs per-item status, not a global banner.
- **Retryable failure.** Network, timeout, 502, rate limit. Show a retry. If it is a rate limit, say
  when to try again.
- **Permanent failure.** Validation, 404, 403, a malformed record. Retry is not the answer and
  offering it is cruel. Say what is wrong and what would fix it.
- **Offline.** Different from failed: it will resolve on its own. Say so, and say what happens to
  anything unsaved.
- **Stale, showing cached data.** The screen is not empty and is not current. Label it and say how old.

The failure copy rule: name the object, the action, and the next step. "Could not load members. Check
your connection and retry." Not "Something went wrong." A user who cannot tell whether the failure is
theirs or yours will assume it is yours and leave.

### Axis 4. Permission and quota

- Can view and act. The state you built.
- **Can view but not act.** The controls should be visibly disabled with a reason, not hidden. Hiding
  them makes the interface look different for different users and generates support tickets that are
  impossible to reproduce.
- **Cannot view at all.** Distinguish "you are not allowed" from "it does not exist", and make that
  distinction deliberately, because for some resources leaking existence is itself a disclosure.
- **Session expired mid-session.** The user was allowed a second ago. Preserve their unsaved input
  across the re-authentication, or you have just deleted their work.
- **At a limit.** Seats used, storage full, plan cap reached. Show it before the action fails, not
  after. A disabled button with "9 of 10 seats used" prevents the failure entirely.
- **Would exceed a limit.** Inviting five people with three seats left. Say so at input time, not on
  submit.

### Axis 5. Content shape

The states that hostile or merely realistic data produces.

- Optional fields absent. Missing avatar, no display name, null timestamp. What renders?
- Very long unbroken strings. A 200 character name with no spaces, an email at the length limit.
  Truncate with the full value available on hover or focus, and never let it break the layout.
- The opposite: a one character name.
- Non-Latin scripts, right-to-left text, combining characters, emoji in names. If the product is
  available in those locales, they are not edge cases.
- Content that looks like markup or a formula. Confirm it is escaped.
- Broken image URLs. Every avatar list needs a fallback, because avatars are user-supplied and will
  404 eventually.
- Extreme numbers. Zero, negative, very large, and whatever the currency or unit formatting does with
  each.
- Dates far in the past or future, and whatever the relative formatter says about them.

### Axis 6. Mutation lifecycle

- Idle.
- **In flight.** The control must be disabled or the action must be idempotent. Double submission is
  the most common bug in this entire axis, and it is invisible in testing because testers click once.
- **Optimistically applied but unconfirmed.** If the change is shown before the server agrees, there
  must be a rollback path and the user must be told when it rolls back.
- **Rolled back.** What the user sees when the optimistic update fails. Silent reversion is
  disorienting and is worse than never having applied it.
- **Conflicting concurrent edit.** Somebody else changed the record. Last-write-wins is a decision, and
  it should be a stated one rather than an accident.
- **Succeeded but the list is now stale.** Refetch, patch locally, or accept staleness. Pick.

### Axis 7. Identity and singularity

The states that come from *who* is looking and from *last-of-a-kind* records. Usually forgotten
entirely, and usually the source of the worst bugs.

- **The row that is you.** Can you remove yourself? Should the button say "Leave" instead of "Remove"?
- **The last administrator.** Removing them, or demoting them, orphans the resource. Block it at the
  interface, with an explanation, and do not rely on the server to be the only guard.
- **The owner.** Usually not removable by an admin. Does the interface reflect that or does it offer
  an action that will fail?
- **A pending or invited member.** Different affordances: resend, revoke, not remove.
- **A suspended or deactivated record.** Visually distinct, and most actions disabled.
- **The item just created.** Highlighted, scrolled into view, or lost at the bottom of an unsorted list.

### Axis 8. Environment

- Narrow viewport. A table is the usual casualty; decide between horizontal scroll, column priority,
  and a card layout, and make it a decision rather than an overflow.
- Browser zoom at 200 percent, which is a legal accessibility requirement in many contexts and is not
  the same as a narrow viewport.
- Keyboard only. Every action reachable, focus visible, and focus placed sensibly after a row is
  removed. Focus that falls to the document body after a deletion strands keyboard users completely.
- Screen reader. Loading, success, and error must be announced, which means a live region, not just a
  visual change.
- Reduced motion.
- Dark mode, if the product has one.

## Deciding which states are reachable

Do not build all of them. Go axis by axis and mark each state **REACHABLE**, **NOT REACHABLE**, or
**OUT OF SCOPE**, with a one-line reason. The reasons are the deliverable, because they are what a
reviewer checks.

- **NOT REACHABLE** requires a mechanism, not a hope. "Cannot be empty because the workspace creator is
  always a member" is a mechanism. "Users will not do that" is not.
- **OUT OF SCOPE** is a legitimate decision when the state is real but deliberately unhandled for now.
  Write it down so it is a known gap rather than an oversight.

A good audit of a moderately complex screen typically marks fifteen to twenty-five states reachable
out of roughly fifty enumerated. If everything is reachable you have not thought. If three are, you
have not looked.

## Build order

When time is limited, this is the priority, ordered by how many users hit the state times how bad it
is when unhandled:

1. **First-run empty**, because every single user passes through it and it is the moment they judge
   the product.
2. **Loading**, because everyone sees it and an unstyled flash of empty content reads as a bug.
3. **Retryable failure**, because it is common and the recovery is cheap to build.
4. **In-flight double submission**, because it silently corrupts data.
5. **Permission and quota**, because it produces support load out of proportion to its frequency.
6. **No-results-from-filter**, because it is constantly confused with first-run empty.
7. Everything else.

## Specifying a state properly

For each reachable state, four things. A state with fewer than four is not specified.

1. **What is on screen**, structurally.
2. **The exact copy.** Not a placeholder. Copy is most of the value of an empty or error state, and
   "TODO: error message" always ships.
3. **The way out.** Every non-terminal state needs an action. An error with no retry and no
   explanation is a dead end.
4. **What is announced**, for assistive technology, and where focus goes.

## Failure modes

**One empty state for all three cases.** The single most common finding, and the most damaging to
first impressions.

**Spinner as the universal answer.** A spinner is correct for exactly one state and is used for six.

**States that exist in code but were never seen.** If nobody has rendered the error state, it is
broken. Make each state reachable in development, with a query parameter, a story, or a mock toggle.

**Layout shift between states.** The empty, loading, and loaded states should occupy comparable space,
or the page jumps and the user loses their place.

**Error copy that blames the user for a server problem, or blames the server for a validation
problem.** Both destroy trust in the message.

**Optimistic updates with no rollback design.** Fast until it is wrong, and then silently wrong.

**Treating identity states as server concerns.** The last-admin guard belongs in both places. An
interface that offers an action the server will refuse has already failed.

## Worked example, compressed

A team members screen with a list, invite, role change, and remove.

Reachable and needing specification: first-run empty (a one person workspace: the owner is alone, so
"empty" means "just you", and the copy is an invite prompt, not a null state); no-results-from-search;
loading skeleton; loading more if paginated; retryable load failure; per-invite partial failure when
inviting several addresses at once; viewer cannot manage, so role and remove controls are disabled with
a reason rather than hidden; seats exhausted, which disables invite before it fails; the row that is
you, where "Remove" becomes "Leave workspace"; the last admin, where demote and remove are blocked with
an explanation; invited members, which get resend and revoke rather than remove; suspended members;
long display names and missing avatars; role change in flight, which disables that row's control only,
not the whole table; role change rolled back; keyboard focus after removing a row, which must move to
the next row rather than to the body; and the narrow viewport, where the table becomes stacked cards.

Not reachable, with mechanisms: the list is never truly empty, because the owner is always a member;
there is no offline mode, because the app requires a connection to render at all.

Out of scope, stated: concurrent edit conflicts, since last-write-wins is accepted for role changes,
and the window is small.

## What this skill does not do

- It does not implement anything. It produces the enumeration and the specification, and the code is a
  separate step.
- It does not tell you which states matter most for your specific product, only a general priority
  order. A read-heavy dashboard and a high-volume data entry tool have different answers.
- It is not an accessibility audit. It covers announcement and focus for the states it enumerates and
  nothing beyond that.
- It does not verify that implemented states are correct. It says what should exist; checking what does
  exist is a separate review.
- Applied to a trivial screen it will over-generate. Use the reachability pass honestly, and be willing
  to mark most of the taxonomy not applicable.
Why import instead of copy

A skill is only as good as what it can read.

These skills all ask your assistant to check things against your actual codebase, your actual schema, your actual design system. Locul keeps that context current on its own, from the files you already have, on your machine. Mac and Windows, free to start.

Start free