Skip to main content
skill CX Codex

designing-user-experience — Claude Code skill: UX states

A skill for designing how an interface behaves: flows, forms, empty, loading and error states. Useful when the UI looks done but falls apart on real data.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add osmontero/opencode-skills --skill designing-user-experience -a claude-code
Open on GitHub

What designing-user-experience does

The skill treats an interface as a set of states, not screens. Its rule: no component is finished until its empty, loading, error and partial states exist. A list that only renders when it has items is a demo, not a component.

Inside is an eight-step checklist. Map the flow with every branch off it, fill the state matrix for anything touching data, design the failure paths, write the button and error copy, set the feedback budget at 100ms, 1s and 10s, handle destructive actions with confirm or undo, then check touch targets and walk the whole thing as a first-time user. Forms get their own step: one column, labels above inputs, requirements shown before typing rather than as an error afterwards. Validating on the third keystroke of an email is hostile, so the skill validates on blur and re-validates live once an error is visible.

It points to companion skills — designing-frontend-interfaces for visual craft, building-accessible-interfaces for access, reviewing-interface-quality to audit a finished UI. The source text is cut off, so I won't guess at the rest. Worth installing if an agent generates your UI and you keep finding empty tables and banner-only errors instead of field-level messages.

What it helps with

  • map a flow into steps and branches before drawing screens
  • fill the state matrix for every component that touches data
  • split four different empty states instead of one "No items found"
  • write error and button copy that tells the user what to do next
  • set validation timing so errors don't fire on the third keystroke

Example prompts for the agent

  • walk the skill checklist for the checkout form
  • build the state matrix for the orders list: empty, loading, error, partial
  • check the account deletion flow for irreversibility

How to install designing-user-experience

1Skills CLI (Vercel)

npx skills add osmontero/opencode-skills --skill designing-user-experience -a claude-code

2Manually: put the files in place

~/.claude/skills/designing-user-experience/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/designing-user-experience/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about designing-user-experience

How do I install designing-user-experience in Claude?

Run npx skills add osmontero/opencode-skills --skill designing-user-experience -a claude-code. The skill lands in ~/.claude/skills/designing-user-experience/SKILL.md.

What does designing-user-experience do?

It helps design interface behaviour: user flows and navigation, forms and validation, empty, loading and error states, plus confirmation and undo for destructive actions.

Which skills does it work with?

It references designing-frontend-interfaces for visual craft, building-accessible-interfaces for accessibility and reviewing-interface-quality for auditing a finished UI.

Source text

SKILL.md · MIT
---
name: designing-user-experience
description: Use when designing how an interface behaves rather than how it looks — user flows, navigation and information architecture, forms and validation, loading/empty/error states, confirmation and undo for destructive actions, button and error message wording, feedback for slow operations, or mobile and touch behavior. Also use when a UI looks finished but breaks on bad input, slow networks, no data, or first use.
---

# Designing User Experience

## Overview

Visual design decides what an interface looks like when everything goes right. UX decides what happens the rest of the time — and the rest of the time is most of the time.

**Core principle:** Design the states, not the screens. A "screen" is one state of a component; shipping only that one is why generated UI looks finished and breaks on contact with real data.

**Companion skills:** `designing-frontend-interfaces` for visual craft, `building-accessible-interfaces` for access, `reviewing-interface-quality` to audit a finished UI.

## The Iron Law

```
NO COMPONENT IS DONE UNTIL ITS EMPTY, LOADING, ERROR, AND PARTIAL STATES EXIST
```

A list that only renders when it has items is not a list component — it is a demo. If the design has no answer for "what does a new user with no data see", the design is incomplete.

## Checklist

Create a task for each item:

1. **Map the flow** — entry point → steps → success, and every branch off it
2. **Fill the state matrix** — for every component that touches data
3. **Design the failure paths** — what breaks, what the user sees, how they recover
4. **Write the copy** — buttons, errors, empty states, confirmations
5. **Set the feedback budget** — what happens at 100ms, 1s, 10s
6. **Handle the destructive and irreversible** — confirm, undo, or both
7. **Check touch and small viewport** — targets, reach, keyboard-open behavior
8. **Walk the flow as a first-time user** — with no data, no permissions, on a slow connection

## Step 1: Map the Flow

Before designing screens, write the flow as text. It takes two minutes and exposes gaps no mockup will.

```
GOAL: Reviewer approves a pending expense

Entry:    Email link → deep link into the item (may not be authenticated)
Step 1:   Authenticate                → already logged in? skip
Step 2:   View item + receipt
Step 3:   Approve | Reject | Request info
Success:  Confirmation + next pending item queued
Exit:     No items left → done state, not an empty table

Branches:
  Not authorized for this item     → explain who is, offer to forward
  Item already actioned by someone → show who and when, no error framing
  Receipt fails to load            → approve still possible, receipt shows retry
  Offline mid-approve              → queue and retry, tell them it is queued
```

**The branches are the design work.** Any flow whose branch list is empty has not been thought about yet.

**Reduce steps before styling them.** Each step is a chance to abandon. Ask of every one: can this be defaulted, inferred, deferred to later, or removed? A form field that can be derived should not be asked.

Worked flows for the patterns that recur — onboarding, search and filtering, multi-step wizards, undo, permissions and paywalls: [references/flow-patterns.md](references/flow-patterns.md).

## Step 2: The State Matrix

Every component that displays data or accepts input has these states. Enumerate them explicitly; do not discover them in production.

| State | Trigger | Requirement |
|---|---|---|
| **Empty — first use** | New user, nothing created yet | Explain the feature and give the primary action. Never "No data." |
| **Empty — no results** | Filter or search matched nothing | Echo the query, offer to clear filters. Different from first-use empty. |
| **Empty — cleared** | User completed everything | Acknowledge it. This is a success state, not an absence. |
| **Loading — first** | No cached data | Skeleton matching real layout, delayed ~200ms |
| **Loading — refresh** | Data exists, updating | Keep old data visible, subtle indicator. Never blank the screen. |
| **Partial** | Some sources failed | Show what loaded, mark what did not, offer retry for that part |
| **Error — recoverable** | Timeout, 5xx, network | What happened, what to do, a retry control |
| **Error — permanent** | 404, deleted, revoked | Say it plainly, offer the nearest useful destination |
| **Error — permission** | 403 | Who does have access, how to request it |
| **Offline** | Connection lost | What still works, what is queued |
| **Too much** | 10,000 rows | Pagination or virtualization, plus a count |
| **Stale** | Cache older than threshold | Timestamp and a refresh control |
| **Success** | Action completed | Confirm it, and say what changed |

**The four empty states are genuinely different.** Collapsing them into one "No items found" is the most common UX shortcut in generated interfaces.

```
First use:   "No expenses yet — add your first to start tracking."     [Add expense]
No results:  "No expenses match 'q3 travel'."                          [Clear filters]
Cleared:     "All caught up. Nothing needs review."
Error:       "Couldn't load expenses. Check your connection."          [Retry]
```

## Step 3: Forms

Forms are where most UX failures live because they are where users actually type.

### Structure

- **One column.** Multi-column forms cause skipped fields and are ambiguous with keyboard order. Exceptions: genuinely paired fields (city/state, expiry/CVC).
- **Labels above inputs**, always visible. Placeholder-as-label disappears on focus, fails at low contrast, and breaks autofill and screen readers.
- **Group related fields** with real spacing (see the proximity rule in `designing-frontend-interfaces`).
- **Mark whichever is rarer** — if most fields are required, mark the optional ones instead. Never mark nothing.
- **Show requirements before typing**, not as an error afterwards. "8+ characters, one number" belongs under the field from the start.

### Validation Timing

| When | Do |
|---|---|
| While typing, field never blurred | Nothing. Validating on keystroke 3 of an email is hostile. |
| On blur, field non-empty | Validate this field |
| While typing, **after** an error was shown | Re-validate live so the error clears as they fix it |
| On submit | Validate all, focus the first invalid field, summarize at top if more than 2 |
| Server rejects | Map errors back to specific fields; never only a banner |

Errors appear **next to the field**, not only in a summary. A summary at the top is an addition for long forms, never a replacement.

### Input Types

Getting these right is most of mobile form UX — the correct keyboard appears automatically.

```html
<input type="email"    inputmode="email"    autocomplete="email">
<input type="tel"      inputmode="tel"      autocomplete="tel">
<input type="text"     inputmode="numeric"  autocomplete="one-time-code" pattern="[0-9]*">
<input type="password" autocomplete="current-password">
<input type="password" autocomplete="new-password">
<input type="text"     autocomplete="street-address">
```

Use `inputmode="numeric"` rather than `type="number"` for codes, PINs, and card numbers — `type="number"` adds spinners, allows `e` and `-`, and silently drops leading zeros.

**Never block paste.** Blocking paste on password or confirmation fields breaks password managers and helps no one.

### Data Handling

- **Never clear the form on error.** Losing typed data is the single most infuriating form behavior.
- **Preserve input across navigation** where the form is long — sessionStorage or server draft.
- **Be liberal in what you accept.** Strip spaces from card numbers and phone numbers rather than rejecting them. Accept `+1 (555) 123-4567` and normalize it yourself.
- **Disable submit only while submitting**, never as a validation gate. A permanently disabled button with no explanation gives the user nothing to act on.

## Step 4: Copy

Interface copy is UX, not decoration. It is often the entire difference between a usable and an unusable state.

### Buttons

Name the action, not the abstraction.

| Weak | Strong |
|---|---|
| Submit | Send invitation |
| OK | Delete 3 files |
| Yes / No | Discard changes / Keep editing |
| Continue | Review order |

A confirmation dialog's buttons must be readable **without** the dialog text. "OK" and "Cancel" on a delete confirmation force the user to re-read the prompt to know which is destructive.

### Errors

Three parts: what happened, why, what to do. Never expose a stack trace or a raw status code to a user.

| Bad | Good |
|---|---|
| "Error: invalid input" | "Enter a date after today — this event has already passed." |
| "Something went wrong" | "Couldn't save. Your connection dropped — we kept your changes. [Retry]" |
| "Error 403" | "You don't have access to this project. Ask its owner, Dana Reyes, for access." |
| "Invalid email" | "Emails need an @ — did you mean [email protected]?" |

Never blame the user. "You entered an invalid date" → "That date has already passed."

### Voice

- Second person, active voice, present tense.
- Short. Cut every sentence that does not change what the user does.
- No exclamation marks in error states.
- No jargon the user did not introduce — "authentication token expired" → "you've been signed out."
- Sentence case for buttons and headings; Title Case reads as shouting in UI.

## Step 5: Feedback and Latency

Every user action needs acknowledgment within 100ms, even if the result takes longer.

| Elapsed | Requirement |
|---|---|
| 0-100ms | Feels instant. Show nothing extra. A flashing spinner is worse than none. |
| 100ms-1s | Immediate local feedback — button enters a pressed/busy state |
| 1-10s | Skeleton or determinate progress. Keep the layout stable. |
| 10s+ | Real progress, an estimate, and a cancel control |

**Optimistic updates** for actions that nearly always succeed (like, star, reorder): apply the change immediately, reconcile on response, and revert with a clear message on failure. Do not use them for anything with financial or destructive consequences.

**Never move content under a pointer.** Content that shifts after load causes mis-clicks. Reserve space for anything that will arrive: images with `width`/`height`, ad slots, async badges.

## Step 6: Destructive and Irreversible Actions

**Prefer undo over confirm.** A confirmation dialog interrupts every user to protect against a rare mistake; undo protects against the mistake without interrupting anyone.

```
Do:      [Delete]  →  item removed  →  toast: "Message deleted. [Undo]"  (8-10s)
Instead of:  [Delete]  →  "Are you sure?"  →  [Yes] [No]
```

**Confirm when undo is genuinely impossible** — sending an email, charging a card, permanently destroying data, anything affecting other people.

A real confirmation dialog:
- States the **specific** consequence with the count and name: "Delete 47 records from *Q3 Archive*? This cannot be undone."
- Labels the buttons with the actions, never Yes/No.
- Puts the destructive action in a destructive color **and** distinct wording.
- Requires typing the resource name only when the action is catastrophic and irreversible. Reserve that friction; overusing it trains users to type past it.
- Focuses the **safe** option by default.

Never use `window.confirm()` in production UI — it is unstyleable, blocks the main thread, and reads poorly to screen readers.

## Step 7: Navigation and Information Architecture

- **The user must always know where they are.** Current section indicated in nav; page title matching the destination they clicked.
- **Breadcrumbs for hierarchies deeper than two levels.**
- **Back must work.** Modals, filters, tabs, and steps should be URL-addressable so back and refresh behave. A filtered view that resets on refresh is broken.
- **Deep links must survive auth.** Land on a login, then return to the originally requested resource — not the dashboard.
- **Flat beats deep.** Seven items at one level are easier than three levels of three. Add a level only when a category genuinely has its own landing content.
- **Search is a feature, not a fallback.** If the answer to "how do users find X" is "search", the IA needs work — but ship search anyway.

## Step 8: Touch and Small Viewports

| Rule | Value |
|---|---|
| Minimum touch target | 44×44 CSS px (WCAG 2.2 SC 2.5.8) |
| Spacing between targets | 8px minimum |
| Primary actions | Bottom half of the screen — thumb reach |
| Destructive actions | Away from primary actions, never adjacent |
| Hover-only affordances | Forbidden. Touch has no hover. |
| Fixed bottom bars | Account for the on-screen keyboard and safe areas |

```css
.bottom-bar { padding-bottom: max(var(--space-s), env(safe-area-inset-bottom)); }
body { min-height: 100dvh; }  /* not 100vh — browser chrome makes it overflow */
```

Every gesture needs a visible equivalent. Swipe-to-delete is fine as an accelerator; it cannot be the only way to delete.

## Self-Review

- [ ] Every data component has all four empty states distinguished
- [ ] Loading never blanks already-visible content
- [ ] Every error says what to do next, and has a control to do it
- [ ] No error text exposes a status code or stack trace
- [ ] Forms never clear on error
- [ ] Validation fires on blur, not on keystroke
- [ ] Every input has the right `type`, `inputmode`, and `autocomplete`
- [ ] Button labels name their action and are readable out of context
- [ ] Destructive actions have undo, or a confirmation naming the specific consequence
- [ ] Filters, tabs, and modals are reflected in the URL
- [ ] Touch targets ≥ 44px, primary actions in thumb reach
- [ ] Nothing is reachable by hover alone
- [ ] Walked the whole flow with no data, no permission, and a throttled connection

## Common Mistakes

**The happy path is the only path.** Everything renders beautifully with three seeded items and collapses with zero or ten thousand.

**"No data" as an empty state.** It tells the user nothing and offers no action. Empty states are the highest-leverage onboarding surface in the product.

**Spinner replacing the whole page on refresh.** The user had content; now they have nothing. Keep it and indicate the update.

**Disabled submit with no explanation.** The user cannot tell what is missing. Enable it and validate on click, or state what is incomplete.

**Confirmation dialogs everywhere.** Confirming everything trains users to dismiss without reading, which is worse than no confirmation. Undo where possible.

**Error toasts that auto-dismiss.** A 4-second toast carrying the only description of a failure is a message the user will miss. Errors persist until dismissed; only successes auto-dismiss.

**Modal-in-modal.** Almost always a sign the flow needs a page, not a layer.

**Icon-only buttons without labels.** Recognizable icons are a short list: close, search, menu, plus. Everything else needs a visible label or, at minimum, a tooltip plus an accessible name.

© osmontero · MIT · osmontero/opencode-skills

Similar skills

Everything for Design →

From the same set2

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor