
Ui Audit
- 696 installs
- 74 repo stars
- Updated August 5, 2026
- mblode/agent-skills
ui-audit is a frontend review skill that runs 35 rule-based UI quality checks with concrete fixes for developers who need structured design QA before merging or releasing a web page or feature.
About
ui-audit is a rule-based web UI quality skill from mblode/agent-skills that audits a page or feature before merge or release. It evaluates accessibility, keyboard interaction, forms, typography surfaces, navigation feedback, layout resilience, performance, motion, and microcopy through 35 prefix-dispatched rules plus separate craft and typography sweep checklists. Findings are reported by file with impact ratings and actionable fixes rather than vague polish notes. Developers reach for ui-audit when they ask to check UI quality, run an accessibility audit, or design-QA a page, while ux-audit in the same repo targets React and Next.js diff-level state and focus bugs. The skill fits frontend teams that want consistent pre-release UI gates without manual checklist drift.
- 35 prefix-dispatched rules covering accessibility, keyboard interaction, forms, typography surface checks, navigation fe
- Reports findings by file with impact ratings and concrete fixes
- Loads only the rule categories required for the current surface
- Distinguishes page/feature-level UI quality from diff-level React/Next.js UX bugs
- Hard-gate: run before merge or release when UI polish or accessibility is required
Ui Audit by the numbers
- 696 all-time installs (skills.sh)
- +54 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #504 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mblode/agent-skills --skill ui-auditAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 696 |
|---|---|
| repo stars | ★ 74 |
| Last updated | August 5, 2026 |
| Repository | mblode/agent-skills ↗ |
How do you audit web UI quality before release?
Get a structured, rule-based audit of web UI quality with concrete fixes before merging or releasing a page or feature.
Who is it for?
Frontend developers and reviewers who want a repeatable, rule-based UI and accessibility audit before merging or releasing a web page.
Skip if: Teams needing React or Next.js diff-level UX bug analysis such as optimistic UI or focus management, which the sibling ux-audit skill covers.
When should I use this skill?
User asks to check UI quality, polish a page, run a design QA pass, or verify accessibility before merge or release.
What you get
Structured audit report with file-level findings, impact ratings, and concrete UI fixes across 35 dispatched rules and typography sweeps.
- file-scoped audit report
- impact-rated findings
- concrete UI fix list
By the numbers
- Dispatches 35 prefix-based UI audit rules plus craft and typography sweep checklists
Files
UI Audit
Page/feature-level audit of web UI quality. Loads only the rule categories the current surfaces need, reports findings with file:line and a concrete fix for each.
- IS: a broad quality audit of rendered web UI (accessibility, keyboard, forms, typography surface checks, navigation feedback, layout resilience, performance, motion, microcopy) at the page or feature level.
- IS NOT: a diff-level React/Next.js UX bug hunt (use
ux-audit), an agentic-app pattern review (useax-audit), a typography system design or pairing audit (usetypography-audit), or motion implementation work (useui-animation).
Audit Workflow
Copy and track this checklist during the audit:
Audit progress:
- [ ] Step 1: Scope. List the surfaces under audit and the rule prefixes they need
- [ ] Step 2: Load rules. Read rules/<prefix>-*.md for selected prefixes only
- [ ] Step 3: CRITICAL pass. a11y, interaction, forms against every scoped file
- [ ] Step 4: HIGH/MEDIUM pass. Remaining selected prefixes
- [ ] Step 5: Optional sweeps. Craft/typography checklists if polish is in scope
- [ ] Step 6: Report. Findings per file with rule id, impact, and fix; clean files as pass1. Scope. Default to changed pages/components only. A full-app sweep must be explicitly requested. Map each surface to the prefixes it can violate (a form screen needs forms-, a11y-, interaction-; a marketing page adds type-, perf-, copy-). 2. Load rules by prefix. Read rules/_sections.md for the category map, then only the rules/<prefix>-*.md files for selected prefixes. 3. CRITICAL first. Run a11y-, interaction-, and forms- before anything else. Do not start visual polish while an unlabeled icon button or keyboard trap is open. 4. HIGH/MEDIUM next. Then type-, nav-, layout-, perf-, motion-, copy- as scoped. 5. Optional sweeps. When the request includes polish, hierarchy, or chrome cleanup, run references/craft-checklist.md. When typography is a named concern, run references/typography-checklist.md. 6. Report and verify. Emit the output contract below. After fixes are applied, rerun the same rule subset on touched files before marking them pass; the rerun output is the evidence the audit is done.
Rule Categories by Priority
35 rules total. Per-rule frontmatter may override the category impact (e.g. perf-image-dimensions-and-priority is CRITICAL inside the HIGH perf- category), so report the rule's own impact, not the category's.
| Priority | Prefix | Category | Impact | Rules |
|---|---|---|---|---|
| 1 | a11y- | Accessibility and Semantics | CRITICAL | 8 |
| 2 | interaction- | Keyboard and Interaction | CRITICAL | 3 |
| 3 | forms- | Forms and Validation | CRITICAL | 5 |
| 4 | type- | Typography and Readability | HIGH | 3 |
| 5 | nav- | Navigation and Feedback | HIGH | 3 |
| 6 | layout- | Layout and Resilience | HIGH | 3 |
| 7 | perf- | Performance and Visual Stability | HIGH | 6 |
| 8 | motion- | Motion and Theme Behavior | HIGH | 2 |
| 9 | copy- | Content and Microcopy | MEDIUM | 2 |
Reference Files
Load on condition, not by default:
rules/_sections.md: category map with impact rationale. Read at Step 2 of every audit.rules/<prefix>-*.md: rule-level guidance and examples. Read only the prefixes selected in Step 1.references/craft-checklist.md: final polish sweep (hit targets, hover states, chrome hierarchy, optical alignment, concentric radii, anti-patterns). Read when the request includes "polish", visual hierarchy, or pre-release sign-off.references/typography-checklist.md: typography surface sweep (punctuation, measure, leading, OpenType basics, link styling, table numerals). Read when typography is explicitly in scope. For typeface pairing, brand identity, or display type, route to thetypography-auditskill instead.
Review Output Contract
Report findings in this format:
## UI Audit Findings
### path/to/file.tsx
- [CRITICAL] `a11y-image-alt-text` (line 42): `<img src="/chart.png" />` has no alt attribute.
- Fix: Add `alt="Revenue grew 40% from Q1 to Q2"` (or `alt=""` if decorative).
- [HIGH] `a11y-icon-controls-labeled` (line 58): Icon button has no accessible name.
- Fix: Add `aria-label="Close dialog"` (or a visible text label).
- [HIGH] `layout-long-content-safety` (line 87): `.card-title` uses `white-space: nowrap` with no overflow handling.
- Fix: Add `min-width: 0` on the flex parent and `overflow: hidden; text-overflow: ellipsis` on the title.
### path/to/clean-file.tsx
- ✓ pass- Group findings by file; include
file:linewhen line numbers are available. - Every finding states the issue and a concrete fix, never just "improve accessibility".
- Use the rule's own impact from its frontmatter.
- Include every scoped file, clean ones as
✓ pass.
Gotchas
- Do not load all 35 rule files for a scoped audit; the context cost flattens finding quality. Load only the prefixes mapped in Step 1; a typical component audit needs 3-4 prefixes.
- Do not invent rule ids. Citing a nonexistent id (e.g.
a11y-focus-trap) breaks the user's ability to look up the rule; cite only filenames that exist underrules/, and describe id-less issues in prose. - Do not widen scope unprompted. Auditing the whole app when one component changed buries the real findings in noise; a full sweep requires an explicit request.
- Do not reorder priorities for convenience. Reporting border-radius polish while an unlabeled form input (
forms-labels-and-autocomplete) or keyboard-inoperable control (interaction-keyboard-operable) ships inverts the table's load-bearing order; CRITICAL categories always run first. - Do not mark
✓ passon a file you did not read against the loaded rules. An assumed pass that later surfaces a contrast or label failure costs more trust than a slower audit. - Do not report findings at category impact when the rule frontmatter says otherwise:
perf-image-dimensions-and-priorityis CRITICAL (CLS) even thoughperf-is a HIGH category. - The anti-patterns list in
references/craft-checklist.mddescribes UI code being audited, not this skill's execution; do not flag the skill's own report format against it.
Related Skills
ux-audit: diff-aware React/Next.js UX bug hunt (state coverage, form data loss, focus management); use it for code-level review of a PR.ax-audit: agentic application patterns and trust design.typography-audit: deep typography, covering pairing, OpenType systems, brand and display type.ui-animation: motion implementation and review (springs, easing, gestures); apply it when audit findings require motion work.ui-design: visual direction and rebuilding the UI when the fix is "redesign", not "repair"; its Responsive and Dark mode modes cover breakpoint repairs and dark-mode contrast work.
Craft Checklist (Detailed)
Final polish sweep for pre-release sign-off. Run after the rule-based CRITICAL/HIGH passes; this catches the craft details the rules layer does not encode (chrome hierarchy, optical alignment, concentric radii, hover affordances).
Contents
- Legibility and typography
- Motion
- Keyboard, focus, and targets
- Forms and input behaviour
- Navigation and feedback
- Resilience and layout
- Performance
- Accessibility and theming
- Extra polish
- Content and copy
- Anti-patterns (flag these)
- Resources
Legibility and typography
- Full punctuation, sizing, measure, and OpenType sweep: run
typography-checklist.md; do not duplicate it here. - Quick spot-checks unique to this pass:
- British/Australian spelling in user-facing copy.
- Limit to <= 2 typefaces; weights >= 400;
clamp()for fluid sizes. font-variant-numeric: tabular-numson data and tables.- Enable
-webkit-font-smoothing: antialiasedandtext-rendering: optimizeLegibility.
Motion
- Validate against the
ui-animationskill (timing, easing, transform/opacity only).
Keyboard, focus, and targets
- Provide full keyboard support and visible focus styles.
- Manage focus in dialogs/menus (trap, restore).
- Hit targets >= 24px (>= 44px on mobile); if the visual target is smaller, expand the hit area.
- Gate hover styles with
@media (hover: hover). - Never disable browser zoom (
user-scalable=no/maximum-scale=1). - Use
touch-action: manipulationon tap targets to prevent double-tap zoom. - Never
outline: none/outline-nonewithout a:focus-visiblereplacement. - Buttons/links need a
hover:state; hover/active/focus should be more prominent than rest state. scroll-margin-topon heading anchors for in-page links.- Set
-webkit-tap-highlight-colorintentionally on tap targets. autoFocussparingly: desktop only, single primary input; avoid on mobile.- Disable pointer events on decorative layers (glows, gradients).
- If it looks clickable, it must be clickable; remove dead zones between items.
- Avoid text selection during drag; use
inertor disable selection where needed.
Forms and input behaviour
- Label inputs; Enter submits; textarea uses Cmd/Ctrl+Enter.
- Inputs must be hydration-safe (no lost focus/value after hydration).
- Use correct
type,name,autocomplete, andinputmode. - Disable spellcheck for emails/codes/usernames; avoid input names that trigger password managers when not needed.
- Mobile input font size >= 16px; avoid autofocus on touch devices.
- Do not block paste or typing; validate after input.
- Show inline errors; focus the first error on submit.
- Allow incomplete submission to surface validation; keep submit enabled until request starts, then disable with spinner and keep the original label.
- Checkboxes/radios: label + control share a single hit target (no dead zones).
- Placeholders end with
…and show an example pattern. autocomplete="off"on non-auth fields to avoid password-manager triggers.- Trim trailing whitespace from IME/text expansion to avoid false errors.
- Ensure password managers and one-time codes work.
- Warn before navigation with unsaved changes (
beforeunloador router guard).
Navigation and feedback
- Use
<a>/<Link>for navigation; preserve URL state; Back/Forward restores scroll. - Supporting chrome should recede beneath the current task; sidebars, tabs, and secondary bars must be quieter than the main content.
- Shared header actions should stay in consistent slots across comparable screens.
- Prefer compact tab groups over full-width bars when they communicate the same state.
- Confirm destructive actions or provide undo.
- Use polite
aria-livefor toasts/validation. - Add a short show-delay (150-300ms) and minimum duration (300-500ms) for spinners/skeletons to avoid flicker.
- Use ellipsis for follow-ups and loading states (Rename…, Loading…).
- Provide designed empty, loading, and error states.
Resilience and layout
- Use flex/grid; avoid JS measurement.
- Respect safe areas and prevent unwanted scrollbars.
- Use
overscroll-behavior: containin modals/drawers. - Ensure text truncation (
min-w-0,line-clamp,break-words) and long content support. - Design for empty/sparse/dense states.
- Use locale-aware formatting (
Intl.*).
Performance
- Above-fold critical images:
priorityorfetchpriority="high"; below-fold images:loading="lazy". - Set explicit
widthandheighton images to prevent CLS. - Critical fonts:
<link rel="preload" as="font">withfont-display: swap. - Add
<link rel="preconnect">for CDN/asset domains. - Virtualize large lists (>50 items).
- No layout reads in render (
getBoundingClientRect,offsetHeight, etc.); batch DOM reads/writes. - Minimise re-renders; profile when needed.
- Use
will-changesparingly; avoid heavy blur and excessive video autoplay.
Accessibility and theming
- Prefer native semantics before ARIA.
- Add
aria-labelto icon-only controls; mark decorative elementsaria-hidden. - Do not attach tooltips to disabled controls; hover-tooltips should not contain interactive content.
- Use
<img>for images; HTML illustrations need an accessible name. - Provide redundant status cues (not colour-only).
- Provide skip link and heading hierarchy.
- Do not animate during theme switches; set
color-schemeand<meta name="theme-color">. - Native
<select>: set explicitbackground-colorandcolor(Windows dark mode fix). - Guard hydration for date/time;
valueinputs requireonChange. suppressHydrationWarningonly where truly needed (dates, theme).
Extra polish
- Match box-shadows and motion to high-quality references.
- Remove redundant icons and coloured icon backgrounds when labels or grouping already carry the meaning.
- Every border and separator should justify itself; avoid stacked dividers and high-contrast grid noise.
- Concentric border radius: check that
outer-radius = inner-radius + paddingon nested elements (cards with inner panels, buttons with icon badges). Mismatched radii are the most common unnoticed visual error. - Optical alignment: for icon+text buttons, use slightly less padding on the icon side. For icon-only buttons, verify the icon is optically centred; triangular and asymmetric shapes sit off-centre geometrically. Fix in the SVG first; use
marginorpaddingadjustments if you can't change the SVG. - Image outlines: images on white or near-white backgrounds benefit from
outline: 1px solid rgba(0,0,0,0.1); outline-offset: -1pxto anchor them to the layout. Add.darkvariant withrgba(255,255,255,0.1). Useoutlinenotborderto avoid layout shift. - Add SEO metadata and dynamic OG images.
- Add keyboard shortcuts where useful.
Content and copy
- Active voice: "Install the CLI" not "The CLI will be installed".
- Sentence case for headings and buttons.
- Numerals for counts: "8 deployments" not "eight".
- Specific button labels: "Save API Key" not "Continue".
- Error messages include fix/next step, not just the problem.
- Second person; avoid first person.
&over "and" where space-constrained.
Anti-patterns (flag these)
user-scalable=noormaximum-scale=1disabling zoom.onPaste+preventDefault.transition: all: list properties explicitly.outline-nonewithout:focus-visiblereplacement.<div>/<span>with click handlers instead of<button>.- Inline
onClicknavigation without<a>. - Images without
width/heightdimensions. - Large arrays
.map()without virtualization. - Form inputs without labels.
- Icon buttons without
aria-label. - Hardcoded date/number formats (use
Intl.*). autoFocuswithout clear justification.- Navigation chrome that is as loud as the primary work surface.
- Headers or cards with stacked high-contrast borders that do not clarify meaning.
Resources
- Devouring Details, Sanding UI, Paul Graham on Taste, Typewolf checklist.
Typography Surface Checklist
Surface-level typography sweep for UI audits: punctuation, sizing, spacing, styles, and text-layout checks that apply to any web surface. For typeface selection, pairing, brand identity, display type, and logo work, route to the typography-audit skill; those are out of this checklist's scope.
Contents
- How to apply
- Punctuation and glyphs
- Capitalization, spacing, and emphasis
- Size, measure, and leading
- Weights, styles, and OpenType
- Letterspacing and casing
- Paragraphs and hierarchy
- Links, contrast, and text on images
- Numerals and tables
- Lists and navigation text
How to apply
1. Scope the typography surfaces being changed (body text, headings, links, data tables, forms, nav). 2. Run only the relevant sections; record findings with file:line per the SKILL.md output contract. 3. Apply fixes, then rerun the same sections on touched files before marking pass.
Punctuation and glyphs
- [ ] Smart quotes and apostrophes, not straight ones; content normalised as UTF-8 at build/render time.
- [ ] En dash for ranges, em dash for breaks/attribution; pick spaced-en or unspaced-em style and keep it consistent; never double hyphens.
- [ ] Prime/double-prime glyphs for measurements (not quote characters); multiplication sign and real fraction glyphs where they appear.
- [ ] Ellipsis character (
…), not three periods, in copy, follow-up labels (Rename…), and loading states. - [ ] Accented characters stored as Unicode and present in loaded fonts; over-subsetting produces empty glyph boxes.
- [ ] Non-breaking space between glued terms: copyright + year, values + units (10 MB), shortcut keys (Cmd + K), brand names.
- [ ] Midpoints (with hair/thin spaces as needed) for inline separators, not bars or bullets; ampersands only in proper names or space-constrained UI.
- [ ] No apostrophes in decades (1990s), no periods in acronyms.
Capitalization, spacing, and emphasis
- [ ] Sentence case or title case for headings: one choice, applied consistently.
- [ ] Exactly one space after sentence-ending punctuation; no double spaces anywhere.
- [ ] Italics for emphasis, not bold-everything, all caps, or quote marks; emphasis used sparingly.
- [ ] Underlines reserved for links only, never decoration or emphasis.
Size, measure, and leading
- [ ] Body size set first: 16-24px desktop, 15-19px mobile; headings scale down on mobile.
- [ ] Line length 45-75 characters (66 ideal), adjusted per breakpoint via
max-widthinchunits. - [ ] Line height ~1.45-1.6, unitless; a bit more for large-x-height sans faces; tighter for large headlines.
- [ ] Fluid sizes via
clamp(); no near-equal sizes in the scale, either same or clearly different. - [ ] Widows/orphans managed:
text-wrap: balanceon headings or non-breaking spaces in headlines/nav.
Weights, styles, and OpenType
- [ ] Real regular/italic/bold/bold-italic loaded via
@font-facemapped to one family, with no faux bold or faux italic. - [ ] Body weight 400-500; ultra-light weights and display cuts never used for body copy.
- [ ] Long body text never monospaced; mono reserved for code and short stylistic blocks.
- [ ] Body OpenType features on:
kern,liga,clig,calt; discretionary ligatures off in body (and in code). - [ ] Real small caps via
font-feature-settings(with slight tracking), never pseudo small caps.
Letterspacing and casing
- [ ] No letterspacing on body text; add ~0.05-0.2em tracking to all-caps and small labels, more as size shrinks.
- [ ] No multi-line all-caps blocks or uppercase paragraphs.
- [ ] Never letterspace monospaced or script fonts; keep metrics kerning, do not over-kern.
- [ ] Never stretch or squish type; use condensed/extended variants if a narrow style is needed.
Paragraphs and hierarchy
- [ ] Long copy broken into paragraphs with subheads/lists; separated by spacing or indents, never both (
p + pfor indents). - [ ] Subheads sit closer to the text they introduce than to the preceding text; dividers go above headings, not below.
- [ ] Centre alignment rare and intentional; no justified text on the web without strong hyphenation support.
- [ ] Hierarchy built one axis at a time (size, weight, caps, colour); heading levels shallow (h1-h3); headings descriptive, not generic.
- [ ] Heading colour distinct from link colour; large headings may lighten weight/colour for balance, but prefer darkened brand hues over flat grey.
Links, contrast, and text on images
- [ ] Links distinct from body via colour or underline; link colour never used on non-links.
- [ ] Link hover states do not shift layout (no weight or size changes on hover).
- [ ] Text/background contrast passes without pure black on pure white; no low-contrast light grey body text.
- [ ] Text over photos has enforced contrast (overlay/scrim or curated images), or the pattern is avoided.
- [ ] Dark backgrounds use off-white text; long light-on-dark passages kept short.
Numerals and tables
- [ ] Table numbers right-aligned with tabular figures (
font-variant-numeric: tabular-nums) or a mono/system stack; thousands separators present. - [ ] Oldstyle figures (
onum) acceptable in running text; lining figures (lnum) next to uppercase and in UI. - [ ] Numerals for counts in UI copy ("8 deployments", not "eight").
Lists and navigation text
- [ ] Proper list markup (
<ul>/<ol>); wrapped item text does not tuck under bullets; vertical spacing between multi-line items. - [ ] Lists tested with long content at narrow widths.
- [ ] Nav spacing via CSS padding, not space characters; current item marked as selected, never greyed out like a disabled control.
- [ ] Captions/descriptions placed closer to the images they describe than to surrounding content.
Sections
Defines all rule categories in audit priority order. The ID in parentheses is the filename prefix that groups rules (<prefix>-<slug>.md). Category impact is the default; individual rules may override it in their frontmatter.
---
1. Accessibility and Semantics (a11y)
Impact: CRITICAL Description: Semantic structure, accessible names, contrast, media alternatives, and document language. Failures exclude assistive-tech users entirely, so run this category first on every audit.
2. Keyboard and Interaction (interaction)
Impact: CRITICAL Description: Every interactive element must be keyboard-operable with visible focus and adequate hit targets. A mouse-only control is broken for keyboard, switch, and many touch users.
3. Forms and Validation (forms)
Impact: CRITICAL Description: Forms are conversion paths. Labels, autocomplete, paste/IME support, error association, and mobile input sizing decide whether users can complete them at all.
4. Typography and Readability (type)
Impact: HIGH Description: Surface-level readability: scale, measure, leading, link distinction. Deep typography (pairing, brand, display) belongs to the typography-audit skill, not this category.
5. Navigation and Feedback (nav)
Impact: HIGH Description: Real links for navigation, live-region announcements, and stable loading-indicator timing. Users need to know where they are and what the system is doing.
6. Layout and Resilience (layout)
Impact: HIGH Description: Layouts must survive long content, sparse/dense data, and edge states without overflow or collapse. Empty, loading, and error states are designed, not accidental.
7. Performance and Visual Stability (perf)
Impact: HIGH Description: Prevent layout shift, lazy-load offscreen work, and keep rendering predictable under realistic content loads. Image-dimension failures are CLS regressions and rate CRITICAL.
8. Motion and Theme Behavior (motion)
Impact: HIGH Description: Animate transform/opacity only and respect prefers-reduced-motion. Unreduced motion can cause vestibular distress; layout-property animation causes jank.
9. Content and Microcopy (copy)
Impact: MEDIUM Description: Specific action labels and actionable error messages. Vague copy lowers completion rates and raises support load, so audit last, after structural issues are clear.
Rule Title Here
Impact: MEDIUM (optional consequence note)
One or two sentences on why the rule matters; name the user-facing failure, not just the best practice. Set impact higher than the category default only when the failure mode justifies it.
Incorrect (what is wrong and why):
// Minimal failing example: only the lines that violate the ruleCorrect (what the fix looks like):
// Same example, fixed; diff against the incorrect block should be obviousMeet Contrast and Avoid Color-Only Meaning
Status and validation states should combine text, iconography, or shape with color.
Incorrect (color-only status):
<span className="text-red-500">Error</span>Correct (redundant cue + label):
<span className="text-red-700" role="status" aria-live="polite">
<WarningIcon aria-hidden="true" /> Error: Invalid email format
</span>Mark Up Data Tables With Real Table Semantics
Tabular data needs a real <table> with a <caption>, header cells, and scope. Divs styled as a grid carry no row/column relationships, so screen readers read cells as a flat list. (Layout-only grids should use CSS, not <table>.)
Incorrect (div grid, no header semantics):
<div className="grid">
<div>Name</div><div>Role</div>
<div>Ada</div><div>Engineer</div>
</div>Correct (caption, header cells, scope):
<table>
<caption>Team members</caption>
<thead>
<tr><th scope="col">Name</th><th scope="col">Role</th></tr>
</thead>
<tbody>
<tr><th scope="row">Ada</th><td>Engineer</td></tr>
</tbody>
</table>Declare Document and Inline Language
Set the primary language on <html lang> with a valid BCP 47 tag, and mark any inline passage in another language with its own lang. Without it, a screen reader reads every page with one pronunciation engine.
Incorrect (no document language, foreign phrase unmarked):
<html>
<body><p>The chef called it a <em>coup de grâce</em>.</p></body>
</html>Correct (document and inline language declared):
<html lang="en">
<body><p>The chef called it a <em lang="fr">coup de grâce</em>.</p></body>
</html>Label Icon-Only Controls
Any control with no visible text requires an accessible name.
Incorrect (no accessible name):
<button onClick={closeModal}>
<XIcon />
</button>Correct (explicit label):
<button type="button" aria-label="Close dialog" onClick={closeModal}>
<XIcon aria-hidden="true" />
</button>Give Every Image a Correct Alt Attribute
Every <img> needs an alt. Describe the image's purpose for informative images; use an empty alt="" for decorative ones so screen readers skip them. A missing alt makes the file name get read aloud.
Incorrect (missing alt, and decorative image announced):
<img src="/chart.png" />
<img src="/divider.svg" alt="decorative swirl divider" />Correct (purpose described; decorative image silenced):
<img src="/chart.png" alt="Revenue grew 40% from Q1 to Q2" />
<img src="/divider.svg" alt="" />Caption Video and Transcribe Audio
Video needs synchronised captions via <track kind="captions">; audio-only content needs a text transcript. Auto-generated captions alone are not sufficient for meaning-critical media.
Incorrect (video with no captions track):
<video src="/demo.mp4" controls />Correct (captions track + transcript link):
<video controls>
<source src="/demo.mp4" type="video/mp4" />
<track kind="captions" src="/demo.en.vtt" srcLang="en" label="English" default />
</video>
<a href="/demo-transcript">Read the transcript</a>Prefer Native Semantics Before ARIA
Use semantic HTML controls first; only add ARIA when native elements cannot express intent.
Incorrect (clickable div):
<div onClick={submitForm}>Save</div>Correct (semantic button):
<button type="button" onClick={submitForm}>Save</button>Provide Skip Link and Logical Heading Order
Include a skip link and keep heading levels sequential.
Incorrect (no skip link, jumps heading levels):
<main>
<h1>Dashboard</h1>
<h4>Recent activity</h4>
</main>Correct (skip link + ordered headings):
<a className="skip-link" href="#main-content">Skip to content</a>
<main id="main-content">
<h1>Dashboard</h1>
<h2>Recent activity</h2>
</main>Make Error Messages Actionable
Error messages should include what failed and what to do next.
Incorrect (problem only):
<p>Something went wrong.</p>Correct (problem + next step):
<p>Upload failed. Check your connection and try again.</p>Use Specific Action Labels
Action text should state outcome, not generic intent.
Incorrect (vague):
<button>Continue</button>Correct (specific):
<button>Save API Key</button>Do Not Block Paste or IME Input
Avoid handlers that prevent paste or aggressively filter keystrokes.
Incorrect (blocks user input):
<input onPaste={(e) => e.preventDefault()} onKeyDown={blockNonDigits} />Correct (accept input, validate after):
<input
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={() => validate(value.trimEnd())}
/>Associate and Announce Form Errors
An error message must be programmatically tied to its input via aria-describedby, the field marked aria-invalid, and the error announced through a live region (role="alert"). A message that is only visually near the field is invisible to screen readers. Complements forms-inline-errors-first-focus, which covers placement and focus.
Incorrect (orphan error text, no announcement):
<input name="email" />
<span className="error">Enter a valid email</span>Correct (associated, marked invalid, announced):
<input
name="email"
aria-invalid={Boolean(error)}
aria-describedby={error ? "email-error" : undefined}
/>
{error && <span id="email-error" role="alert">Enter a valid email</span>}Show Inline Errors and Focus the First Invalid Field
On submit, reveal all relevant errors and move focus to the first failing field.
Incorrect (generic top error only):
{hasError && <p>Form invalid</p>}Correct (field-level message and focus management):
{errors.email && <p id="email-error">Enter a valid email address</p>}
<input aria-invalid={Boolean(errors.email)} aria-describedby="email-error" />
if (errors.email) {
emailRef.current?.focus()
}Label Inputs and Set Autocomplete Metadata
Inputs require explicit labels and appropriate type, name, and autocomplete values.
Incorrect (placeholder-only label):
<input placeholder="Email" />Correct (explicit label + metadata):
<label htmlFor="email">Email</label>
<input
id="email"
name="email"
type="email"
autoComplete="email"
inputMode="email"
/>Keep Mobile Input Text at Readable Size
Set input text to at least 16px on mobile and avoid autofocus on touch-first flows.
Incorrect (tiny field text):
input,
textarea {
font-size: 13px;
}Correct (touch-safe field text):
input,
textarea,
select {
font-size: 16px;
}Preserve Visible Focus States
Never remove outlines without a clear :focus-visible replacement.
Incorrect (focus removed):
button:focus {
outline: none;
}Correct (high-contrast focus ring):
button:focus-visible {
outline: 2px solid var(--focus-ring);
outline-offset: 2px;
}Ensure Full Keyboard Operability
Pointer-only handlers are not acceptable for critical actions.
Incorrect (mouse only):
<div onClick={openMenu}>Open menu</div>Correct (keyboard + pointer by default):
<button type="button" onClick={openMenu}>Open menu</button>Meet Minimum Hit Target Size
Tap targets should be at least 24px (44px preferred on mobile).
Incorrect (small tap area):
.icon-button {
width: 18px;
height: 18px;
}Correct (expanded hit area):
.icon-button {
min-width: 44px;
min-height: 44px;
display: inline-grid;
place-items: center;
}Design Empty, Loading, and Error States Explicitly
Every primary surface should define behavior for no data, loading, and failure.
Incorrect (missing fallback states):
return <ResultsList items={data.items} />Correct (state-aware rendering):
if (isLoading) return <ResultsSkeleton />
if (error) return <ErrorState retry={refetch} />
if (data.items.length === 0) return <EmptyState />
return <ResultsList items={data.items} />Prefer Flex/Grid Over JS Measurement
Use CSS layout systems before runtime measurement logic.
Incorrect (measurement-driven layout):
const width = ref.current?.getBoundingClientRect().width ?? 0
setColumns(Math.floor(width / 280))Correct (declarative layout):
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem;
}Handle Long and Unbroken Content Safely
Protect UI against long names, URLs, and dense content blocks.
Incorrect (overflow risk):
.card-title {
white-space: nowrap;
}Correct (safe truncation/wrapping):
.card {
min-width: 0;
}
.card-title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-body {
overflow-wrap: anywhere;
}Respect prefers-reduced-motion
Gate non-essential animation, parallax, and autoplay behind prefers-reduced-motion. Users who set "reduce motion" should get an instant or cross-fade transition instead of large movement. Keep essential motion (e.g. a loading spinner) but tone down decorative effects.
Incorrect (animation always runs):
.card {
transition: transform 400ms ease;
}
.card:hover { transform: translateY(-12px) scale(1.05); }Correct (motion reduced on request):
@media (prefers-reduced-motion: reduce) {
.card { transition: none; }
.card:hover { transform: none; }
}Animate Transform and Opacity, Not Layout
Avoid animating properties that trigger layout/reflow.
Incorrect (layout-thrashing animation):
.panel {
transition: width 220ms ease, left 220ms ease;
}Correct (compositor-friendly animation):
.panel {
transition: transform 220ms ease, opacity 220ms ease;
}Announce Status Changes with Live Regions
Toasts and validation summaries should use polite live regions unless interruption is critical.
Incorrect (visual-only toast):
<div className="toast">Saved</div>Correct (announced toast):
<div role="status" aria-live="polite" className="toast">
Changes saved
</div>Stabilize Loading Indicator Timing
Apply a short reveal delay and minimum visible duration for spinners/skeletons.
Incorrect (instant flicker):
{isLoading && <Spinner />}Correct (delay + minimum duration):
const shouldShowSpinner = isLoading && elapsedMs > 180
const keepSpinner = shouldShowSpinner || spinnerVisibleForMs < 320Use Semantic Links for Navigation
Navigation should use <a> or framework <Link> components, not click handlers on generic elements.
Incorrect (non-semantic navigation):
<div onClick={() => router.push('/settings')}>Settings</div>Correct (semantic navigation):
<Link href="/settings">Settings</Link>Preload Critical Fonts and Preconnect Asset Origins
Preload the smallest set of critical fonts and preconnect to remote asset domains.
Incorrect (late font fetch):
<link rel="stylesheet" href="https://fonts.example.com/site.css" />The crossorigin attribute on the preload tag is not optional. Fonts loaded via CSS @font-face always use CORS anonymous mode. If the <link rel="preload"> omits crossorigin, the browser preloads the font with a different CORS mode, then discards the preloaded response and fetches the font again when CSS references it, doubling the download and defeating the preload entirely.
Correct (critical path optimized):
<link rel="preconnect" href="https://fonts.example.com" crossorigin />
<link rel="preload" as="font" href="/fonts/Inter-Variable.woff2" type="font/woff2" crossorigin />Set Image Dimensions and Priority Intentionally
Declare width/height (or aspect ratio) and prioritize only above-the-fold hero images.
Incorrect (layout shift risk):
<img src="/hero.jpg" alt="Product screenshot" />Correct (stable image rendering):
<Image
src="/hero.jpg"
alt="Product screenshot"
width={1600}
height={900}
priority
/>Lazy-Load Offscreen Media, Never the LCP Element
Add loading="lazy" to offscreen images and iframes so they defer until the user scrolls near them. Never lazy-load the LCP/above-the-fold hero; that delays the largest paint. Pair lazy with eager/priority on the hero.
Incorrect (hero lazy-loaded, offscreen image eager):
<img src="/hero.jpg" alt="..." loading="lazy" />
<img src="/footer-logo.png" alt="..." />Correct (hero eager, offscreen deferred):
<img src="/hero.jpg" alt="..." fetchPriority="high" />
<img src="/footer-logo.png" alt="..." loading="lazy" />
<iframe src="/map" loading="lazy" title="Location map" />Preload Critical Resources and Preconnect to Origins
Preload the LCP image and the critical web font so the browser fetches them early; preconnect to third-party origins you will request from. Don't over-hint; preloading everything cancels the benefit and wastes bandwidth.
Incorrect (no hints; hero font and image discovered late):
<head><link rel="stylesheet" href="/app.css" /></head>Correct (targeted hints for above-the-fold assets):
<head>
<link rel="preconnect" href="https://cdn.example.com" crossorigin />
<link rel="preload" as="image" href="/hero.avif" fetchpriority="high" />
<link rel="preload" as="font" type="font/woff2" href="/inter.woff2" crossorigin />
</head>Load Scripts With defer, async, or module
A bare <script> in <head> blocks parsing and paint. Use defer for app code that needs the DOM and ordering, async for independent third-party scripts, and type="module" (deferred by default) for modern code.
Incorrect (render-blocking script in head):
<head>
<script src="/app.js"></script>
</head>Correct (deferred app code, async third-party):
<head>
<script src="/app.js" defer></script>
<script src="https://cdn.example.com/analytics.js" async></script>
</head>Virtualize Long Lists
Large lists (roughly >50 visible items) should use virtualization/windowing.
Incorrect (renders entire dataset):
<ul>
{items.map(item => <Row key={item.id} item={item} />)}
</ul>Correct (windowed rendering):
<VirtualizedList
itemCount={items.length}
itemSize={48}
renderItem={(index) => <Row item={items[index]} />}
/>Distinguish Links Without Layout Shift
Links should remain visually distinct, but hover states must not change text metrics.
Incorrect (hover changes weight and shifts layout):
a {
text-decoration: none;
}
a:hover {
font-weight: 700;
}Correct (stable hover treatment):
a {
text-decoration: underline;
text-underline-offset: 0.12em;
}
a:hover {
color: var(--link-hover);
}Keep Line Length and Leading in Range
Target roughly 45-75 characters per line with line-height around 1.45-1.6.
Incorrect (long measure, cramped leading):
.article {
max-width: none;
line-height: 1.2;
}Correct (controlled measure):
.article {
max-width: 65ch;
line-height: 1.5;
}Set a Readable Type Scale
Use body sizes and weights that stay readable across desktop and mobile.
Incorrect (too small and too light):
body {
font-size: 12px;
font-weight: 300;
line-height: 1.2;
}Correct (readable defaults):
body {
font-size: clamp(0.95rem, 0.2vw + 0.9rem, 1.125rem);
font-weight: 400;
line-height: 1.45;
}Related skills
How it compares
Pick ui-audit for holistic page-level design QA; use ux-audit when the goal is React or Next.js interaction bugs inside a specific diff.
FAQ
How many rules does ui-audit check?
ui-audit dispatches 35 prefix-based UI rules plus craft and typography sweep checklists. It reports findings by file with impact ratings and concrete fixes across accessibility, forms, layout, performance, motion, and microcopy.
How is ui-audit different from ux-audit?
ui-audit evaluates page- or feature-level UI quality with 35 rule-based checks and typography sweeps. ux-audit in the same repo targets code-level React and Next.js UX bugs such as state coverage, focus management, and optimistic UI in diffs.