← Portfolio Case study · 2026
Acts of Faith · Consumer mobile app · iOS and Android

Three themes, one build, one set of tokens.

Acts of Faith is a community app for asking for prayer and helping people nearby. I was the only designer and the only builder. I designed it in Figma, built it in React Native for iOS and Android, kept every color, space, and tappable element in one token file, and shipped it to testers through App Store Connect and TestFlight.

Role
Founding designer and builder (via A.Team). Product, design system, and code
When
Jul – Sep 2026
Platforms
iOS and Android (React Native / Expo), plus web (Next.js)
Shipped
External TestFlight beta, build 1.0.1, with over-the-air updates
01

Three themes the client switches himself

The client wanted to compare directions on his own phone, not in a meeting. So the shipped build carries three themes, and a switch at the bottom of My Profile flips between them. Every screen below is the same code and the same layout. Only the token values change: the primary color, the corner radius, the icon set, and how the two doors are filled.

Home screen, Brutalist theme: red primary, square corners
Brutalist · defaultBrand red, square-ish corners, filled door
Home screen, Apple theme: iOS blue primary
AppleSame structure, red becomes iOS blue, 12-pt corners
Home screen, Classic theme: soft tinted doors, vermillion and green
ClassicVermillion and green, soft tinted doors, pill buttons

Screens captured from the current build in the iOS Simulator, October 2026. Names and portraits are demo personas.

02

What the job asks for, and where it lives

Each line below is a real file or rule in the Acts repo.

Design systems requirementIn Acts
Foundational tokens with semantic names and consistent scalesColor named by meaning (need, help, parish, flame, ink, line), an 8-pt spacing scale (SP.xs to SP.xl), and radius tokens, in expo/lib/theme.ts. The web app carries the same names in CSS under a theme attribute.
Components with variants, states, and behavior across iOS and AndroidButton, link, and tab tokens (BTN, LINK_TEXT, TAB_UNDERLINE) with a written rule for four levels of tappable. Platform differences live in the token, for example Platform.select for the serif. Android checked in three-button navigation.
Library kept in sync with codeFigma is the source of truth (DESIGN.md §0: when a screen differs from Figma, match Figma), mapped to tokens per theme and never to raw hex.
Guidelines designers, engineers, and AI agents can followCLAUDE.md and DESIGN.md hold the rules the coding agent reads before it touches UI. A precommit guard (scripts/guard.mjs) blocks new raw hex and arbitrary type on the web.
Auditing the system in useA grid audit of the native code found 176 off-grid values across 18 files and fixed them in one pass.
AI tools for design and prototypingBuilt in Claude Code against the token file, verified screen by screen in the simulator.
03

The token file

One TypeScript file holds the native system. Theme differences are expressed inside the tokens, so no screen ever asks which theme it is in.

expo/lib/theme.ts · excerpt

/** THE design system, native dialect: the same tokens the web carries. */
export const T = {
  ink:    "#26211a",
  line:   BRUT ? "#e2e1de" : "#e9e0d0",
  need:   APPLE ? "#0970e6" : BRUT ? "#b5302a" : "#c14b2a", // the ask side
  help:   "#1f6b58",                                       // the give side
  parish: "#2e6ca8",                                       // community, links elsewhere
  flame:  "#e8862a",                                       // the candle's flame, and only that
  radiusBtn: APPLE ? 12 : BRUT ? 8 : 999,
};

/** SPACING: the 8-pt grid. Use these, never ad-hoc numbers. */
export const SP = { xs: 4, sm: 8, stack: 12, md: 16, screen: 20, lg: 24, xl: 32 };

/** BUTTONS: every button spreads these (HIG: 44-pt targets or larger). */
export const BTN = { minHeight: 50, borderRadius: T.radiusBtn, paddingVertical: 14, paddingHorizontal: 20 };

export const serif = {
  fontFamily: Platform.select({ ios: "Georgia", android: "serif" }),
  fontStyle: "italic",
};
04

Color roles carry meaning

The two sides of the app never share a color. Asking is the need role, giving is the help role, and the request page shows all three actions as secondary buttons in their own roles, so the color tells you which side of the exchange you are on before you read the label.

Ask screen in the need color
needAsk: the title and primary in the ask color
Offer screen in the help color
helpOffer: the same layout in the give color
Request detail with Pray, Go there, and Comment outlined buttons
L2 secondaryPray · Go there · Comment, each in its role
05

Four levels of tappable, and nothing in between

A design review found tappable and decorative elements drifting into each other. The clearest case: a section heading's decorative rule was styled exactly like a selected tab. The fix was a written rule with four levels, each with one look, kept in the token file where both I and the coding agent read it before building a screen.

L1 · PRIMARY

Do the act

Filled button. Need color on the ask side, help color on the give side. One per screen.

L2 · SECONDARY

The alternative or next step

Outlined button in its role color. Never filled.

L3 · TERTIARY

Change, settings, elsewhere

Quiet text in the parish blue with a chevron. Never a box.

What does this mean? ›
L4 · NAV STATE

Tabs and segments

Ink text with a 2-pt underline in the need color when selected. That underline is the only colored line in the app; decoration uses line.

AllNear meMy parish
06

Respecting each platform

BehavioriOSAndroid
Display serifGeorgia italicThe platform serif, via Platform.select
Sticky footers and pinned headersClear the home indicatorClear the three-button bar; moved onto safe-area insets after testing on a Pixel emulator
BackBack button and edge swipeSystem Back pops routes correctly
Tap targets44 pt or larger everywhere; buttons are 50 pt tall
DistributionApp Store Connect, external TestFlight groupInstallable APK on the same over-the-air channel as iPhone. One invite link sends each phone to the right install.
07

Auditing the system in use

AI-generated code drifts toward one-off values. When the app started to feel sloppy, I audited the native code against the grid and fixed everything in one pass, then turned on lint for the native project.

176
off-grid values found across 18 files
78
padding, margin, and gap values snapped to the spacing scale
21
bare tap areas given a 44-pt reach
10
card radii moved onto radius tokens

Still open, and the next audit: on native, font sizes are still set per screen. The named type scale (34 · 28 · 20 · 17 · 15 · 13) is enforced on the web; bringing type roles into theme.ts is next.

08

A tester's question, answered the same day

One of the first TestFlight testers opened the app and said they were not sure what to do with it. The home screen assumed you had heard the pitch.

That day I designed and shipped a four-screen welcome on first launch (what Acts is, what you can ask for, what you can do, where it adds up), built entirely from existing components, and pushed it to every tester as an over-the-air update. It is also on My Profile as "How Acts works," an L3 link.

Welcome screen: Someone near you needs a prayer
Welcome · 1 of 4Shipped over the air the same day