Skip to main content
skill Claude Code CX Codex Cursor GitHub Copilot

break-ui — Claude Code skill: UI stress testing and installation

This skill tests a UI with realistic edge-case data and reports what breaks. Use it to stress-test a component or screen during development.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add emilkowalski/skills --skill break-ui -a claude-code
Open on GitHub

What break-ui does

The skill feeds a component long names and email addresses, missing fields, one-letter names, large counts, and empty lists. It builds a worst-case fixture, adds a Demo data / Worst case switch for development, then reports failures and suggests a fix for each.

It also checks values that are easy to overlook, including labels, counts, statuses, avatars, and list length. The workflow looks for limits in validation schemas, database migrations, API types, and maxLength attributes; large lists are checked for performance issues too. Install it when you want to test a specific screen against edge cases. It is not for visual design critique.

What it helps with

  • find text overflow caused by long names or email addresses
  • check how missing fields and avatars render
  • catch incorrect singular or plural text for a count of 1
  • test empty states and screens with a single item
  • spot performance issues with large lists

Example prompts for the agent

  • Stress-test this screen with realistic edge-case data and add a Demo data / Worst case switch.
  • Find what breaks in the member list with long names, missing fields, and a count of 1.
  • Check the empty state, a single item, and a large list. Report issues and fixes before changing anything.

How to install break-ui

1Skills CLI (Vercel)

npx skills add emilkowalski/skills --skill break-ui -a claude-code

2Manually: put the files in place

~/.claude/skills/break-ui/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/break-ui/

From the same set15

The whole set →
emil-design-eng emilkowalski This skill guides UI polish, component details, and animation choices. Use it when an interface works but its states and transitions need closer review. Claude Code CX Codex Cursor +1 385,500installs review-animations emilkowalski Reviews UI animation and motion code against Emil Kowalski's design standards. Flags slow easings, non-GPU properties, and unnecessary transitions. Claude Code CX Codex Cursor +1 254,100installs apple-design emilkowalski Translates Apple's fluid interface principles from WWDC talks into web code with Pointer Events and spring physics. Essential for gesture-driven sheets, drags, and sliders. Claude Code CX Codex Cursor +1 242,100installs animation-vocabulary emilkowalski This skill maps a vague web animation description to the right term. Use it when you need wording for an AI prompt or a designer. Claude Code CX Codex Cursor +1 236,900installs improve-animations emilkowalski Surveys motion and animation code across your project, then generates self-contained fix plans. It spots janky keyframes and poor easing without altering source files. Claude Code CX Codex Cursor +1 222,000installs find-animation-opportunities emilkowalski Scans a codebase or UI for motion that would clarify states and interactions, while rejecting decoration that gets in the way. It is read-only. Claude Code CX Codex Cursor +1 206,800installs pick-ui-library emilkowalski A skill that maps a frontend task to one library from Emil Kowalski's curated list: toasts, charts, OTP fields, virtualization, drag and drop. Runs only when you call it. Claude Code CX Codex Cursor +1 183,400installs prototype emilkowalski The skill builds several genuinely different versions of one piece of UI and puts them behind a visual picker, so you flip through them live and keep the one that feels right. Claude Code CX Codex Cursor +1 172,400installs

Similar

next-dev-loop vercel Checks that an edit to your Next.js app actually works in a running app, not just that it compiles. Built for the next dev loop. Claude Code CX Codex Cursor +1 22,000installs next-cache-components-adoption vercel This skill helps enable Cache Components in a Next.js app and work through route errors until the build passes. Use it when migrating an App Router project. Claude Code CX Codex Cursor +1 21,200installs next-cache-components-optimizer vercel Optimizes Next.js App Shell UI on hard and soft navigations. It sets up failing Playwright instant() checks and guides routes to green. Claude Code CX Codex Cursor +1 18,700installs schema coreyhaines31 This skill helps add or repair structured data on site pages. Use it to draft JSON-LD and check whether Google currently supports the rich result you want. Claude Code CX Codex Cursor +1 77,800installs design-md official google-labs-code Extracts screens and styles from Google Stitch via MCP to synthesize a semantic design system into a DESIGN.md file. Claude Code CX Codex Cursor +1 64,700installs signup coreyhaines31 A CRO playbook for signup and trial activation. The agent audits your registration form field by field and hands back fixes in priority order. Claude Code CX Codex Cursor +1 71,300installs frontend-design official anthropics This skill guides intentional visual design for new interfaces and redesigns. Use it when a generic generated UI is not enough. Claude Code Claude.ai 1,025,790installs design-taste-frontend Leonxlnx This skill directs the design of public pages and work screens, then checks the result in a browser through screenshots. Use it when an interface feels generic or unfinished. Claude Code CX Codex Cursor +1 673,100installs

Questions about break-ui

How do I install break-ui in Claude Code?

Run npx skills add emilkowalski/skills --skill break-ui -a claude-code.

What does break-ui do?

It tests a component or screen with realistic edge-case data, adds a development-only fixture switch, and reports failures with a suggested fix for each.

Why does break-ui add a Demo data / Worst case switch?

The switch lets you compare normal and edge-case fixtures while inspecting the UI. It should stay limited to development or a prototype route.

Source text

SKILL.md · MIT
---
name: break-ui
description: Try to break a piece of UI by feeding it worst-case data — long names, unbreakable emails, one-letter names, missing fields, huge counts, zero items, long labels, non-Latin text, emoji, extreme numbers — then render it behind a "Demo data / Worst case" toggle and report everything that broke, with the fix for each. Use when the user asks to stress-test, break, or find edge cases in a component or screen, or to "try the worst case". For visual design critique use emil-design-eng; for motion use review-animations.
---

# Breaking UI

## Initial Response

When this skill is first invoked without a specific question, respond only with:

> I'm ready to throw the worst realistic data at your UI and show you what breaks, my standards come from Emil Kowalski's design engineering philosophy.

Do not provide any other information until the user asks a question.

An adversarial skill. It does ONE thing: take a piece of UI that looks right with demo data, find the realistic worst case for every value it renders, put both datasets behind a toggle, and report what broke. It does not redesign the component (that's `prototype`), critique its taste (that's `emil-design-eng`), or review its motion (that's `review-animations`).

## Operating Posture

You are the most annoying real user this component will ever meet. Your name is Aleksandra Wiśniewska-Kowalczyk, your colleague's email is `[email protected]`, your intern is called Jo, and your workspace has 1,284 members. None of that is contrived. Every one of those people exists in production somewhere, and the UI was designed against "Jane Doe, [email protected], 12 members".

Demo data is chosen, usually without anyone noticing, to make the design look good: names that fit on one line, counts that never need a separator, every optional field filled in. The job here is to undo that choice, one field at a time.

Two failure modes, and the first is worse:

1. **Nonsense data.** `"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"` and 5,000-character names prove nothing. The designer will rightly say "that never happens" and stop listening. Every worst-case value must be something a real user could plausibly produce, or the longest value the backend actually accepts.
2. **Stopping at long text.** Long names are the obvious break. The ones that ship are the short name that leaves an orphaned dash, the missing avatar, the count of exactly 1 ("1 members"), the empty list, the badge whose text got translated.

## Hard Rules

1. **Plausible or schema-backed, never random.** Each worst-case value is either a realistic example (a real naming pattern, a real email shape) or the actual limit from the validation schema, database column, or API contract. If you find no limit, that's a finding in itself: say "unbounded" and test something long but believable.
2. **Change the data, not the component.** The worst case enters through the same boundary the demo data does: the fixture, the mock, the props, the API stub. Never hand-edit markup or CSS to produce a break; that tests your edit, not the component.
3. **One dataset, many failures.** A single worst-case dataset should hit every row of the catalog that applies to this component at once. Mix them across rows (row 1 is the long name, row 2 is the long email, row 3 is the one-letter name), as real data does.
4. **The toggle is dev-only.** It never ships to production. Gate it behind the dev environment or keep it inside a prototype route.
5. **Report before fixing.** Some breaks are design decisions (truncate or wrap? hide the role or show "—"?). List them all, propose a fix for each, then stop. Fix only when asked.

## Workflow

### Phase 1 — Map the surface

Read the component and list every value it renders, with its source:

| Field | Source | Type | Limit | Optional? |
| --- | --- | --- | --- | --- |
| `name` | `member.name` | string | 255 (`schema.ts:14`) | No |
| `email` | `member.email` | string | none found | No |
| `role` | `member.title` | string | 120 | Yes |
| `status` | enum | `active` / `invited` / `expired` | — | No |
| `count` | `workspace.memberCount` | int | — | No |

Include the values people forget: counts in headers, relative timestamps, badge and status text, button labels that come from data, tooltips, avatar images, the list itself (its length is a value too).

Look up limits in the validation schema (Zod, Yup, Valibot), database migrations, API types, and form `maxLength` attributes. Note where the frontend and backend disagree; a 50-character input that saves to a 255-character column means something longer will turn up eventually, through an import or the API.

**Completion criterion:** every rendered value is in the table, with a source and either a limit or "unbounded".

### Phase 2 — Build the worst case

For each field, pick values from [CATALOG.md](CATALOG.md). Load it now. It covers text, identifiers, numbers, collections, time, media, states, and environment, each with the specific values that break things and why.

Assemble a single worst-case fixture next to the existing demo data, shaped exactly like it (same type, same file conventions). The first few rows of a list matter most because that's what's on screen, so spread the different failures across them instead of stacking every one into row 1.

Also cover the cases that aren't one dataset:

- **Empty**: zero items, the no-results state.
- **One**: a single item, and every count at exactly 1 (pluralization).
- **Huge**: the realistic upper bound for list length (1,000+ rows if the list is unpaginated, since that's a performance break as well as a visual one).

These can be extra toggle positions or extra fixtures. Don't skip them because they don't fit the two-state toggle.

### Phase 3 — Wire the toggle

Put a segmented control labeled **Demo data / Worst case** where the user can flip it while looking at the component, and swap the fixture at the data boundary (Hard Rule 2). Extra states go in as extra segments: **Demo / Worst case / Empty / One / 1,000 rows**.

- **In a project with a dev server**: a dev-only switch (a `?data=worst` URL param read where the fixture is chosen, or a prototype route that renders the component with each fixture). The selection persists in the URL so a reload keeps it.
- **No project / standalone component**: a single self-contained HTML file with the component and both datasets inline.

Fixed at the bottom-center of the viewport, out of the component's way. Small, neutral, obviously chrome. It is not part of the design under test, so keep it plain: a gray track, a white pill on the active segment, system font. Switching is instant, with no animation on the content.

### Phase 4 — Break it

View the worst case and look for each failure signature below. Check it:

- at the component's **real container width** (a sidebar list is not a full-page list), then at **320px** and at the **widest** layout it supports;
- with **browser zoom at 200%** (or root font size raised), because text grows and boxes don't;
- in **dark mode** and **RTL** (`dir="rtl"` on a wrapper) if the product supports either.

If browser tooling is available, screenshot both states at each width and compare. If it isn't, reason from the CSS and say which findings you verified visually and which you inferred.

#### Failure signatures

Each of these appears in the screenshot. The cause in the right column is almost always it.

| What you see | Cause | Fix |
| --- | --- | --- |
| Avatar or icon squished into an oval or pill | Flex child shrinking | `flex-shrink: 0` on the avatar, icon, and any fixed-size box |
| Text overflows its box instead of wrapping or truncating | Flex/grid child has `min-width: auto` | `min-width: 0` on the text column (`minmax(0, 1fr)` in grid) |
| Email or URL runs past the edge | No break opportunities in the string | `overflow-wrap: anywhere` on that element |
| Trailing action (••• menu, button) pushed off-screen or clipped | Middle content took all the space | `min-width: 0` on the middle, `flex-shrink: 0` on the action |
| Badge wraps onto two lines | Badge allowed to shrink | `white-space: nowrap; flex-shrink: 0` on the badge, and decide what yields instead |
| Avatar centered against a three-line name looks adrift | `align-items: center` on rows of varying height | Top-align (`align-items: flex-start`) once text can wrap, |
| Last row cut off at a hard edge mid-glyph | Fixed-height container with no fade or scroll affordance | Visible scrollbar or a fade mask, and ensure `overflow` is intended |
| Long word breaks mid-word in a heading | `word-break: break-all` | `overflow-wrap: anywhere` breaks only when it has to |
| Wrong initials (`"J"` for "Jo", `"CI"` for "… Montgomery III", `"�"` for an emoji-first name) | `.split(' ')[0][0]` style code | Initials from grapheme clusters (`Intl.Segmenter`), first + last word, fallback icon |
| Orphaned `—` or empty line where the role was | Placeholder rendered for a missing optional field | Omit the line, or reserve its height intentionally |
| "1 members", "0 member" | Hardcoded plural | `Intl.PluralRules`, or separate strings per count |
| Numbers jitter when they update, columns misalign | Proportional figures | `font-variant-numeric: tabular-nums` |
| `1284`, `1,284.000000001`, `NaN`, `undefined` | Raw number rendered | `Intl.NumberFormat` with the user's locale; guard null |
| Long translated button label overflows | Fixed-width button | Width from content with `min-width`, never a fixed `width` |
| Diacritics or tall scripts (Vietnamese, Thai) clipped top or bottom | Tight `line-height` with `overflow: hidden` | Looser `line-height` or no clipping on text boxes |
| Broken-image icon in the avatar | No `onError` fallback | Fall back to initials; `object-fit: cover` for any aspect ratio |
| Truncated text with no way to read it | `text-overflow: ellipsis` and nothing else | `title` attribute or a tooltip, and the full value elsewhere (detail view) |
| Scrolling 1,000 rows stutters | Every row rendered | Virtualize, or paginate, and say which |
| Content renders raw `<b>`, `&amp;`, or `**text**` | Wrong escaping layer | Escape once, at render; never `dangerouslySetInnerHTML` user data |

#### Truncate, wrap, or clamp

Every long string forces this choice. Make it per field, not globally:

- **Wrap** text the user needs in full to identify something: names, titles in a detail view. Two lines is usually fine; four is a sign the column is too narrow.
- **Truncate at the end** for secondary metadata where the start carries the meaning: role, description, last message preview. Always pair with a way to see the full value.
- **Truncate in the middle** when items differ at the *end*: file names (`Q3-report…v12-final.pdf`), emails sharing a long domain, paths, hashes. End-truncation makes them identical.
- **Clamp** (`line-clamp: 2`) for multi-line previews in cards, so card heights stay predictable.
- **Never truncate** numbers, amounts, dates, or anything the user compares. Give them the room.

**Completion criterion:** every catalog row that applies has been tried, every width and environment above has been checked, and every break has a signature, a cause, and a fix.

### Phase 5 — Report and stop

Present the findings in the format below, leave the toggle running, and stop. The user flips the toggle, looks, and decides.

### Phase 6 — Fix on request

When the user says which to fix, apply those fixes in the component, using the project's existing conventions and tokens. Then flip the toggle through every state again (Demo too: a fix for the worst case must not regress the demo) and confirm each fixed break is gone.

Keep the worst-case fixture afterward unless the user says otherwise. It's the regression test for the next time someone touches the component. The toggle stays dev-only either way.

## Required Output Format

### Part 1 — What broke

One row per break, worst first. Severity: **Broken** (content unreadable, action unreachable, wrong data shown), **Ugly** (readable but visibly wrong: squished avatar, wrapped badge), **Fragile** (fine now, one realistic step from breaking: no limit, no fallback).

| # | Severity | Field | Worst-case value | What happens | Fix |
| --- | --- | --- | --- | --- | --- |
| 1 | Broken | `email` | `[email protected]` | Pushes the ••• menu off the row; menu unreachable at 400px | `min-width: 0` on text column, `overflow-wrap: anywhere` on email, `flex-shrink: 0` on menu |
| 2 | Ugly | avatar | name with long email | Avatar squishes to a 28×56 pill | `flex-shrink: 0` on avatar |
| 3 | Ugly | `count` | 1 | "1 members" | `Intl.PluralRules` |
| 4 | Fragile | `name` | — | No max length in schema or form | Add a limit in both, matching |

Every row has `file:line` for the fix location in the Fix cell or directly below the table.

### Part 2 — Decisions for you

Breaks with more than one right answer: truncate vs wrap for a field, what an empty role should show, whether a 1,000-row list paginates or virtualizes. One line each, with your recommendation and why.

### Part 3 — What held up

List the worst cases the component already handles. This shows the test was real and tells the user what not to touch.

Close with where the toggle is (URL or file path), the states it has, and: "Say `fix all` or `fix 1, 3` and I'll apply them."

## Invocation Variants

| Invocation | Behavior |
| --- | --- |
| `<component or screen>` | Full workflow: map → worst case → toggle → break → report, then stop |
| `<component> + fix` | Same, then apply every fix that isn't in "Decisions for you" |
| `fix all` / `fix 1, 3` | Apply the named fixes from the last report, re-verify all states |
| `data only <component>` | Produce the worst-case fixture and toggle without the report |

## Tone

Matter-of-fact, never smug. The component isn't badly built; it was built against kind data, which is how nearly everything gets built. Name the break, show the value that caused it, give the fix. If nothing breaks for a field, say so. A short report on a sturdy component is a good result.

© emilkowalski · MIT · emilkowalski/skills

See also

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor