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.
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.
Similar popular skills
Related neighbors and high-traction skills in the same topics — useful to compare before installing.
Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.
Claude CodeNot declared
CursorNot declared
CodexNot declared
GitHub CopilotNot declared
WindsurfNot declared
Gemini CLINot declared
ClineNot declared
OpenCodeDeclared
Repository health
Stars9
LicenseLICENSE
Default branchmain
Open issues0
Status
Active
Skill metadata
Parsed from SKILL.md frontmatter.
Declared agentsopencode
Package contents
Files included with this skill beyond the listing page.
skill mdSKILL.md15,110 B
docsSUMMARY.md446 B
History
First seen on skills.sh
First recorded snapshot · 104 installs
SKILL.md
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:
Map the flow — entry point → steps → success, and every branch off it
Fill the state matrix — for every component that touches data
Design the failure paths — what breaks, what the user sees, how they recover
Write the copy — buttons, errors, empty states, confirmations
Set the feedback budget — what happens at 100ms, 1s, 10s
Handle the destructive and irreversible — confirm, undo, or both
Check touch and small viewport — targets, reach, keyboard-open behavior
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.
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."
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.
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
.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.