MondeeMiraee

Miraee: A Design System for AI

How we got the AI to reuse our components instead of regenerating them

Design Operations & ArchitectureDesign System
Before
The old admin dashboard, with charts and numbers laid straight onto a warm page
After
The new admin dashboard, with each metric and chart in its own card on a neutral grey grid

Product

  • Miraee by Mondee, a B2B2C travel app on iOS, Android and Web.
  • One design system meant to serve all three platforms, living in code.

My role

Lead product designer. I ran the audit, set the strategy and rebuilt the system, working with the engineers on what to delete and the PM on what to protect.

Team

  • Shashank Sharma (PM)
  • Harsha Arrimala (UI/UX Designer)
  • Shubham, Raunak & Akhil (Engineers)
  • Mridula & PC (Cross-functional Leads)

Timeline

About a month in 2026. Audit, rebuild, then the audit again.

Speed

2.5×

faster to build a screen. Days became hours.

Bugs

~60% fewer

Jira tickets about UI drift and visual bugs.

Load time

35% faster

page loads on web, once duplicate styles and dead code were gone.

TL;DR

We went AI-first to ship faster, and we did. But every screen the AI built made the design system a little worse. When I went looking for our brand orange in the code, I found ten oranges. Ours was sixth.

We couldn't change how the AI works, so we changed what was around it: a rules file, a website it can read, and fewer things to choose from. Then people forgot to add the file to their prompts, so we moved notes for the AI into the code, above every page. Now it reads them on its own.

In the new system, 4,250 hand-typed colours became zero, and 287 card components became one.

The problem

How did going AI-first make the design system worse?

It started with a search bar. I had designed one toolbar with the search, the filter and the view toggle built in, and the pages were not using all of it. It's not rocket science to notice. But once I started looking, it was everywhere.

Miraee is one product on three platforms: web, iOS and Android. We had one designer for all three, and three full-stack engineers. The company wanted speed, so we went AI-first. Engineers built screens with AI, and the gaps a designer would normally fill got filled by a generator.

A design system is supposed to be the one place every screen comes from. Build a button once, use it everywhere, change it in one place. Going AI-first was supposed to make that faster. It did the opposite. Every time the AI built a screen, it lost the context of what already existed. It made a new button rather than find ours. It copied a colour from the nearest file, even when that file was a mistake. It built the happy path and nothing else.

A system built to be reused
AI-first development, to ship faster
The AI keeps losing context, and copies what is nearest

Where this project steps in

Duplicated components
Hardcoded values
Dead code next to live code
Missing loading, empty and error states

Spread across web · iOS · Android

Two things pulled against each other. The company wanted velocity, and the AI-first mandate was not up for debate. The product needed consistency, and one designer could not police every screen on three platforms. Slowing the engineers down was not an option. The system had to work at the speed they were already going.

The company wants

Velocity

Ship every feature faster. AI-first, three engineers, no slowing down.

The system← it had been built for this side

The product needs

Consistency

One button, one card, one grey, the same on web, iOS and Android.

The goal

Make the design system the thing the AI reuses instead of regenerates, so the codebase gets leaner, faster and consistent across web, iOS and Android, without slowing the engineers down.

The audit

Counting the damage, in all three codebases

Two passes: source, then browser.

~4,250

Hardcoded colours, three apps

122

iOS card structs

74 / 78

Card pages, no empty state

39 / 52

Web components never imported

57%

Body text failing contrast

10

Oranges; brand ranked sixth

Primary buttons

10 kinds · 163 hand-rolled

New Report on ReportsBook a Trip on All TripsRequest (7) on Booking queueNew workflow on Approval workflowsCreate Budget on BudgetsView Trip on My TripsNew Policy on PoliciesAdd Employee on UsersStart planning on Booking queueAdd Code on AccountingApprove on Team ApprovalsPending70 on Team ApprovalsPlan on Booking queue

Status badges

36 styles · High is red and amber

The Upcoming badgeThe Planning badgeThe Partially Booked badgeThe Ongoing badgeThe HIGH badgeThe High badgeThe Critical badgeThe low badgeThe Active badgeThe Active badgeThe Active badgeThe Expired badgeThe Airline badgeThe Modification badgeThe Pending Review badgeThe Approved badgeThe DEFAULT badgeThe Policy Change badgeThe Business badge

Flight cards

Card in chat, row elsewhere

Flight cards in the AI chatThe same flight as a list row on the trip detail page

Hotel cards

Card in chat, row elsewhere

Hotel cards in the AI chat, with the assistant avatar over oneThe same hotel as a list row on the trip detail page
Show all 29

Trip cards

10 ways, one trip

My Trips: The current trip on My Trips: a square photo, a grey Business pill, a title, one line of dates and a black View Trip button, with no card around itPersonal Trips: A trip card with a photo header, a blue Upcoming pill, a hairline border and no shadowTeam Trips: A trip card with a photo header, a blue Upcoming pill, no border and a soft shadow, with two avatarsAll Trips: Trips as table rows with a small thumbnail, status as coloured text, avatars and a budget barBooking queue: Trip requests as a list card beside a detail card, each with a black Plan or Nudge pillExpenses: Trips as rows inside a bordered list card, with a receipt count and an amber Partially Booked pillManager Overview: Pending trip approvals as rows with an avatar, a priority label, an amount and Approve and Deny buttonsTeam Approvals: Trips as table rows with avatar, policy icons, an AI verdict pill and a black Approve buttonA trip card on iOSA trip card on Android

Nothing found

13 searches · 5 blank

Team Approvals after a search that matches nothingAll Trips after a search that matches nothingPolicies after a search that matches nothingRoles after a search that matches nothingTeam trips after a search that matches nothingUsers after a search that matches nothingLoyalty after a search that matches nothingPersonal trips after a search that matches nothingAudit logs after a search that matches nothingCorporate rates after a search that matches nothingUnused credits after a search that matches nothingBudgets after a search that matches nothingHelp after a search that matches nothing

Tables

18 tables · 26 hand-rolled

The Accounting tableThe All Trips tableThe Approvals tableThe Mileage tableThe Per diem tableThe Team analytics tableThe Travel summary tableThe Traveler report tableThe Unused credits tableThe Users tableThe Well-being tableThe HR overview tableThe Admin overview tableThe Subscription tableThe Travel spend tableThe Travel desk overview tableThe Travel desk overview tableThe Trip expenses table

Search bars

20 pages · 6 own

The search bar on ApprovalsThe search bar on Personal tripsThe search bar on Audit logsThe search bar on All TripsThe search bar on UsersThe search bar on Booking queueThe search bar on ReimbursementsThe search bar on Spend trackingThe search bar on Travel desk overviewThe search bar on Live map

Side panels

30 web · 0 iOS · 0 Android

The seat map panelThe receipts panelThe trip workspace over the chat

Metric bars

3 pages · 3 builds

The Overview metric barThe Team Approvals metric barThe Travel Impact metric cards

Page titles

3 sizes, 2 weights

Travel Spend title, 32 at 600Payment Methods title, 28 at 700Reports title, 25 at 700

Tabs

Underline or segment

Tabs on My TripsTabs on UsersTabs on Spend and BudgetTabs on Plan and Billing

Charts

6 charts · 5 palettes

Chart on Team AnalyticsChart on Spend and BudgetChart on CO2 and SustainabilityChart on CO2 and SustainabilityChart on Travel SpendChart on Finance Overview

Item cards

3 pages · 3 builds

Add-on cards in the AI chatTrips as rows inside a list card on ExpensesA list card and a detail card on the booking queue

Table headers

7 specs · 3 of 34 sort

The Accounting table headerThe All Trips table headerThe Team analytics table headerThe HR overview table headerThe Admin overview table headerThe Subscription table headerThe Travel desk overview table header

Table row heights

10 heights

49
51
53
55
57
65
67
71
73
97

Table dividers

5 treatments

#EBEBEB
#F5F5F5
#F9F7F2
none
#F3F1EC

Filter buttons

4 treatments

FilterFilters☰Filter ▾

Page widths

47 pages · 6 widths

Users at 1920 wideTeam Approvals, All Trips, Reimbursements at 1920 wideReports at 1920 widePlan & Book at 1920 wideAudit logs, Corporate rates at 1920 wideTraveler report at 1920 wide

Tablet width

83 of 164 pages fixed

Users at 768 wide: the sidebar stays and the content squeezesTravel Spend at 768 wideTeam Approvals at 768 wide

Users at real volume

5 of 34 tables paginate

The Users table at 8 rows and at 500 rows

Avatars

13 styles

Avatar on Header, signed-in userAvatar on Header, companyAvatar on Reimbursements, listAvatar on Reimbursements, detailAvatar on Travel deskAvatar on Travel desk, at riskAvatar on Spend trackingAvatar on Approval workflowsAvatar on Team tripsAvatar on All TripsAvatar on Team ApprovalsAvatar on Manager overviewAvatar on AI chat, the assistant

Primary colour

4 primaries

Confirm
web
Confirm
iOS
Confirm
Android
Select Flight
AI chat

Destructive reds

3 platforms, 3 reds

Delete trip
web · #B7596C
Delete trip
iOS · #D4453A
Delete trip
Android · #EF4444

Oranges

10 in use · official sixth

172
23
18
13
8
8 · official
7
6
6
5

Grey scales

3, one documented

stone
neutral
slate

Raw hex

2,813 web · 959 iOS · 484 Android

#0F172B
#0F172B · the most typed value, from a Figma export

Text contrast

57% of body fails

Employee
#ADA79C · 2.39 : 1
Employee
#A3A3A3 · 2.52 : 1
Employee
#8A857D · 3.66 : 1
Employee
#403D38 · 10.8 : 1

Heading font

179 headings fall back

Travel Spend
asked for Google Sans Flex, got this

This is only some of it. The same thing shows up everywhere else we looked. Dates are written at least twelve different ways in the code. There are 124 modals across the three apps. Icons come from one library, but 32 files still draw their own. On mobile it's the same story: 29 corner radii on iOS, 165 card composables on Android, and eight different progress bar heights. Most likely there's a lot more we missed.

Same mistakes everywhere. One fix.

Insights

Why it drifted: the AI, and the people using it

Two things caused this, and they fed each other: how the AI writes code, and how people worked with it. The bigger the circle, the more of the product it shows up in.

AI: Copies the nearest code (One orange, 172 uses); Starts fresh every session (36 badge styles); Hardcodes values not tokens (2,813 raw hex on web); Restyles shared components (10 of 11 tables differ); Falls back to library defaults (Tailwind grey, 1,706 uses); Looks finished when it isn't (Roles prints raw codes); Builds only what it's asked for (Empty state on 4 of 78)AIWhat it doesCopies thenearest codeOne orange, 172 usesStarts freshevery session36 badge stylesHardcodes valuesnot tokens2,813 raw hex on webRestyles sharedcomponents10 of 11 tables differFalls back tolibrary defaultsTailwind grey, 1,706 usesLooks finishedwhen it isn'tRoles prints raw codesBuilds only whatit's asked forEmpty state on 4 of 78
Humans: Design page by page, not the app (6 page widths); Paste values from Figma (#0F172B, top hardcoded); Deadlines beat consistency (163 own vs 105 shared); Test with demo data (Users shipped at 8 rows); Ship code that looks right (96% doubt it, 48% check); Leave unused code in place (39 of 52 never imported); One designer for three apps (1 designer, 3 engineers)HumansWhat they doDesign page bypage, not the app6 page widthsPaste values from Figma#0F172B, top hardcodedDeadlines beatconsistency163 own vs 105 sharedTest withdemo dataUsers shipped at 8 rowsShip code thatlooks right96% doubt it, 48% checkLeave unused code in place39 of 52 never importedOne designerfor three apps1 designer, 3 engineers

Most numbers come from the audit. One comes from outside research: trust and checking, from Sonar's State of Code survey.

Generated UI is only going to grow, so the fix had to work with the AI, not around it.

Strategy

Stop fighting the cheapest path. Put the right thing on it.

If you can't change the behaviour, change the environment.

An idea stolen from Atomic Habits. Thanks, James.

We couldn't change how the AI works, and people didn't want to do more work either. So instead of asking anyone to change, we changed what was around them, so the right thing happens on its own.

Five moves

Each move answers behaviours from the two maps.

  1. A file called design-system.md listing every component with a short rule

    01

    A rules file

    One Markdown file with a list of all the components and how to use them. You give it to the AI along with your prompt.

    Fixes

    • Copies the nearest code
    • Starts fresh every session
    • Design page by page
  2. An example design system website, showing a Button page with its rule, variants and tokens

    02

    A website the AI can read

    117 pages in the repo, made from one rules file. The AI reads these instead of Figma.

    Fixes

    • Falls back to library defaults
    • Paste values from Figma
    • One designer for three apps
  3. Manrope and Barlow Semi Condensed crossed out, Google Sans kept

    03

    Less to choose from

    One typeface, fewer components, token names the AI can guess, and scales with no gaps to invent a value in.

    Fixes

    • Hardcodes values not tokens
    • Restyles shared components
  4. A file called CHECKLIST.md asking what shows when a screen is empty, loading or failed, and whether it works at 500 rows

    04

    Checklists

    A screen is done when it has its states, uses only tokens and works at 500 rows.

    Fixes

    • Builds only what it's asked for
    • Looks finished when it isn't
    • Test with demo data
    • Ship code that looks right
  5. A components folder with dead files crossed out and one correct file left to copy

    05

    Remove the wrong options

    We deleted the 39 dead components, so the AI can't copy them. What's left is a correct example on every platform.

    Fixes

    • Deadlines beat consistency
    • Leave unused code in place
    • Copies the nearest code

Before. A prompt on its own

Build a trip card

    Thinking

    1. No design system in context
    2. Opening the nearest file
    3. Copying the card in it
    4. Picking an orange, #FF5733
    5. Adding a shadow
    6. Skipping empty and loading

    Result

    San Francisco

    Upcoming

    12 Nov 2026

    A new card, one more to maintain.

    After. A prompt with the rules

    Build a trip card

    • design-system.md
    • CHECKLIST.md

    Thinking

    1. Reading design-system.md
    2. Found Card, using it
    3. Colours from tokens only
    4. Reading CHECKLIST.md
    5. Adding empty, loading and error
    6. Checking 500 rows

    Result

    San Francisco

    12 Nov 2026Upcoming

    The shared Card, reused.

    Same AI, same prompt. An illustration of what we kept seeing, not a recording.

    What we chose not to do

    • A code check listing colours that are not tokens, ending in 4,250 errors and blocked

      Lint rules and CI gates

      Automatic checks that stop code from merging when it breaks a rule.

      Why not

      Engineers would have had to build and manage these checks, and they weren't willing to give that time at the moment.

    • A before and after screenshot with the changed button outlined, while the AI says it looks fine

      Visual regression testing

      The AI compares screenshots after each change to catch anything that moved.

      Why not

      When we tested it internally, the AI didn't catch the mistakes, so we couldn't rely on it. It also used a lot of credits.

    • Figma and code joined by a two-way arrow, kept in sync by hand

      A Figma MCP server

      A connection that lets the AI read Figma directly while it works.

      Why not

      It makes Figma the source of truth again, and keeping Figma and code in sync is what caused the drift. The website gives the AI the same answers with nothing to sync.

    • An arrow from Figma to Paper, with something new to learn

      Switching to Paper

      Another design tool, paper.design. From what I know, it's meant to be a little better for designing UI.

      Why not

      It would have needed some learning and getting used to.

    The test for everything: can the AI pull the right component faster than it can write a new one?

    What worked and what didn't

    People kept forgetting the file

    The fix had to work even when people forgot.

    The first version depended on people. Anyone prompting the AI had to add the rules file and the checklist every time. When they missed it, the AI went back to copying the nearest code.

    • A prompt to build the trips page with the rules file and the checklist both forgotten

      Didn't work

      Adding files to every prompt

      It worked when people remembered. No matter how many times we said it, someone would eventually miss it.

    • A page of code that opens with a note for the AI above the code

      Worked

      Notes for the AI, above every page

      The notes sit in the codebase itself, so the AI goes through them on its own whenever it works on a page. Nobody has to remember anything.

    Three decisions along the way

    Three other things did not go the way I had planned, and they taught me the most.

    What I kept, against the cleanup

    Android had a habit of dimming every card except the one you tapped. It was undocumented and nobody had designed it. It was also the clearest working pattern in the product, so it became the standard everywhere.

    What I reversed

    I had planned to delete the undocumented grey scale. Then I tested the colours, and the unofficial greys were the ones that passed contrast. So I kept them and dropped the "official" ones instead.

    What I did not build

    Special handling for long lists. 500 rows rendered fine. The real problem was that nothing sorted or paged, so I fixed that instead.

    Results

    The same audit, run on the new system

    Before and after, in numbers.

    • Colours typed by hand

      4,2500

    • Oranges

      100

    • Grey scales

      31

    • Reds

      31

    • Badge styles

      364

    • Text failing contrast

      57%0%

    • Typefaces

      31

    • Text sizes

      1310

    • Card components

      2871

    • Primary button kinds

      101

    • Unused components

      390

    • Modals

      1241

    • Pages with no empty state

      740

    • Corner radii

      296

    • Shadows

      2184

    • Progress bar heights

      81

    • Icon sets

      31

    • Date formats

      121

    And the same wall as the audit, with one of each thing instead of many.

    Primary buttons

    1 kind · 6 variants

    The six button variants: primary, secondary, tertiary, ghost, danger and the agent's gradient

    Status badges

    4 values · a dot and a word

    Four status badges: In policy, Awaiting approval, Out of policy, Booked

    Flight cards

    1 card

    One flight card with times, price, policy status and two actions

    Hotel rows

    1 row

    One hotel row with name, nights and price

    Trip rows

    1 row · 48px

    One trip row: traveller, route, date, status, total

    Nothing found

    1 empty state · names the query

    No trips match. Nothing matches the search.

    Tables

    1 table · sorts and pages

    The trips table with a sortable header and no lines between rows

    Search bars

    1 toolbar

    The table toolbar with search, filter, sort and view

    Metric tiles

    1 tile · every number has a comparison

    Four metric tiles with a delta chip each

    Page titles

    1 header · one primary action

    The Trips page header with Export and New trip

    Tabs

    1 kind

    Upcoming and Past tabs

    Charts

    1 line style

    The monthly spend line chart

    Avatars

    1 style

    A grey circle with initials

    What else changed

    Rebuilding the system was also an opportunity to rethink some of the other things that were causing lots of issues down the road.

    From pages to cards

    Earlier, a page was like one component. Now each part of a page is a separate thing, usually a card, designed on its own, and these fit into a responsive grid. It's a little inspired by the Carbon design system.

    Before. The whole page is one thing

    The old admin dashboard, with charts and numbers laid straight onto a warm page

    After. Each part stands on its own

    The new admin dashboard, with each metric and chart in its own card on a neutral grey grid

    From warm to neutral

    One of the costs was visual warmth. Midway, we were asked to move to a neutral colour palette instead of a warm one, so the product would feel modern and new instead of old.

    No more pictures

    We realised people were not adding pictures, so All Trips no longer depends on them. The table is just names, routes and status.

    Before. Warm palette, three fonts, pictures

    The old All Trips page on a warm beige background

    After. Neutral palette, one font, no pictures

    The new All Trips page with a neutral grey palette, no pictures, and the table inside one card

    Both versions use sample data.

    Impact

    What changed for the team

    Screens went from days to hours.

    • 2.5×

      Faster to build a screen

    • ~60%

      Fewer UI bug tickets

    • 35%

      Faster page loads on web

    • 95%

      Old code cleared on global updates

    How we measured it

    We did not have a pilot group the way a product feature would. What we had was the audit. We re-ran it at each milestone, against the same baseline, so adoption was a number we could show rather than a feeling we had. Alongside it we counted Jira tickets, and timed how long a screen took from handoff to merged.

    Leadership & reflections

    What leading this actually involved

    Designing for the environment

    We stopped designing for one particular user and designed the environment everyone works in instead, people and the AI alike. That is not a comfortable thing to tell a team of designers and engineers. The audit helped. Once everyone had seen the numbers, it stopped being a provocation and became the obvious conclusion.

    Prioritising workflow over aesthetics

    Going from three fonts to one, and simplifying a lot of other things, gave up some visual character. I chose that on purpose. How the team and the AI actually work mattered more than how rich any one screen looked.

    What I would do differently

    1. 01

      Track adoption from day one

      Not after the fact. The audit script should have existed before the first system did.
    2. 02

      Test with real data volumes earlier

      Most lists were designed against six sample items. 500 rows would have shown the problem in week one.
    3. 03

      Decide how the system stays maintained before designing it

      Manual token syncing between Figma and code is what caused the drift the first time.

    In conclusion

    The first design system assumed a human would come and look. The second one assumes nobody will, and that whatever is nearest will get copied. So we made sure the nearest thing was the right thing.