---
title: "Service audit, ui cards"
description: "The ui context of the service review: 5 cards, each with a FAIL and a PASS example and the organisations that back the rule."
canonical: "https://goodcodereview.com/service-ui"
updated: "2026-11-06"
---

# service, ui

The stable service cards for the `ui` context. The whole level is at https://goodcodereview.com/service.md.

## svc-async-content-reserves-space

severity: should-fix  
concerns: usability, performance  
applies_when: A page loads images, embeds, fonts, ads, or fetched content after the first paint.  
context: ui  
tool gate: Lighthouse CI assertion on `cumulative-layout-shift`.  
Backed by: Google (unjudged), Apple (unjudged), Nielsen Norman Group (unjudged), Mozilla (unjudged), WHATWG (unjudged), Shopify (supports), Cloudflare (supports). Backing: mixed.

**Rule.** Reserve the space for late-loading content with set dimensions or an aspect ratio, so nothing already on screen moves.

**Why.** A page that jumps while someone reads or taps makes them hit the wrong target. A cumulative layout shift of 0.1 or less is good, and over 0.25 is poor, measured on real users at the 75th percentile.

**FAIL**

```html
<img src="/hero.jpg">
<div id="promo"></div>   <!-- script inserts a banner above the article -->
```

**PASS**

```html
<img src="/hero.jpg" width="1200" height="630" style="max-width: 100%; height: auto">
<div id="promo" style="min-height: 90px"></div>
```

**Counterpoint.** "Ads vary in height." Reserve the common height, or the largest, and accept some blank space. A banner that pushes the article down is the worse trade.

## svc-no-redundant-entry

severity: should-fix  
concerns: usability, accessibility  
applies_when: A multi-step flow, such as checkout or signup, asks for data in more than one step.  
context: ui  
tool gate: none; a walk through the flow that lists each field asked twice.  
Backed by: W3C (unjudged), Nielsen Norman Group (unjudged), GOV.UK (unjudged), Baymard Institute (unjudged). Backing: unjudged. People cited: Ben Shneiderman (unjudged).

**Rule.** Do not ask for information the user already gave in the same flow. Fill it in, or let the user pick it.

**Why.** Typing the same address twice costs time and adds typos. People recognise what they gave more easily than they recall it, so show it and let them confirm it.

**FAIL**

```html
<!-- step 1 -->  <input name="ship_address">
<!-- step 2 -->  <input name="billing_address">   <!-- empty again -->
```

**PASS**

```html
<!-- step 2 -->
<label><input type="checkbox" name="same" checked> Billing address is the same as shipping</label>
<input name="billing_address" value="12 Elm Road, Leeds" hidden>   <!-- hidden while the box is checked; shown, pre-filled, when it is unchecked -->
```

**Counterpoint.** "Retyping a new password confirms it." That is a fair exception. So is data that is no longer valid, and re-entry that is the point of the task, such as a memory test. Browser autofill does not count: the page itself must supply the value.

## svc-reflow-and-zoom

severity: should-fix  
concerns: accessibility, usability  
applies_when: A page or app is served to browsers on screens of any size.  
context: ui  
tool gate: axe `meta-viewport`; a rendered check at a 320 px viewport and at 200% text zoom.  
Backed by: W3C (unjudged), Deque (unjudged), The A11y Project (unjudged), WebAIM (unjudged), Mozilla (unjudged), Google (unjudged), Microsoft (unjudged). Backing: unjudged.

**Rule.** Make the page usable at 320 CSS pixels wide and at 200% text zoom, with no sideways scrolling, no clipped content, and no blocked pinch-zoom.

**Why.** Low-vision users enlarge the page instead of using a small screen. A fixed width or a clipped container hides content from exactly those users. `user-scalable=no` takes away their only tool.

**FAIL**

```html
<meta name="viewport" content="width=device-width, user-scalable=no, maximum-scale=1">
```

```css
.layout { width: 960px; }
.card   { height: 120px; overflow: hidden; }
```

**PASS**

```html
<meta name="viewport" content="width=device-width, initial-scale=1">
```

```css
.layout { max-width: 60rem; padding-inline: 1rem; }
.card   { min-height: 7.5rem; }
.table-wrap { overflow-x: auto; }   /* a data table scrolls in its own box */
```

**Counterpoint.** "Maps and data tables need two-way scrolling." They are exempt, but only the part that needs it. Wrap the table in its own scroll box, and keep the headings and controls around it reflowing.

## svc-slow-operation-shows-progress

severity: should-fix  
concerns: usability, accessibility  
applies_when: A page action, such as a save, an upload, a search, or a page load, can take more than about a second.  
context: ui  
tool gate: none; throttle the network and run the action.  
Backed by: Nielsen Norman Group (unjudged), Apple (unjudged), W3C (unjudged), Microsoft (unjudged), Atlassian (unjudged). Backing: unjudged. People cited: Ben Shneiderman (unjudged).

**Rule.** Show that a slow action is under way, and show its progress when it runs long.

**Why.** With no feedback, users click again, reload, or leave, and a double click can run the action twice. A bar tells them whether to wait. A message that says what is happening is better than "Loading". As a guide, show feedback after about one second. Past about ten seconds, show how much is done and offer Cancel where stopping is safe.

**FAIL**

```jsx
<button onClick={upload}>Upload</button>   // 40 s, nothing changes
```

**PASS**

```jsx
<button onClick={busy ? undefined : upload} aria-disabled={busy}>Upload</button>
{busy && <progress value={sent} max={total} aria-label="Uploading report.pdf" />}
{busy && <button onClick={cancel}>Cancel</button>}
<div role="status">{message}</div>   // "Uploading report.pdf", then "Upload complete"
// aria-disabled keeps keyboard focus on the button; disabled would drop it
```

**Counterpoint.** "A spinner on a 200 ms call flickers." Right: below about a second, show nothing, or the placeholder just flashes. Skeleton screens suit page loads, and progress bars suit uploads and other long jobs.
