# Put the yes where the thumb is

> A job screen that opens with cancel, decline and reschedule teaches the worker that those are the expected answers. Pin the one positive action for the current state to the bottom of the viewport where a thumb rests, demote the negative paths to quiet outline controls below the content, and verify it on the deployed page — a fixed bar can be perfectly present in the DOM and invisible underneath the app's own navigation.

URL: https://biloh.com.au/docs/engineering-notes/put-the-yes-where-the-thumb-is
Category: Engineering notes | Audience: builder | Updated: 2026-08-07

Open a job in a field worker's app and count what you see before you scroll. If the first controls are *cancel*, *decline* and *change date*, the screen is telling the worker those are the expected answers — and the thing they actually came to do is somewhere below the fold.

That was the state of a contractor job screen: three ways out at the top, the site details and scope in the middle, and **Start Job** far enough down that a worker on a large phone had to scroll for it. Every individual control was fine. The hierarchy was backwards.

## Prominence is a recommendation

Visual weight is not decoration; it is instruction. The largest, highest, most saturated control on a screen is read as *what you should do here*. So a job screen should present exactly **one positive action for the current state**:

- work order sent, not yet accepted → **Review & Accept**
- accepted, not started → **Start Job**
- started → **Mark Complete**

One state, one obvious yes. The negative and secondary paths — can't complete, change the date, decline the job — stay fully available, but as quiet outline controls in their own section **below** the job content, under a heading that frames them honestly ("Need something else?"). Nothing is hidden; the defaults are simply no longer arguing with each other.

## The thumb zone is not a metaphor

On a phone held one-handed — which is how a job screen is read, because the other hand is holding a ladder, a hose, or a set of keys — the reachable area is an arc from the bottom corner. The top of a modern phone screen is the *least* reachable real estate on the device.

So the primary action is **pinned to the bottom of the viewport**, not placed at the end of the document:

- fixed to the bottom, spanning the content width;
- a larger target than any other control on the page (comfortably past the 44px minimum — this is the one you want hit while wearing a glove);
- padded for the device's safe-area inset, so a gesture-navigation home bar never sits on top of it;
- with the content wrapper reserving clearance beneath, so the last section is never covered by the bar.

A useful extra: when the primary action is disabled by a requirement, the bar itself says why — *"1 more photo needed"* — rather than presenting a dead button and leaving the worker to hunt for the cause.

## The bug only the deployed page can show you

Here is the part worth the whole note. That bar shipped with correct markup, passing structural tests, and a green deploy — and was **completely invisible on the live page**.

The contractor portal's layout owns its own fixed bottom navigation. The new action bar was pinned to `bottom: 0` at a lower stacking order, so the navigation sat directly on top of it. The button was in the DOM. An accessibility-tree read found it. A screenshot showed nothing but the nav.

Source tests could not catch this: the component's markup was exactly what the test asserted. The defect lived in the *relationship* between two components that never appear in the same file — the page and the layout wrapping it. The fix was to pin the bar above the navigation's height plus the safe-area inset instead of to zero.

> A rendered-DOM assertion is not a visibility assertion. Two components can each be correct and still occupy the same pixels.

This is the same family as [the gap between a green build and a live deploy](/docs/engineering-notes/green-build-vs-live-deploy): a green check on one side of a boundary says nothing about the other side. The cheap defense is to open the real page after deploying and look at it — and, once found, to lock the fix with an assertion that names the offset rather than merely asserting the element exists.

## Rules worth keeping

1. One positive action per state, pinned in the thumb zone, larger than everything else.
2. Destructive and secondary paths go below the content, quiet but never hidden.
3. A disabled primary action must say what would enable it.
4. Respect the safe-area inset, and reserve clearance so the bar covers nothing.
5. Check the deployed page with your eyes. Presence in the DOM is not visibility on the glass.

## Related

- [Drag is a desktop idiom; the phone wants a tap](/docs/engineering-notes/drag-is-a-desktop-idiom)
- [The gap between a green build and a live deploy](/docs/engineering-notes/green-build-vs-live-deploy)
- [Who decides whether a job needs photos?](/docs/concepts/completion-requirements-cascade)
