Driver app: the feed from the road
The Android app that closes the loop between the cab and the dispatch desk. Designed in 2022–23 through dispatchers as a proxy for drivers, and rebuilt on a token and component foundation in 2026 after I audited the file.
Mobile
Android
Offline-first
Design system
Role
Product Designer — sole designer on this surface until 2026
Primary user
Long-haul drivers, reached through dispatchers
Timeline
Designed 2022–23, rebuilt 2026
Status
Live
Methods
Shadowing Users · Design Audit · Design System · Accessibility · Interaction Design · UX Writing

One stop in focus, its own counter, two status buttons, the next card peeking below the fold.
Overview
This is the only surface in the ecosystem where the user’s hands are busy, the signal drops, and every screen I draw competes with an SMS that already works.
Every other screen in this TMS is read at a desk. This one is read at a plant gate in Germany with roaming off, one-handed, by someone who has been driving since six in the morning. It carries the events the whole system runs on: arrived at loading, documents scanned, delay reported, stop done. Without that feed the dispatch board is a guess and the back office has nothing to reconcile against.
The case study has two halves. In 2022–23 I designed the app without direct access to drivers, through the dispatchers who spoke with them all day. In 2026 I came back to the same Figma file, audited it, and rebuilt its foundation: a token layer, a real component system, and status colours a driver can read in sunlight.
Since June 2026 there are two designers on this product. The newest driver features, surcharges and the vehicle-inspection link, are specced by the second designer. The audit, the colour foundation, the component system and the attachment work below are mine.
Problem
The desk’s picture of the road was assembled from phone calls, and the coordinator was the routing layer.
Before the app, a route reached a driver as an SMS. Status came back by phone. Documents came back as photos sent to the carrier, or straight to the office at the end of the day — a complete set is usually three documents and anywhere from 3 to 16 pages, and by the client’s own account, drivers never sent all of them at once. Coordinators absorbed the gap: they were human message routers who knew which driver was on WhatsApp and which was on Viber.
The cost landed on the two screens I had already worked on. The dispatch board is only as accurate as its last phone call, and the back office can’t reconcile a load whose paperwork surfaces a week later. Fixing that meant putting a data-entry surface into the cab, for a user with no reason to like it. Coordinators were still working around the app years after launch: one of them, during shadowing, described sending the SMS first and updating the TMS afterwards.
Every screen upstream depends on four taps from a cab.
Constraints
Five constraints shaped this app. The first one shaped the rest: I could not talk to the users directly.
No access to drivers. The fleet was on the road, and the client couldn’t arrange ride-alongs. What I had was daily informal access to the dispatchers, coordinators and team leaders who spoke with drivers dozens of times a day. They became a proxy lens — they couldn’t tell me what drivers thought, but they could tell me what drivers complained about and where they called back.
The context of use is hostile. Glare, gloves, one hand, a phone mount at eye level, dead zones abroad with roaming switched off, and a battery that has to last a working day. Drivers are typically in their forties and fifties, with mixed appetite for software, and some crews speak Ukrainian rather than Polish.
The device isn’t personal. From the earliest scoping notes: the same phone can serve several drivers, and the same phone moves between trucks. That killed conventional accounts and set login as phone number plus SMS code.
Adoption was never given. The app competes with a channel that already works. A team leader asked for a marker in the fleet view showing which trucks even have the app installed, which tells you plainly how partial the rollout felt from the desk.
The driver isn’t the client’s employee. Many drive for carriers. Surcharges are the carrier’s costs, not the driver’s pay, and commercial terms between the client and the carrier are none of the driver’s business. Information boundaries are a requirement here, not a preference.
Every claim I had about drivers arrived second-hand. That shows up later, in the outcome.
Key decisions
Two decisions from the original design, two from the 2026 rebuild. The rebuild is where the senior work is.
Decision 01 · 2023
One stop in focus — and status buttons only on the stop the driver is standing at
Driving splits attention by default, so the brief I set myself was zero precision demands, minimum taps, minimum reading. The route arrives already decided: the desk composes it, the driver executes it. From the first scoping notes, drivers get trimmed information and no time ranges — “unloading tomorrow, first at 7:00, the second as soon as possible after the first” instead of a window they would have to interpret.
What shipped: the current stop is a card in focus, carrying its own progress counter (“Unloading 2 of 4”) and two large buttons — report a delay, or mark done. A delay adds a reason, chosen from a few tiles with a text fallback, because “warehouse strike” is a thing that happens and a dropdown of weather conditions doesn’t cover it. Past and future stops sit above and below on a vertical timeline. Future stops render without an action block at all.
That last rule is the one I would defend hardest, and my evidence for it arrived from the wrong direction. Years later, in shadowing notes, a coordinator relays a driver saying he had accidentally tapped “done” on the next stop. A future stop that carries no action cannot be completed by mistake — error prevention built into structure rather than into a confirmation dialog. The same note tells me the rule wasn’t applied consistently everywhere it should have been.
Why
One action per state, and no state the driver can reach by accident. The route belongs to the desk; the app is a task list and should read like one.
Trade-off
The client pushed for full-route visibility, worried drivers would miss upcoming pickups if they could not see the whole sequence. I argued the other way: a driver glancing down needs no map of the next six stops. It landed as three small affordances: the per-stop counter, a continuous vertical progress line showing the route's length, and a deliberate peek of the next card at the screen edge. The cost: a driver who wants to plan his own day still has to scroll. Planning belongs to the desk, and the design makes that visible.
Decision 02 · 2023 → 2026
Offline is a state the interface shows, not a failure it hides
The first instinct in the 2022 notes was the wrong one, and it is in my handwriting: force the driver to be online. SMS was listed next to it as the safest fallback. Neither survived contact with the actual context — a plant gate abroad with the data connection off is not an edge case, it is Tuesday. Status changes became local-first: the tap lands immediately, queues, and syncs on reconnect.
The 2026 attachment work is where that principle got tested properly. Dispatchers wanted to attach a yard map or an entry instruction to a specific stop, because a driver standing at the wrong gate calls the desk. Two models were on the table: download on demand with the state shown on the chip, or silent auto-caching. I picked the visible one — if everything is always downloaded, the status has nothing to say, and the driver loses the ability to check “do I have this one?” before leaving signal.
Then I read the Android repository before finalising the spec, which changed what the handoff had to say. Files today come from short-lived signed URLs, metadata lives in memory and dies on restart, and only the route itself is cached. So the handoff carries hard requirements rather than a hopeful note: prefetch the bytes on route sync rather than the URL, because the signed URL expires; write them to permanent storage rather than the cache directory; render the preview from the local file. And one screen exists purely to be honest — a file that didn’t make it down, with no signal, shows “can’t open offline” and a retry, instead of a spinner pretending.
Why
The moment of use is the moment of no signal. Also WCAG 1.4.1: no file state relies on colour alone — progress is a bar, offline is a check, failure is an error icon, unavailable is an info icon.
Trade-off
Manual download puts a step on the driver in exchange for a state he can read. Worse, the same download icon means “not fetched yet” in one model and “not ready yet” in the other — a semantic collision I documented rather than solved. And prefetching bytes spends storage and mobile data on a fleet phone; I have no policy yet for a full disk, or for a dispatcher swapping the file after the route was sent.
Decision 03 · 2026
I audited the file I had built and rebuilt the card instead of redrawing screens
In June 2026 I ran a full audit of the driver Figma file: 27 pages, around 14,500 nodes sampled. The screens held up. The plumbing did not. The findings that mattered:
Status text failed contrast on the colours that carry safety meaning. Warning text measured 2.25:1 and success text around 3.01:1 against white, and worse on their own tinted lozenges. A driver reads those in direct sunlight.
No token layer. One variables collection, three stray variables, and 33 paint styles doing the real work. The palette documentation listed wrong hexes: three of its headline values were hover variants, so anyone generating code from the doc would generate the wrong thing.
Touch targets under Android's 48 dp minimum: buttons around 33 dp, icon buttons at 24.
The core card was not a component. The loading-point card had been rebuilt by hand on nearly every screen, in three incompatible architectures and three widths.
The dark-mode request arrived at the same time and I used it as the lever. Dark mode is mostly not a colour job: you cannot theme what is not tokenised. I put both options in front of the client with numbers, a three-to-five day reskin that doubles every future screen, or an eight-to-fourteen day token foundation that leaves the app with the design system it never had, and recommended the second.
Then the card. Its variant model had nine properties, eight of them booleans: 768 theoretical combinations, 43 hand-built and scattered across the canvas. The cause was booleans used as variant axes where they should have been properties toggling visibility inside one variant. The rebuilt model keeps two axes that change structure, position (past, current, future) and expanded state. Six base variants, everything else a property, and every property renamed to say what it does.
Why
A contrast failure on a driver's status chip is a safety issue, not a polish issue. And the four things that make a file readable by a developer, semantic names, variables, real components, auto layout, are the same four that make it maintainable by me.
Trade-off
This was weeks of work that shipped no new user-visible feature, argued for against a live feature roadmap. I also chose not to make the model maximally convenient: the full delayed treatment still needs two controls, a variant on the card body and a state on the footer, because collapsing them would have meant another variant axis. The component matches the structure the work actually has, and it costs clicks every time it is used — mine.
Decision 04 · 2026
A dispute prevented by choosing one word over another
Surcharges — night stops, tunnels, motorway tolls, ADR, manual loading — used to be agreed in chat and then vanish into it. Nobody could later confirm whether a given one had been settled. Putting them in the driver’s app looks like a list-view task and isn’t, because of an asymmetry in the plumbing: the dispatcher approves a surcharge and the driver sees it immediately, but the carrier only sees it once it is bundled into a settlement basket and sent. Ship that without a signal and you manufacture the exact argument you were trying to end — the driver insisting it is approved, the carrier honestly unable to see it.
Two options were rejected before the interface question came up. Hiding surcharges until the basket goes out removes the whole point of the feature. Showing them with no state creates the dispute. So: show immediately, with the asymmetry stated. The driver gets two states rather than the four-step ladder the office sees — “to be passed on” in amber, “passed on to the carrier” in green — plus a permanent banner in the detail view, no hover, explaining that the dispatcher has approved the amount, that the carrier will see it with the next settlement basket, and that there is no need to raise it in chat again.
The wording took longer than the layout. I rejected “Pending” because it reads as “not approved yet”, which sends the driver straight to chat — the behaviour we were trying to remove. I rejected “Awaiting upload” because it collides with the state of a file being uploaded from the same screen. Category visibility followed the same logic: the operational surcharges are shown, the commercially sensitive ones — weekly extra kilometres — are hidden, and hidden completely rather than greyed out, because an empty slot is an invitation to ask. The driver can attach a toll receipt; that is the only write action in the view.
Why
This status carries more than a workflow step. It manages what two parties with a commercial relationship each believe. Contrast was measured rather than eyeballed: 6.46:1 on the amber chip, 10.81:1 on the green, and every chip carries text as well as colour, because drivers read these in sunlight and gloves.
Trade-off
Two states is a deliberate simplification of a four-state process, so a driver who wants to know exactly where his surcharge sits still has to ask someone. The second designer owns this feature's spec. My part was the flow-and-scope audit before handoff, the status wording, the loading, offline and error states, and the accessibility pass.
Outcome
The app closed the loop between the cab and the desk, and the 2026 rebuild gave it the foundation it launched without.
70%
fewer “where are you?” calls from drivers after launch, client's figure
43 → 6
hand-built card variants, after the refactor
2.25:1 → 6.46:1
status text contrast, audited then rebuilt
~500
trucks whose road events flow through it
“We used to get pinged constantly, now they figure it out.”
— forwarders, on the drop in driver calls after launch
The client's figure for the launch: calls from drivers asking the desk about their route fell by around 70%. Forwarders said the same thing in their own words: “we used to get pinged constantly, now they figure it out.” The product's own success metrics from the PRD, document-capture completeness above 95% of loads and drivers reporting the app “doesn't get in the way”, were never measured.
Three years in, shadowing notes still carried complaints: a driver who found the app hard to read, a coordinator asking for the simplest possible view with everything in one place, another who still sent the SMS first and updated the TMS afterwards. The 2026 audit explained part of that. Status colours had shipped at 2.25:1 contrast, a problem no dispatcher would report because no dispatcher reads them in sunlight.
What the 2026 work produced: a semantic token layer with light and dark modes replacing hardcoded fills, a six-variant card component with its atoms extracted and its spacing bound to the scale, the loading, offline and error states the app had launched without, and a prioritised backlog for the rest.
Signals and counter-signals from the launch, side by side.
Reflection
The thing I would change is not a screen. It is how long I designed for a user I had only heard about.
A proxy channel filters information in one direction. Dispatchers reported what annoyed dispatchers: drivers who had not updated a status, drivers who called too often. When driver observations finally reached me second-hand in 2026, they were about legibility and accidental taps, the class of problem a dispatcher has no reason to notice. Four years of proxy access never surfaced the contrast problem.
What I would do differently: insist on one ride-along before v1, and when that was refused, run a remote diary study with three drivers instead. Voice notes from a cab for two weeks. A few days of effort against three years of guessing. I would also build the token foundation while the app was still ten screens; the audit and migration cost weeks that a week in 2023 would have prevented.
One item stays open. The driver persona in our research files is a synthesis from proxy sources with an illustrative quote attached. The task on it, attach a real driver's words, is still unticked.






