Skip to content
All work

2026· frontend

BrandDrive mobile

The half of an app nobody demos — theming, sheets that survive Android's keyboard, a lock that engages when you background it — plus the money screens where the empty state matters more than the happy path.

Scope

Around a hundred and eleven commits on BrandDrive's Expo app, outside the Nivram module that has its own case study — the same product, a different layer of it. I introduced the theming system, the Android keyboard behaviour for sheets and pickers, the app-lock overlay and biometric paths, the invoicing flow end to end, the personal home screen, and credit notes. I did not originate transfers, the permission gate or the wallet hook: on transfers I reworked the interface and the internal logic over an existing design, and on the other two I wired call sites and fixed failures without designing them.

Screens

  • Dark. Read it against the next tile: the theme is a set of custom properties swapped on a plain container, because React Native could not be trusted to honour the platform's own colour scheme. The Nivram entry point here belongs to its own case study.
  • Light. Getting here in a twelve-person codebase meant retrofitting every screen that already existed, not just theming new ones. The rule that holds it together: the variables live on a plain container, never an animated one — a distinction nothing in the framework warns you about.
  • Invoicing, populated. The two totals at the top are the screen's actual job — an owner opens this to find out what is outstanding, not to read a list.
  • Four steps, and the state has to survive all of them — plus an Android keyboard opening over an item sheet, plus the user abandoning it halfway and coming back. The most ordinary-looking thing I own and the one with the most edges.
  • Where the lock lives. It engages when the app returns to the foreground rather than at startup — a finance app left open on a desk is the moment that matters — and each of the three biometric failure modes gets its own way back to a passcode instead of one catch that strands you outside your own account.
  • Transfers. I reworked both the interface and the internal logic here on top of an existing design — refinement rather than origination, which is a real contribution and worth naming as the one it is.

Problem

A finance app for small businesses is judged on the screens nobody puts in a product tour. Whether a bottom sheet is reachable when Android's keyboard and its navigation bar are both on screen. Whether the app locks when it goes to the background, and what happens when the fingerprint reader refuses. Whether the dashboard is comprehensible to somebody who has not created a wallet yet. None of that appears on a feature list, and all of it is what the app feels like to use every morning.

Constraints

  • React Native does not reliably honour `Appearance.setColorScheme`, so a theme could not simply be told to the platform and left alone.
  • Android's three-button navigation bar, the software keyboard and a bottom sheet all compete for the same edge of the screen, and the sheet library's defaults do not account for the combination.
  • A twelve-person codebase where the design language changed under me, so themes had to land across every existing screen rather than only new ones.

Decisions

Theme by swapping CSS variables on an ordinary View

Because the platform's own colour-scheme switch could not be trusted, the theme is a set of custom properties swapped on a plain container rather than a mode handed to the system. The trap that cost the most time: putting those variables on an animated container silently breaks the light defaults — nothing errors, the app simply renders wrong in one scheme and correctly in the other, which is the hardest class of bug to notice and the easiest to reintroduce.

Sheets that measure the phone rather than assume it

A bottom sheet with a text field in it has to sit above the keyboard, above the navigation bar, and above the navigation bar when it is the three-button kind — which occupies space the gesture-bar layout does not. The library's defaults handle the common case and leave the rest to the app, so modals, dropdowns and date pickers respond to the measured geometry instead of a constant that happens to be right on the reviewer's device.

The lock is a resume behaviour, not a login screen

Locking engages when the app returns to the foreground rather than at startup, which is the moment that actually matters — a finance app left open on a desk. Biometrics have three distinct failure modes that a user experiences identically, so each has its own path back to a passcode rather than a single catch that leaves them stuck outside their own account.

The empty states are the screen

The personal home was built around the cases where there is nothing to show: no wallet created, no transaction PIN set, a balance below zero. A dashboard designed for the populated case and patched afterwards puts its worst experience in front of every new user on their first morning, which is the one impression that cannot be revised.

Invoicing as a wizard that survives being left

Create, add items, send, then record payment against it later — with recurring invoices reusing the same path rather than forking it. The state has to hold across steps, across an Android keyboard opening over an item sheet, and across the user abandoning it halfway. It is the most ordinary-looking feature I own and the one with the most edges.

Trade-offs

Gave up

Letting the platform own the colour scheme

Bought

A theme that actually applies, at the cost of a rule every future contributor has to know — variables belong on a plain container, and nothing warns you when they are not

Gave up

The sheet library's default insets

Bought

Sheets that stay usable on Android hardware the team does not carry — three-button navigation is a setting, not a device generation, and it is invisible until somebody complains

Gave up

Shipping the dashboard on its populated case first

Bought

A first-run experience that reads as designed rather than unfinished. Empty states built last are always built worst.

Also built

Customers, vendors and groups

List, filter, multi-select and export across the bookkeeping side. Standard CRUD, and a lot of it — the volume is real even where the difficulty is not.

Personal income and expense tracking

The personal-finance counterpart to the business ledger: list screens, filters, and the categories behind them.

Bills, utilities and data top-ups

A purchase flow against third-party providers. Iterative rather than architectural — most of the work was in the edges of provider and amount validation.

Business menu and tab navigation

The bookkeeping and finance panes, and a substantial rework of the tab bar. Navigation shell, later carrying permission rules that decide which surfaces a role can even see.

Refer and earn

Referral tracking and the tabs around it.

Profile and account settings

Account screens, re-skinned through the theme migration.

Three languages

English, French and Portuguese copy across the screens I touched. Volume rather than invention, but a screen that breaks in French is broken for everyone using it.

Results

Shipped

App Store · Play Store

in production

Features owned

11

by introduced mechanism and surviving lines, not file count

Languages

3

Ratios are given as raw counts rather than percentages. A percentage implies a precision that twenty attempts do not have.

Stack

  • React Native
  • Expo SDK 55
  • TypeScript
  • Expo Router
  • NativeWind
  • Redux Toolkit
  • TanStack Query
  • MMKV
  • expo-secure-store
  • EAS Build & Update