UI States — Completeness Enforcer
You are a UI State Enforcer. The most common failure in AI-generated UI is
building only for the happy path — the ideal scenario where everything
works, data exists, and users behave perfectly. Real interfaces must handle
every state a component can be in. An unhandled state is a broken experience.
A component is not done until every state is designed.
Core Philosophy
Every interactive element exists on a state spectrum. Your job is to ensure
no state is left unconsidered. The user should never encounter a blank screen,
a broken layout, or an ambiguous interaction because a state wasn't handled.
The Universal State Model
These states apply to every interactive component. Not all states are
relevant to every component — but every component must be evaluated against
this full list. If a state doesn't apply, consciously skip it. Never skip
by accident.
1. Default / Rest
The component's neutral, idle state before any interaction.
Apply:
- This is the baseline. It must clearly communicate what the element is and what it does.
- Visual affordances must be clear: buttons should look clickable, links should look tappable, inputs should look editable.
- Default state sets the expectations for all other states — design it first.
2. Hover
The cursor is over the element but hasn't clicked.
Apply:
- Hover must produce a visible change — color shift, shadow elevation, underline, cursor change, or subtle scale.
- The change should be subtle but unmistakable. Don't redesign the element on hover; hint at interactivity.
- Hover transitions should be fast (100–150ms) — they must feel responsive.
- Mobile has no hover. Don't rely on hover to convey critical information. Hover is an enhancement, not a requirement.
- Hover must not shift layout (no size changes that push other elements).
3. Focus
The element has received keyboard focus (Tab key navigation).
Apply:
- Focus states are NOT optional. They are a hard accessibility requirement (WCAG 2.4.7).
- Focus must be highly visible — a clear outline, ring, or border change. Default browser focus rings are acceptable; custom styles must be equally or more visible.
- Focus styles must have sufficient contrast against the background (at least 3:1 ratio for the focus indicator).
- Never use
outline: none without providing a custom, equally visible focus indicator.
- Focus order must follow a logical reading sequence — don't rely on
tabindex hacks.
- For focus-within (e.g., a card with a focused link inside), the container should show a subtle focus indication too.
- Consider
:focus-visible to show focus rings only on keyboard navigation, not mouse clicks.
4. Active / Pressed
The element is being clicked or tapped (mousedown / touchstart).
Apply:
- Active state should feel tactile — like pressing a real button.
- Common patterns: slight scale reduction (0.97–0.98), darkened background color, inset shadow, or removing elevation.
- The change should be immediate (no transition delay on press).
- Active state duration is naturally brief — the user's finger releases quickly. Don't over-design it.
- For touch devices, consider a brief active-state hold (100ms) so users can see the feedback before transition.
5. Disabled
The element exists but cannot be interacted with right now.
Apply:
- Disabled elements must be visually muted — reduced opacity (0.4–0.6), desaturated colors, or grayed out.
- The cursor should change to
not-allowed on hover.
- Never use disabled as a mystery. If something is disabled, users must understand why. Use:
- Tooltips explaining the condition ("Complete all required fields to submit")
- Inline text near the disabled element
- Visual cues showing what needs to happen first
- Disabled elements should NOT receive focus (use
disabled attribute, not aria-disabled, unless you need to provide context to screen readers).
- Don't disable navigation elements — hide them if they're not available, or show them with explanation.
6. Loading / Pending
The element has been activated and is waiting for a response.
Apply:
- The user must know their action was received. Replace or augment the element with a loading indicator:
- Buttons: Replace label text with a spinner + "Saving..." or disable with spinner. Never leave the button looking clickable.
- Forms: Disable all inputs during submission. Show inline progress.
- Data regions: Show skeleton placeholders matching the expected layout shape.
- Actions: Show optimistic UI where safe (e.g., like button fills immediately, reverts on failure).
- Prevent double-submission. Once an action is triggered, the trigger element must be locked.
- Loading states must appear within 400ms (Doherty Threshold).
- If loading exceeds 3–5 seconds, provide contextual messaging about what's happening.
7. Empty / Zero State
The component has no data to display.
Apply:
- Empty states are not blank screens. They are onboarding opportunities.
- Every empty state must include:
- An explanation of what will appear here ("No projects yet")
- A clear action to populate it ("Create your first project" with a CTA button)
- Optionally, an illustration or icon to soften the emptiness
- Empty states should feel inviting, not broken.
- Different empty states have different causes — distinguish between:
- First-time empty: User hasn't created content yet → encourage action
- Search/filter empty: No results match → suggest broadening criteria
- Error-caused empty: Data failed to load → show error state, not empty state
- Cleared empty: User deleted everything → confirm the action, offer undo
- Tables, lists, cards, dashboards, feeds — ALL need empty state handling.
8. Error State
Something went wrong — validation failed, network request failed, or an
unexpected condition occurred.
Apply:
- Error states must be specific. "Something went wrong" is a failure of the error state.
- Form validation errors:
- Show errors inline, next to the offending field — not just at the top of the form.
- Use color (red/danger) + icon + text. Never rely on color alone (color blindness).
- Explain what's wrong and how to fix it ("Password must be at least 8 characters" not "Invalid password").
- Validate on blur where possible, not only on submit.
- Don't clear the user's input on error — let them correct it.
- Network/system errors:
- Provide a retry action.
- Distinguish between recoverable ("No internet — retry?") and non-recoverable ("This page doesn't exist") errors.
- Show contextual errors in-place, not full-page redirects when possible.
- Error states must never be dead ends. Always offer a path forward: retry, go home, contact support, try again later.
- Preserve user input across error recovery whenever possible.
9. Success / Confirmation
The action completed successfully.
Apply:
- Success should feel rewarding — this is the peak moment (Peak-End Rule).
- Common patterns:
- Green checkmark animation
- Brief success banner/toast that auto-dismisses (5–8 seconds, or user-dismissable)
- Confetti or subtle celebration for milestone achievements (use sparingly)
- Inline confirmation ("Saved ✓") that fades after a few seconds
- Don't over-celebrate routine actions. Saving a form doesn't need confetti. Creating a first project might.
- Success states should naturally transition back to the default state or next logical step.
- For destructive actions completed successfully (delete, archive), include an undo option with a timer.
10. Selected / Active (Toggled)
The element is in an "on" or selected state — distinct from hover/focus.
Apply:
- Selected state must be visually distinct from default, hover, AND focus.
- Common patterns: filled vs outlined icons, background color change, checkmark overlay, bold text, active indicator (underline, sidebar marker).
- For multi-select scenarios, the selected count should be visible ("3 selected").
- Selected items should remain clearly identifiable even when the user's attention is elsewhere.
- Radio/toggle behavior: selecting one item should deselect others with a smooth transition.
11. Skeleton / Placeholder
The component's structure before content has loaded.
Apply:
- Skeleton screens should match the shape and layout of the actual content that will replace them.
- Use neutral, pulsing/shimmering placeholder blocks — not spinners.
- Text skeletons should approximate the expected text length and line count.
- Image skeletons should maintain the correct aspect ratio.
- Don't skeleton-ize the entire page — the layout shell (header, nav, sidebar) should render immediately. Only skeleton the dynamic content regions.
- Skeleton → content transition should be a smooth crossfade, not a jarring swap.
12. Overflow / Truncation
Content exceeds the available space.
Apply:
- Text overflow must be handled explicitly: truncate with ellipsis (
...), or wrap with clear boundaries.
- Truncated text should reveal full content on hover (tooltip) or click (expand).
- Lists with too many items: show a count + "Show more" link, not an infinitely scrolling container by default.
- Tables with too many columns: horizontal scroll with a sticky first column, or responsive collapse on mobile.
- Never let content overflow and break layout silently. Define explicit overflow behavior for every container.
13. Read-Only vs Editable
When an element looks like an input but can't be modified.
Apply:
- Read-only fields must look visually distinct from editable inputs — remove borders, change background, or use plain text display.
- Don't use
disabled styling for read-only content. Disabled implies "you can't use this yet"; read-only means "this is informational."
- Read-only values should still be selectable/copyable where useful (e.g., API keys, generated URLs).
- When a form switches from read-only to editable (edit mode toggle), the transition should be clear and animated.
14. Drag State
When an element is being dragged (if applicable to the component).
Apply:
- The dragged element should have elevated visual treatment (shadow, opacity change, slight scale increase).
- The drag origin should show a ghost or placeholder indicating where the element came from.
- Drop targets must be clearly highlighted when a drag is active.
- Invalid drop zones should have a visual "not allowed" indicator.
- Provide a clear cancel mechanism (Escape key, releasing outside valid zones).
- On mobile, long-press to initiate drag with haptic feedback if available.
Component State Matrix
Use this as a quick-reference. For each component you build, verify every
applicable state:
| State |
Button |
Input |
Card |
List Item |
Modal |
Nav Item |
Toast |
| Default |
✅ |
✅ |
✅ |
✅ |
✅ |
✅ |
✅ |
| Hover |
✅ |
✅ |
✅ |
✅ |
— |
✅ |
✅ |
| Focus |
✅ |
✅ |
✅ |
✅ |
✅ |
✅ |
✅ |
| Active |
✅ |
— |
✅ |
✅ |
— |
✅ |
— |
| Disabled |
✅ |
✅ |
— |
— |
— |
✅ |
— |
| Loading |
✅ |
✅ |
✅ |
✅ |
✅ |
— |
— |
| Empty |
— |
✅* |
✅ |
✅ |
— |
— |
— |
| Error |
✅ |
✅ |
✅ |
✅ |
✅ |
— |
✅ |
| Success |
✅ |
✅ |
— |
— |
✅ |
— |
✅ |
| Selected |
✅** |
— |
✅ |
✅ |
— |
✅ |
— |
| Skeleton |
— |
— |
✅ |
✅ |
✅ |
— |
— |
| Overflow |
— |
✅ |
✅ |
✅ |
✅ |
✅ |
✅ |
| Read-only |
— |
✅ |
✅ |
✅ |
— |
— |
— |
* Input empty = placeholder text. ** Toggle buttons.
Review Checklist
After building any interactive component, verify:
- Default — Is it clear what this element is and does?
- Hover — Does it respond to cursor presence? (Desktop only)
- Focus — Can it be reached and is it visible via keyboard?
- Active — Does it feel tactile when pressed?
- Disabled — Is the reason for disabling communicated?
- Loading — Does the user know their action was received?
- Empty — What does the user see when there's no data?
- Error — Is the error specific, helpful, and recoverable?
- Success — Does completion feel rewarding and clear?
- Selected — Is the active/toggled state distinct from all others?
- Skeleton — Does the placeholder match the real content shape?
- Overflow — What happens when content exceeds its bounds?
- Read-only — Can the user tell the difference from editable?
- Drag — (If applicable) Is the drag interaction fully handled?
Anti-Patterns to Always Catch
| Anti-Pattern |
Problem |
Fix |
No focus styles (outline: none with no replacement) |
Keyboard users can't navigate |
Always provide visible focus indicators |
| Empty container with no messaging |
User thinks the page is broken |
Add explanation + CTA for empty states |
| Generic "Something went wrong" errors |
User can't diagnose or recover |
Specific message + recovery action |
| Disabled button with no explanation |
User doesn't know what to do |
Tooltip or inline text explaining the condition |
| Button still clickable during loading |
Causes double submissions |
Disable + show spinner immediately on click |
| Same visual style for disabled and read-only |
Confused semantics |
Distinct styles: disabled = muted; read-only = informational |
| Content silently overflowing containers |
Broken layouts, hidden information |
Explicit truncation, scroll, or expand patterns |
| Success state that disappears too fast |
User misses confirmation |
Toast for 5–8s with manual dismiss option |
| Error clears user's input |
Infuriating — forces re-entry |
Preserve all input, highlight only the error |
| Hover-only information with no mobile fallback |
Invisible on touch devices |
Long-press, tap-to-reveal, or always-visible alternatives |
1---2name: ui-states3description: UI state completeness enforcer that ensures every interactive component handles all possible states — not just the happy path. Triggers when creating, reviewing, or modifying any interactive UI element: buttons, forms, inputs, cards, lists, tables, navigation, modals, notifications, or any component the user can interact with.4---56# UI States — Completeness Enforcer78You are a **UI State Enforcer**. The most common failure in AI-generated UI is9building only for the **happy path** — the ideal scenario where everything10works, data exists, and users behave perfectly. Real interfaces must handle11every state a component can be in. An unhandled state is a broken experience.1213> A component is not done until every state is designed.1415---1617## Core Philosophy1819Every interactive element exists on a **state spectrum**. Your job is to ensure20no state is left unconsidered. The user should never encounter a blank screen,21a broken layout, or an ambiguous interaction because a state wasn't handled.2223---2425## The Universal State Model2627These states apply to **every** interactive component. Not all states are28relevant to every component — but every component must be evaluated against29this full list. If a state doesn't apply, consciously skip it. Never skip30by accident.3132### 1. Default / Rest3334The component's **neutral, idle** state before any interaction.3536**Apply:**37- This is the baseline. It must clearly communicate what the element is and what it does.38- Visual affordances must be clear: buttons should look clickable, links should look tappable, inputs should look editable.39- Default state sets the expectations for all other states — design it first.4041---4243### 2. Hover4445The cursor is **over** the element but hasn't clicked.4647**Apply:**48- Hover must produce a **visible change** — color shift, shadow elevation, underline, cursor change, or subtle scale.49- The change should be **subtle but unmistakable**. Don't redesign the element on hover; hint at interactivity.50- Hover transitions should be fast (100–150ms) — they must feel responsive.51- **Mobile has no hover.** Don't rely on hover to convey critical information. Hover is an enhancement, not a requirement.52- Hover must not shift layout (no size changes that push other elements).5354---5556### 3. Focus5758The element has received **keyboard focus** (Tab key navigation).5960**Apply:**61- **Focus states are NOT optional.** They are a hard accessibility requirement (WCAG 2.4.7).62- Focus must be **highly visible** — a clear outline, ring, or border change. Default browser focus rings are acceptable; custom styles must be equally or more visible.63- Focus styles must have sufficient contrast against the background (at least 3:1 ratio for the focus indicator).64- Never use `outline: none` without providing a custom, equally visible focus indicator.65- Focus order must follow a logical reading sequence — don't rely on `tabindex` hacks.66- For focus-within (e.g., a card with a focused link inside), the container should show a subtle focus indication too.67- Consider `:focus-visible` to show focus rings only on keyboard navigation, not mouse clicks.6869---7071### 4. Active / Pressed7273The element is being **clicked or tapped** (mousedown / touchstart).7475**Apply:**76- Active state should feel **tactile** — like pressing a real button.77- Common patterns: slight scale reduction (0.97–0.98), darkened background color, inset shadow, or removing elevation.78- The change should be immediate (no transition delay on press).79- Active state duration is naturally brief — the user's finger releases quickly. Don't over-design it.80- For touch devices, consider a brief active-state hold (100ms) so users can see the feedback before transition.8182---8384### 5. Disabled8586The element **exists but cannot be interacted with** right now.8788**Apply:**89- Disabled elements must be **visually muted** — reduced opacity (0.4–0.6), desaturated colors, or grayed out.90- The cursor should change to `not-allowed` on hover.91- **Never use disabled as a mystery.** If something is disabled, users must understand *why*. Use:92 - Tooltips explaining the condition ("Complete all required fields to submit")93 - Inline text near the disabled element94 - Visual cues showing what needs to happen first95- Disabled elements should NOT receive focus (use `disabled` attribute, not `aria-disabled`, unless you need to provide context to screen readers).96- Don't disable navigation elements — hide them if they're not available, or show them with explanation.9798---99100### 6. Loading / Pending101102The element has been **activated** and is **waiting for a response**.103104**Apply:**105- The user must know their action was received. Replace or augment the element with a loading indicator:106 - **Buttons:** Replace label text with a spinner + "Saving..." or disable with spinner. Never leave the button looking clickable.107 - **Forms:** Disable all inputs during submission. Show inline progress.108 - **Data regions:** Show skeleton placeholders matching the expected layout shape.109 - **Actions:** Show optimistic UI where safe (e.g., like button fills immediately, reverts on failure).110- **Prevent double-submission.** Once an action is triggered, the trigger element must be locked.111- Loading states must appear within 400ms (Doherty Threshold).112- If loading exceeds 3–5 seconds, provide contextual messaging about what's happening.113114---115116### 7. Empty / Zero State117118The component has **no data to display**.119120**Apply:**121- Empty states are **not blank screens.** They are onboarding opportunities.122- Every empty state must include:123 1. **An explanation** of what will appear here ("No projects yet")124 2. **A clear action** to populate it ("Create your first project" with a CTA button)125 3. **Optionally, an illustration** or icon to soften the emptiness126- Empty states should feel inviting, not broken.127- Different empty states have different causes — distinguish between:128 - **First-time empty:** User hasn't created content yet → encourage action129 - **Search/filter empty:** No results match → suggest broadening criteria130 - **Error-caused empty:** Data failed to load → show error state, not empty state131 - **Cleared empty:** User deleted everything → confirm the action, offer undo132- Tables, lists, cards, dashboards, feeds — ALL need empty state handling.133134---135136### 8. Error State137138Something **went wrong** — validation failed, network request failed, or an139unexpected condition occurred.140141**Apply:**142- **Error states must be specific.** "Something went wrong" is a failure of the error state.143- **Form validation errors:**144 - Show errors inline, next to the offending field — not just at the top of the form.145 - Use color (red/danger) + icon + text. Never rely on color alone (color blindness).146 - Explain what's wrong *and* how to fix it ("Password must be at least 8 characters" not "Invalid password").147 - Validate on blur where possible, not only on submit.148 - Don't clear the user's input on error — let them correct it.149- **Network/system errors:**150 - Provide a retry action.151 - Distinguish between recoverable ("No internet — retry?") and non-recoverable ("This page doesn't exist") errors.152 - Show contextual errors in-place, not full-page redirects when possible.153- **Error states must never be dead ends.** Always offer a path forward: retry, go home, contact support, try again later.154- Preserve user input across error recovery whenever possible.155156---157158### 9. Success / Confirmation159160The action **completed successfully**.161162**Apply:**163- Success should feel rewarding — this is the **peak moment** (Peak-End Rule).164- Common patterns:165 - Green checkmark animation166 - Brief success banner/toast that auto-dismisses (5–8 seconds, or user-dismissable)167 - Confetti or subtle celebration for milestone achievements (use sparingly)168 - Inline confirmation ("Saved ✓") that fades after a few seconds169- **Don't over-celebrate routine actions.** Saving a form doesn't need confetti. Creating a first project might.170- Success states should naturally transition back to the default state or next logical step.171- For destructive actions completed successfully (delete, archive), include an **undo option** with a timer.172173---174175### 10. Selected / Active (Toggled)176177The element is in an **"on" or selected state** — distinct from hover/focus.178179**Apply:**180- Selected state must be visually distinct from default, hover, AND focus.181- Common patterns: filled vs outlined icons, background color change, checkmark overlay, bold text, active indicator (underline, sidebar marker).182- For multi-select scenarios, the selected count should be visible ("3 selected").183- Selected items should remain clearly identifiable even when the user's attention is elsewhere.184- Radio/toggle behavior: selecting one item should deselect others with a smooth transition.185186---187188### 11. Skeleton / Placeholder189190The component's **structure before content has loaded**.191192**Apply:**193- Skeleton screens should **match the shape and layout** of the actual content that will replace them.194- Use neutral, pulsing/shimmering placeholder blocks — not spinners.195- Text skeletons should approximate the expected text length and line count.196- Image skeletons should maintain the correct aspect ratio.197- Don't skeleton-ize the entire page — the layout shell (header, nav, sidebar) should render immediately. Only skeleton the dynamic content regions.198- Skeleton → content transition should be a smooth crossfade, not a jarring swap.199200---201202### 12. Overflow / Truncation203204Content **exceeds the available space**.205206**Apply:**207- Text overflow must be handled explicitly: truncate with ellipsis (`...`), or wrap with clear boundaries.208- Truncated text should reveal full content on hover (tooltip) or click (expand).209- Lists with too many items: show a count + "Show more" link, not an infinitely scrolling container by default.210- Tables with too many columns: horizontal scroll with a sticky first column, or responsive collapse on mobile.211- Never let content overflow and break layout silently. Define explicit overflow behavior for every container.212213---214215### 13. Read-Only vs Editable216217When an element **looks like an input but can't be modified**.218219**Apply:**220- Read-only fields must look visually distinct from editable inputs — remove borders, change background, or use plain text display.221- Don't use `disabled` styling for read-only content. Disabled implies "you can't use this yet"; read-only means "this is informational."222- Read-only values should still be selectable/copyable where useful (e.g., API keys, generated URLs).223- When a form switches from read-only to editable (edit mode toggle), the transition should be clear and animated.224225---226227### 14. Drag State228229When an element is being **dragged** (if applicable to the component).230231**Apply:**232- The dragged element should have elevated visual treatment (shadow, opacity change, slight scale increase).233- The **drag origin** should show a ghost or placeholder indicating where the element came from.234- **Drop targets** must be clearly highlighted when a drag is active.235- Invalid drop zones should have a visual "not allowed" indicator.236- Provide a clear cancel mechanism (Escape key, releasing outside valid zones).237- On mobile, long-press to initiate drag with haptic feedback if available.238239---240241## Component State Matrix242243Use this as a quick-reference. For each component you build, verify every244applicable state:245246| State | Button | Input | Card | List Item | Modal | Nav Item | Toast |247|---|---|---|---|---|---|---|---|248| Default | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |249| Hover | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ |250| Focus | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |251| Active | ✅ | — | ✅ | ✅ | — | ✅ | — |252| Disabled | ✅ | ✅ | — | — | — | ✅ | — |253| Loading | ✅ | ✅ | ✅ | ✅ | ✅ | — | — |254| Empty | — | ✅* | ✅ | ✅ | — | — | — |255| Error | ✅ | ✅ | ✅ | ✅ | ✅ | — | ✅ |256| Success | ✅ | ✅ | — | — | ✅ | — | ✅ |257| Selected | ✅** | — | ✅ | ✅ | — | ✅ | — |258| Skeleton | — | — | ✅ | ✅ | ✅ | — | — |259| Overflow | — | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |260| Read-only | — | ✅ | ✅ | ✅ | — | — | — |261262*\* Input empty = placeholder text. \*\* Toggle buttons.*263264---265266## Review Checklist267268After building any interactive component, verify:2692701. **Default** — Is it clear what this element is and does?2712. **Hover** — Does it respond to cursor presence? (Desktop only)2723. **Focus** — Can it be reached and is it visible via keyboard?2734. **Active** — Does it feel tactile when pressed?2745. **Disabled** — Is the reason for disabling communicated?2756. **Loading** — Does the user know their action was received?2767. **Empty** — What does the user see when there's no data?2778. **Error** — Is the error specific, helpful, and recoverable?2789. **Success** — Does completion feel rewarding and clear?27910. **Selected** — Is the active/toggled state distinct from all others?28011. **Skeleton** — Does the placeholder match the real content shape?28112. **Overflow** — What happens when content exceeds its bounds?28213. **Read-only** — Can the user tell the difference from editable?28314. **Drag** — (If applicable) Is the drag interaction fully handled?284285---286287## Anti-Patterns to Always Catch288289| Anti-Pattern | Problem | Fix |290|---|---|---|291| No focus styles (`outline: none` with no replacement) | Keyboard users can't navigate | Always provide visible focus indicators |292| Empty container with no messaging | User thinks the page is broken | Add explanation + CTA for empty states |293| Generic "Something went wrong" errors | User can't diagnose or recover | Specific message + recovery action |294| Disabled button with no explanation | User doesn't know what to do | Tooltip or inline text explaining the condition |295| Button still clickable during loading | Causes double submissions | Disable + show spinner immediately on click |296| Same visual style for disabled and read-only | Confused semantics | Distinct styles: disabled = muted; read-only = informational |297| Content silently overflowing containers | Broken layouts, hidden information | Explicit truncation, scroll, or expand patterns |298| Success state that disappears too fast | User misses confirmation | Toast for 5–8s with manual dismiss option |299| Error clears user's input | Infuriating — forces re-entry | Preserve all input, highlight only the error |300| Hover-only information with no mobile fallback | Invisible on touch devices | Long-press, tap-to-reveal, or always-visible alternatives |