# How Maybe's Tailwind CSS Design System Is Organized with Functional Tokens

> Discover how Maybe Finance organizes its Tailwind CSS design system using functional tokens that map to CSS custom properties and manage theme variants automatically.

- Repository: [Maybe/maybe](https://github.com/maybe-finance/maybe)
- Tags: architecture
- Published: 2026-03-07

---

**The Maybe app uses a single-source-of-truth design system where functional tokens—defined via the `@utility` directive—map directly to CSS custom properties and automatically handle light/dark mode switching through centralized theme variants.**

The [maybe-finance/maybe](https://github.com/maybe-finance/maybe) repository implements a modular Tailwind CSS architecture that separates design intent from markup implementation. Rather than scattering arbitrary utility classes across ViewComponents and ERB templates, the codebase relies on **functional tokens**—semantic utility classes that abstract color, spacing, and component states. This approach ensures consistent theming across the entire Rails application while simplifying maintenance.

## Central Entry Point

All design system tokens flow through a single entry point located at [`app/assets/tailwind/maybe-design-system.css`](https://github.com/maybe-finance/maybe/blob/main/app/assets/tailwind/maybe-design-system.css). This file acts as the hub that imports specialized token groups and declares global CSS variables.

The import structure organizes utilities by function:

```css
@import './maybe-design-system/background-utils.css';
@import './maybe-design-system/foreground-utils.css';
@import './maybe-design-system/text-utils.css';
@import './maybe-design-system/border-utils.css';
@import './maybe-design-system/component-utils.css';

```

This modular structure—found at lines 8–12 of [`maybe-design-system.css`](https://github.com/maybe-finance/maybe/blob/main/maybe-design-system.css)—allows developers to locate specific token definitions quickly without navigating monolithic stylesheets.

## Core Color Variables

The same entry file contains a `@theme` block that declares **CSS custom properties** for every hue used across the UI. These variables define the raw color palette—grays, reds, greens, blues—and semantic colors like `--color-success` or `--color-shadow`.

Located at lines 21–45 in [`maybe-design-system.css`](https://github.com/maybe-finance/maybe/blob/main/maybe-design-system.css), this `@theme` declaration serves as the single source of truth that all functional tokens ultimately reference. By centralizing raw values here, the system ensures that changing a color in one place propagates through every utility class.

## Functional Token Groups

The architecture divides tokens into discrete categories based on their application context. Each group uses the **`@utility`** directive—a Tailwind CSS v4 feature—to generate first-class utility classes.

### Background Utilities

The [`background-utils.css`](https://github.com/maybe-finance/maybe/blob/main/background-utils.css) file defines surface and container colors through the `bg-*` namespace. Each utility applies a light-mode color by default and overrides it via the `@variant theme-dark` block when the user selects dark mode.

For example, the `bg-surface` token applies `bg-gray-50` in light mode and switches to `bg-black` when the theme changes:

```css
@utility bg-surface {
  @apply bg-gray-50;
  @variant theme-dark {
    @apply bg-black;
  }
}

```

This definition appears at lines 1–6 of [`app/assets/tailwind/maybe-design-system/background-utils.css`](https://github.com/maybe-finance/maybe/blob/main/app/assets/tailwind/maybe-design-system/background-utils.css).

### Foreground Utilities

Separate from text colors, **foreground utilities** handle icons and inner elements that need to contrast against their parent backgrounds. Defined in [`foreground-utils.css`](https://github.com/maybe-finance/maybe/blob/main/foreground-utils.css), these use the `fg-*` prefix.

The `fg-primary` utility (lines 25–30) applies `text-gray-900` in light mode and `text-white` in dark mode, ensuring icons remain visible regardless of theme:

```css
@utility fg-primary {
  @apply text-gray-900;
  @variant theme-dark {
    @apply text-white;
  }
}

```

### Text Utilities

Direct text color tokens reside in [`text-utils.css`](https://github.com/maybe-finance/maybe/blob/main/text-utils.css) and use the `text-*` namespace. These differ from foreground utilities in that they target typography nodes (paragraphs, headings, links) rather than decorative elements.

The `text-primary` token (lines 1–6) mirrors the foreground pattern but applies specifically to typographic color inheritance, while specialized tokens like `text-link` (lines 33–38) provide semantic meaning for interactive elements:

```css
@utility text-link {
  @apply text-blue-600;
  @variant theme-dark {
    @apply text-blue-500;
  }
}

```

### Component Utilities

Higher-level UI abstractions live in [`component-utils.css`](https://github.com/maybe-finance/maybe/blob/main/component-utils.css), combining multiple lower-level tokens into composite utilities. These handle complex states like button backgrounds, tab indicators, and navigation highlights.

The `button-bg-primary` token (lines 2–9) demonstrates this layering:

```css
@utility button-bg-primary {
  @apply bg-gray-900;
  @variant theme-dark {
    @apply bg-white;
  }
}

```

Additional variants like `button-bg-primary-hover` (lines 12–19) define interactive states, allowing developers to compose complete button styles through class combination rather than custom CSS.

## Dark Mode Architecture

The design system centralizes dark mode handling through a custom **theme variant** triggered by the `data-theme="dark"` attribute on the root element (or any ancestor). Every functional token includes an `@variant theme-dark` block that overrides the base color.

This approach eliminates the need for manual dark mode logic in templates. Developers apply `bg-surface` once, and the system automatically renders the appropriate background color based on the current theme state. The variant definition relies on Tailwind's `theme-dark` custom variant, which queries the `data-theme` attribute.

## Usage in Application Templates

Because tokens compile to standard Tailwind classes, they integrate seamlessly with Rails ViewComponents and ERB templates. Developers reference functional tokens directly in markup without importing additional stylesheets.

A typical container implementing the surface pattern uses:

```erb
<div class="bg-surface text-primary rounded-md p-4">
  <h2 class="text-primary">Welcome</h2>
  <button class="button-bg-primary hover:button-bg-primary-hover text-inverse rounded">
    Save
  </button>
</div>

```

In this example:
- `bg-surface` provides the adaptive container background
- `text-primary` ensures accessible contrast for headings
- `button-bg-primary` and its hover variant handle interactive states
- `text-inverse` (defined in [`foreground-utils.css`](https://github.com/maybe-finance/maybe/blob/main/foreground-utils.css) lines 18–22) guarantees button text remains readable against both light and dark button backgrounds

## Summary

- **Centralized imports**: [`maybe-design-system.css`](https://github.com/maybe-finance/maybe/blob/main/maybe-design-system.css) serves as the single entry point for all token groups
- **CSS custom properties**: Raw colors defined in the `@theme` block provide the foundation for all utilities
- **Functional tokens**: The `@utility` directive creates semantic classes like `bg-surface`, `fg-primary`, and `button-bg-primary`
- **Theme-aware by default**: Every token includes `@variant theme-dark` blocks that respond to `data-theme="dark"` attributes
- **Component-ready**: Tokens compose in ERB templates and ViewComponents without additional configuration

## Frequently Asked Questions

### What is the difference between foreground and text utilities in Maybe's design system?

Foreground utilities (`fg-*`) target icons and decorative elements that sit inside containers, while text utilities (`text-*`) apply directly to typographic content. Both handle color adaptation, but they serve different semantic purposes—foreground utilities often pair with `text-inverse` to ensure contrast against colored backgrounds, whereas text utilities define the primary reading color for content blocks.

### How does the design system detect and apply dark mode?

The system uses a custom Tailwind variant named `theme-dark` that activates when an ancestor element carries the `data-theme="dark"` attribute. Every functional token includes an `@variant theme-dark` block that overrides the base color, allowing the UI to switch themes without JavaScript manipulation of individual classes.

### Where are the raw color values defined in the Maybe codebase?

Raw color values reside in the `@theme` block of [`app/assets/tailwind/maybe-design-system.css`](https://github.com/maybe-finance/maybe/blob/main/app/assets/tailwind/maybe-design-system.css) (lines 21–45). This declaration establishes CSS custom properties for the entire palette, which functional tokens in the utility files reference via `var()` notation or Tailwind's `@apply` directive.

### Can I use these tokens outside of Rails ViewComponents?

Yes—the tokens compile to standard CSS classes during the Tailwind build process. While the Maybe app primarily consumes them in ERB templates and ViewComponents, any HTML markup can utilize classes like `bg-surface` or `text-primary` provided the compiled CSS is loaded. The tokens follow standard Tailwind conventions, making them portable to other contexts within the Rails asset pipeline.