PurposeLink to this section
Mockups use "Jane Doe", a perfectly cropped photo, and three list items. Production has a 90-character company name, no photo, a count of 1,284,332, a title in German, and a URL with no spaces. This skill defines what content each component must survive and how it behaves at the extremes — before production discovers it.
When to UseLink to this section
- Building any component that renders content you do not control
- Converting a design made with ideal placeholder content
- Supporting multiple languages or user-generated content
- Bugs: overlapping text, blown-out cards, squashed buttons, horizontal scroll on mobile
Core PrinciplesLink to this section
- Design with a range, not a sample. Every field has a minimum, typical, and maximum. Know all three.
- Truncation hides information; only truncate what can be recovered. If users need the full value, it must be reachable (title, tooltip on focus, detail view, expand).
- Some content must never be truncated. Amounts, prices, dates, error messages, legal text, and identifiers users need to match.
- Layouts give way; content keeps its meaning. Containers wrap, grow, or scroll; they do not clip meaning or overlap.
- Absence is content too. Missing avatar, missing description, null value — each needs a defined rendering.
- Quantity changes layout. 0, 1, a few, many, and thousands are different designs.
- Format for the user's locale, not the developer's.
WorkflowLink to this section
1. Write the content contractLink to this section
For each component, list every piece of dynamic content.
| Field | Source | Min | Typical | Max | Can be empty? | Overflow rule |
|---|---|---|---|---|---|---|
| Project name | user | 1 char | 12–30 | 100 | no | wrap to 2 lines, then clamp; full name in title + detail page |
| Owner avatar | user upload | — | image | — | yes | initials on colored background derived from id |
| Description | user | 0 | 80–200 | 5,000 | yes | clamp 3 lines in card; "No description" in muted text only on detail page |
| Task count | computed | 0 | 5–200 | 1,000,000+ | no | compact ("1.2M") in badge; exact in tooltip/detail |
| Updated | system | — | — | — | no | relative < 7 days ("3h ago"), absolute otherwise; exact timestamp on hover/focus |
| Tags | user | 0 | 1–3 | 50 | yes | show first 3 + "+N" |
Get real maxima from the database schema, API validation, or production data — not guesses. If there is no maximum, that is a finding: add one or design for unbounded.
2. Choose overflow behavior per fieldLink to this section
| Behavior | Use for | Rules |
|---|---|---|
| Wrap | Names, titles, headings, messages | Allow overflow-wrap: anywhere for long unbroken strings |
| Clamp N lines | Descriptions, previews | Full content must be reachable |
| Truncate end | Single-line labels in dense lists | Full value in accessible name / tooltip |
| Truncate middle | File names, paths, hashes, emails where the end matters | report-final-v…-2024.pdf |
| Compact | Large numbers in badges/charts | Exact value accessible |
| Scroll | Code, logs, wide tables | Scroll within its own container, never the page |
| Fade / "Show more" | Long user content | Expand in place; keep position |
| Never truncate | Money, dates, error text, legal, IDs to match | Let the layout adapt |
3. Handle absence and failureLink to this section
| Content | Missing / failed rendering |
|---|---|
| Avatar | Initials or generic icon; stable color per user; same size as image |
| Hero / thumbnail | Neutral placeholder with preserved aspect ratio; no broken-image icon |
| Optional text | Omit the row, or show "—" in tables; don't show "null", "undefined", "N/A" inconsistently |
| Name | Fallback chain: display name → email → "Unnamed user" |
| Numbers | Distinguish 0 from unknown ("—") |
| Dates | Distinguish "never" from unknown |
Reserve image space with aspect-ratio or width/height to avoid layout shift, and handle onerror.
4. Design for quantityLink to this section
For every list, grid, tag set, avatar stack, or menu:
| Count | Question |
|---|---|
| 0 | Empty state (see interface-states) |
| 1 | Does singular grammar and layout look intentional? |
| Few (2–5) | Typical design |
| Many (20–100) | Scrolling, grouping, "show more"? |
| Huge (1,000+) | Pagination, virtualization, search, filters, server-side counts |
5. Handle user-generated and unusual contentLink to this section
Include in fixtures:
- Long unbroken strings:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, long URLs, email addresses - Emoji and combined emoji (👩🏽💻), including in names and initials
- RTL text (Arabic, Hebrew) and mixed-direction strings
- CJK text (no spaces for wrapping)
- Accents and diacritics, including stacked combining marks
- Leading/trailing whitespace, multiple spaces, line breaks in single-line fields
- HTML/markdown-looking input (
<b>,**bold**) — rendered as text unless intentionally supported - Very short values: one character, one emoji
6. Handle translation and localeLink to this section
- Budget 30–40% text expansion for short UI strings (more for very short ones: "OK" → "Aceptar")
- Buttons and tabs grow; do not fix their width
- Use logical CSS properties (
margin-inline-start) for RTL - Format numbers, currency, dates, and relative times with locale-aware APIs (
Intl) - Do not build sentences by concatenation; word order changes
7. Build and run the stress fixturesLink to this section
Create a fixture set per component: minimum, typical, maximum, missing, unusual, and quantity cases. Render them together (story, test page, or visual regression) at the narrowest supported width.
ChecklistLink to this section
- Content contract written with real min/typical/max
- Every field has an overflow rule
- Nothing that must not be truncated is truncated
- Truncated values are recoverable (including for keyboard and screen reader users)
- Long unbroken strings cannot overflow containers
- Every image has a sized placeholder and a failure fallback
- Missing values render consistently
- 0 vs unknown is distinguishable
- Lists designed for 0, 1, many, and huge
- Numbers, dates, currency formatted per locale
- Layout tested with ~40% longer strings and RTL
- Fixtures render together at narrowest width
Common MistakesLink to this section
- Flex children without
min-width: 0, so a long name pushes the action button off screen. - Ellipsis on a price or amount.
- Truncated text with no way to see the rest, or a tooltip only reachable by mouse.
- Fixed-width buttons that clip translated labels.
- Initials fallback that breaks on emoji or single-word names.
- "1000000" rendered raw or "1.2M" with no way to see the exact value where precision matters.
- Relative dates only ("2 months ago") in contexts where the exact date matters (invoices, audit logs).
- Rendering 5,000 DOM rows because the design showed 8.
ExampleLink to this section
Component: notification item
Fixtures and expected behavior:
| Case | Content | Expected |
|---|---|---|
| Typical | "Ana commented on Q3 plan" | single line |
| Long actor | "Maximilian Alexander von Hohenberg-Schwarzenbach commented…" | wraps to 2 lines; timestamp stays right-aligned on its own line on narrow |
| Long object | object name 100 chars | object name clamps with ellipsis after 2 lines; full name in link's accessible name |
| Unbroken | "commented on AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" | breaks anywhere, no horizontal overflow |
| No avatar | actor has no image | initials "MA" on stable color |
| Emoji name | actor "🦊 Fox" | initials use first grapheme "🦊" |
| Deleted actor | actor null | "A former member" + generic avatar |
| RTL | object name in Arabic | isolated with <bdi>, punctuation not reordered |
| Many | 2,400 unread | badge "99+", list virtualized, "Mark all as read" |
Implementation NotesLink to this section
- CSS:
min-width: 0on flex/grid children that contain text;overflow-wrap: anywherefor user content;line-clampfor multi-line truncation;text-overflow: ellipsiswithwhite-space: nowrapandoverflow: hiddenfor single line. - Wrap user-provided inline strings in
<bdi>(ordir="auto") inside mixed-direction sentences. - Use
Intl.NumberFormatwithnotation: "compact"andIntl.RelativeTimeFormat/Intl.DateTimeFormat. - Split graphemes with
Intl.Segmenterfor initials, notstring[0]. - Put fixtures in stories or a test harness so the extremes are reviewed visually on every change.
Output ExpectationsLink to this section
Produce:
- Content contract per component —
Field | Source | Min | Typical | Max | Empty? | Overflow rule. - Absence rules — rendering for each missing/failed content type.
- Quantity plan — 0 / 1 / many / huge behavior for each collection.
- Stress fixture set — concrete values, ready to paste into stories or tests.
- Findings — when reviewing, each break with the case that triggers it and the fix, with file/line.