Projects / Roots Design Tokens

I built a multiplatform design token pipeline that helped a small front-end team ship dark mode and a rebrand across six codebases.

Roots Design Tokens Roots Design Tokens
Year
2025
Company
Aspiration / GreenFi
Role
Design, Engineering, Project Management
Tools
Style Dictionary, Leonardo, VSCode, GitHub, CircleCI
Platform
Web, iOS, Android, Figma

Context

Aspiration’s product spanned six main user-facing codebases: native iOS and Android apps, a React Native codebase powering account signup across platforms, two web apps serving the logged-in experience (one modern React, one legacy Angular), and a marketing site. Each one had its own way of defining and managing styles.

In 2023, layoffs took the company from around 300 people to ~50. The Aspiration consumer banking brand and remaining team were acquired. Following an additional headcount reduction in 2024, we were left with a drastically reduced front-end team: three engineers—one each for web, iOS, and Android—and me, the only designer, chipping in on web front-end work and occasionally hopping into the apps, too.

A Growing Need

We managed to ship some important features in 2024, but design debt kept accumulating, and the roadmap ahead was ambitious: dark mode and a full company rebrand were both on the horizon. Our styling situation wasn’t ready for any of that. Our brand colors had names like “midnight,” “drizzle,” and “sand.” I knew what each color looked like from having used them so much, but they weren’t named in a way that helped engineers recognize or know where to use them. Aging code with scattered UI patterns had drifted out of alignment with our Figma styles and components. Any visual change meant touching six codebases by hand.

A Path Forward

With the various upcoming roadmap initiatives, I knew my design time was going to be limited. From what I’d seen out of other design-forward companies, Config talks, etc… I arrived at the conclusion that a strong system of Design Tokens could help me keep our styling somewhat controlled.

I had a few specific goals for the system:

  • a single source of truth for design decisions
  • a taxonomy that was easy to understand and extend
  • automated code distribution with change tracking
  • a system that could support per-platform customization.

Building the Pipeline

I used Figma and our cleanest web codebase to refine the process and naming, then worked through each remaining codebase to understand its styling system and the format it expected style definitions to arrive in.

Color Scales with Leonardo

For the palette itself, I used Leonardo, an open-source tool from Adobe. It takes key colors and desired contrast ratios as input, maps a lightness curve through the key colors, and returns the value at each contrast ratio you ask for.

I fed our existing brand colors in as the key colors and generated 100–900 scales for each color family—neutral, blue, green, red, and gold. Because the scales derive from our original colors, we could roll the new palette out without any visible change, and use old and new colors side by side during the transition.

Style Dictionary as the Engine

Style Dictionary, an open-source tool from Amazon, takes design tokens defined in a standard JSON format and transforms them into platform-specific outputs. It handles the syntax differences across CSS, JS, Swift, Kotlin, XML, and so on, and it’s extensible with custom actions, formats, and transforms—which I leaned on heavily.

The tokens were defined in two layers: primitive palette tokens (palette.color.blue.200) and semantic theme tokens that reference them (color.background.sentiment.info). The naming convention mattered most at the semantic layer—each token’s name describes its purpose and usage rather than its appearance.

I wrote custom transforms and build steps to generate outputs for every destination we had: Swift for iOS, Kotlin and XML for Android, JS and TS for the modern web codebases, SCSS for the legacy one, JSON for Figma via the Tokens Studio plugin, and a general-purpose JSON output reserved for a future docs site.

Chroma.js for easy token relations

I also integrated Chroma.js to support build-time color modifications—brighten, darken, alpha. That gave us precise control over unique UI situations without ballooning the primitive palette to cover every one-off.

Flexible, No-Fuss Theming

Dark mode wasn’t on the immediate roadmap when I started, but I knew it would come, so I structured our setup to support theming from the start. I learned a lot from concepts Danny Banks had explored for defining themes in Style Dictionary, and ended up setting our Style Dictionary up in very similar ways. Between the semantic token layer and the light and dark scales from Leonardo, 80–90% of dark mode came for free, with most tokens resolving to the right dark value automatically.

Handling Tricky Theme Issues

The remaining 10–20% was made up of various cases where the light mode colors didn’t work visually by simply inverting for dark mode. Elevation is a good example. Raised surfaces in a UI are perceived as closer to the light source, so they should always appear brighter than non-raised surfaces. With a direct invert for dark mode, you’d get the opposite effect… the lightest surface in light mode would become the darkest in dark mode.

The same home screen in light and dark mode. Mapping the neutral palette primitives directly to the prominent and elevated background tokens works in light mode, but in dark mode the elevated account cards render darker than the page behind them.

For those, I defined explicit dark-mode overrides. In some cases I used Chroma.js to derive a token’s value from another, using modifiers like (brighten, 0.2) that would ensure the derived token, e.g. color.background.elevated would always be a lighter value than the base token, e.g. color.background.primary.

The same comparison with the fix: deriving background.elevated from background.prominent with a brighten modifier, so the account cards correctly read lighter than the page in both light and dark mode.

This token naming structure, where semantic tokens can stay constant across themes, and simply map to a different value as needed, made the work in Figma a lot easier, too. I (along with our other FE web engineer who occasionally chipped in on design work in Figma) could work in a single theme, knowing color names would stay consistent across both. Figma’s variable collections would allow us to swap an entire screen between light and dark any time we wanted to see both.

Proving It Out

I intentionally kept the early, unofficial proof-of-concept project small: color tokens only, with outputs for just Figma and JS. I duplicated our Figma component library and replaced its colors with token-backed variables, which let us validate the system in parallel without disrupting ongoing design work. That process exposed some gaps in my token definitions, and a few sets of components with nearly identical styling that we took the chance to unify.

On the code side, I worked with our web engineer on NPM package distribution, then put together a massive PR replacing every locally defined color in the modern web app with Roots tokens. Despite the size of the PR, we merged it without any significant visible changes for users. The new foundation was now integrated throughout the codebase, ready for when we’d actually start evolving the visual language.

Multi-Platform Delivery

To deliver tokens for the other platforms, I set up a delivery pipeline using GitHub and CircleCI.

  1. Make adjustments to token names, definitions, and values

    The Style Dictionary build process generates code outputs

  2. Push code changes to GitHub

    CircleCI automatically distributes appropriate files to separate PRs for each platform for review

  3. Front-end engineer for each platform approves their PR

    A new Roots package version is tagged and available for use

  4. Minimal risk to end users

    Design token changes are implemented by each platform independently and safely

When changes to token names, definitions, or values show up in a PR, the Style Dictionary build generates fresh outputs, and CircleCI automatically distributes the appropriate files to separate PRs for each platform. The engineer for each platform reviews and approves their PR, and a new Roots package version is tagged and ready to use, with Intuit Auto handling the versioning mechanics.

This lets each platform adopt token changes independently, whenever it fits amidst other project work.

Flowchart of the Roots distribution pipeline: Leonardo color generation and core plus semantic token definitions feed Style Dictionary transforms, which build outputs for each destination—Token Studio JSON for the Figma component library, Kotlin and XML for Android, Swift for iOS, JS and TS published as an NPM package for the web apps, SCSS for the legacy web app, and JSON reserved for a future docs site—each published privately and consumed by its platform.

Leveraging Roots

I want to call out a couple projects where having this system of design tokens in place really proved its worth.

Shipping Dark Mode

Dark mode sat at the top of our feature request board for a long time, but it was tough to get buy-in to slot it into our small team’s roadmap. Leadership viewed it as a major undertaking that didn’t directly move the needle on any of our financials. I think that was partially true, but I’d argue the lack of dark mode is exactly the sort of feature that can frustrate users, so when your company makes actual mistakes, your users aren’t so forgiving.

Following our initial experiment project with design tokens, we were able to make the case to actually tackle the feature. The iOS and Android engineers advocated for it after seeing how the web experimentation had gone, and with Roots already in place, the scope suddenly looked a lot more manageable. The project made it onto our roadmap as “Dark Mode”, but internally it was basically just an integration project for the design tokens packages.

While Roots did significantly reduce the scope of the effort, it wasn’t something we could tackle with a global find-and-replace. Old colors mapped to different semantic tokens depending on usage: “midnight” might become primary text in one place, a button background in another, an icon color in a third. I paired extensively with all three engineers to hunt down hardcoded colors and decide the right mapping case by case, using the earlier web and Figma integrations as a reference. Beyond the tokens, some UI components had drifted apart across platforms and needed reconciling, and some image assets needed dark variants.

Complete dark mode support shipped across iOS, Android, and web in about six weeks.

The GreenFi Rebrand

In early 2025, Aspiration became GreenFi—a complete rebrand with new colors, typography, and visual language, coordinated with an external agency on an aggressive timeline.

Inside Roots, the rebrand started as a new branch with a new set of token values, but we were able to keep nearly all of the color token names the same. It also became the opportunity to grow the system well beyond colors: radius, spacing, and shadow tokens, and full typography support—fontSize, lineHeight, weight, letterSpacing, and family, which were then referenced in composite typography tokens such as PageTitle, Headline, Label, and Caption.

Roots even took over font file delivery. The new brand was designed to use two fonts: Gabarito for headlines, and Inter for body text. Instead of forcing each platform to manage the two fonts and their usage, I built a custom script that would check the defined typography tokens at build time, then copy—and in some cases, convert—the necessary font weights into each platform’s dist folder to be included in the Roots token package for that platform.

Because restyling every platform was mostly a token swap, the team could spend more time on UI cleanup, information architecture changes, and brand-related content overhauls instead. The release of the rebrand was synchronized across all platforms and marketing, but thanks to our versioning system for Roots, users who hadn’t yet updated their installed apps were never at risk of a broken in-between state.

Before and after of the account screen: the Aspiration Spend layout next to the reworked GreenFi Checking screen with cleaner balance, actions, and activity list
UI cleanup
Before and after of the Settings screen: the old flat settings list next to the reorganized account and preferences structure
Information architecture changes
Before and after of the subscription screen: the old Aspiration Plus copy next to the rewritten GreenFi Plus membership and benefits layout
Content overhauls

Reflection

I’m glad we could prove the system worked on one platform before asking anyone else to commit, and let each platform adopt changes on its own schedule. I was also happy to see how much we leveraged the early extensibility I baked in, both for theming support with the dark mode project, and when we added more token types to support the GreenFi rebrand. While there was some manual migration work involved, I enjoyed getting to pair directly with each engineer and use the time to get them familiar with Roots and take ownership for it as we worked through replacing legacy styling. It also didn’t hurt that the system got to prove itself through user-facing wins—shipping dark mode did more to justify design system investment than any internal pitch could have.

Key Takeaways

What Worked

  • Incremental rollout
  • Building extensibility from the start
  • Design+Dev pairing collaboration
  • User-facing features help justify design systems work

What I Learned

  • Every platform has a unique approach to defining and consuming core styles
  • Dynamic documentation wins
  • Naming is always the hardest part

What I’d Do Differently

  • Build with Figma-as-source or two-way sharing of definitions
  • Add generated docs from day one
  • Figure out a way to support easier exploration and variations