# UI Design Agents

Course: [AI Agents](https://www.devtutorials.ai/learn/ai-agents) · Section: Agents for Development Workflows
Source: https://www.devtutorials.ai/learn/ai-agents/dev-workflows/ui-design-agents

> How a UI design agent turns a design task into a layout using only design tokens, and fixes accessibility problems before handing over.

Think about decorating a room in a shop that follows a strict style guide.

The shop gives you a style card with approved paint colours, fonts, and spacing values. You can arrange things however you like, but you must use only the approved styles. This helps every shop look like part of the same brand.

There are also safety rules. Signs must be easy to read, and every door must be clearly marked.

A **UI design agent** works in the same way. The style card is the **design system**, and the safety rules are **accessibility checks**.

**The main idea:** A UI design agent turns a design task into a page layout. It uses only values from the design system and does not hand the design over until the accessibility checks pass.

## A Real-World Example

The planning agent from Lesson 17 created the task: "Design the export status panel: 4 states and a Retry button."

The UI design agent:

1. Reads the feature request to understand what the panel must display.
2. Reads `design/tokens.json`, which contains the team's colours and spacing values.
3. Creates the first draft in `web/export-panel.html`.
4. Runs `check_a11y` and `check_tokens`.
5. Fixes any reported problems and runs the checks again.

The first draft contains 3 accessibility issues. That is normal. What matters is that the agent finds and fixes them *before* the frontend agent starts building from the design.

**In simple words:** Create the design using the design system, check accessibility, fix the problems, check again, and then hand the design over.

## What You Will Learn

In this lesson, you will learn:

1. What a wireframe is and how an agent creates one.
2. How layouts use spacing values from the design system.
3. What design tokens are and why the agent must use only approved tokens.
4. The three accessibility checks covered in this lesson: alt text, button names, and contrast.

The UI design agent has four tools: `read_file`, `write_file`, `check_a11y`, and `check_tokens`. It may only modify files in `web/`. Open the **Files** tab after a run to view `web/export-panel.html`.

## Wireframes

A **wireframe** is a simple sketch of a page that shows where things go, without visual styling. For an agent, a wireframe is often a short list of page elements in order, or simple HTML with very little styling.

The export status panel contains four parts: a heading, a progress image, the status text, and a Retry button. Each of the four statuses displays a slightly different version.

```js runnable title="A wireframe as data: what each status shows"
const states = {
  queued: ["heading", "status text"],
  running: ["heading", "progress image", "status text"],
  done: ["heading", "status text with row count"],
  failed: ["heading", "status text", "Retry button"],
};

for (const [status, parts] of Object.entries(states)) {
  console.log(status.padEnd(8) + "| " + parts.join(" | "));
}
```

Expected output:

```text
queued  | heading | status text
running | heading | progress image | status text
done    | heading | status text with row count
failed  | heading | status text | Retry button
```

Writing every state down prevents the agent from designing only the happy path. The Failed state is the one most often forgotten, and it is often the one users need most.

## Layout

**Layout** determines where each part appears and how much space surrounds it. A design system provides a small set of approved spacing values, such as 4px, 8px, and 16px. Using only those values keeps the design consistent across the whole application.

```js runnable title="Snap a spacing value to the nearest token"
const space = { s: 4, m: 8, l: 16 };

function nearestToken(px) {
  let best = "s";
  for (const [name, value] of Object.entries(space)) {
    if (Math.abs(value - px) < Math.abs(space[best] - px)) best = name;
  }
  return best + " (" + space[best] + "px)";
}

for (const px of [5, 10, 15, 30]) console.log(px + "px -> " + nearestToken(px));
```

Expected output:

```text
5px -> s (4px)
10px -> m (8px)
15px -> l (16px)
30px -> l (16px)
```

A designer who chooses "15px" is close to the 16px design token, but it is still not the approved value. Small differences like that add up over time, and the page gradually stops matching the rest of the product.

## Design Systems

A **design system** is the team's shared collection of colours, spacing values, fonts, and reusable components. These values are stored as **design tokens**: named values such as `"subtle": "#5f6b7a"`.

The agent should use only values from the design tokens. The `check_tokens` tool enforces this rule. Every colour and spacing value used in the HTML must exist in `design/tokens.json`.

```js runnable title="Find colours that are not design tokens"
const tokens = { text: "#1b1f24", subtle: "#5f6b7a", muted: "#9aa3ad", accent: "#0056d2" };
const html = '<h2 style="color: #222222">Export</h2><p style="color: #5f6b7a">Running</p><button style="color: #0077cc">Retry</button>';

const used = html.match(/#[0-9a-f]{6}/gi);
const allowed = Object.values(tokens);
for (const c of used) console.log(c + ": " + (allowed.includes(c) ? "token" : "NOT a token"));
```

Expected output:

```text
#222222: NOT a token
#5f6b7a: token
#0077cc: NOT a token
```

`#222222` looks very similar to the `text` token. However, a value that is only similar still breaks the design system. If the team changes the `text` token later, this heading will not update with the rest of the product.

This agent never read `tokens.json`, so it guessed the values. The token check finds 4 values that do not match any design token.

One of the guessed grey colours also fails the contrast check. The agent must read the tokens and create a new draft.

## Accessibility Checks

**Accessibility** (often written as **a11y**) means that everyone can use the page, including people who use screen readers, keyboards, or zoom features. The `check_a11y` tool checks three rules.

### Images Need Alt Text

A screen reader cannot see an image. `alt="Export in progress"` tells the screen reader what to read aloud.

### Buttons Need a Name

A button that shows only an icon, such as ⟳, has no visible text for a screen reader to announce. `aria-label="Retry the export"` gives the button a readable name.

```js runnable title="Find images with no alt and buttons with no name"
const html = [
  '<img src="spinner.svg">',
  '<img src="chart.png" alt="Rows exported per day">',
  "<button>⟳</button>",
  '<button aria-label="Retry the export">⟳</button>',
  "<button>Retry</button>",
];

for (const tag of html) {
  let problem = "ok";
  if (tag.startsWith("<img") && !tag.includes("alt=")) problem = "image has no alt text";
  const inside = tag.replace(/<[^>]+>/g, "");
  if (tag.startsWith("<button") && !tag.includes("aria-label=") && !/[A-Za-z]/.test(inside)) problem = "button has no name";
  console.log(problem.padEnd(22) + tag);
}
```

Expected output:

```text
image has no alt text <img src="spinner.svg">
ok                    <img src="chart.png" alt="Rows exported per day">
button has no name    <button>⟳</button>
ok                    <button aria-label="Retry the export">⟳</button>
ok                    <button>Retry</button>
```

A button that already contains the word "Retry" has a name that a screen reader can read. Only buttons that use an icon without text need an `aria-label`.

### Text Needs Enough Contrast

**Contrast** measures how clearly text stands out from its background. For normal text, the WCAG guideline requires a contrast ratio of at least **4.5 to 1**. Light grey text on a white background often fails this requirement.

```js runnable title="Contrast ratio against a white background"
function contrastOnWhite(hex) {
  const ch = [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255)
    .map((v) => (v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4));
  const lum = 0.2126 * ch[0] + 0.7152 * ch[1] + 0.0722 * ch[2];
  return 1.05 / (lum + 0.05);
}

for (const [name, hex] of [["text", "#1b1f24"], ["subtle", "#5f6b7a"], ["muted", "#9aa3ad"], ["guess", "#777777"]]) {
  const r = contrastOnWhite(hex);
  console.log(name.padEnd(7) + r.toFixed(2) + ":1 " + (r >= 4.5 ? "pass" : "FAIL"));
}
```

Expected output:

```text
text   16.56:1 pass
subtle 5.43:1 pass
muted  2.56:1 FAIL
guess  4.48:1 FAIL
```

The `muted` token exists for things like disabled borders, not for text that people need to read. And `#777777` misses the requirement by a very small amount: 4.48 is still not 4.5.

The draft uses token-based colours, so the token check passes. However, the accessibility check finds 3 problems: the image is missing alt text, the small text uses the `muted` token, and the ⟳ button has no accessible name. The agent fixes all three issues, runs both checks again, and hands the design over after 9 steps.

Open the **Preview** tab and select **export-panel.html** to view the panel in a browser-like view. Enable **Show the files before the agent ran** to compare the result. Before the run, the page does not exist.

**Easy Way to Remember:** **tokens for looks, checks for people.** The design system keeps pages consistent. Accessibility checks keep them usable.

A design that looks good can still fail accessibility requirements. Never skip the accessibility check just because the page looks fine on your own screen.

## Common Mistakes

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| No tokens in context | "The agent knows good colours." | Read `tokens.json` first. | Near-match colours break the design system. |
| Light grey text | "It looks elegant." | Check contrast: 4.5:1 or higher. | People cannot read the status text. |
| Icon-only button | "Everyone knows ⟳ means retry." | Add an `aria-label`. | Screen readers announce only "button" and nothing more. |
| Only the happy state | "Done is the main case." | Design all 4 states. | The Failed state does not include a Retry button. |
| Checking only once | "The draft passed earlier." | Run checks after the final change. | A later fix introduces a new problem. |

## Exercises

Each exercise ends with a value on the last line. Press **Check** to compare it with the expected answer.

## What to Remember

- Draft using the design system, run the checks, fix the issues, run the checks again, and only then hand the design over.
- A wireframe should include every state, not just the happy path.
- Use only design tokens for colours and spacing values.
- The three accessibility checks in this lesson are alt text, button names, and text contrast of at least **4.5:1**.
- Hand the design over only after both the accessibility check and the token check pass following the final change.

Next: **Frontend Development Agents**, the agent that turns the design into a working component.
