Craftsmanship Standards
A checklist for implementing UI to a high craft bar, derived from 16 years in tech at Apple, Google, Facebook, and Meta RL. Use this when writing or reviewing any user-facing component. Treat each item as an acceptance criterion, not a suggestion.
Slash command: /craft-check — run the full audit workflow below against the current file, component, or PR diff.
1. Interactive element states
Every clickable/tappable element must have explicit designs/implementations for:
- Default
- Hover
- Pressed / active
- Focused (keyboard nav — visible focus ring, not just browser default)
- Disabled
- Loading (if the action triggers async work)
Cursor states must be set deliberately, not left at default:
pointerfor anything clickablenot-allowedfor disabled elements- Never leave a clickable element with the default arrow cursor
2. Loading & progress feedback ("Manage Expectations")
Match the feedback pattern to the expected wait time:
| Wait time | Pattern |
|---|---|
| < ~1–3s | Simple spinner or skeleton screen. No progress bar — it adds noise, not clarity. |
| ~3–10s | Determinate progress indicator (bar or %). Users start doubting responsiveness past this point. |
| 10s+ | Staged, labeled progress ("Step 2 of 3: uploading"). Explicitly tell the user whether to stay on screen or whether it's safe to navigate away and get notified later. |
Never show a bare spinner with no context for anything over ~3 seconds.
3. No dead ends
Every screen state must leave the user at least one viable next action. Do not ship a state where all controls are disabled with no path forward (e.g., an empty error state with no retry/back/help action).
4. Restraint / one primary action
- One primary button per screen or section. Secondary actions are visually subordinate.
- Primary action placement should be consistent across equivalent screens.
- Flag scope/edge-cases that add complexity for marginal value — treat unnecessary technical debt as a craft defect, not just an engineering cost.
5. Localization & internationalization
- All button/label strings must be pulled from localization files, never hardcoded, and tested against expansion (e.g., German strings run ~30% longer than English — layout must not break or truncate).
- Full RTL (right-to-left) support for RTL locales: this means mirroring the entire layout and reading flow, not just flipping text alignment. Cursor movement, date entry, and number entry must follow the locale. Cultural context should meet that population's needs, not just translated strings.
- Don't assume high-end devices or fast networks. Build with graceful degradation for older devices and low-bandwidth/high-latency connections (lazy loading, compressed assets, offline-tolerant states).
- Don't assume motion reads the same way in every culture. A side-to-side "shake" for an error (e.g., incorrect password) echoes "no" in Western head-shake convention, but can echo "yes" in cultures like India. Check motion patterns for cultural assumptions, not just text and layout.
- App names must be tested against truncation, in store listings and at increased/accessibility text sizes.
6. Accessibility
Accessibility items are consolidated here rather than scattered across sections — check all of these as one pass:
- Support OS-level text scaling (e.g., iOS Dynamic Type / Android font scale). At larger text sizes, layout must reflow cleanly — do not rely on hyphenation or truncation as a fix. This applies to buttons too: test buttons specifically for label truncation at larger text sizes.
- Maintain sufficient contrast in every state (default, hover, disabled, focus) — not just the default state.
- Run anything that communicates meaning through color (icons, glyphs, status indicators) through a color-blindness simulation — don't rely on typical color vision alone.
- Full parity between dark mode and light mode — every component must be verified in both, neither is the "primary" version.
- Tabbed navigation is fully keyboard-operable: tabs are reachable and switchable via keyboard alone (arrow keys to move between tabs, Tab/Shift+Tab to move in and out of the tab list), with a visible focus indicator on the active tab at every step.
- Tabbed navigation uses correct ARIA roles and states (
tablist,tab,tabpanel,aria-selected,aria-controls) so the tab structure and current selection are announced correctly to screen readers. - Only the active tab panel's content is in the tab order / exposed to assistive tech — hidden panels are properly hidden, not just visually collapsed.
- Recommended tooling: axe-core (and tools built on it) covers WCAG 2.0–2.2 at A/AA/AAA — a solid default for automated contrast and accessibility checks, including for agent-run audits.
7. Typography
- Limit the type system to a small, defined set of text styles. Don't introduce new one-off styles per screen.
- Check for orphaned words at line/paragraph ends.
8. Iconography
App icons
- Universal: recognizable across cultures without relying on text.
- Differentiated from competitors, while still nodding to the app's actual functionality.
- Color-blindness tested (see Accessibility, above).
- Stands alone with zero supporting context or label.
- Consistent padding around the icon's main content — no crowded edges, no inconsistent bleed.
- Not too thin — content/line weight must scan clearly at true icon size, not just at large preview size.
- Verified against both light and dark home-screen backgrounds.
- Verified readable at low display brightness, not just ideal lighting.
In-product glyphs
- Legible and fast to scan — a glyph should let someone find an action faster than reading text would.
- Paired with a label by default, but degrades gracefully to standing alone when there's no room for a label.
- Reuses consistent visual primitives across the set (e.g., one arrow token used for share, download, and "opens externally," not five different arrows).
- Consistent stroke weight across the entire glyph set.
- Legible at both large and small screen sizes.
- Leans on system/OS-level icon conventions rather than reinventing them, and responds to OS-level changes where relevant.
- Stays two-dimensional/flat — dimensionality is reserved for app icons.
- The glyph itself insinuates the specific action it performs (a trash can reads as delete), not just an abstract or decorative shape.
- Consistent padding between a glyph and its paired label inside a button.
9. Motion
- Animation must be purposeful: confirm an action, show where navigation went, or reward task completion.
- Never add animation purely for decoration with no communicative function.
- Empty, loading, and error states deserve the same visual/motion care as the primary happy-path flow — they are not lower priority.
- Motion patterns are checked for cultural assumptions (see Localization, above).
10. Copy & error messages
- Error messages must state specifically what happened and, where possible, what to do next. Never ship a generic "Something went wrong" when the underlying error is knowable.
- Predictive button labels: the label should tell the user the outcome before they click (e.g., "Delete Draft" not "OK").
- Actions that leave the product (e.g., open an external link) say so plainly ("Opens in a new window"), typically paired with the outbound-arrow glyph.
11. Consistency ("multiple doors to the same home")
- Core actions/tools should be reachable the same way from every relevant surface in the product — don't create a one-off access path.
- If a UI pattern already exists elsewhere in the product for the same purpose, reuse it rather than inventing a new one.
- When changing an existing UI, minimize the relearning burden: consider in-product explanation, gradual rollout, or preserving old paths temporarily rather than a silent breaking change.
12. Performance
- Only optimize performance for validated user needs — check that a performance investment is backed by evidence (research, data, a proven complaint) before prioritizing it.
- Performance and craft work should be a joint design/engineering effort from the start of a feature, not a design handoff followed by an engineering pass.
How to run /craft-check — the audit workflow
Don't silently apply fixes. Run this as an interactive audit:
- Scan. Walk sections 1–12 against the target file(s), component, or PR diff.
- Report, don't fix. Produce a rundown of every violation found. Each item should name the section it violates and the specific location (file/line/component) — cite section numbers so they're traceable back to this document.
- Prioritize the list by:
- Frequency — violation types that recur many times across the codebase surface first.
- Visibility — issues on high-traffic, highly visible surfaces outrank issues buried in rarely-seen states.
- Ask before acting. After presenting the prioritized list, ask whether to fix all, some (let the user pick), or none of the violations. Never auto-apply fixes without this checkpoint.
- Track progress over time. Keep a lightweight running log of past
/craft-checkruns for this codebase (e.g., a.craft-check-logfile or equivalent — violation counts by section, per run, with a timestamp). On each new run, compare against the most recent prior entry and call out the trend. - Report with personality. Keep the tone conversational and encouraging rather than clinical — celebrate real improvement, and be light and forgiving about a slip-up or two rather than scolding. This is a discipline, not a finish line: the goal is to make people want to run it again.
This skill is meant to be run as an interactive checklist, not summarized narratively — walk the sections, cite them, and don't skip the prioritization and confirmation steps.