UI Copy and States skill for Codex

No blank screens, vague errors or mute buttons. Codex builds a state matrix for every component that waits, fails or changes, chooses spinners, skeletons, progress or optimistic updates by expected wait time, and writes error copy that names the problem and the next step without losing input. Disabled controls explain themselves, success feedback matches its importance, and live regions and focus moves make screen readers hear the same story.

  • Design & UX
  • $ui-copy-states
  • 148 lines
  • 2 files
  • ~1,950 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- ui-copy-states

Installs to ~/.agents/skills/ui-copy-states/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $ui-copy-states, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Designs and writes the non-happy-path states of an interface - loading (spinner, skeleton, progress, optimistic), error (validation, network, server, permission, not found), success and confirmation, disabled and read-only, and partial or stale data - with timing thresholds, copy patterns, placement, ARIA live-region behaviour and a state matrix per component. Use when screens show blank or frozen UI while loading, errors say "Something went wrong", buttons are disabled with no explanation, or a feature needs all its states specified.

Use it when

  • A feature needs its loading, error, success and disabled states specified
  • Users see blank screens, flickering spinners or frozen buttons
  • Error messages are generic or show raw codes
  • Screen readers miss status changes and errors

Use something else when

How to check the result

Each state is triggered with network throttling, blocked requests and a restricted role, every message states a next step, and a screen reader announces loading, success and errors once.

The mistake it prevents

Disabling a submit button with no visible reason, which blocks keyboard users from even discovering why they cannot proceed.

~/.agents/skills/ui-copy-states/SKILL.md

--- name: ui-copy-states description: Designs and writes the non-happy-path states of an interface - loading (spinner, skeleton, progress, optimistic), error (validation, network, server,… license: MIT ---

Every component that waits, fails or changes has designed states with words that tell the user what is happening and what to do next. Done well: no blank screens, no generic errors, no unexplained disabled controls, input never lost, and screen readers hear the same story sighted users see.

Workflow

  • List the components that fetch, submit or change (rg -n "useQuery|useMutation|fetch\(|isLoading|isPending|error" src).
  • Build a state matrix for each (template below).
  • Pick the loading pattern by expected duration.
  • Write error copy per failure type; decide placement and recovery action.
  • Define success feedback and disabled-state explanations.
  • Wire ARIA: live regions, aria-busy, focus management.
  • Trigger every state and check it visually and with a screen reader.

Read AGENTS.md and existing copy for voice (sentence case or title case, “we” or not), and reuse existing toast, alert and skeleton components.

Loading

Expected waitPattern
Under ~300 msNothing. Delay any indicator ~300 ms so fast responses do not flicker
300 ms - 2 sInline spinner in the control, or skeleton if layout is known
2 - 10 sSkeleton for content; for actions, a determinate progress or step text (“Uploading 3 of 8”)
Over 10 sProgress with estimate, allow leaving; notify when done (“We’ll email you when the export is ready”)
User action likely to succeedOptimistic update, with rollback and an error message on failure

Rules:

  • Skeletons match the real layout’s shape and size to avoid layout shift. Never show a skeleton for content that may be empty; show the empty state instead.
  • Button loading: keep the label (or change to the progressive verb, “Saving…”), add a spinner, keep width fixed, prevent double submit, set aria-busy="true" or aria-disabled="true".
  • Once a spinner shows, keep it at least ~500 ms to avoid a flash.
  • After 10-15 s with no response, change the message (“This is taking longer than usual”) and offer cancel or retry.

Errors

Every error message answers: what happened, why (if known and useful), and what to do.

TypePlacementCopy patternRecovery
Field validationUnder the field, linked via aria-describedby“Enter a date after 1 Jan 2026” (the rule, phrased as an instruction)Keep input, focus field on submit
Form-level (several fields)Summary at top listing errors as links, plus inline“2 fields need attention”Focus summary on submit
Network / offlineInline at the component, or banner“You’re offline. Changes will sync when you reconnect.”Auto-retry plus manual Retry
Server 5xxInline where the action was“We couldn’t save your changes. Try again in a moment.”Retry; keep input; reference ID if support needs it
Permission (403)Replace content area“You don’t have access to Billing. Ask a workspace admin.”Link to request access or go back
Not found (404)Page“This project was deleted or moved.”Search, recent items, home
Conflict (409)Inline“Priya edited this while you were working.”Show both, let user choose
Rate limit (429)Inline“Too many attempts. Try again in 30 seconds.”Countdown, then enabled

Copy rules:

  • Plain words, no codes as the message (ERR_CONN_RESET, “Error 500”). Add a reference ID in small text when support needs it.
  • No blame (“You entered an invalid…”); describe the fix.
  • No false comfort (“Oops!”, jokes) for errors that cost the user something.
  • Never clear the user’s input after an error.
  • Specific beats generic: map known backend error codes to messages; reserve the generic fallback for truly unknown errors, and still give a next step.

Success

SignificancePattern
Expected, visible result (item appears in list)The result itself is the feedback; maybe a brief highlight
Not visible where the user is (saved settings, sent invite)Toast, 4-6 s, polite live region; include Undo where possible
Important or irreversible (payment, deletion done)Confirmation page or inline panel with details and next steps

Toasts are not for errors that need action; they disappear. Toasts must not cover primary controls on mobile, and must pause on hover/focus if they contain actions.

Disabled and read-only

  • Prefer enabled controls with validation on submit over disabled buttons that never explain themselves.
  • If a control must be disabled, say why next to it: “Add a payment method to upgrade”.
  • Native disabled removes the control from tab order, and tooltips on it are unreachable for keyboard users. When the reason matters, use aria-disabled="true" and keep it focusable, blocking the action in the handler.
  • Read-only (viewer role) differs from disabled: show values as text, explain the role once (“You have view access”).
  • Disabled styling still needs to be identifiable; it is exempt from contrast rules but should not vanish.

Partial, stale and empty data

  • Partial failure: render what loaded and mark the failing widget with its own error and retry, rather than failing the whole page.
  • Stale data: show “Updated 5 min ago” and a refresh when freshness matters.
  • Empty: explain what will appear here and the action that fills it. Distinguish “no data yet” from “no results for this filter” (offer clear filters). First-run design belongs to $onboarding-empty-states.

Accessibility wiring

<div role="status" aria-live="polite" class="sr-only" id="announcer"></div>   <!-- success, progress -->
<div role="alert" id="form-error"></div>                                       <!-- urgent errors -->
<input id="email" aria-invalid="true" aria-describedby="email-err">
<p id="email-err">Enter an email address like [email protected]</p>
<section aria-busy="true">...skeleton...</section>
  • Live regions must exist in the DOM before content is injected, or announcements are missed.
  • Use role="alert" sparingly; it interrupts.
  • After a failed submit, move focus to the error summary or first invalid field.
  • Announce completion of long operations (“Export ready”).

State matrix template

### <Component>: Invite teammates form
| State | Trigger | Visual | Copy | ARIA / focus |
|---|---|---|---|---|
| Idle | default | Enabled Send | "Send invites" | - |
| Validating | blur | Inline error | "Enter a valid email address" | aria-invalid, describedby |
| Submitting | click Send | Spinner in button, fixed width | "Sending..." | aria-busy, disable double submit |
| Success | 200 | Toast + pending rows in list | "Invites sent to 3 people" | role=status |
| Partial | 207 | Inline list of failed emails | "2 sent. 1 couldn't be sent: x@y (already a member)" | focus list |
| Server error | 5xx | Inline alert above button | "We couldn't send invites. Try again." | role=alert, keep input |
| Seat limit | 402/limit | Inline panel with upgrade link | "Your plan has 5 seats. Upgrade to add more." | - |
| Disabled | viewer role | Hidden form, explanation | "Only admins can invite people." | - |

Verification

  1. Trigger each state: throttle network (DevTools “Slow 4G” and “Offline”), force errors with a mock or by blocking the request URL, test with a viewer-role account.
  2. Read every message aloud: does it say what to do next?
  3. Screen reader pass (VoiceOver or NVDA): loading, success and errors are announced once, at the right time.
  4. Check copy against the product’s voice and casing rules.

Related: wording of error messages in depth in $error-messages; error boundaries and retry logic in code in $frontend-error-handling.

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "UI Copy and States"
  short_description: "No blank screens, vague errors or mute buttons"
  default_prompt: "$ui-copy-states Design all the loading, error and success states for our invite form"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem