Water Truth
Case Study
The narrative entry: the story you’re inside of right now.
fíor · Irish · true, real, pure
A mobile app paired with RYAKKA, a bespoke IoT sensor that reads six drinking-water parameters at home and collapses them into four plain verdicts: Excellent, Good, Medium, Bad. Designed for an Irish context where the official reports are annual PDFs and compliance is reported as averages, but the person at the tap still has no idea what came out today.
In Ireland, an annual EPA compliance report still outpaces what the person at the tap can confirm today. Surface waters slip: only 52% of surface waters are in satisfactory ecological condition, down from 54%. The lead limit halves in 2036 (10 → 5 µg/L), with the highest risk on infants, children, and pregnant women (EPA, 2025). Headline compliance reads reassuring on paper. The kettle disagrees.
Fior is a mobile companion to RYAKKA, a home sensor that pairs six readings with a four-state Verdict Ladder. The language is calm, not alarmist; every claim is sourced, not asserted. What you can do next, not what you should fear.
Conceptual MVP, not shipped. Research drove a persona pivot from rural-first to urban-renter for go-to-market. The output: a token-first design system (294 variables across three layers, as of v3.2), 15 navigable hi-fi screens, and an ecosystem of three coordinated surfaces (case study, process doc, hi-fi prototype) sharing one design system.
I moved to Ireland in 2022. The first thing my kettle taught me was what limescale looks like: a white crust on the heating element after a week, no matter how often I rinsed it. The tap water was technically safe; the EPA said so. I still didn't really know what was in it.
That gap, between “water is technically safe on the national average” and “I, today, in my house, can confirm it”, became the brief. The consumer category barely exists in Ireland: EPA data sits in institutional reports, not in people's pockets. Fior closes the distance.
Sources: EPA · Drinking Water Quality 2024 · EPA · Water Quality Report 2019–2024
Started broad, on purpose. The prompt was “what are the impacts of untreated water in Ireland?”, not “what app should we build?”. Mapping the blast radius first stops a project from collapsing into a single feature before the problem is understood.
Each category unlocked a different angle for the next round of questions.
First cut of the problem statement: “Rural households in Ireland need a reliable and affordable way to access clean and safe water, because untreated water can cause health, environmental, and economic issues.”
That statement survived until personas were sketched, and then it pivoted.
The sharpest primary persona turned out to be the Concerned Renter (urban, 25–40, Dublin / Cork / Galway). Rural well owners have higher objective risk, but renters have higher engageable risk: they can't modify plumbing, they suspect lead pipes, they already buy filtered jugs, and they live inside the quantified-self product category.
Rural well owners became the secondary persona; new parents the tertiary. The problem statement held. The primary target shifted from who was most at risk to who was most likely to act.
Eight How-Might-We prompts, one per impact vector. The aim was not to pick a winner here. It was to surface angles so the ideation round wouldn't start pre-committed.
The HMWs collapsed into four plausible products. Each was scored against three filters: personal daily value, defensibility, and scope discipline (could a solo designer prototype it end-to-end?).
Personal value every single day: you don't check a map every day, but you do drink water every day. A hardware moat through RYAKKA, the companion IoT sensor that turns six parameters into one verdict. And a scope a solo designer can actually deliver end-to-end: six readings, one verdict, one app. Clean boundary.
Before any pixel, a four-screen ASCII sketch. Home → Test → Result → Suggestions. That spine is still visible in the 15-screen hi-fi prototype: Home became Dashboard, Test became Measuring, Result became the verdict screen, Suggestions stayed as a hub.
┌────────────────┐ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ HOME │ │ TEST │ │ RESULT │ │ SUGGESTIONS │ │ │ │ │ │ │ │ │ │ Last test: │ → │ [ Dip RYAKKA] │ → │ GOOD · 76 │ → │ 1. Boil 3 min │ │ 08:42 · Good │ │ measuring… │ │ TDS · TOC │ │ 2. Filter jug │ │ │ │ ◉ ◉ ◉ │ │ COD · UV275 │ │ 3. Log source │ │ [Test again] │ │ │ │ EC · TEMP │ │ │ └────────────────┘ └────────────────┘ └────────────────┘ └────────────────┘// 4-screen sketch · direct ancestor of the 15-screen hi-fi ← Scroll horizontally to see the full flow →
Everything that followed (a three-layer token system, five pillar colours, the Verdict Ladder component, the Inter → Nunito migration) was thickening the spine. The spine itself never moved.
Fior's design system lives in Figma and in code: a Figma library built on variables, components and auto layout, and the token layers implemented in code. Three layers of tokens, a typography migration mid-project, and a single four-state component that carries the whole product's point of view on health and honesty.
// Figma library · v3.2 · Oct 2026
Every token in the system sits in one of three layers. Primitives hold raw values. Semantic tokens map meaning onto those values (primary, surface, danger). Component tokens bind the semantic layer to a specific UI part. Swap a primitive, the upper layers update automatically.
Mid-project the brand tone broke the typeface. Inter is crisp and neutral: right for SaaS, wrong for a calm Irish utility that wants to feel like a knowledgeable friend. Nunito's rounded terminals land warmer without surrendering legibility.
With a flat system this migration would have been a search-and-replace across every text style. With the three-layer architecture, the change in code was a single primitive swap: --font-family-base pointed from Inter to Nunito, and every semantic and component token propagated automatically.
Ten minutes of work, zero visual regressions. The architecture wasn't theoretical. It paid back the cost of setting it up on the first structural change.
The Figma file was slower to follow: it only left Inter in October 2026. That story is chapter 04.
Six sensor parameters (TDS, TOC, COD, UV275, EC, TEMP) are complicated. A verdict is not. The Verdict Ladder is the single component that turns the reading into a headline anyone can act on in under two seconds. Four states. Plain English. No numeric wall.
The dashboard leads with the verdict, not the readings. COD, TDS, UV275 are evidence. They live one tap deeper for the user who wants them. Most users don't. Calm tone over scary tone: the four states never say “unsafe” or “warning”. They say what you should do next (boil, filter, drink).
The five pillar colours and their supporting hues, the Nunito type scale, and the base-4 spacing grid: the surfaces the three-layer architecture ultimately resolves to.
Fior lives in three live surfaces: the case study you’re reading, a mobile-first process doc, and an interactive hi-fi prototype. They share the same palette, the same Verdict Ladder, the same calm voice. One design system. Three voices.
The narrative entry: the story you’re inside of right now.
A single mobile-first document with four modes: Process, Wireframe, Prototype, System. Walks the design trail from problem framing to the embryonic system.
Vite + React 18 + TypeScript strict. 15 screens across 6 phases and 3 branches. The 4-state Verdict Ladder lives here, end-to-end. Hash routing, no build SSR.
Behind the three surfaces sits the Figma file, where Fior started: 27 screens across seven feature flows. The hi-fi prototype came later, as a 15-screen navigable path. In October 2026 I audited the file against the shipped code and rebuilt it in three passes, from v3 to v3.2. Every number in this chapter is as of v3.2.
The process doc’s System page used to say it plainly: “Figma is the source of truth. If this artefact ever drifts, Figma wins.” The audit said the opposite. The Figma file was the one behind: still set in Inter, still on the old type scale. The variable count matched the online system to the unit. The values didn’t.
The findings that mattered were the ones no screenshot shows. The input border was aliased to a spacing token. The button radius token was bound to rectangles that weren’t buttons. And the Button component itself had been deleted while screens were still using it.
Before changing any value, I re-bound each layer to a token with the same value, so nothing on screen could move while the structure underneath did. Only then did the values change. Across 27 screens, zero regressions.
The sentence came off the site. The System page now publishes the rule the work actually follows: the shipped code sets the values; Figma mirrors it, except where the code fails accessibility.
The documentation also claimed zero hex hardcoded. Inside the screens, that wasn’t true yet. I could have softened the sentence. I fixed the file instead: the 67 loose colours inside the screens now resolve to variables, and gradients live as paint styles, because Figma’s plugin API can’t bind a variable to a gradient stop. The census reads zero across all 27 screens.
The structure follows the code’s API too. Button became a Type × State set, because in code disabled is a state, not a type. The nav bar’s active tab became a variant, mirroring activeTabForScreen(), and adding the Map tab fixed a navigation error on the way.
Where parity and accessibility disagreed, accessibility won, in both directions. The component library’s success button failed AA at 2.2:1, so the code moved to green-600 (5.6:1). Figma’s input error text failed AA at 3.9:1, so Figma moved to the code’s red-600 (7.9:1).
The audit found the button everyone sees failing too: white on blue-400 was 3.48:1. Primary moved to blue-500 (5.22:1), in code first, then in Figma.
An automated check agrees: axe-core 4.10, WCAG 2.1 A and AA rules, run on the live hi-fi at 375 px and on desktop, reports 0 violations across the 15 screens (Oct 2026). It is an automated check, not a full audit: no screen reader was involved.
Figma’s Token Reference page is the only bridge to code for anyone without a paid seat. It listed CSS names that don’t exist in the code, like --color-primary and --spacing-1.
I didn’t rename Figma to look like the code. Renaming would have produced the worst kind of drift: same name, different value. Instead, each variable learned the code’s word for it, but only where the resolved values match. 262 of 294 do. The other 32 carry a dash and a reason.
| Code (hi-fi CSS) | Value | Figma variable (Fior/Semantic) |
|---|---|---|
| --text-primary | #0D1E2E | color/text-primary |
| --text-muted | #6A849E | color/text-secondary |
| – | #8FA5BC | color/text-tertiary |
| --text-secondary | #354D62 | color/text-strong |
| --text-tertiary | #4E6680 | color/text-meta |
| – | #B8C8D8 | color/text-disabled |
| --text-on-deep | #FFFFFF | color/text-inverse |
There’s no Code Connect: it needs an Organization or Enterprise plan, and the file is on Pro. Instead, every component set names its source file and props in its description.
A review still caught me. A value match is necessary, not sufficient: one pair passed my script and was still wrong, because the shared button weight matched the primary button and hid two lighter variants in code. The rule I kept: role beats nearest value.
Before any of this went public, five independent reviewers, separate AI agents with one lens each, read the material against the code, the Figma file and the site. They found errors I had written with confidence: an “AAA” that was AA, comparisons between counts with different scopes, and the site still saying “Figma wins”. All fixed before this chapter was written.
Every number here is a census I can rerun, and every bulk write ran all-or-nothing: the script compared every pair and wrote nothing if a single one diverged.
The brief wasn’t in the lab. It was in the bin.
Irish public water is 99.8% compliant, and Dublin recycling bins are full of bottled water. That gap is the real brief. People weren’t fleeing unsafe water; they were fleeing the absence of personal confirmation. Research lives less in what people say is wrong, and more in the small workaround they’ve quietly built around it.
Most health apps lead with the reading (TDS 128, pH 7.2) and leave you to translate it. That’s a failure of voice dressed up as transparency. Fior’s Verdict Ladder leads with the state and tucks the numbers one tap away. Calm isn’t a tone; it’s an information-architecture decision.
All of it built solo, 15 screens, 294 variables, 29 passing tests, WCAG 2.1 AA: I designed hands-on in Figma and directed the system (personas, IA, the calm voice, the verdict ladder as the central decision), while AI wrote the code under my direction.