Design system text+

Design system text+

Ten years of app, two platforms and no shared language. The system started by counting what was there before deciding anything.
Client TextPlus
Role Product Designer
Year 2026
Status Delivered

01 The problem

text+ is a messaging and calling app with more than ten years on iOS and Android. It never had a formal design system: styles lived in the code, each platform did its own thing, and the inconsistencies grew with every release. The Flutter rewrite of the app was already under way when I joined.

Before defining a single token I ran a full inventory of the interface: eight flows, 216 screens across both platforms, 571 elements classified by what they do rather than how they look. Grouping by function instead of colour was the decision that made everything else make sense: two “Continue” buttons, one yellow and one green, are the same button implemented badly twice, not two components.

Frequency by variance: the most used and most inconsistent pieces are the first ones to build.
The list row is the skeleton of the app: chats, calls, contacts and settings are the same piece with different content.

My role

I was the only designer on the project, in a team of three: the CEO, who set priorities; a Flutter developer, my technical counterpart on every decision; and me. Two constraints: a team that size cannot maintain a complex system, and the developer was going to build screens from it without me reviewing each one, so nothing could stay implicit.

  • Audit of 216 screens and 571 elements
  • Token architecture in three modes
  • Kit of 15 components with auto layout
  • Usage rules written with their rationale
  • System documentation
  • Handoff to the Flutter theme

02 The decisions

A system for a team of three forces you to decide few things and write them down. These six are the system’s governance; each one is documented with its rationale, so the team can argue with it instead of reinventing it.

01

Tokens describe roles, never colours

brand/action, not brand/tangerine. If the name is a colour, the name is wrong. The brand group has exactly two tokens, action and emphasis, and does not accept a third.

A colour changes in one place and no name goes stale.

02

Two levels instead of three

Primitives and semantics. Component tokens add maintenance a small team cannot afford. Instead, every component carries a description with its intent and the tokens it consumes.

The system grows without its upkeep growing.

03

OKLCH so that modes are configuration

Eight palettes in a perceptually uniform colour space: the same lightness reads the same in any hue. Dark mode stops being a design project and becomes a mode change on a variable. AA contrast is checked on the token pair and fixed in the variable.

Three modes from the same 67 tokens, nothing repainted.

04

Yellow acts, green confirms

They never compete on the same screen. In dark modes yellow is replaced by lime automatically, because the action token resolves to a different value. Everything that is not the main action is secondary or a link.

One primary action per screen, in dark modes too.

05

Native Material 3, no custom widgets

The contract is simple: ThemeData and ThemeExtension, no custom widget classes. So the system leans on native Material 3 widgets, and hover and pressed states are not exported: Flutter handles them, not Figma.

The developer consumes tokens from the theme, with no translation.

06

A real device floor

Minimum width of 360 dp, from Android data; minimum height of 667 pt, from real traffic on iPhone 7 and 8. Below it a Flutter breakpoint hides or reflows, by design. Tablet and desktop wait for data.

Every component fits 360 × 667 and everything else stays out of scope.

The same 67 semantic tokens in Light, Carbon and Navy. The token does not change; the value does.

03 The system

Five layers, bottom up. Each one rests on the previous one and none skips ahead: a component never touches a primitive, and a rule never names a colour.

The layers of the system
  1. 01 Primitives 83 variables Eight OKLCH palettes with the same lightness at every step
  2. 02 Semantics 67 × 3 modes Roles, not colours: scheme, brand, feedback, text, button, stroke, avatar
  3. 03 Scales 13 spacing · 7 radii · 107 type Zilla Slab for the large headlines, Inter for everything else
  4. 04 Components 15 sets Auto layout, every fill bound to a variable, closed contracts
  5. 05 Rules and theme Usage guidelines · ThemeData When each thing is used, and the same token in Figma and in Flutter

Tokens. 277 variables in Figma, the single source of truth of the system. Spacing works in bands: 4 to 8 inside a component, 12 to 20 as padding, 24 to 40 between sections. Crossing a band breaks the rhythm, and that is written down.

The scale is named by value and used by band. You never jump from one to another.

Component kit. Fifteen sets built on the tokens: button, social button, list row, text field, switch, radio, checkbox, segmented control, action card, avatar, badge, plan selector, tabs, bottom bar and top app bar. Each set carries its description with the layer architecture, the tokens it consumes and the handoff notes for Flutter.

Eighteen variants. Hover and pressed are not among them: the Flutter theme resolves those.

The list row as a contract. It is the component that unlocks the most screens, so it has a closed contract that works as its API: the leading slot can be nothing, an icon or an avatar; the trailing slot is a fixed list of nine options, from chevron to switch, badge or value. No new type ships without updating the spec first. In Flutter that contract is an enum, not a free widget.

One row, three slots. All 47 versions in the app fit here.
The app chrome: neutral top app bar, tabs and bottom bar bound to the tokens.

One-click theme switch. The reference screens are duplicated by changing only the variable mode on the section. Nothing repainted.

Light on top, Carbon below. The only difference is the mode selected on the section.

Iconography. The app mixed icons from several eras and weights. Instead of buying a new set, I consolidated the one the company already owned: a single library, 1.5 stroke, line style, 24 px base, and the rule that no icon is ever hand-drawn.

04 Results

Primary button 44 versions scattered across the app One set of 18 variants with usage rules: when primary, when secondary, what happens with two actions
List row 47 different implementations, none as a component One component with a closed contract, an enum in Flutter
Colour and theme Loose styles in the code, one mode 277 variables as the single source of truth and three modes from the same tokens, checked on real flows
Handoff Conversational, screen by screen Fifteen components with handoff notes, consumed from the Flutter theme with no translation layer
Documentation Decisions in the head of whoever made them Every decision published with its rationale on its own site, the next case in this portfolio

05 Learnings

  1. Audit before you tokenise.

    The inventory was not preliminary work: it was the most important design decision of the project. It surfaced the component that resolved the most screens and that nobody had asked for.

  2. Rules are worth more than pieces.

    A kit without rules produces eyeballed decisions on every screen. Writing down when each thing is used, and why, is what turns a library into a system, and what lets others decide without asking me.

  3. Design for a machine to read it.

    Names by role, documented intent, closed contracts. That is what lets the developer, or whoever joins next, build a screen and have it come out close to what the system intends.

A design system is not a deliverable. It is a decision about who can change what without asking permission.

216 screens audited 8 flows on iOS and Android, before defining a single token
3 modes from one token Light, Carbon and Navy: switching theme is switching a mode
15 component sets auto layout, every fill bound to a variable, handoff notes for Flutter
47 → 1 versions of the list row one closed slot contract replaces them all

Do you have a product that needs to scale with purpose?

Let's talk. hola@franmarrero.com