Audit UI implementations against Nielsen's 10 Usability Heuristics for User Interface Design. Produces a structured issue log with severity ratings, heuristic mappings, and remediation guidance. Use when conducting heuristic evaluations, reviewing UI code for usability compliance, or auditing existing interfaces.
82
81%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
This skill applies Nielsen's 10 Usability Heuristics to review and audit user interface code. It serves two purposes:
This skill focuses on evaluation methodology and structured reporting. Where a heuristic overlaps with a Law of UX, this skill cross-references the relevant law rather than duplicating guidance. Use both skills together for comprehensive coverage:
usability-heuristics (this skill) = audit methodology + structured issue loglaws-of-ux = theoretical grounding + detailed UX principlesWhen reviewing code, start with the Audit Checklist to identify which heuristics are most relevant, then refer to the detailed sections for guidance and examples. Always produce findings in the Issue Log Format below.
Keywords: heuristic evaluation, usability audit, nielsen heuristics, ui audit, usability review, heuristic review, ux compliance, usability issues
When conducting a heuristic evaluation, produce all findings in this structured format.
Begin every audit with a severity summary:
## Heuristic Evaluation Summary
| Severity | Count |
|----------|-------|
| Critical | X |
| Major | X |
| Minor | X |
| Advisory | X |
| **Total** | **X** |Each finding follows this structure:
### [H#] Heuristic Name — Finding Title
- **Severity**: Critical | Major | Minor | Advisory
- **Heuristic**: #N — Heuristic Name
- **Location**: `file_path:line_range` or component/view name
- **Description**: What the issue is and why it matters for usability.
- **Evidence**:
```html
<!-- Code snippet showing the violation --><!-- Code snippet showing the fix -->laws-of-ux: Law Name (if applicable)### Audit Completion
End every audit with:
```markdown
## Recommendations Priority
1. **Fix immediately** (Critical): List critical findings by ID
2. **Fix soon** (Major): List major findings by ID
3. **Plan for next iteration** (Minor): List minor findings by ID
4. **Consider for enhancement** (Advisory): List advisory findings by IDUse this table for quick scanning. Each check maps to a heuristic and tells you what to look for in code.
| Check | Heuristic | What to Look For |
|---|---|---|
| Loading/progress feedback on all async actions | H1: Visibility of System Status | Spinners, skeleton screens, disabled states, aria-busy, aria-live regions, data-disable-with |
| System uses plain language, not developer jargon | H2: Match Between System and Real World | Error messages, labels, help text — check for HTTP codes, model names, technical terms |
| Undo/back/cancel available for destructive or multi-step actions | H3: User Control and Freedom | Cancel buttons, undo patterns, data-turbo-confirm, modal close (X + Escape), back links |
| UI elements are consistent in style and behavior | H4: Consistency and Standards | Button classes, form layouts, terminology, nav patterns, design token usage |
| Dangerous actions require confirmation; inputs constrain invalid values | H5: Error Prevention | data-turbo-confirm, disabled states, input type/required/min/max/pattern |
| Labels, icons, and affordances are always visible | H6: Recognition Rather than Recall | Visible labels, breadcrumbs, persistent nav, icon+text combos, autocomplete |
| Shortcuts and accelerators exist for experienced users | H7: Flexibility and Efficiency of Use | Keyboard shortcuts, accesskey, search, bulk actions, customizable views |
| UI shows only essential information | H8: Aesthetic and Minimalist Design | Content density, competing CTAs, decorative vs functional elements, disclosure toggles |
| Error messages explain what happened and suggest a fix | H9: Help Users Recover from Errors | Flash messages, inline validation, error summaries, field-level error placement |
| Contextual help is available where needed | H10: Help and Documentation | Tooltips, aria-describedby, help text, empty state guidance, <details> elements |
Definition: The design should always keep users informed about what is going on, through appropriate feedback within a reasonable amount of time.
Key Takeaway: Every user action should produce visible feedback. Async operations need loading states. State changes need visual confirmation. Users should never wonder "did that work?"
What to look for in code:
data-disable-with or Stimulus controllers that show loading statearia-busy="true"aria-live="polite" or aria-live="assertive" for dynamic content updatesExamples:
GOOD
<form data-controller="form-submit" data-action="submit->form-submit#disable">
<button type="submit" class="btn btn--primary"
data-disable-with="Saving..."
data-form-submit-target="button">
Save Changes
</button>
</form>
<div aria-live="polite" class="notification-area">
<!-- Status messages appear here after actions -->
</div>BAD
<form>
<button type="submit" class="btn btn--primary">Save Changes</button>
<!-- No loading state, no disabled state, no confirmation -->
<!-- User clicks and nothing visibly happens until page reload -->
</form>No feedback on submission — user may click multiple times, causing duplicate submissions.
Review flags:
aria-live region for dynamic status updatesCross-references: laws-of-ux: Doherty Threshold (response within 400ms), Goal-Gradient Effect (progress visualization)
Definition: The design should speak the users' language. Use words, phrases, and concepts familiar to the user, rather than internal jargon. Follow real-world conventions, making information appear in a natural and logical order.
Key Takeaway: Labels, error messages, and instructions should use plain language the user understands. Information should be ordered logically from the user's perspective, not the system's.
What to look for in code:
created_at instead of "Date created")Examples:
GOOD
<div class="alert alert--error" role="alert">
<p>We couldn't save your changes. Please check the highlighted fields below.</p>
</div>
<label for="start_date">Start date</label>
<input type="date" id="start_date" name="project[start_date]">BAD
<div class="alert alert--error" role="alert">
<p>422 Unprocessable Entity: Validation failed: start_date can't be blank,
end_date must be after start_date</p>
</div>
<label for="project_created_at">project_created_at</label>
<input type="text" id="project_created_at" name="project[created_at]">Raw technical error message and database column name as label — users should never see system internals.
Review flags:
Cross-references: laws-of-ux: Jakob's Law (users expect conventions from familiar interfaces)
Definition: Users often perform actions by mistake. They need a clearly marked "emergency exit" to leave the unwanted action without having to go through an extended process.
Key Takeaway: Every destructive or commitment action should have an escape route. Users should be able to undo, cancel, go back, or dismiss at any point without penalty.
What to look for in code:
data-turbo-confirm)Examples:
GOOD
<!-- Delete with confirmation -->
<a href="/projects/5" data-turbo-method="delete"
data-turbo-confirm="Delete this project? This cannot be undone."
class="btn btn--danger">
Delete Project
</a>
<!-- Modal with multiple exit paths -->
<dialog class="modal" data-controller="modal"
data-action="keydown.esc->modal#close click->modal#closeOnBackdrop">
<button class="modal__close" data-action="click->modal#close" aria-label="Close">
×
</button>
<div class="modal__body">
<!-- content -->
</div>
<div class="modal__footer">
<button class="btn btn--outline" data-action="click->modal#close">Cancel</button>
<button class="btn btn--primary">Confirm</button>
</div>
</dialog>BAD
<!-- Delete with no confirmation -->
<a href="/projects/5" data-turbo-method="delete" class="btn btn--danger">
Delete
</a>
<!-- Modal with no close button and no escape handler -->
<div class="modal">
<div class="modal__body">
<p>Are you sure?</p>
<button class="btn btn--primary">Yes, continue</button>
<!-- No cancel button, no X, no escape key, no backdrop click -->
</div>
</div>Destructive action with no confirmation. Modal traps the user with only a forward path.
Review flags:
Cross-references: laws-of-ux: Peak-End Rule (error recovery is a critical "peak" moment in the experience)
Definition: Users should not have to wonder whether different words, situations, or actions mean the same thing. Follow platform and industry conventions.
Key Takeaway: Maintain both internal consistency (same patterns throughout your app) and external consistency (follow platform conventions). Same function = same appearance and behavior everywhere.
What to look for in code:
optics-context skill)bem-structure skill)Examples:
GOOD
<!-- Consistent button patterns across all views -->
<!-- In projects/show -->
<div class="actions">
<a href="/projects/5/edit" class="btn btn--primary">Edit</a>
<a href="/projects" class="btn btn--outline">Back</a>
</div>
<!-- In tasks/show — same pattern -->
<div class="actions">
<a href="/tasks/12/edit" class="btn btn--primary">Edit</a>
<a href="/tasks" class="btn btn--outline">Back</a>
</div>BAD
<!-- In projects/show -->
<div class="actions">
<a href="/projects/5/edit" class="btn btn--primary">Edit</a>
<a href="/projects" class="btn btn--outline">Back</a>
</div>
<!-- In tasks/show — inconsistent -->
<div class="task-buttons">
<button class="btn btn--secondary" onclick="location='/tasks/12/edit'">Modify</button>
<a href="/tasks" class="link">Return to list</a>
</div>Same conceptual actions ("edit" and "go back") use different labels, different HTML elements, different CSS classes, and different interaction patterns.
Review flags:
Cross-references: laws-of-ux: Jakob's Law (follow platform conventions), Law of Similarity (consistent styling signals relatedness), Aesthetic-Usability Effect (visual consistency increases perceived quality). Also see optics-context for design token consistency and bem-structure for CSS naming consistency.
Definition: Good error messages are important, but the best designs carefully prevent problems from occurring in the first place. Either eliminate error-prone conditions, or check for them and present users with a confirmation option before they commit to the action.
Key Takeaway: Constrain inputs to valid values. Confirm before destructive actions. Use appropriate input types. The system should make it hard to do the wrong thing.
What to look for in code:
type attributes (email, tel, url, number, date)required, min, max, minlength, maxlength, pattern, step attributesExamples:
GOOD
<form data-controller="form-validation">
<label for="email">Email address</label>
<input type="email" id="email" name="user[email]" required
aria-describedby="email-hint"
data-action="blur->form-validation#validate">
<span id="email-hint" class="hint">We'll send a confirmation to this address.</span>
<span class="field-error" data-form-validation-target="error" hidden></span>
<label for="age">Age</label>
<input type="number" id="age" name="user[age]" min="13" max="120" required>
<label for="start_date">Start date</label>
<input type="date" id="start_date" name="project[start_date]"
min="2024-01-01" max="2030-12-31">
</form>BAD
<form>
<label for="email">Email</label>
<input type="text" id="email" name="user[email]">
<!-- type="text" allows any value; no required, no validation -->
<label for="age">Age</label>
<input type="text" id="age" name="user[age]">
<!-- Accepts letters, negative numbers, anything -->
<label for="start_date">Start date (YYYY-MM-DD)</label>
<input type="text" id="start_date" name="project[start_date]">
<!-- User must type the date in exact format with no picker -->
</form>All fields use type="text" with no constraints — every input error will only be caught on server-side submission.
Review flags:
type="text" on fields that should use email, tel, url, number, or daterequired attribute on fields that must be filledmin/max constraintsCross-references: laws-of-ux: Tesler's Law (system absorbs complexity from the user), Postel's Law (accept flexible input formats)
Definition: Minimize the user's memory load by making elements, actions, and options visible. The user should not have to remember information from one part of the interface to another. Information required to use the design should be visible or easily retrievable.
Key Takeaway: Show, don't make them remember. Labels should be visible (not just placeholders). Navigation should persist. Previously entered information should be displayed in context.
What to look for in code:
<label> elements, not just placeholder textaria-label and tooltipExamples:
GOOD
<!-- Visible labels alongside placeholders -->
<div class="form__group">
<label for="search" class="form__label">Search projects</label>
<input type="search" id="search" placeholder="e.g., Marketing website"
list="recent-searches" autocomplete="on">
<datalist id="recent-searches">
<option value="Marketing website">
<option value="Q4 Report">
</datalist>
</div>
<!-- Icon button with visible text -->
<button class="btn btn--icon-text">
<svg aria-hidden="true"><!-- pencil icon --></svg>
<span>Edit</span>
</button>
<!-- Breadcrumbs for deep navigation -->
<nav aria-label="Breadcrumb">
<ol class="breadcrumb">
<li><a href="/projects">Projects</a></li>
<li><a href="/projects/5">Marketing</a></li>
<li aria-current="page">Settings</li>
</ol>
</nav>BAD
<!-- Placeholder as the only label -->
<input type="text" placeholder="Search...">
<!-- Label disappears on focus — user forgets what field is for -->
<!-- Icon-only buttons with no labels -->
<div class="toolbar">
<button><svg><!-- icon --></svg></button>
<button><svg><!-- icon --></svg></button>
<button><svg><!-- icon --></svg></button>
<button><svg><!-- icon --></svg></button>
</div>
<!-- User must hover each icon to discover its purpose -->Placeholder-only labels disappear when the field has focus. Icon-only toolbar forces recall of icon meanings.
Review flags:
placeholder but no visible <label> elementaria-label, title, or visible textCross-references: laws-of-ux: Miller's Law (working memory limits), Serial Position Effect (placement of key discoverable items)
Definition: Shortcuts -- hidden from novice users -- may speed up the interaction for the expert user so that the design can cater to both inexperienced and experienced users. Allow users to tailor frequent actions.
Key Takeaway: Provide accelerators for power users without complicating the interface for beginners. Support keyboard navigation, bulk operations, and customization.
What to look for in code:
accesskey attributes, custom keyboard event handlers, or shortcut hint UIExamples:
GOOD
<!-- Skip link for keyboard users -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<!-- List with bulk operations -->
<div class="list-controls">
<label class="checkbox">
<input type="checkbox" data-action="change->bulk-select#toggleAll">
Select all
</label>
<div class="bulk-actions" data-bulk-select-target="actions" hidden>
<button class="btn btn--outline" data-action="click->bulk-select#archive">
Archive selected
</button>
<button class="btn btn--danger" data-action="click->bulk-select#delete">
Delete selected
</button>
</div>
</div>
<!-- Keyboard shortcut hint -->
<button class="btn btn--primary" title="New project (N)">
New Project
<kbd class="shortcut-hint">N</kbd>
</button>BAD
<!-- No skip link -->
<!-- No bulk operations — each item must be managed individually -->
<ul class="project-list">
<li>Project A <a href="/projects/1/edit">Edit</a> <a href="/projects/1" data-method="delete">Delete</a></li>
<li>Project B <a href="/projects/2/edit">Edit</a> <a href="/projects/2" data-method="delete">Delete</a></li>
<!-- 50 more items, each requiring individual action -->
</ul>No bulk operations, no keyboard shortcuts, no skip link — inefficient for power users managing many items.
Review flags:
Cross-references: laws-of-ux: Pareto Principle (prioritize the 20% of actions used 80% of the time)
Definition: Interfaces should not contain information that is irrelevant or rarely needed. Every extra unit of information in an interface competes with the relevant units of information and diminishes their relative visibility.
Key Takeaway: Show only what matters for the current task. Move secondary information behind progressive disclosure. Reduce visual noise so that primary actions and content stand out.
What to look for in code:
<details>/<summary>, expand/collapse patterns, or "Show more" linksExamples:
GOOD
<div class="settings-section">
<h3>Notifications</h3>
<label class="toggle">
<input type="checkbox" name="email_notifications" checked>
Email notifications
</label>
<label class="toggle">
<input type="checkbox" name="push_notifications">
Push notifications
</label>
<details class="settings-advanced">
<summary>Advanced notification settings</summary>
<div class="settings-advanced__body">
<!-- Frequency, quiet hours, per-channel settings -->
</div>
</details>
</div>BAD
<div class="settings-page">
<h3>Notifications</h3>
<label><input type="checkbox" checked> Email notifications</label>
<label><input type="checkbox"> Push notifications</label>
<label><input type="checkbox"> SMS notifications</label>
<label><input type="checkbox"> Slack notifications</label>
<label><input type="checkbox"> Weekly digest</label>
<label><input type="checkbox"> Daily summary</label>
<h4>Email frequency</h4>
<select><option>Immediate</option><option>Hourly</option><option>Daily</option></select>
<h4>Quiet hours</h4>
<input type="time" name="quiet_start"> to <input type="time" name="quiet_end">
<h4>Per-channel overrides</h4>
<!-- 15 more fields for each notification channel -->
</div>All settings dumped on one page with no hierarchy or progressive disclosure. Advanced options compete with basic ones for attention.
Review flags:
Cross-references: laws-of-ux: Occam's Razor (simplest solution), Law of Prägnanz (visual simplicity), Hick's Law (fewer choices speed decisions). Also see optics-context for minimal, token-based styling.
Definition: Error messages should be expressed in plain language (no error codes), precisely indicate the problem, and constructively suggest a solution.
Key Takeaway: When errors happen, tell the user what went wrong in human terms, highlight where the problem is, and offer a clear path to fix it. Never show raw system errors.
What to look for in code:
Examples:
GOOD
<!-- Error summary with links to fields -->
<div class="error-summary" role="alert" tabindex="-1">
<h3>There were 2 problems with your submission:</h3>
<ul>
<li><a href="#user_email">Email address is not valid</a></li>
<li><a href="#user_password">Password must be at least 8 characters</a></li>
</ul>
</div>
<!-- Inline field error -->
<div class="form__group form__group--error">
<label for="user_email">Email address</label>
<input type="email" id="user_email" class="input--error"
aria-describedby="email-error" aria-invalid="true">
<span id="email-error" class="field-error" role="alert">
Please enter a valid email address, e.g., name@example.com
</span>
</div>BAD
<!-- Generic error with no specifics -->
<div class="alert alert--error">
<p>There were errors.</p>
</div>
<!-- No inline errors, no field highlighting -->
<div class="form__group">
<label for="user_email">Email</label>
<input type="text" id="user_email">
<!-- User must guess which field has the error and what's wrong -->
</div>Generic error banner with no indication of which fields are wrong or how to fix them.
Review flags:
aria-invalid="true" and aria-describedby linking to the error messageCross-references: laws-of-ux: Peak-End Rule (error recovery is a "peak" moment that shapes overall experience), Postel's Law (accepting flexible input reduces errors in the first place)
Definition: It's best if the system doesn't need any additional explanation. However, it may be necessary to provide documentation to help users understand how to complete their tasks. Any such information should be easy to search, focused on the user's task, list concrete steps to be carried out, and not be too large.
Key Takeaway: Provide contextual help where users need it — tooltips on complex fields, empty state guidance, onboarding hints. Help should be task-focused and appear in context, not require navigating to a separate page.
What to look for in code:
title, aria-describedby, or Stimulus tooltip controllers on complex fields<span class="hint"> or similar elements below complex form fields<details>/<summary> for expandable help contentExamples:
GOOD
<!-- Empty state with guidance -->
<div class="empty-state">
<svg class="empty-state__icon" aria-hidden="true"><!-- illustration --></svg>
<h3>No projects yet</h3>
<p>Projects help you organize your work and track progress with your team.</p>
<a href="/projects/new" class="btn btn--primary">Create your first project</a>
<a href="/help/projects" class="link">Learn more about projects</a>
</div>
<!-- Form field with contextual help -->
<div class="form__group">
<label for="api_key">API Key</label>
<input type="text" id="api_key" aria-describedby="api-key-help">
<span id="api-key-help" class="hint">
Find your API key in your
<a href="/account/integrations" target="_blank">integration settings</a>.
</span>
</div>BAD
<!-- Empty state with no guidance -->
<div class="empty-state">
<p>No projects.</p>
</div>
<!-- Complex field with no help -->
<div class="form__group">
<label for="cron">Schedule (cron expression)</label>
<input type="text" id="cron" name="schedule[cron]" placeholder="* * * * *">
<!-- No explanation of cron syntax, no link to docs, no examples -->
</div>Empty state provides no guidance on what projects are or how to create one. Complex field requires specialized knowledge with no help.
Review flags:
aria-describedby on fields that have associated help textCross-references: laws-of-ux: Zeigarnik Effect (motivate completion of setup and onboarding tasks)
Several heuristics reinforce each other. When auditing, group related findings rather than reporting the same issue under multiple heuristics.
These three heuristics form a complete feedback cycle:
When auditing a form or workflow, check all three together. A form that lacks loading feedback (H1), shows generic errors (H9), and has no cancel button (H3) has a compounding usability problem — report it as a cluster.
These three heuristics support each other:
Inconsistent UIs force recall (violating H6) and add visual complexity (violating H8). When you find inconsistency, check whether it also causes recognition or minimalism problems.
These are two sides of error handling:
When auditing error handling, check both. A form that neither prevents invalid input (H5) nor explains errors clearly (H9) has a compounding problem.
These heuristics both address communication:
If the system uses jargon (H2 violation), the need for documentation increases (H10). When fixing H2 issues, check whether H10 coverage is also needed.
laws-of-ux: Use for detailed UX law guidance when a heuristic finding maps to a specific law. The heuristic evaluation provides the audit methodology; laws-of-ux provides the theoretical grounding and additional code-level examples.bem-structure: Use for CSS class naming conventions when implementing fixes for heuristic violations, especially H4 (Consistency) and H8 (Minimalism).optics-context: Use for design token usage when implementing visual fixes, especially for H4 (Consistency — token vs hard-coded values), H8 (Minimalism — clean styling), and H1 (Visibility — color and spacing for feedback states).097ad6b
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.