All posts

AI App Missing Empty, Error and Loading States? Fix It

Use this 10-state checklist, screen-state matrix and paste-ready prompts to find and fix missing UI states in AI-generated apps.

AI-generated apps miss empty, error and loading states because most prompts describe only the successful path. Fix this by auditing every screen against ten possible states, recording the results in a screen x state matrix, and requiring a visible frame, story or working preview for every applicable state. Then save the rules in Lovable, Bolt, v0, Cursor or Claude Code and repair one screen per message.

Why AI-generated apps miss empty, error and loading states

Builders implement the conditions named in your prompt or represented by available data. Sample-data previews usually expose the happy path, while slow, empty, offline, forbidden and failed conditions appear during real use.

Lovable's prompting guidance explicitly asks what happens when data is empty, loading or failed. Figma says its agent does not automatically check a design for absent states. This is not a defect unique to one tool. It is a specification and review problem.

Lovable documentation page 'Build with the backend in mind' listing auth logic, dynamic content and states: what happens if the data is empty, still loading, or fails

Caption: Lovable's prompting guide puts states on your list. Source: Lovable documentation, captured October 2026.

What a UI state is - and how to measure coverage

A UI state is a distinct version of a screen caused by data, network conditions, permissions or user action. Each version needs an appropriate layout, clear copy and a recovery or next action.

Measure state coverage per screen:

rendered applicable states / all applicable states

Set a 100% target for core screens. Count a state only when a frame, story or working preview exists. A ticket note does not count as designed. Not all ten states apply to every screen.

The 10-state checklist for every screen

Grid of ten wireframes of the same Bookings list screen: default, loading skeleton, empty first run, empty no results, partial, error with retry, offline, permission denied, success and long content

Caption: One screen, ten states. A happy-path prompt describes only the first. Original graphic by Mowgli.

1. Default

Use realistic, normal-volume data. Show every primary action and write real interface copy, not lorem ipsum.

2. Loading

Use a skeleton shaped like the final layout for a full-page load, an inline spinner for one component, and percent-complete progress for a long operation. Reserve the final layout space to prevent movement.

Use no indicator under 1 second, a skeleton or spinner from 2 to 10 seconds, and percent-complete progress beyond 10 seconds.

3. Empty - first run

Explain what will appear, why nothing is there yet and how the feature helps. Provide one primary action such as create, import or connect.

4. Empty - no results

Repeat the search term and active filters. Include "Clear filters" and suggest a useful expansion, such as a wider date range. Do not reuse first-run copy because the cause and remedy differ.

5. Partial

Keep successfully loaded sections visible. Mark sections that are still loading or failed, and explain when remaining data may appear. One failed request should not erase the page.

6. Error and retry

Explain the problem in plain language, say whether entered or saved data remains safe, preserve input and provide Retry or another recovery action. Put form errors beside the relevant field. Avoid "invalid," generic apologies and technical codes.

7. Offline

Show an offline banner and cached or last-synced data with an "as of" time. Mark queued actions as pending and retry automatically after reconnection.

Do not cover available content with a full-page loader. Treat failed requests as the meaningful signal because navigator.onLine is "inherently unreliable."

8. Permission denied

Identify the restricted content or action, explain who can grant access and provide "Request access." For camera, location or push access, link to Settings. Request OS permission after a relevant user action, not on initial load.

9. Success and confirmation

Confirm what happened, point to the result and offer the next step. Include Undo for destructive actions. Ensure assistive technology announces toast-style status messages.

10. Long content and overflow

Define wrapping and truncation, keep full truncated values accessible, cap counts with "99+," and allow for large values and translations. Test a 60-character product name, a seven-figure total and a German label.

Decide which states apply to each screen

Ask seven questions:

  • Does the screen fetch data?
  • Can its main collection be empty?
  • Can users search or filter it?
  • Does it combine sources or paginate?
  • Is anything restricted by role, plan or OS permission?
  • Can users submit, save or delete?
  • Will it run on mobile or unreliable connections?

Map network fetching to loading and error, an empty collection to first-run empty, search to no-results empty, multiple sources to partial, gates to permission denied, mutations to success and unreliable connections to offline. Every screen needs default and long-content testing.

A settings form seldom needs partial loading. A dashboard may need nearly every state.

Decision table with seven questions mapping to states, such as 'Does it fetch data over the network?' leading to loading and error, plus the state-coverage test formula and four steps to run it

Caption: Seven questions per screen, and how to run the state-coverage test. Original graphic by Mowgli.

Map the whole app in a screen x state matrix

Put one screen in each row, one state in each column and order rows by the user journey. Use designed with a frame or story link, missing when an applicable state is absent, n.a. with a reason when it cannot occur, and global for an app-wide pattern.

Review the matrix by column. Repeated gaps often call for one reusable pattern rather than separate design tasks.

| Screen \ State | Default | Loading | Empty | No results | Partial | Error | Offline | No access | Success | Other |
|---|---|---|---|---|---|---|---|---|---|---|
| Sign in        |         |         |       |            |         |       |         |           |         |       |
| Home           |         |         |       |            |         |       |         |           |         |       |
| Detail         |         |         |       |            |         |       |         |           |         |       |
| Create / edit  |         |         |       |            |         |       |         |           |         |       |
| Settings       |         |         |       |            |         |       |         |           |         |       |
| Missing count  |         |         |       |            |         |       |         |           |         |       |

Cell values: designed (link the frame or story) | missing (applies, nothing drawn)
             n.a. (cannot happen here; give the reason)
             global (covered once by an app-wide pattern, such as one offline banner)

Generate a state contact sheet from Storybook

Storybook exposes index.json, described as "a static index of all the stories." This script selects screen-level stories, classifies their names, captures them with Playwright and produces an HTML contact sheet.

// contact-sheet.mjs: screenshot every screen-level story and lay them out as a screen x state grid.
// Usage: node contact-sheet.mjs <storybook-url> [title-prefix]
import { chromium } from 'playwright';
import fs from 'node:fs';

const SB = process.argv[2] ?? 'http://localhost:6006';
const PREFIX = process.argv[3] ?? 'Pages/';
const COLUMNS = {
  'Default': null,
  'Loading': /loading|skeleton/i,
  'Empty / not found': /empty|missing|not ?found|no ?results/i,
  'Error': /error|fail/i,
  'Offline': /offline/i,
  'No permission': /permission|denied|forbidden|unauthori[sz]ed/i,
};
const columnOf = (name) =>
  Object.keys(COLUMNS).find((c) => COLUMNS[c]?.test(name)) ?? 'Default';

const { entries } = await (await fetch(`${SB}/index.json`)).json();
const stories = Object.values(entries).filter((e) =>
  e.type === 'story' && e.title.startsWith(PREFIX) &&
  e.title.split('/').length === PREFIX.split('/').length); // screens, not their sub-components

fs.mkdirSync('shots', { recursive: true });
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
for (const s of stories) {
  await page.goto(`${SB}/iframe.html?id=${s.id}&viewMode=story`);
  await page.waitForTimeout(2000); // let mocked requests settle (a loading story never does)
  await page.screenshot({ path: `shots/${s.id}.png` });
}
await browser.close();

const screens = [...new Set(stories.map((s) => s.title.slice(PREFIX.length)))];
const cell = (screen, col) => {
  const hits = stories.filter((s) => s.title === PREFIX + screen && columnOf(s.name) === col);
  if (!hits.length) return '<td class="gap">no story</td>';
  const more = hits.length > 1 ? ` +${hits.length - 1} more` : '';
  return `<td><img src="shots/${hits[0].id}.png"><p>${hits[0].name}${more}</p></td>`;
};
const rows = screens.map((sc) =>
  `<tr><th>${sc}</th>${Object.keys(COLUMNS).map((c) => cell(sc, c)).join('')}</tr>`).join('\n');
fs.writeFileSync('contact-sheet.html', `<!doctype html><meta charset="utf-8">
<style>body{font:14px system-ui;margin:24px;width:max-content}table{border-collapse:collapse}
th,td{border:1px solid #e5e7eb;padding:8px;vertical-align:top;text-align:left}
td{width:220px}td img{width:220px;display:block}td p{margin:6px 0 0;color:#6b7280}
td.gap{background:#fff1f2;color:#e51a4c;font-weight:600;text-align:center;vertical-align:middle}</style>
<table><tr><th>Screen \\ State</th>${Object.keys(COLUMNS).map((c) => `<th>${c}</th>`).join('')}</tr>
${rows}</table>`);
console.log(`${screens.length} screens, ${stories.length} stories -> contact-sheet.html`);

Run npm i playwright, then node contact-sheet.mjs https://your-storybook-url 'Pages/'.

For the Mealdrop public Storybook, the output contains 6 pages x 6 states, 12 screenshot cells and 24 "no story" cells. Red cells prompt review; they are not automatically defects.

Contact sheet of the Mealdrop Storybook: 6 pages by 6 states, 12 cells with screenshots, 24 marked no story

Caption: Script output for Mealdrop's public Storybook, run October 2026. Red cells have no story.

Add the missing states with three prompts

Save persistent rules once, audit without editing, then fix one screen per message. Keep each change small enough to review.

Step 1 - save the rules

UI STATE RULES (apply to every screen and every data-fetching component)
For each screen, design and build every state that applies:
1. Default: realistic data at normal volume. No lorem ipsum.
2. Loading: skeleton matching the final layout for full-page loads; inline spinner
   for single components; progress with an estimate for jobs over 10 seconds.
   No layout shift when data arrives.
3. Empty, first run: what will appear here, why it is empty, one primary action.
4. Empty, no results: repeat the query and filters, offer "Clear filters".
   Never reuse the first-run empty state.
5. Partial: render what loaded, mark sections still loading or failed.
   One failed request must not blank the page.
6. Error: plain-language cause, whether data is safe, a Retry action, user input
   preserved. Form errors inline next to the field, saying how to fix it.
7. Offline: banner, last-synced data with an "as of" time, queued actions marked
   pending, auto-retry on reconnect.
8. Permission denied: what is restricted, who can grant it, a request-access or
   Settings link. Never request OS permissions on page load.
9. Success: what happened, where to find it, next step; Undo for destructive actions.
10. Long content: handle the longest realistic names, numbers and translations;
    truncate with the full value available; cap counts (99+).
In development, every state must be reachable with a ?state= URL parameter.
At the end of every change, list the states you built for each screen touched.

Step 2 - audit before changing code

Audit this app for missing UI states. Do not change any code yet.
1. List every screen and route.
2. For each screen answer: does it fetch data? can its main list be empty? can it
   be searched or filtered? does it combine sources or paginate? is anything gated
   by role, plan or OS permission? does the user submit, save or delete here?
   will it be used on mobile or flaky connections?
3. Output a table: screen | states that apply | states that exist today | missing.
   Use these state names: default, loading, empty-first-run, empty-no-results,
   partial, error, offline, permission-denied, success, long-content.
4. Give each screen a state-coverage score (existing / applicable).
Then stop and wait for me to choose which screens to fix first.

Step 3 - repair one screen

Add the missing states to the Bookings screen: loading (skeleton matching the
layout), empty-first-run, empty-no-results, and error with retry. Follow the UI
state rules. Reuse the components, spacing and colors of the default state.
Wire each state to the real condition that triggers it, add the ?state= preview
switch, and list the states you added.

Where to save the rules in Lovable, Bolt, v0, Cursor and Claude Code

For Lovable, use Project settings -> Knowledge. Knowledge accepts 10,000 characters. Run the audit in Plan mode, or prepare a design-first Lovable handoff.

For Bolt, use Project settings -> Knowledge or a root-level agents.md.

For v0, select Prompt bar + -> Instructions -> New Instruction and title it "State coverage."

Build the BookingsList component with a single `state` prop:
"default" | "loading" | "empty" | "no-results" | "partial" | "error" |
"offline" | "forbidden". Render each state per the State coverage instruction,
and add a preview page that shows all eight side by side.

For Cursor, save this as .cursor/rules/ui-states.mdc. The glob attaches the rule when matching files enter context.

---
description: UI state coverage rules for screens and data-fetching components
globs: src/**/*.tsx
alwaysApply: false
---
(paste the UI STATE RULES here)

For Claude Code, save this as .claude/rules/ui-states.md and scope it with paths. See the Claude Code UI design workflow.

---
paths:
  - "src/**/*.tsx"
---
(paste the UI STATE RULES here)

Make every state visible before merging

Invisible states are easily omitted from review. Ask Claude Code, Codex, Cursor or another coding agent to give each view one state prop, map runtime conditions to it and build a labelled development-only gallery that is excluded from production.

Refactor each screen so its view component takes a single `state` prop and the
data hook maps real conditions (isLoading, error, empty array, 403, offline) to it.
Then add a dev-only /states page that renders every screen in every state in a grid,
labelled "Screen / State". Exclude /states from production builds.

Design every screen and state together with Mowgli

We make Mowgli.

It fits best when a product is not built yet or an existing app needs a coordinated redesign across all screens and states. Mowgli starts with a guided questionnaire, writes a product spec with user journeys and a data model, then generates the multi-screen design and implied states together. The spec stays synchronized with the design.

Every screen and state appears on an infinite canvas for chat iteration and click-through prototyping. The habit-tracker demo produced 12 screens and 46 states from one prompt. The cloud-cost Overview includes default, loading skeleton, no-cloud-connections empty, 67%-complete sync partial, team-lead and over-budget variants.

Each screen exports as a React + Tailwind component with one state prop switching variants. Figma export is also available. Explore AI design generation, app screen generation or whole-product redesign.

Mowgli has an MCP server, CLI and agent skill, so Claude Code, Cursor and Codex can read every screen, its states and the product spec, and push code changes back into the design. See the Mowgli MCP workflow.

Mowgli designs and prototypes; to host and ship, you hand off to a builder or coding agent (Claude Code, Codex, Cursor, Lovable).

Free to start (300 credits); credit packs from $12; plans from $15/mo.

Six states of one generated cloud cost Overview dashboard in Mowgli: default, loading skeleton, empty with no cloud connections, partial data with a sync progress banner, team lead view and over budget

Caption: One screen, six states, from a prompt that never mentioned states. Mowgli cloud cost dashboard demo, October 2026.

Four generated mobile states in Mowgli: Today screen with no active habits, create account with validation errors, sign in with an authentication error, and daily reminder with push permission denied

Caption: Empty, error and permission states from the Mowgli habit tracker demo (12 screens, 46 states), October 2026.

Mowgli editor with the screens sidebar listing Today and its five states, and the Today screen shown in several states side by side on the canvas

Caption: Each screen's states in the sidebar and side by side on the canvas: one row of the matrix. Captured October 2026.

Tools that can include UI states during design

Coding agents add states when instructed. The design-tool descriptions below use claims from their own product pages.

Prices are from each vendor's pricing page, October 2026.

ToolStates without being asked?OutputFree optionPaid from
MowgliYes, derived from the product specDesign canvas, React + Tailwind, Figma, MCPFree to start (300 credits)Credit packs from $12; plans from $15/mo
UXMagic Flow ModeClaimed: "interprets screens, states, and transitions"Figma, HTML/React/Tailwind20 credits/day$35/mo
UX PilotClaimed for Max Web Design: "states, and product edge cases"Figma, code, GitHub sync80 credits/day$19/mo yearly
Figma's agentNo: one state per request, in your design systemFigma framesPaid plans$16/mo Full seat
Lovable, Bolt, v0, Cursor, Claude CodeOnly when promptedRunning codeVariesVaries

Mowgli - whole multi-screen products from a spec

This is the option for designing every screen and state before implementation, with the product spec and design kept together.

UXMagic Flow Mode - editable multi-screen flows

UXMagic says Flow Mode "interprets screens, states, and transitions" and proposes a screen list before generation. It exports to Figma, HTML, React or Tailwind. The free tier allows up to 3 screens a day with 20 credits per day. Pro costs $35/mo or $17.50/mo billed yearly. Its product page does not enumerate the state set generated per screen.

UXMagic Flow Mode product page showing a flow prompt, a reviewed list of screens to generate, and a grid of generated screens with the caption AI interprets screens, states, and transitions

Caption: Source: UXMagic website, captured October 2026.

UX Pilot - states in Max Web Design

UX Pilot says Max Web Design can "Add depth, states, and product edge cases automatically," while Autoflow can "Generate every screen in the UX journey." It supports exploring several looks, Figma export and GitHub code sync. The free option includes 80 credits per day. Paid plans start at $19/mo yearly. Its product page does not enumerate which states Max Web Design adds.

Figma's agent - requested states in an existing design system

Figma's agent creates requested empty, loading and error variants from team components and tokens. It fits Figma teams that need exact library consistency. It handles one state per request and does not automatically audit for missing states.

It is available on paid plans. A Full seat costs $16/mo. Since October 6, 2026, the agent uses AI credits.

Figma solutions page titled Generate error states right on your canvas, describing form validation errors, empty states, connection failures and system messages created with AI in Figma Design

Caption: Source: Figma website, captured October 2026.

Choose the right workflow for your situation

For an existing Lovable or Bolt app, save the rules, run the audit and begin with the three most-used screens.

For Cursor or Claude Code, add the relevant rules file and review the /states gallery before merging. For a Storybook-based UI, generate the contact sheet.

For a Figma team with an established library, use the checklist as the audit list and request individual variants through Figma's agent.

For an unbuilt product, use Mowgli's app screen workflow to derive the screen-and-state system before handoff. If an existing app also needs a visual redesign, run the manual audit first, then redesign every screen and state together. Read the vibe-coded app redesign workflow for the full process.

Verdict

For an existing product, persist the rules, audit every route, repair one screen at a time and require a visible state preview before merge.

For an unbuilt product, design the states before implementation. Mowgli can create the synchronized spec, screens and states for handoff to Claude Code, Codex, Cursor or another coding agent.

Next, review complete B2B SaaS screens, AI design for complex apps, the habit-tracker screen example, or the Mowgli MCP connection.

FAQ

What states does each app screen need?

Every screen needs default and long-content checks. Add loading and error for fetched data, empty variants for collections and filters, partial for multiple sources, permission denied for gates, success for mutations and offline where connectivity can fail.

How do I find missing loading, empty and error states?

List every route, answer the seven applicability questions and build a screen x state matrix. Score each screen as existing applicable states divided by all applicable states. Require a frame, story or working preview before marking a cell designed.

Why does my Lovable app have no loading or empty states?

Lovable implements the conditions you describe, and its guide asks authors to define empty, loading and failure behavior. Put the rules in Project Knowledge, audit in Plan mode and repair one screen per message.

What is the difference between an empty state and an error state?

An empty state means the request succeeded but returned nothing, while an error state means the operation failed. An empty state should explain the absence and provide a useful first action. An error should explain the problem, preserve input and offer recovery. Never show an empty state after a failed request because it can imply that user data disappeared.

How do I view every screen state in one place?

Use a linked screen x state matrix, a Storybook contact sheet or a development-only /states gallery. In Mowgli, you can view a screen's states together on the canvas.

Which AI design tool generates empty, error and loading states?

Mowgli derives states from the product spec and designs them with the complete multi-screen product. UXMagic Flow Mode and UX Pilot make vendor claims about states, while Figma's agent produces states requested individually. Compare them by job and verify competitor prices against the cited October 2026 pricing pages.

Sources