Home/Blog/Inside Roulocal: one design system across iOS, Android and web

Inside Roulocal: one design system across iOS, Android and web

How we shipped iOS, Android and a web app off one design tokens package, one product team and one Friday demo cadence — and why that decision saved the project six months.

Roulocal is a mobility marketplace for Réunion Island — verified-driver carpooling on one side, short-term car rentals on the other, three platforms shipped from one engineering team. iOS, Android and web went to production in the same release window. A single principal engineer scoped the project; the same engineer is on the on-call rotation today.

This post is about the technical decisions that made that possible.

The framing: design system as the only shared dependency

Most agencies ship mobile-and-web as two projects. Two designers, two PMs, two timelines, two codebases that drift on day one and never quite catch up. We were brought in with an explicit mandate to ship all three platforms as one product — feature parity, visual parity, accessible to the same engineer.

The architectural answer was a shared design tokens package as the only common dependency across the three codebases. Not a shared component library — that always becomes a battleground — just the tokens. Colours, spacing, typography, motion, radius, focus rings. Everything else is native to its platform.

1. The tokens package

// @roulocal/design-tokens
export const tokens = {
  color: {
    bg: "#070709",
    surface: "#101015",
    text: "#ebebef",
    textMuted: "#a0a0ad",
    accent: "#f5a524",     // warm amber, the Roulocal brand
    accentInk: "#1a1100",
    /* …+ 24 more */
  },
  space: { 1: 4, 2: 8, 3: 12, 4: 16, 5: 24, 6: 32, 7: 48, 8: 64 },
  radius: { sm: 8, md: 12, lg: 18, pill: 9999 },
  font: { sans: "Inter", display: "Instrument Serif", mono: "JetBrains Mono" },
  motion: { fast: 150, base: 220, slow: 420 },
  ringFocus: { width: 1.5, offset: 2 },
} as const;

The package is published as a private npm module. Flutter consumes it via a small tokens.dart generator that runs in CI; Next.js consumes the TypeScript directly via Tailwind theme extension.

A colour change ships to all three platforms with one PR.

2. Flutter for mobile, Next.js for web

We chose Flutter over React Native for mobile because the team needed Android-first to ship — Réunion is a heavily Android market — and the Flutter rendering pipeline meant we could get visual parity with the web design without per-platform overrides.

Web is Next.js 15 + TypeScript with React Server Components for everything that doesn't need a client. The web app shares its API contract with mobile via OpenAPI generation — both clients are generated from one schema.

That sharing is the second discipline. Different codebases, same wire format.

3. The Friday demo cadence covered all three

Friday demos are the heartbeat of every Devmint project. On Roulocal, the demo was always all three: iOS build, Android build, web build, deployed to staging, walked through in the same 30-minute meeting. The founder could open whichever device was nearest and try the same flow.

What that cadence enforced was visual and behavioural parity. A platform that fell behind got caught the same Friday. We never landed the project in a state where one platform "shipped" and the others were "catching up."

4. AI support is the same surface everywhere

The AI ride assistant is the most-used surface in the product. Its job is to handle the long tail of inbound — "can I bring a surfboard", "my driver is 5 minutes late", "can I split the cost three ways". It runs the same orchestration on all three platforms, called from the same API.

The mobile clients render it as a bottom-sheet chat surface. The web client renders it as a side-panel. The orchestration, the prompts, the eval harness, the audit log — all shared. About 78% of inbound is resolved by the assistant, measured at the orchestration layer, not per platform.

5. One on-call rotation

This is the human metric. The same engineers who shipped Roulocal are the on-call rotation for Roulocal. The Flutter engineer takes Flutter incidents; the Next.js engineer takes web incidents; both can be the AI-layer on-call.

There is no hand-off layer. There is no junior team taking the pager. The principal engineer who shipped a feature in week six is the engineer who fixes it in month nine.

That, more than any architectural choice, is what one team across three platforms actually means.


Want the full case study? Read the Roulocal engagement →.

Let's build

Building this kind of system? Let's talk.

Most discovery calls take 30 minutes. A fixed proposal lands in your inbox within 48 hours. Work begins inside three weeks.