GitHub

Semantic Colors v2 Migration Guide

This guide helps you migrate from the old numbered color system to the new semantic color levels in Frontile v0.18+.

Overview

The semantic color system has been redesigned to use named levels instead of numbered scales. This provides:

  • Better semantic meaning: Names describe the emphasis rank (subtle, soft, firm, strong), not a numbered scale
  • Improved accessibility: Contrast colors are explicitly defined
  • Theme consistency: Colors adapt better to light/dark modes
  • Clearer usage patterns: Names indicate intended use cases

Breaking Changes

Color Categories Renamed

Old Name New Name Notes
default-* neutral-* Renamed to better indicate non-semantic UI elements

Inverted Surfaces

There is no inverse color category. Content on an inverted surface is handled one of two ways.

For a single element sitting on a filled background, use the automatic contrast color for that background level:

<div class="bg-neutral-strong text-on-neutral-strong">
  Content with guaranteed contrast
</div>

For a whole region that should read as the opposite theme — a dark panel in light mode, or a light one in dark mode — add the theme-inverse class. Every semantic token inside it resolves to its other-theme value, so ordinary classes keep working:

<div class="theme-inverse bg-surface-canvas p-6">
  <h3 class="text-neutral-bolder">Reads as dark in light mode</h3>
  <p class="text-neutral-strong">And as light in dark mode.</p>
</div>

theme-inverse is a selector the theme defines, not a color. It is unrelated to the inverse category that appeared briefly during the 0.18 alpha cycle and was removed before release.

Surface Roles Simplified

The surface system (introduced during the v0.18 alpha cycle) has been simplified before its first stable release:

Removed Replacement Notes
surface-solid-0surface-solid-11 (removed) The 12-step scale is gone. Use a surface role (app, canvas, card, modal, input) instead
text-surface-solid-* text-neutral-* or text-on-surface-modal Use the neutral text scale for body copy, or the auto-generated on-color for content on surface-modal
surface-popover surface-modal Popovers, dropdowns, modals, and drawers now share one "highest elevation" surface
surface-overlay-content surface-modal Same merge as above — this was already identical in value to surface-popover
surface-panel (removed) Had no real consumer; use surface-card for elevated sidebar/panel containers
surface-inset (removed) Had no real consumer; use surface-overlay-subtle/soft for recessed translucent wells
surface-overlay-inverse-* surface-overlay-strong (for backdrops) Had no real consumer. If you need a heavy backdrop tint, use strong; for a normal darken/lighten step, use subtle/soft/mild/firm
(new) surface-input Dedicated surface for text inputs, checkboxes, and radios (previously surface-solid-0)

surface-card also changed value: it's now translucent (white @ 90% in light mode) instead of fully opaque, and resolves to base-800 in dark mode instead of base-700.

// Before
<div class="bg-surface-popover rounded-md shadow-lg">Menu</div>
<div class="bg-surface-overlay-content rounded-lg">Modal</div>
<input class="bg-surface-solid-0" />

// After
<div class="bg-surface-modal rounded-md shadow-lg">Menu</div>
<div class="bg-surface-modal rounded-lg">Modal</div>
<input class="bg-surface-input" />

See the Surfaces guide for the full, current token set.

Surface Overlay Levels Renamed

The translucent overlay levels were renamed to line up with the named levels used by every other color category (subtlesoftmildfirmstrong). The values did not change — only the names:

Old New Notes
surface-overlay-medium surface-overlay-mild Same value, renamed to match the shared level names
surface-overlay-strong surface-overlay-firm Same value, shifted down one name
surface-overlay-scrim surface-overlay-strong The heavy modal/drawer backdrop is now just the top level
// Before
<div class="bg-surface-overlay-medium" />
<div class="border-surface-overlay-strong" />
<div class="fixed inset-0 bg-surface-overlay-scrim" />

// After
<div class="bg-surface-overlay-mild" />
<div class="border-surface-overlay-firm" />
<div class="fixed inset-0 bg-surface-overlay-strong" />

New: Surface Lift

surface-lift-* is a new translucent family alongside surface-overlay-*, with the same five levels. Overlay darkens in light mode and lightens in dark mode, pressing an element into the page; lift does the opposite, so the element floats above what it covers.

// Recedes — hover states, section fills
<div class="bg-surface-overlay-soft" />

// Floats — frosted panels, sticky headers, captions over media
<header class="bg-surface-lift-firm backdrop-blur-md" />

Nothing is deprecated by this: existing surface-overlay-* usage is unaffected. See the Surfaces guide for the level table and usage guidance.

Color Scale Changed

The numbered scale (50, 100, 200, ..., 950) has been replaced with named levels:

Old Pattern New Pattern Description
{color}-{number} {color}-{level} Named emphasis levels
{color}-foreground on-{color}-{level} Automatic contrasting colors (black/white)

Color Level Mapping

Understanding the New Levels

Each semantic color is organized into two bands that share one emphasis vocabulary but are consumed by different CSS properties. Names describe emphasis rank, never brightness — a level may be dark in light mode and light in dark mode, since interaction direction inverts between the two schemes.

Surface band — fills for backgrounds and decorative borders (low → high emphasis):

  • subtle — Faintest tint, for hairline backgrounds and tonal rests
  • muted — Light tint, for hover on tonal surfaces
  • soft — Soft fill, the hover step for solid fills
  • DEFAULT — Resting fill, the bare bg-{color} token
  • firm — Most emphatic fill, for pressed/active backgrounds

Ink band — legible foregrounds for text and outlined borders (low → high emphasis):

  • strong — Default legible foreground, for body text and outlined-control text/borders
  • bolder — Highest-emphasis foreground, for headings and hover/active text

on-{color}-{level} — Automatic contrasting color (black or white) calculated for optimal WCAG contrast on the specified background level.

Migration Mapping Table

Neutral (formerly Default)

Old Class New Class Context
bg-default-50 bg-neutral-subtle Very light backgrounds
bg-default-100 bg-neutral-subtle Light backgrounds, borders
bg-default-200 bg-neutral-subtle Light surfaces
bg-default-300 bg-neutral-soft Moderate backgrounds
bg-default-400 bg-neutral-soft Borders, dividers
bg-default-500 bg-neutral-soft or bg-neutral Medium emphasis
bg-default-600 bg-neutral Standard emphasis
bg-default-700 bg-neutral or bg-neutral-strong Strong emphasis
bg-default-800 bg-neutral-strong Maximum emphasis, buttons
bg-default-900 bg-neutral-strong Darkest backgrounds
bg-default-950 bg-neutral-strong Maximum contrast
text-default text-neutral-strong or text-neutral-bolder Body text
text-default-foreground text-on-neutral Contrasting text on bg
border-default border-neutral-soft Standard borders

Primary

Old Class New Class Context
bg-primary-500 bg-primary-soft or bg-primary Hover states
bg-primary-600 bg-primary Default buttons
bg-primary-700 bg-primary or bg-primary-strong Strong emphasis
bg-primary-800 bg-primary-strong Active/pressed states
text-primary-foreground text-on-primary Contrasting text on bg
ring-primary-500 ring-primary-soft Focus rings

Success

Old Class New Class Context
bg-success-100 bg-success-subtle Alert backgrounds
bg-success-500 bg-success-soft or bg-success Moderate emphasis
bg-success-600 bg-success or bg-success-strong Buttons, hover
bg-success-800 bg-success-strong Active states
text-success-foreground text-on-success Contrasting text on bg
ring-success-500 ring-success-soft Focus rings

Warning

Old Class New Class Context
bg-warning-100 bg-warning-subtle Alert backgrounds
bg-warning-500 bg-warning-soft or bg-warning Moderate emphasis
bg-warning-600 bg-warning or bg-warning-strong Buttons, hover
bg-warning-800 bg-warning-strong Active states
text-warning-foreground text-on-warning Contrasting text on bg

Danger

Old Class New Class Context
bg-danger-100 bg-danger-subtle Alert backgrounds
bg-danger-500 bg-danger-soft or bg-danger Moderate emphasis
bg-danger-600 bg-danger or bg-danger-strong Buttons, hover
bg-danger-800 bg-danger-strong Active states
text-danger-foreground text-on-danger Contrasting text on bg

Common UI Pattern Migrations

Buttons

Default Button

// Before
<button class="bg-default-800 text-default-100 hover:bg-default-800/80">
  Click me
</button>

// After
<button class="bg-neutral-strong text-on-neutral-strong hover:bg-neutral">
  Click me
</button>

Primary Button

// Before
<button class="bg-primary text-primary-foreground hover:bg-primary-500">
  Submit
</button>

// After
<button class="bg-primary text-on-primary hover:bg-primary-soft">
  Submit
</button>

Outlined Button

// Before
<button class="border-primary text-primary hover:bg-primary hover:text-primary-foreground">
  Cancel
</button>

// After
<button class="border-primary text-primary hover:bg-primary hover:text-on-primary">
  Cancel
</button>

Alerts and Notifications

Success Alert

// Before
<div class="bg-success-100 border-success-500 text-success-900">
  <Icon class="text-success" />
  <p>Success message</p>
</div>

// After
<div class="bg-success-subtle border-success-soft text-on-success-subtle">
  <Icon class="text-success" />
  <p>Success message</p>
</div>

Error Alert

// Before
<div class="bg-danger-100 border-danger text-danger-foreground">
  Error occurred
</div>

// After
<div class="bg-danger-subtle border-danger text-on-danger-subtle">
  Error occurred
</div>

Form Inputs

// Before
<input class="border-default-700 focus:border-primary text-default-900" />

// After
<input class="border-neutral-soft focus:border-primary text-neutral-strong" />

Text Hierarchy

// Before
<h1 class="text-default-900">Heading</h1>
<p class="text-default-700">Body text</p>
<span class="text-default-500">Caption</span>

// After
<h1 class="text-neutral-bolder">Heading</h1>
<p class="text-neutral-strong">Body text</p>
<span class="text-neutral-firm">Caption</span>

Badges

// Before
<span class="bg-success text-success-foreground">
  Active
</span>

// After
<span class="bg-success-strong text-on-success-strong">
  Active
</span>

Dark Mode Considerations

The new semantic colors automatically adapt to dark mode. Key changes:

  • In dark mode, strong becomes the lightest shade (inverted from light mode)
  • subtle uses darker base colors with lower alpha
  • on-{color}-{level} automatically maintains WCAG accessibility standards

No changes needed — your dark mode classes will work automatically:

// Both light and dark modes handled
<div class="bg-neutral-subtle text-neutral-strong">
  Content
</div>

Migration Strategy

Step 1: Identify Old Patterns

Search your codebase for old color patterns:

# Find numbered color classes
grep -r "default-[0-9]" --include="*.{tsx,ts,gts,gjs,md}"
grep -r "primary-[0-9]" --include="*.{tsx,ts,gts,gjs,md}"

# Find foreground classes
grep -r "foreground" --include="*.{tsx,ts,gts,gjs,md}"

Step 2: Update Theme Configuration

If you've customized Frontile's theme, update your color definitions:

// Before
import { themeColors } from '@frontile/theme';

colors: {
  default: themeColors.light.default,
  primary: themeColors.light.primary,
}

// After
import semanticColors from '@frontile/theme/colors/semantic';

colors: {
  neutral: semanticColors.light.neutral,
  primary: semanticColors.light.primary,
}

Step 3: Update Component Styles

Replace old color classes with new semantic levels:

  1. Start with theme components (highest impact)
  2. Move to your custom components
  3. Update documentation and examples
  4. Update tests

Step 4: Test Thoroughly

  • Visual regression: Check all components in both light and dark modes
  • Accessibility: Verify contrast ratios still meet WCAG AA standards
  • Interactive states: Test hover, focus, active, and disabled states

Interactive states

The tables above map each old number to a resting level. They deliberately do not try to guess hover, pressed, or disabled variants, because the old numbered scale encoded those by stepping the number and the new one has dedicated levels:

Old New Level
hover:bg-primary-500 on a bg-primary-600 element hover:bg-primary-soft soft is the hover step for solid fills
active:bg-primary-800 active:bg-primary-firm firm is the most emphatic fill
hover:bg-default-200 on a tonal surface hover:bg-neutral-muted muted is the hover step for tonal surfaces
A fill between resting and firm bg-primary-mild mild

If you had a three-state solid button — bg-primary-600, hover:bg-primary-500, active:bg-primary-700 — it becomes bg-primary, hover:bg-primary-soft, active:bg-primary-firm.

Automated migration

There is no supported codemod for this. A find-and-replace can do the unambiguous half — the category rename and the foreground suffix — but it cannot pick emphasis levels, and it cannot tell a resting fill from a hover one:

# The mechanical part only. Review everything it touches.
# GNU sed: drop the '' after -i
find . -type f \( -name "*.hbs" -o -name "*.gts" -o -name "*.gjs" -o -name "*.ts" \) \
  -exec sed -i '' \
  -e 's/\bbg-default-/bg-neutral-/g' \
  -e 's/\btext-default-/text-neutral-/g' \
  -e 's/\bborder-default-/border-neutral-/g' \
  -e 's/\bbg-background\b/bg-surface-canvas/g' \
  {} +

Then find what's left by hand — anything still carrying a number needs a level chosen for it:

grep -rnE '(bg|text|border|ring|from|to|via)-(neutral|primary|secondary|tertiary|success|warning|danger)-[0-9]{2,3}' .

That grep is worth keeping in CI for a release or two. A leftover numbered class produces no CSS and no error, so it will not show up any other way.

Decision Guide

When choosing between levels, ask:

Background Colors

  • Maximum emphasis button?{color}-strong
  • Standard button/element?{color} (DEFAULT)
  • Hover state?{color}-soft
  • Light alert background?{color}-subtle

Text Colors

  • On colored background?on-{color}-{level} (automatically white or black based on WCAG contrast)
  • On neutral background?neutral-bolder (heading), neutral-strong (body), neutral-firm (caption)

Borders

  • Colored semantic borders?{color} or {color}-soft
  • Neutral dividers?neutral-soft or neutral-subtle
Released under MIT License - Created by Josemar Luedke