LexiGrow: a clinical-grade tracker for my kids' first words

When you have a baby, you start writing down the words. "Dada" got logged on a sticky note. "More" went into the Notes app. By the time you have a toddler you have words in four places, none of them queryable, none of them comparable to anything clinical.

LexiGrow is the app I built so my wife and I would stop losing track. Then I realized the proper version of this tool already existed in the speech-language pathology world — it's just not consumer-facing. So I rebuilt it from the spec.

MB-CDI as the foundation

The MacArthur-Bates Communicative Development Inventories are the standard parent-report instrument for early language acquisition. Two relevant forms:

  • Words & Gestures — 8–18 months, 396 vocabulary items + a gestures inventory
  • Words & Sentences — 16–30 months, expanded vocabulary + early grammar

LexiGrow ships with both vocabulary banks bundled as JSON (assets/data/mbcdi_vocabulary.json, assets/data/mbcdi_gestures.json). You don't track free-text words — you check off MB-CDI items as your child produces them. That means the data is directly comparable to clinical norms, not just a personal log.

The stack

Framework        Flutter 3.27+ / Dart 3.6+
Local DB         Isar (offline-first, fast, type-safe)
State / arch     BLoC, feature-first
Theming          Material 3 light + dark
Future backend   Firestore for sync (Phase 8 of the modernization plan)
Compliance       COPPA/GDPR services in core/compliance/

The project layout is feature-first so each module owns its own state:

lib/
├── main.dart
├── core/
│   ├── compliance/      COPPA/GDPR services
│   └── theme/           Material 3 light + dark
├── data/
│   ├── models/          Isar collections (VocabularyItem,
│   │                                       GestureItem,
│   │                                       ChildProfile)
│   └── repositories/    Isar-backed data access
└── features/            each feature owns its BLoC
    ├── analytics/
    ├── gestures/
    ├── home/
    ├── onboarding/
    ├── reports/
    ├── vocabulary/
    └── quick_add/       voice + photo input

The data model is two tables and an inventory: VocabularyItem, GestureItem, and a ChildProfile. Everything else — frequency-of-use, age-at-first-production, comprehension-vs-production split — derives from those.

Bilingual children

This is where consumer apps fall over. If your kid says "dog" in English and "perro" in Spanish, those are not two vocabulary items — they're one concept. The MB-CDI norms only work if you count conceptual vocabulary, not surface form.

I baked that algorithm in. The doc is at design/bilingual_logic.md in the repo. The short version:

  1. Every MB-CDI item has a concept ID
  2. Each child profile has 1–N spoken languages
  3. A vocabulary entry is (concept_id, language, age_at_first_production)
  4. Comprehension and production are scored at the concept level, not the surface form

That single decision means LexiGrow can give a bilingual kid a fair number against the norms instead of penalizing them for speaking two languages.

What's hard about this

The hardest part is not over-medicalizing it. Parents don't want a clinical assessment tool that makes them anxious about every milestone. The MB-CDI is a percentile system; it's normal for a 14-month-old to be at the 30th percentile and a 22-month-old to be at the 90th. LexiGrow has to communicate that without either downplaying real concerns or generating false alarms.

The current design is:

  • Show progress against age-band norms, never against a single rigid line
  • Surface percentile ranges, not a single number
  • Explicitly flag the "this is a wide range" framing in onboarding
  • Provide a "share with your pediatrician" report — the only output styled like a clinical artifact

If a parent wants the clinical output, they can ask for it. If they want the encouraging output, that's the default.

What's next

Phase 1 of the modernization plan adds integration tests. Phase 5 renames voice/ to quick_add/ and consolidates the photo + voice inputs. Phase 8 adds Firestore sync for multi-device families. After that, the obvious next thing is a longitudinal report — a year-by-year report card you can keep across siblings.

This was the first Flutter project I'd shipped to a place where it actually had to feel like a native iOS app. Material 3 helps. The remaining iOS-feels-different work is mostly haptics, swipe-back, and the photo picker — and the photo picker is half of why God invented image_picker_ios.

If you're a parent who wants to log first words properly, or a clinician who's tired of recommending tools that don't use the MB-CDI, the repo lives on my GitHub.