DEV Community

Cover image for What to Check When Reviewing AI-Generated UI
Janarthanan Soundararajan (Jana)
Janarthanan Soundararajan (Jana)

Posted on AI-assisted

What to Check When Reviewing AI-Generated UI

AI can help us build a form, a modal, or a dashboard quickly.

The result may look good. The inputs accept text. The buttons respond. The layout fits the screen.

But before merging the code, there is another question to ask:

Does the UI work well beyond the happy path?

Some details are easy to miss during a visual review. A label may not be connected to its input. An icon button may have no accessible name. A secondary button may submit a form by accident.

These are review checkpoints, not claims that every AI tool makes these mistakes. They apply to code we write ourselves too.

The examples below use React, but most of the ideas come from HTML and apply across frameworks. The snippets focus on individual details rather than a complete application.

1. Are labels connected to their inputs?

This looks reasonable:

<label>Email address</label>
<input type="email" />
Enter fullscreen mode Exit fullscreen mode

The text is visible, but the label and input are not connected. In JSX, use htmlFor with a matching input id:

<label htmlFor="email">Email address</label>
<input id="email" name="email" type="email" />
Enter fullscreen mode Exit fullscreen mode

This gives the input an accessible name. Clicking the label also focuses the input. In plain HTML, the attribute is for; React uses htmlFor.

Wrapping an input inside its label is another valid approach. The point is to create the association, not to add htmlFor everywhere.

Also, remember that a placeholder is a hint. It should never replace a visible label.

2. Will IDs stay unique when components are reused?

A hardcoded id="email" can work for one field. But what happens when the same component appears twice on the same screen?

Duplicate IDs break label associations and assistive technology references. For reusable React components, React's useId hook ensures instance-level uniqueness:

import { useId } from "react";

function EmailField() {
  const id = useId();

  return (
    <div>
      <label htmlFor={id}>Email address</label>
      <input
        id={id}
        name="email"
        type="email"
        autoComplete="email"
      />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Each instance gets its own unique ID for the label connection.

Rule of thumb: useId is for accessibility relationships. List keys should come from your underlying data.

3. Do icon-only buttons have accessible names?

A magnifying glass icon may clearly mean “Search” to someone looking at the screen. Make sure the button also provides a programmatic name for screen readers, as well as a visual cue for sighted users who might find the icon ambiguous:

<button type="button" aria-label="Search" title="Search">
  <svg aria-hidden="true" viewBox="0 0 24 24">
    <circle cx="10" cy="10" r="6" fill="none" stroke="currentColor" />
    <path d="m15 15 6 6" stroke="currentColor" />
  </svg>
</button>
Enter fullscreen mode Exit fullscreen mode

Here, aria-label supplies the accessible name, while aria-hidden="true" hides the decorative SVG to avoid redundant announcements. Adding title or a tooltip gives sighted mouse and keyboard users clarity on hover and focus.

If a button already contains visible text:

<button type="submit">Save changes</button>
Enter fullscreen mode Exit fullscreen mode

It does not need an additional aria-label. Prefer visible text whenever the design allows.

4. Are button types intentional?

Inside a <form>, an ordinary <button> without an explicit type defaults to type="submit".

That becomes an immediate issue when its purpose is “Cancel” or “Show password.” Make the intention explicit on every button:

<button type="button" onClick={onCancel}>
  Cancel
</button>

<button type="submit">
  Create account
</button>
Enter fullscreen mode Exit fullscreen mode

Review every secondary button inside a form. A single missing type="button" attribute can trigger accidental form submissions.

5. Are we using the right HTML elements?

A clickable <div> can look identical to a button:

<div onClick={openSettings}>Settings</div>
Enter fullscreen mode Exit fullscreen mode

However, a <div> does not provide native button keyboard handling, focusability, or form integration. Adding role="button" without implementing Enter and Space key handlers still leaves keyboard users stranded.

Use native semantic elements:

{/* For an action */}
<button type="button" onClick={openSettings}>
  Settings
</button>

{/* For navigation */}
<a href="/settings">Settings</a>
Enter fullscreen mode Exit fullscreen mode

Native HTML elements give you focus management, accessibility traits, and keyboard operability for free before you write a single line of extra script.

6. Can someone use the UI with a keyboard?

Set your mouse aside and test the page using only Tab, Shift + Tab, Enter, Space, and Escape:

  • Can you clearly see which element currently has focus?
  • Can you open, navigate, and close custom dropdowns and dialogs?
  • When a modal opens, does focus move inside and stay trapped within it?
  • Does focus return to the trigger element when the modal closes?

When styling focus states in CSS, prefer :focus-visible over :focus:

/* Better: Shows strong outline only during keyboard navigation */
button:focus-visible {
  outline: 2px solid #2563eb;
  outline-offset: 2px;
}
Enter fullscreen mode Exit fullscreen mode

Never set outline: none without providing a prominent, high-contrast :focus-visible replacement.

7. Are validation errors connected and useful?

A red border alone does not explain the issue to users. An error message should identify what went wrong and be programmatically tied to the field:

import { useId } from "react";

function UsernameField({ error }) {
  const id = useId();
  const errorId = `${id}-error`;

  return (
    <div>
      <label htmlFor={id}>Username</label>
      <input
        id={id}
        name="username"
        aria-invalid={error ? true : undefined}
        aria-describedby={error ? errorId : undefined}
      />
      {error && (
        <p id={errorId} role="alert">
          {error}
        </p>
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • aria-invalid flags the field's state to assistive technology.
  • aria-describedby associates the error text with the input so it reads aloud when the field receives focus.
  • Adding role="alert" or using aria-live="polite" ensures dynamically rendered client-side errors are announced immediately, even if the input is already focused.

8. What happens while data loads or when a request fails?

Review more than just the successful response.

Ask:

  • Is it obvious that a network request is running?
  • Can a user accidentally submit multiple times?
  • Does a failed submission preserve the user's filled-in data?
  • If a search input fires rapid requests, can an older response overwrite newer results?

When indicating submission progress:

<button type="submit" disabled={isSubmitting} aria-busy={isSubmitting}>
  {isSubmitting ? "Saving…" : "Save changes"}
</button>
Enter fullscreen mode Exit fullscreen mode

Note on disabled buttons: Adding disabled directly to a focused button can cause screen readers to immediately drop focus back to the top of the <body>. For critical flows, consider keeping the button focusable with aria-disabled="true" while ignoring pointer and click events in your handler.

9. Does the layout survive real content?

AI prompts and sample mocks often feature convenient, short text. Real production data is messy:

  • Test extreme character lengths, such as very long email addresses, multiline error messages, and long URLs.
  • Test localized strings that might expand by 30% or more.
  • Test zoom up to 200% to ensure text does not clip or overflow out of containers.
  • For images, ensure non-decorative images have descriptive alt text, while decorative illustrations use alt="" to avoid cluttering screen readers.

Before merging: quick checklist

Use this checklist during code review:

  • [ ] Inputs have programmatically connected labels (htmlFor / useId).
  • [ ] Generated IDs remain unique when components repeat.
  • [ ] Icon-only controls have accessible names (aria-label) and visual tooltips.
  • [ ] Buttons have explicit type="button" or type="submit".
  • [ ] Interactive actions use <button>, while navigation uses <a>.
  • [ ] Keyboard navigation works smoothly with visible :focus-visible indicators.
  • [ ] Field errors use aria-invalid and aria-describedby.
  • [ ] Loading, disabled, empty, and failure states are explicitly handled.
  • [ ] Layout holds up under browser zoom and long, unexpected text strings.

Turn the checklist into agent instructions

This checklist does not have to remain a manual reference. You can also include it in your coding agent’s review instructions.

Ask the agent to check generated UI for:

  • Connected labels and unique IDs
  • Accessible names for icon-only controls
  • Explicit button types
  • Native semantic elements
  • Keyboard and focus handling
  • Programmatically connected validation errors
  • Loading, empty, disabled, and failure states
  • Long content, zoom, and responsive-layout issues

An agent can catch many code-level problems before a pull request reaches manual review. However, it cannot fully reproduce how the interface feels with a keyboard, screen reader, browser zoom, or real production content. Use agent review as an additional review layer, then verify the important interactions manually.

Top comments (0)