# Atomic UX Principles — Tactical Supplement

Sharp, operational rules that **supplement** the foundational references — they do not replace them. Where this file overlaps with another reference, that other reference is the authority on the principle; this file ships the specific tactical pattern.

Pair with:
- [`heuristics.md`](heuristics.md) — Nielsen H-01..H-10, Fitts, Hick, Miller, Jakob, Peak-End, Doherty, Cognitive Load.
- [`accessibility.md`](accessibility.md) — WCAG ratios, 44pt / 48dp targets, focus rings, color-not-only.
- [`form-patterns.md`](form-patterns.md) — labels, validation timing, error placement, autocomplete, password UX.
- [`anti-patterns.md`](anti-patterns.md) — bans and AI-slop tells.

Use during Discover (audit), Plan (decisions), Verify (scoring).

---

## Mobile & touch

### Primary CTA in the thumb zone
Phone held one-handed → only the bottom third is thumb-reachable without grip change. Place Send / Pay / Continue at the bottom edge or in a sticky bottom bar. Never put the single primary action in the top-right of a long scrolling form.

### Long-press exposes row-level context
On message / file / transaction rows, long-press opens per-item actions (copy, share, delete). Always pair with an explicit overflow (⋯) button so the gesture is a shortcut, not the only path.

### Pull-to-refresh in any updatable feed
Scrollable lists with externally-updating content (transactions, messages, notifications) must respond to a downward pull at the top with a refresh + visible spinner. Skip on static screens (settings, profile, articles).

### Swipe gestures expose row-level shortcut actions
Swipe-left / swipe-right reveals archive, delete, mark-unread on list rows. Pair every swipe action with an alternative discovery path (long-press menu or row overflow) so the gesture is acceleration, not the only access.

### Destructive separated from primary on at least two axes
"Delete" and "Save" sitting next to each other at equal weight = inevitable mis-taps. Differ on at least **two of three**: position (farther from thumb), size (smaller), colour (red / de-emphasised). Never adjacent at equal visual weight.

---

## Navigation & information architecture

### Main screen reflects the top use case
80% of users open the app for the same one or two things. Banking app = balance + transactions. Chat app = conversation list. Maps = map with search ready. Hero space goes to the most frequent user action, not brand marketing.

### Key actions within 2 taps from home
Top-3 user actions must be reachable in ≤ 2 taps from the main screen. If analytics show "Transfer" is #1, it cannot be 4 menus deep.

### Tab bar items are destinations, not actions
Tabs are persistent places (Home / Search / Profile). The compose / add action belongs as a FAB or separate icon. A tab labelled "+" that opens a sheet misuses positional convention.

### Breadcrumbs on pages 3+ levels deep
Pages nested ≥ 3 levels below the root display a tappable breadcrumb. Each segment jumps to that level; current is non-tappable. Mobile can substitute reliable system back + clear screen title.

---

## Loading & feedback

### Loading indicator thresholds (sharper than Doherty 400ms)
- 0–300ms: no indicator needed.
- 300ms–3s: inline spinner or indeterminate progress at the trigger.
- > 3s: determinate progress with ETA / percent.
- > 10s: also surface what is happening ("Uploading 3 of 5 photos…").

### Skeleton screens for structured content loads
For feeds / lists / profile pages that load > 1s, render a skeleton matching the eventual layout (grey blocks where text will appear, circles for avatars). Skeleton outperforms a centered spinner because perceived wait is shorter.

### Retry button on load failures
Every load that can fail surfaces a tappable **Retry** plus a one-line cause. Never leave a blank page or infinite skeleton. Auth / permission errors get the relevant fix path (sign-in, settings) instead of retry alone.

### Show offline state explicitly
When the device loses connectivity, surface a persistent banner ("Offline. Showing cached data.") or status dot. Queue user actions and mark them visibly as pending ("Will send when online"). A silent "Sent" while offline is a trust bug.

### Optimistic UI for non-critical state changes
For likes / archives / toggles, update the UI immediately and send the request in the background. On failure, revert the UI and toast "Couldn't save. Retry?". Never use optimistic UI for financial / external-effect actions — those wait for confirmation.

### Persist success toasts ≥ 4 seconds
Snackbars and success toasts persist a minimum of 4 seconds. Pause the dismiss timer on hover / focus. Provide a manual close (X) for screen-reader users who need the full duration. A 1-second flash equals no feedback.

### Haptic / shake on failed authentication
Wrong PIN / passcode triggers a brief field shake (~200ms, two oscillations) + short error haptic. Clear and refocus the field. Visible message stays short and constructive — never a blocking modal.

---

## Choice architecture

### Highlight one recommended option
On any choice with 3+ comparable options (pricing plans, shipping speeds, delivery slots), badge one as **"Recommended"** / **"Most popular"** / **"Best value"**. Preselect it when defaults are acceptable. A 5-tier price page with no recommendation depresses conversion.

### Progressive disclosure of advanced options
Surface the 3–5 most common options inline. Tuck the rest behind "More options" / "Advanced". The user who needs the extras will look; the user who doesn't won't see them. Skip for power-user dashboards where breadth is the value.

### Defer non-essential onboarding to just-in-time
Onboarding asks the minimum to deliver first value. Profile photo, notification permissions, optional integrations → defer to JIT prompts when the feature is actually used. The exception is regulated identity verification.

---

## Form patterns

### Minimise required fields
Run every field through: "What breaks if we remove this?". If the answer is "nothing critical right now", remove or defer it. Required = needed *for this exact transaction*.

### Input masks for fixed-format fields
For phone, card number, postcode, IBAN, date, expiry, OTP — apply a live mask as the user types. Spaces appear automatically; characters outside the spec are silently rejected. Faster than catching format errors on submit.

### Disable submit until required fields are valid
Keep the submit button visibly disabled until all required fields pass presence + format. On hover, an inline hint may explain what's missing. Beats "Email required" appearing after the tap.

### Section headers for forms longer than 5 fields
Wall of 12 inputs feels endless; 3 sections of 4 with headers ("Personal", "Address", "Payment") feels short. Visual separation by gap, divider, or background tint.

### Sticky CTA on long forms
Any mobile form that exceeds one viewport pins the primary CTA to the bottom edge above the keyboard. State updates as fields become valid. Never make the user scroll past everything to find Submit.

### Autosave drafts on accidental exit
Forms longer than ~30 seconds of work persist their state when the user backgrounds the app or hits Back. Restore on return. Prompt "Continue draft?" if the entry point is reopened. Exception: password / payment-card fields legitimately should not cache.

### Placeholder shows a real example, not the label
Placeholders demonstrate format (`name@example.com`, `+1 555 123 4567`, `MM / YY`), never restate the label. Keep a real `<label>` above the field — placeholder-as-label fails on focus and on accessibility.

### Show field constraints up front, not as post-submit errors
Display constraints as persistent helper text under the input. Password rules ("8+ chars, 1 number") visible from the start; each turns green as it's met. Same for phone-format example, IBAN length, username pattern. Hiding rules until failure trains users to fail first.

---

## Numbers & identifiers

### Chunk numeric strings
Format and display numeric strings in their conventional chunks: card in groups of 4 (`4242 4242 4242 4242`), phone by locale (`+1 (555) 123-4567`), IBAN in 4s, OTP 6+ split 3+3. Apply chunking on both input mask AND display.

### Copy button next to long identifiers
IBAN, transaction ID, wallet address — anything the user is likely to copy gets a visible copy button + a brief "Copied" toast. Long-press-to-select is failure-prone, especially on tap-zone-crowded mobile.

---

## Power-user shortcuts

### Biometric auth for frequent sensitive actions
Face ID / Touch ID / fingerprint is the primary path for app unlock, payment confirmation, sign-in resumption. PIN / password is fallback only. Typing a PIN 20 times a day pushes users to disable security entirely.

### Allow saving templates for repeated actions
Multi-field actions that repeat (payments, expense reports, recurring orders) get a one-tap "Save as template" at the success step. Next invocation is two taps from the template list.

---

## Data tables

### Right-align numbers, left-align text
Numeric columns right-aligned; text left-aligned; currency aligned by decimal point. Use tabular numerals (`font-variant-numeric: tabular-nums`) so digits stack across rows.

### One inline row action; the rest in overflow
At most one visible action per row (the most common — Open / Edit). Move the rest behind a `⋯` overflow menu. The data is the point; actions are accessories. Exception: triage-heavy lists (inbox) where actions live inline.

### Zebra stripes on wide / dense tables
Wide tables (5+ columns or full-width on desktop) get a subtle alternating row background (`rgba(0,0,0,0.02)` on white). Helps the eye stay on a single row across long horizontal scans. Skip on narrow tables and on tables with status-coloured rows.

### Sticky column headers on long tables
Tables longer than one viewport stick the header row to the top. For wide tables, lock the identifying first column (name, ID) horizontally. Without it, by row 200 the user has no idea which column is "amount".

---

## Modal & overlay

### Modals close via multiple paths
Every modal closes via ≥ 2 of: visible close button (X) in a predictable corner, tap-outside on the backdrop, swipe-down (bottom sheets), Escape (desktop). Hard blockers requiring an explicit choice are the only exception.

### Don't stack modals on modals
A modal opening another modal obscures the original context entirely. Replace the modal contents (wizard step inside the same modal) instead of opening a sub-modal. Exception: genuinely interruptive system prompts (low battery).

---

## Cards & layout

### Visible card boundaries
A "card" without a shadow, border, background tint, or clear padding-plus-divider stops reading as a card. Pick one boundary signal and apply uniformly to every card in the product.

### Uniform form field sizing
All inputs within one form share the same height (44–56pt mobile, 36–40px desktop). Width can vary by content type. Buttons in the same form follow the same height to align cleanly. Textarea legitimately differs.

---

## Typography

### Limit fonts to one or two families
One typeface for UI (body, labels, headings). Optionally one display face for hero, one mono for code/numerics. Cap there. Mixing 4+ families feels patchwork and costs render decisions on every screen.

---

## Smart defaults

### Prefill data the system already knows
Every input audited for: "Does the system already have this?" — name, email, country, postcode, saved address, KYC fields. If yes, prefill. Never re-ask. Exception: per-transaction values (delivery slot, custom note, gift recipient).

### Smart default for complex settings
Multi-option configuration ships with a preselected recommended option, not a blank composition task. Power users override. Exception: opt-in consent for data sharing, which must remain unchecked by default.

### Auto-detect country, currency, locale from the device
The OS knows where the user is, their language, currency, date format. Default everything from locale. Offer a switcher for the override case. Never open onboarding with a "Select your country" picker.

---

## Recognition

### Confirmation screens echo the load-bearing parameters
Confirmation screens show amount, recipient name, destination address, item count — whatever the user is actually verifying. "Confirm transfer?" with no details forces the user to remember from the previous screen.

---

## Promotion & commerce

### Show full cost before payment details
Total amount with all fees + taxes + shipping appears before payment-card entry. If tax depends on address, show running total live as address is typed. Surprise fees on the final screen are the #1 cart-abandonment trigger.

### Offer guest checkout
Forcing account creation at checkout costs ~25% of conversions. Expose a guest path that requires only the data needed for this transaction. Offer account creation on the success screen, not as a checkout prerequisite.

### Avoid carousels for important content
Slide-2-and-beyond click-through is ~1%. If the message must reach every user, render it as a static block or 3-up grid. Carousels are for browse-equal-priority galleries (product imagery), not for hero promo content.

### Critical path stays free of promotional interstitials
No promo modal, email-capture popup, or cross-sell sheet during checkout / KYC / payment / sign-up. Promo content lives on the home screen, success screen, or a dedicated discovery tab. Legal consent overlays are the only exception.

---

## Permissions

### Prime before the system permission prompt
Notifications / camera / location / contacts / mic — show an in-app priming screen first explaining why + what value the user gets + a Continue button that triggers the OS prompt. Decline path on the prime simply defers; the OS prompt fires later when the user opts in.

---

## Fintech-specific

### Account balance on the home screen
Banking app's home screen first viewport = the balance, prominently sized. Optional eye toggle for privacy. Hiding the most-asked-for data behind a tap is hostile.

### Save payment recipients automatically
After the first successful transfer to a new recipient, save them automatically with masked account info + a default label. Next transfer = one tap from the recipient list. Anonymous / charitable transfers excepted.

### Detect and show card vendor on input
Card scheme detected from the first 4–6 digits (BIN). Display the Visa / Mastercard / Amex / etc. logo inside or beside the card field. Confirms the user grabbed the right card from their wallet.

---

## Time & localisation

### Render timestamps in the user's local clock
Recent: relative ("2 min ago", "Today, 14:32"). This week: weekday + time. Older: absolute date in locale format. If timezone is load-bearing (meetings, deadlines), show both local and source TZ. Never expose raw UTC to consumer surfaces.

---

## Native controls

### Use the OS's native pickers
Native date, time, contact, file pickers carry years of platform polish. A custom version usually misses keyboard ergonomics, locale rules (week-start day), dark mode, accessibility. Override only when the native control genuinely cannot do the job.

---

## Async behaviour

### Don't block typing while async validation runs
Username uniqueness checks, address autocomplete, server-side lookups run in the background without locking the input. Subtle inline "Checking…" + result on settle. Debounce ~300ms; cancel stale requests on new keystroke.

---

## Security / privacy

### Block screen capture on sensitive screens
Screens showing OTPs, full card / account numbers, or transfer confirmations apply platform screen-capture flags (`FLAG_SECURE` Android, `UIScreen.captured` checks iOS). Obscure when screen recording is active. Receipts the user is expected to save are excepted.

---

## Guidance & help

### Teach onboarding by doing, not by slides
Replace 5-slide walkthroughs with inline coachmarks that appear only when a feature is first touched. Each coachmark = one element + one sentence + dismiss-on-tap. Product is the best tutor.

### Link errors to specific help articles
Specific error codes map to specific help articles. Error UI surfaces a contextual link: "Why did this happen?" → exact article, not the FAQ root. Trivial errors stay inline-only.

### Warn before unusual or first-time actions
First payment to a recipient, an unusually large amount, an account change from a new device — surface a soft inline warning before the action, not a confirmation dialog. The action stays available; the user just sees the deviation.

---

## Truncation

### Long content scrolls or expands; never truncate to a dead end
Cards / rows that exceed visible space offer an expansion path: inline "Show more", tap to detail, or enough height to fit 90% of cases. Truncating with `…` and no escape is information loss the user can't recover.

---

## Dark mode

### Follow the system colour scheme by default
Apps follow the OS dark/light setting by default. Provide an in-app override (System / Light / Dark). Test every screen in both modes; don't just invert. Pure `#000` dark mode is harsh; use `oklch(12–18% 0 0)` near-black per [`anti-patterns.md`](anti-patterns.md) BAN-05.

---

## Pre-ship cross-check

Run every artefact through:

1. **Discovery anchors:** is the principle this design follows nameable from this file or its companions?
2. **Mobile reach:** would a user holding their phone one-handed reach the primary action?
3. **Recipient-anonymity test:** if the user came back tomorrow, do they have to remember the recipient / destination, or is it surfaced?
4. **Failure path:** does every load and every form have a visible recovery path (retry / undo / next step)?
5. **Prefill audit:** any required field the system could have prefilled but didn't?
6. **Promo discipline:** is the critical task path free of cross-sell / email-capture interstitials?
7. **Native controls:** any custom-built date / time / contact picker that the OS could have served?
