# Upgrading to v0.18 Source: /docs/migrations/v0-18/index.md # Upgrading to v0.18 Frontile v0.18 is a major release with several breaking changes to theming, colors, DOM anatomy, and component APIs. This guide gives an overview of all of them and links to a detailed migration guide for each. ## What breaks, and how loudly Two of these changes stop your app from working. The rest are cleanup, and one of them is optional for the whole 0.18 line. | Change | If you skip it | Fails how? | | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------- | | Missing `@import "@frontile/theme"` | No Frontile styles at all | Loudly — the app is visibly unstyled | | Numbered color classes (`bg-primary-500`) | Those elements render unstyled | **Silently** | | `bg-background` → `bg-surface-canvas` | Those elements render unstyled | **Silently** | | `--frontile-*` variable references | The declaration is dropped | **Silently** | | Nested `LayoutTheme` config | Build or type error | Loudly, and only if you customize the theme | | `@frontile/*` package imports | Nothing — they still work in 0.18.x | Deprecation warning only | | `@frontile/forms-legacy` / `@frontile/changeset-form` | Nothing — they still work in 0.18.x, but are removed in 0.19.0 | Deprecation warning only | | Derived border-radius scale | Slightly rounder corners on menus and small marks | Visual only — nothing to fix | | Multi-select renders chips | Multi-selects look different — selections become removable chips | Visual only — nothing to fix | | Filtered lists are ranked | `Autocomplete`/`Select` list the closest match first instead of source order | Visual only — nothing to fix | | `text-body-pico`/`-nano`/`-micro` | Those elements render unstyled | **Silently** | | Body text-scale font sizes corrected | Body text (`xs` through `xl`) renders larger than intended | Visual only — nothing to fix | | `data-fr-*` / mismatched `data-component` / most `data-test-id` selectors | Those selectors stop matching | **Silently**, if you select Frontile-rendered elements yourself | **The silent ones are the reason to take this in order.** A class Tailwind can't resolve produces no error, no warning, and no CSS — the element just renders without the style you asked for. We hit this in Frontile's own documentation during the 0.18 work: thirteen demos were shipping with `bg-success-50` and `bg-warning-50`, rendering with no background at all, and nobody noticed until a linter went looking. Budget time for looking at the result, not just for the find-and-replace. ## Migration order ### 1. Theme configuration — do this first Nothing else is verifiable until the theme loads. Add the CSS import, and update your config shape if you customize it. **Impact:** required. **Time:** 15–30 minutes. - Add `@import "@frontile/theme"` to your `app/styles/app.css` - `LayoutTheme` moved from a flat to a nested structure (`hoverOpacity` becomes `opacity: { hover }`) - CSS variables lost the `--frontile-` prefix; colors gained `--color-` **See:** [Theme Configuration](./theme-configuration.md) ### 2. Colors and surfaces — the bulk of the work One pass over your classes and custom CSS. Everything in this step fails silently, so verify visually as you go rather than at the end. **Impact:** required, and touches every colored element. **Time:** an hour or two for a small app, a day or more for a large one. - Numbered scales (`50`, `100`, … `950`) become named levels (`subtle`, `muted`, `soft`, `mild`, DEFAULT, `firm`, `strong`, `bolder`) - `default-*` becomes `neutral-*` - `bg-background` becomes `bg-surface-canvas` - `{color}-foreground` and `contrast-1`/`contrast-2` become `on-{color}-{level}` - `text-foreground` is gone - `theme-inverse` flips every semantic token in a region, for panels that should read as the opposite theme Colors also moved from HSL to OKLCH. That part is automatic — you may notice small perceptual differences, but there is nothing to change. **See:** [Semantic Colors](./semantic-colors.md) ### 3. Package consolidation — optional, any time before 0.19 Seven `@frontile/*` component packages became the single `frontile` package. The old packages still re-export everything and only log a deprecation warning, so **your imports keep working for all of 0.18.x.** Leave this until the app builds and looks right. It rewrites every Frontile import in your codebase, and doing that first buries the changes above in a diff you can't read — which matters precisely because those changes fail silently. **Impact:** none until 0.19. **Time:** 10–30 minutes, mostly automated. **See:** [Package Consolidation](./package-consolidation.md) ### Forms Legacy & Changeset Form — optional, any time before 0.19 `@frontile/forms-legacy` and `@frontile/changeset-form` are deprecated and, unlike the wrapper packages above, are being removed entirely in 0.19.0, not just re-exported. If you depend on either, migrate to the modern `frontile` forms (Form + Field pattern with Valibot, Zod, or a custom validator). **Impact:** none until 0.19, unless you already depend on one of these packages. **Time:** varies with form count and validation complexity — see each guide's migration checklist. **See:** [Forms Legacy Migration Guide](./forms-legacy.md), [Changeset Form Migration Guide](./changeset-form.md) ### Filtered lists are ranked by relevance — visual only `Autocomplete` and filterable `Select` used to filter with a case-insensitive "contains" check and render whatever survived **in the order you passed it**. They now score each option and list the closest match first, and additionally match acronyms (`nz` finds "New Zealand"). Nothing that matched before stops matching, so there is nothing to fix unless you pass your own `@filter` — which now also accepts a score: ```ts filter?: (itemValue: string, inputValue: string) => boolean | number; ``` **See:** [Filter Ranking](./filter-ranking.md) ### 4. Multi-select renders chips — visual only `Select` with `@selectionMode="multiple"` used to show its selections as a comma-joined string inside the trigger. It now renders each selection as a removable [Chip](https://frontile.dev/docs/components/buttons/chip), so users can drop one selection without reopening the dropdown. The control grows taller as chips wrap, and a chips field is deliberately the same height as a same-size single select (46px at `md`). Nothing breaks, but the field is taller and looks different. If a layout depends on the old fixed-height appearance, opt back out per-select: ```gts // After
Menu
Modal
``` See the [Surfaces](../../theming/design-tokens/surfaces.md) 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 (`subtle` → `soft` → `mild` → `firm` → `strong`). 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 | ```gts // Before
// After
``` ### 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. ```gts // Recedes — hover states, section fills
// Floats — frosted panels, sticky headers, captions over media
``` Nothing is deprecated by this: existing `surface-overlay-*` usage is unaffected. See the [Surfaces](../../theming/design-tokens/surfaces.md) 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 ```gts // Before // After ``` #### Primary Button ```gts // Before // After ``` #### Outlined Button ```gts // Before // After ``` ### Alerts and Notifications #### Success Alert ```gts // Before

Success message

// After

Success message

``` #### Error Alert ```gts // Before
Error occurred
// After
Error occurred
``` ### Form Inputs ```gts // Before // After ``` ### Text Hierarchy ```gts // Before

Heading

Body text

Caption // After

Heading

Body text

Caption ``` ### Badges ```gts // Before Active // After Active ``` ## 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: ```gts // Both light and dark modes handled
Content
``` ## Migration Strategy ### Step 1: Identify Old Patterns Search your codebase for old color patterns: ```bash # 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: ```typescript // 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: ```bash # 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: ```bash 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` --- # Theme Configuration Migration Source: /docs/migrations/v0-18/theme-configuration.md # Theme Configuration Migration Guide This guide helps you migrate your Frontile theme configuration to v0.18, which adopts Tailwind v4's CSS-first configuration approach. ## Overview Frontile v0.18 updates the theme system to align with Tailwind CSS v4's CSS-first configuration. Theme values are now CSS variables you can read and override directly in your stylesheets, instead of JavaScript config processed at build time, and the `--frontile-` prefix is gone from variable names. ## Breaking Changes ### 1. CSS Import Required **You must add** `@import "@frontile/theme"` to your application's entry stylesheet — `app/styles/app.css` on a classic Ember build, `app/app.css` under Vite. It has to come after the `@plugin` line. #### Before (v0.17) ```css @import 'tailwindcss' source('../../'); @plugin "@frontile/theme/plugin/default"; ``` #### After (v0.18) ```css @import 'tailwindcss' source('../../'); @plugin "@frontile/theme/plugin/default"; @import "@frontile/theme"; ``` **Why:** The `@import "@frontile/theme"` statement loads Frontile's base CSS styles, custom variants, and animations that are now CSS-based rather than plugin-generated. ### 2. Tailwind Content Detection This one is a consequence of [package consolidation](./package-consolidation.md) rather than of the theme itself, and it is the easiest to miss. Tailwind v4 skips `node_modules` when it scans for classes. Frontile's own component templates live there, so they have to be pointed at explicitly or their classes are purged and components render unstyled — with no error, since a purged class is simply a class that no longer exists. In v0.17 one `@source` covered everything, because every component shipped under the `@frontile` scope: ```css @source '../../node_modules/@frontile'; ``` In v0.18 the components moved to the unscoped `frontile` package, so that line no longer reaches them: ```css @source '../../node_modules/frontile'; /* Keep the scoped line too if you use @frontile/forms-legacy or @frontile/changeset-form, which remain separate packages. */ @source '../../node_modules/@frontile'; ``` Paths are relative to the CSS file, so adjust the depth to match where yours lives. This applies whether or not you have migrated your imports: the classes come from `frontile` either way, because the old packages only re-export from it. **If components look unstyled after upgrading and the theme import is present, this is almost always why.** ### 3. CSS Variable Names The `--frontile-` prefix is gone. What replaces it depends on the kind of token: | Token kind | v0.17 | v0.18 | | --- | --- | --- | | Colors | `--frontile-primary-500` | `--color-primary-firm` (a `--color-` prefix, and a named level) | | Everything else | `--frontile-hover-opacity` | `--opacity-hover` (no prefix) | Colors are the case to watch. They did not simply lose a prefix — they gained `--color-`, and the numbered scale they used is gone, so there is no `--color-primary-500` either. Pick the level that matches the emphasis you wanted; see the [semantic colors guide](./semantic-colors.md) for the mapping. #### Before (v0.17) ```css .my-component { background: var(--frontile-primary-500); opacity: var(--frontile-hover-opacity); } ``` #### After (v0.18) ```css .my-component { background: var(--color-primary); opacity: var(--opacity-hover); } ``` **Migration:** search your codebase for `--frontile-`. Nothing errors if you miss one — an undefined custom property silently resolves to nothing, so the declaration is simply dropped: ```bash grep -rn -- "--frontile-" --include="*.css" --include="*.scss" \ --include="*.gts" --include="*.gjs" --include="*.ts" --include="*.js" . ``` ### 4. LayoutTheme Interface (Nested Structure) If you're using TypeScript and customizing the theme configuration, the `LayoutTheme` interface has changed from flat to nested. #### Before (v0.17) ```typescript import { frontile } from '@frontile/theme/plugin'; module.exports = frontile({ hoverOpacity: 0.9, disabledOpacity: 0.4 }); ``` #### After (v0.18) ```typescript import { frontile } from '@frontile/theme/plugin'; module.exports = frontile({ opacity: { hover: 0.9, disabled: 0.4 } }); ``` **Why:** The nested structure provides better organization for related theme properties and aligns with CSS custom property conventions. ### 5. Border Radius Is Now a Derived Scale Every `rounded-*` step is now `calc(var(--radius) * n)` instead of a hardcoded value, so `--radius` is a single knob for how round the whole library looks. #### Before (v0.17) ```css --radius-xs: 1px; --radius-sm: 2px; --radius-md: 4px; --radius: 8px; --radius-xl: 12px; --radius-2xl: 16px; --radius-default: 20px; ``` #### After (v0.18) ```css --radius: 0.5rem; /* 8px — the base */ --radius-xs: calc(var(--radius) * 0.25); /* 2px */ --radius-sm: calc(var(--radius) * 0.5); /* 4px */ --radius-md: calc(var(--radius) * 0.75); /* 6px */ --radius-lg: calc(var(--radius) * 1); /* 8px */ --radius-xl: calc(var(--radius) * 1.5); /* 12px */ --radius-2xl: calc(var(--radius) * 2); /* 16px */ --radius-3xl: calc(var(--radius) * 3); /* 24px */ --radius-4xl: calc(var(--radius) * 4); /* 32px */ --radius-default: calc(var(--radius) * 2.5); /* 20px */ ``` **What actually changes visually.** Three steps got slightly larger, bringing the scale back in line with stock Tailwind v4: | Utility | v0.17 | v0.18 | | --- | --- | --- | | `rounded-xs` | 1px | 2px | | `rounded-sm` | 2px | 4px | | `rounded-md` | 4px | 6px | Everything from `rounded` upward keeps its value. If you relied on `rounded-sm` being 2px, use `rounded-xs`, or pin the step explicitly: ```css @theme { --radius-sm: 2px; /* opt this one step out of the derived scale */ } ``` **Popover surfaces are rounder.** Dropdown and Popover panels moved from `rounded-sm` (2px) to `rounded-xl` (12px), Listbox and menu items to `rounded-lg` (8px), and NotificationCard to `rounded-xl`. Select and Autocomplete inherit the change through Popover. This is a visual change only — no API moved. To go back to square-ish menus, set `--radius: 0`, or override the component classes via `@classes`. **Why:** The old scale was smaller than stock Tailwind at every step, which made `rounded-sm` a surprise for anyone reading the class name, and left popover surfaces visibly sharper than the Modal and Drawer they sit alongside. Deriving the scale from one value fixes the inconsistency and makes overall roundness a one-line theme decision. **Dialing it from the plugin config**, including per theme: ```typescript module.exports = frontile({ layout: { radius: { DEFAULT: '0.75rem' } } }); ``` ## Migration Steps ### Step 1: Update CSS Imports Add the `@import "@frontile/theme"` statement to your entry stylesheet: ```css @import 'tailwindcss' source('../../'); @plugin "@frontile/theme/plugin/default"; @import "@frontile/theme"; /* Your custom styles */ ``` **For custom theme configurations**, update to reference your custom config file: ```css @import 'tailwindcss' source('../../'); @plugin "./../../frontile.js"; @import "@frontile/theme"; ``` ### Step 2: Update CSS Variable References If you reference Frontile's CSS variables directly in your own stylesheets or components: 1. **Find all references:** ```bash grep -rn -- "--frontile-" --include="*.css" --include="*.scss" \ --include="*.gts" --include="*.gjs" . ``` 2. **Rename them.** Colors take a `--color-` prefix and a named level; other tokens just drop the prefix: ```diff - background: var(--frontile-primary); + background: var(--color-primary); - border-color: var(--frontile-primary-700); + border-color: var(--color-primary-firm); - opacity: var(--frontile-hover-opacity); + opacity: var(--opacity-hover); ``` 3. **Update computed styles in JavaScript:** ```diff - const color = getComputedStyle(el).getPropertyValue('--frontile-primary-500'); + const color = getComputedStyle(el).getPropertyValue('--color-primary'); ``` `getPropertyValue` returns an empty string for a variable that doesn't exist, so a missed rename here reads as "no color" rather than throwing. ### Step 3: Update Theme Configuration (If Customized) If you have a custom `frontile.js` configuration file, update the structure: #### Before (v0.17) ```javascript const { frontile } = require('@frontile/theme/plugin'); module.exports = frontile({ hoverOpacity: 0.9, disabledOpacity: 0.4, // other flat properties }); ``` #### After (v0.18) ```javascript const { frontile } = require('@frontile/theme/plugin'); module.exports = frontile({ opacity: { hover: 0.9, disabled: 0.4 }, // other nested properties }); ``` **Common property mappings:** | Old Property (v0.17) | New Property (v0.18) | | --- | --- | | `hoverOpacity` | `opacity.hover` | | `disabledOpacity` | `opacity.disabled` | ### Step 4: Test Your Application After making these changes: 1. **Rebuild your application** to ensure all changes are applied 2. **Test interactive states** (hover, disabled) to verify opacity values 3. **Check custom styled components** that reference CSS variables directly 4. **Verify in both light and dark modes** if your app supports theming ## Common Issues and Solutions ### Issue: Styles Not Applied **Problem:** Components don't have expected Frontile styles **Solution:** Ensure you've added `@import "@frontile/theme"` to your CSS file. This import is now required. ### Issue: CSS Variables Undefined **Problem:** Browser console shows `undefined` for CSS variables **Solution:** 1. Check that you removed the `--frontile-` prefix 2. Verify the import order in your CSS file (plugin first, then theme import) 3. Rebuild your application ### Issue: TypeScript Errors in Config **Problem:** TypeScript errors in `frontile.js` configuration **Solution:** Update to the nested structure. Ensure you're using `opacity.hover` instead of `hoverOpacity`. ## Migration Checklist Use this checklist to track your theme configuration migration: - [ ] Added `@import "@frontile/theme"` to app.css - [ ] Removed `--frontile-` prefix from all CSS variable references - [ ] Updated `frontile.js` config to use nested structure (if applicable) - [ ] Rebuilt application and verified styles are applied - [ ] Tested hover and disabled states - [ ] Verified custom components using CSS variables - [ ] Tested in both light and dark modes (if applicable) ## Need Help? If you encounter issues: - Review the [Theme documentation](../../theming/overview.md) for the latest configuration options - Check the [Getting Started guide](../../get-started/index.md) for setup examples - Search or create an issue on [GitHub](https://github.com/josemarluedke/frontile/issues) --- # Package Consolidation Source: /docs/migrations/v0-18/package-consolidation.md # Package Consolidation Migration Guide ## Overview Starting with version **0.18.0**, Frontile consolidates seven separate `@frontile/*` packages into a single `frontile` package: - `@frontile/buttons` - `@frontile/collections` - `@frontile/forms` - `@frontile/overlays` - `@frontile/notifications` - `@frontile/status` - `@frontile/utilities` > **This one is optional for all of 0.18.x.** The old packages re-export > everything and only log a deprecation warning, so your imports keep working > until 0.19. Do the [theme configuration](./theme-configuration.md) and > [color](./semantic-colors.md) migrations first — those actually break things, > and mostly do it silently. Rewriting every import before them produces a diff > large enough to hide the changes you need to review. **Why?** Modern Ember.js applications use explicit imports via `.gts`/`.gjs` template tag format. With explicit imports, bundlers can tree-shake unused code automatically, making the multi-package architecture unnecessary overhead. A single package simplifies installation, version management, and dependency resolution without sacrificing bundle size. **Timeline:** | Version | What happens | |---------|--------------| | **0.18.0** | The old `@frontile/*` packages become thin deprecation wrappers that re-export from `frontile`. All existing import paths continue to work, but emit deprecation warnings at build time. | | **0.19.0** | The old `@frontile/*` wrapper packages are removed. You must update imports to use `frontile` directly. | ## Quick Start Update your app in two steps: ### 1. Swap packages ```bash # Remove old packages npm uninstall @frontile/buttons @frontile/collections @frontile/forms \ @frontile/overlays @frontile/notifications @frontile/status @frontile/utilities # Install the consolidated package (if not already installed) npm install frontile @frontile/theme ``` If you use **pnpm** or **yarn**, substitute the appropriate commands: ```bash # pnpm pnpm remove @frontile/buttons @frontile/collections @frontile/forms \ @frontile/overlays @frontile/notifications @frontile/status @frontile/utilities pnpm add frontile @frontile/theme # yarn yarn remove @frontile/buttons @frontile/collections @frontile/forms \ @frontile/overlays @frontile/notifications @frontile/status @frontile/utilities yarn add frontile @frontile/theme ``` ### 2. Update imports Replace `@frontile/` with `frontile/` (or just `frontile`) in your source files. See the [Import Path Changes](#import-path-changes) section below for details. ## Import Path Changes Three import styles are supported with the consolidated package: ### Flat barrel import Import everything from one place: ```typescript import { Button, Modal, Input, ProgressBar } from 'frontile'; ``` ### Scoped sub-exports (by category) Import from a category-specific path. This mirrors the old package names without the `@frontile/` prefix: ```typescript import { Button, ButtonGroup, Chip } from 'frontile/buttons'; import { Modal, Drawer, Overlay, Popover } from 'frontile/overlays'; import { Input, Select, Checkbox, Textarea } from 'frontile/forms'; import { Table, Listbox, Dropdown } from 'frontile/collections'; import { NotificationCard, NotificationsContainer } from 'frontile/notifications'; import { ProgressBar } from 'frontile/status'; import { Avatar, Collapsible, Divider, Spinner } from 'frontile/utilities'; ``` ### Direct component file imports For maximum control and tree-shaking, import directly from the component file: ```typescript import Button from 'frontile/components/buttons/button'; import Modal from 'frontile/components/overlays/modal'; import Header from 'frontile/components/overlays/modal/header'; import Input from 'frontile/components/forms/input'; ``` ## Import Mapping Table The table below shows how old import paths map to the new paths. ### Barrel imports | Old Import | New Import | |---|---| | `from '@frontile/buttons'` | `from 'frontile/buttons'` or `from 'frontile'` | | `from '@frontile/forms'` | `from 'frontile/forms'` or `from 'frontile'` | | `from '@frontile/collections'` | `from 'frontile/collections'` or `from 'frontile'` | | `from '@frontile/overlays'` | `from 'frontile/overlays'` or `from 'frontile'` | | `from '@frontile/notifications'` | `from 'frontile/notifications'` or `from 'frontile'` | | `from '@frontile/status'` | `from 'frontile/status'` or `from 'frontile'` | | `from '@frontile/utilities'` | `from 'frontile/utilities'` or `from 'frontile'` | ### Sub-path imports | Old Import | New Import | |---|---| | `from '@frontile/overlays/components/modal'` | `from 'frontile/components/overlays/modal'` | | `from '@frontile/overlays/components/modal/header'` | `from 'frontile/components/overlays/modal/header'` | | `from '@frontile/overlays/components/drawer'` | `from 'frontile/components/overlays/drawer'` | | `from '@frontile/overlays/components/overlay'` | `from 'frontile/components/overlays/overlay'` | | `from '@frontile/overlays/components/popover'` | `from 'frontile/components/overlays/popover'` | | `from '@frontile/forms/test-support'` | `from 'frontile/test-support'` | | `from '@frontile/utilities/utils/safe-styles'` | `from 'frontile/utils/safe-styles'` | | `from '@frontile/notifications/services/notifications'` | `from 'frontile/services/notifications'` | ## Automated Migration Script Save the following script as `migrate-frontile.sh` and run it from your project root to update all import paths automatically. ```bash #!/bin/bash # Migrate Frontile imports from @frontile/* to frontile # Run from your project root # # Usage: # chmod +x migrate-frontile.sh # ./migrate-frontile.sh set -euo pipefail echo "Migrating @frontile/* imports to frontile..." # Sub-path imports (must run BEFORE barrel imports to avoid partial matches) find app tests -name '*.ts' -o -name '*.gts' -o -name '*.js' -o -name '*.gjs' | \ xargs sed -i '' \ -e "s|from '@frontile/overlays/components/|from 'frontile/components/overlays/|g" \ -e "s|from '@frontile/notifications/services/|from 'frontile/services/|g" \ -e "s|from '@frontile/forms/test-support|from 'frontile/test-support|g" \ -e "s|from '@frontile/utilities/utils/|from 'frontile/utils/|g" \ -e "s|from '@frontile/collections/utils/|from 'frontile/utils/|g" # Barrel imports find app tests -name '*.ts' -o -name '*.gts' -o -name '*.js' -o -name '*.gjs' | \ xargs sed -i '' \ -e "s|from '@frontile/buttons'|from 'frontile/buttons'|g" \ -e "s|from '@frontile/collections'|from 'frontile/collections'|g" \ -e "s|from '@frontile/forms'|from 'frontile/forms'|g" \ -e "s|from '@frontile/overlays'|from 'frontile/overlays'|g" \ -e "s|from '@frontile/notifications'|from 'frontile/notifications'|g" \ -e "s|from '@frontile/status'|from 'frontile/status'|g" \ -e "s|from '@frontile/utilities'|from 'frontile/utilities'|g" echo "Done! Review the changes with: git diff" ``` **Note:** The `sed -i ''` syntax is for macOS. On Linux, use `sed -i` (without the empty quotes) instead. ## Packages NOT Affected The following packages are **not** part of this consolidation. Their imports remain unchanged: - **`@frontile/theme`** -- The styling system. Stays as a separate package because it provides Tailwind CSS configuration and has a distinct role in the build pipeline. - **`@frontile/forms-legacy`** -- Legacy form components. Deprecated, and removed in 0.19.0 alongside the wrapper packages above. See the [Forms Legacy migration guide](./forms-legacy.md). - **`@frontile/changeset-form`** -- Changeset integration. Deprecated, and removed in 0.19.0 alongside the wrapper packages above. See the [Changeset Form migration guide](./changeset-form.md). ## Deprecation Timeline | Version | Status | |---------|--------| | **0.18.0** | Old `@frontile/*` packages become thin wrappers that re-export from `frontile`. `@frontile/forms-legacy` and `@frontile/changeset-form` are also marked deprecated. All of them emit deprecation warnings at build time. All existing import paths continue to work. | | **0.19.0** | All of the above are removed: the `@frontile/*` wrapper packages, `@frontile/forms-legacy`, and `@frontile/changeset-form`. You must update imports to use `frontile` directly, and migrate off legacy forms and changeset integration. | During the **0.18.x** cycle, you can migrate at your own pace. Both old and new import paths work simultaneously. However, we recommend migrating sooner rather than later to avoid a last-minute rush before 0.19.0. ## For .hbs Template Users If you use `.hbs` templates (not `.gts`/`.gjs`), components remain auto-importable through the deprecated wrapper packages during the 0.18.x cycle. However, you should plan to migrate to `.gts` explicit imports before 0.19.0, as the wrapper packages will be removed. During 0.18.x, `.hbs` users should: 1. Keep the deprecated `@frontile/*` packages installed so that auto-imports continue to work. 2. Plan migration to `.gts`/`.gjs` template format for new code. 3. Use explicit imports in any new `.gts`/`.gjs` files. Once you have migrated all templates to `.gts`/`.gjs`, you can remove the deprecated wrapper packages and use the new `frontile` imports exclusively. --- # Filter Ranking Source: /docs/migrations/v0-18/filter-ranking.md # Filtered lists are now ranked by relevance `Autocomplete` and filterable `Select` used to filter with a case-insensitive "contains" check and render whatever survived **in the order you passed it**. They now score each item and list the closest match first. Nothing to change unless you pass your own `@filter`. The results are ordered differently, which is the point. ## Why it changed The old default could only answer "does this item match?", never "how well?": ```ts // before function defaultFilter(itemValue: string, filterValue: string): boolean { return itemValue.toLowerCase().includes(filterValue.toLowerCase()); } ``` A predicate filters but cannot reorder, so an exact match sat wherever `@items` happened to put it. Typing `button` into a list ordered alphabetically returned `ButtonGroup` above `Button` — both "match", and source order decided the rest. ## What you get now Typing `butt`: | | Before | After | | --- | --- | --- | | 1 | ButtonGroup | **Button** | | 2 | Button Group | ButtonGroup | | 3 | Button | Button Group | Acronyms also match now, which "contains" could never do: - `bg` → `ButtonGroup` - `nz` → `New Zealand` - `prog` → `ProgressBar` **Nothing that matched before stops matching.** The threshold is calibrated so every result the old `includes()` filter returned is still returned; the change is additive plus reordering. ## `@filter` accepts a score ```ts filter?: (itemValue: string, inputValue: string) => boolean | number; ``` Return a **number** to rank — higher sorts first, `0` means no match. Return a **boolean** to filter only, preserving the order of `@items`. Existing boolean filters are unaffected: ```gts {{! still works exactly as before, including source ordering }} ``` ```ts startsWith = (itemValue: string, inputValue: string) => itemValue.toLowerCase().startsWith(inputValue.toLowerCase()); ``` ### Keeping the old behavior Pass the previous implementation explicitly: ```ts const containsFilter = (itemValue: string, inputValue: string) => itemValue.toLowerCase().includes(inputValue.toLowerCase()); ``` ```gts <:startContent> <:endContent> ``` #### Key Changes - `@hint` → `@description` - `@containerClass` → `@classes={{hash base="..."}}` - `@inputClass` → `@classes={{hash input="..."}}` - Removed `@hasSubmitted`, `@hasError`, `@showError` - Added `@isClearable` option - Added `<:startContent>` and `<:endContent>` slots - Added `@startContentPointerEvents` and `@endContentPointerEvents` for click handling ### FormTextarea → Textarea Minimal changes required for textarea migration. #### Before (forms-legacy) ```hbs ``` #### After (forms) ```hbs