Migration guide

Upgrade ngwr from any earlier major.

Update

Commit first, then run one command from any major.

ng update ngwr
MajorWhat ng update does
v15Rewrites paths, config calls, -rgb reads, renamed tokens and two values, lists files for the rest.
v14Rewrites six renames, lists files for the rest.
v13Lists files with a host id selector, rewrites nothing.
v12Rewrites the date import paths.
v10, v11Nothing. Colour changes only.
v9Rewrites <wr-checkbox value>.
v8Rewrites density and pagination sizes, lists removed components.
v7Rewrites tags, imports and @use paths.
  • Never target a 7.x, 8.x or 9.x release with ng update, ng add or ng g ngwr:*. Their schematics do not load.
  • pnpm: on ERR_PNPM_IGNORED_BUILDS, allow the builds and run again.
  • pnpm holds a new release back for a day, so ng update may find nothing yet.

If an update failed

A failed or killed run skips its migration: update, commit, then run it by name.

ng update ngwr
git commit -am "chore: update ngwr"
ng update ngwr --migrate-only --name=migration-v14
IfMigration
ng update ngwr@7, @8 or @9 failed with exports is not defined in ES module scopemigration-v7, -v8, -v9: each one that failed, oldest first
ng update ngwr hung on 14.5.0 or earliermigration-v14
You are on 15.0.0-rc.0 or rc.1, which shipped without a codemodmigration-v15. The next ng update ngwr runs it too.

v14 to v15

Needs Angular 22 and Chrome 123, Firefox 120 or Safari 17.5. ng update runs migration-v15.

ng updateWhat
RewritesChart, bit, ngwr/drag-drop and ngwr/icon/adapters/lucide paths. Config calls into one provideWr(). rgba(var(--…-rgb), a) into color-mix(). Reads of the renamed tokens. tone="dark", tone="medium", <wr-timeline-item color="medium">.
Lists filesWhat needs your decision, by file and line: @use 'ngwr', removed intents, [data-theme], removed entry points and providers, the sortable list loop, validation copy.
Leaves aloneThe behaviour and visual changes in the last two sections.
  • Most of what it lists fails silently. Work through the report rather than waiting for the build.
  • Each table below says what the codemod does with each row.

v14 to v15: providers

One provideWr() call from ngwr, one feature per setting, with the same options.

- provideWrDensity({ defaultDensity: 'sm' }),
- provideWrToastConfig({ duration: 6000 }),
+ provideWr(
+   withDensity({ defaultDensity: 'sm' }),
+   withToast({ duration: 6000 }),
+ ),
BeforeAfterCodemod
provideWrDensity(o)withDensity(o) from ngwr/densityRewrites
provideWrToastConfig(o)withToast(o) from ngwr/toastRewrites
provideWrStorage(o)withStorage(o) from ngwr/storageRewrites
provideWrMeta(o)withMeta(o) from ngwr/metaRewrites
provideWrTheme(o)withTheme(o) from ngwr/themeRewrites
provideWrDateAdapter(o)withDates(o) from ngwr/date. Delete a call with no options.Rewrites
provideWrOverlay()Delete. Isolated overlays are the default.Lists files
WrDateAdapterOptions, ProvideWrStorageOptionsWrDatesOptions, WrStorageOptionsRewrites
  • Put withDensity(), withToast() and withTheme() in the app providers. A lazy route never reads them.
  • provideWrIcons(), the i18n providers and provideWrLoadingBarRouter() are unchanged.
  • The codemod moves a call only when it is a plain entry of a providers array. It lists one kept in a variable, a spread or a ternary, a second call of one kind in the same array, and a provideWrTheme() that passes attribute.

v14 to v15: imports and styles

Symbols keep their names; only import, @use and /testing paths move.

BeforeAfterCodemod
ngwr/bar-chart, calendar-heatmap, donut-chart, gauge, line-chart, meter-group, sparklinengwr/charts/<name>Rewrites
ngwr/aurora, blur-text, border-glow, circular-text, click-spark, confetti, decrypt-text, falling-text, fuzzy-text, glitch-text, gradient-text, marquee, rotating-text, shiny-text, splash-cursor, split-text, spotlight-card, star-border, tilt-card, typewriter, wavesngwr/bits/<name>Rewrites
ngwr/drag-dropngwr/sortable-listRewrites
ngwr/icon/adapters/lucidengwr/iconRewrites
@use 'ngwr'@use 'ngwr/theme' first, then one @use per entry point you renderLists files
  • The umbrella also carried the density scale. With withDensity() or [wrDensity], add @use 'ngwr/density', or the setting does nothing.
  • ngwr/reset, ngwr/animations and ngwr/typography-utilities load the theme now. Write them below a configured @use 'ngwr/theme' with (…), or the build fails.

v14 to v15: colour tokens

Neutral colours are role tokens now, such as --wr-color-surface and --wr-border-base.

BeforeAfterCodemod
--wr-color-light, --wr-color-outline--wr-border-baseRewrites reads
--wr-color-dark--wr-color-on-surfaceRewrites reads
--wr-color-muted-text, --wr-color-medium--wr-color-on-surface-mutedRewrites reads
--wr-color-border, --wr-color-border-subtle, --wr-color-border-strong--wr-border-subtle, or --wr-border-base for a firmer lineRewrites reads, to the subtle one
--wr-color-white-contrast (dark theme only)--wr-color-on-surfaceRewrites reads
Every -rgb token, e.g. rgba(var(--wr-color-primary-rgb), 0.2)color-mix(in srgb, var(--wr-color-primary) 20%, transparent)Rewrites, lists what it cannot read
--wr-color-backdrop-rgbcolor-mix(in srgb, var(--wr-color-backdrop) 45%, transparent)Rewrites
--wr-calendar-accent-rgb--wr-calendar-accentRewrites
--wr-color-picker-rgb--wr-color-picker-colorRewrites
The -rgb of a renamed token, e.g. rgba(var(--wr-color-dark-rgb), 0.5)color-mix(in srgb, var(--wr-color-on-surface) 50%, transparent)Rewrites
color="secondary", --wr-color-secondary-*, and the colour, shades and classes of light, medium, darkNo replacement. Pick another intent or your own colour.Lists files
Those four keys in $base-colorsDelete themLists files
  • A var() at a removed token drops its whole declaration, with no error. The codemod rewrites the reads, and lists a declaration, since which role it should set is your call. That includes a [style.--…] binding, setProperty() and a style-object key.
  • Without strictTemplates, a removed color value renders neutral, with no error.
  • --wr-border-strong is new, and darker than any border v14 had.

v14 to v15: dark mode

[data-theme] is gone: the theme follows the OS through color-scheme and light-dark().

- .hero { background: #fff; }
- [data-theme='dark'] .hero { background: #0b1120; }
+ .hero { background: light-dark(#fff, #0b1120); }

- .panel {
-   color: #111;
-   @include theme.dark { color: #eee; }
- }
+ .panel { color: light-dark(#111, #eee); }

- <html data-theme="dark">
+ <html style="color-scheme: dark">
BeforeAfterCodemod
theme.dark-selector(), $theme-attribute, the attribute option of provideWrTheme() and wrThemePrePaintScript()DeleteLists files
Reading data-theme in codeWrTheme.resolved()Lists files
wrThemeTokens(), wrIntentTokens(), wrContrastFor()$base-colors or theme.rebrand(), at build time. See Theming.Lists files
--wr-star-border-stopbackground on .wr-star-border .wr-star-border__rayLists files
  • A leftover [data-theme] selector compiles and matches nothing.
  • To stay light, add :root { color-scheme: light; } after @use 'ngwr/theme'.
  • Add withTheme() so a stored choice applies at boot; on a prerendered page also ship wrThemePrePaintScript().
  • light-dark() takes colours only.
  • In canvas code, read a token with resolveCssColor(), not getPropertyValue().
  • For the old dark wr-border-glow, set --wr-border-glow-halo-blend: plus-lighter, --wr-border-glow-fill-blend: soft-light and --wr-border-glow-fill-scale: 1 on the dark section.
  • [wrTilt]'s glare blends normal in both schemes, so your own --wr-tilt-glare-color paints at face value.

v14 to v15: components

- import { WrSortableList } from 'ngwr/drag-drop';
+ import { WrSortableList, WrSortableItem } from 'ngwr/sortable-list';

  <wr-sortable-list [(items)]="rows">
-   <ng-template let-row let-i="index">
-     <div>{{ i + 1 }}. {{ row.label }}</div>
-   </ng-template>
+   @for (row of rows(); track row.id; let i = $index) {
+     <wr-sortable-item>{{ i + 1 }}. {{ row.label }}</wr-sortable-item>
+   }
  </wr-sortable-list>
BeforeAfterCodemod
<wr-sortable-list> with an <ng-template>Your own @for with one <wr-sortable-item> per row. trackBy is gone.Rewrites the path, lists the loop
wrSquircle from ngwr/squircleshape="squircle" on wr-btn, wr-avatar, wr-badge; the theme.smooth-br() mixin elsewhereLists files
<wr-action-sheet><wr-drawer position="bottom" rounded> with your own rowsLists files
<wr-drawer showHandle>, .wr-drawer__handle, __grabber, .wr-drawer__panel--handleDelete. Swipe-to-dismiss is gone.Lists files
responsive on wr-select, [wrDropdown], [wrPopover], wr-command-palette, WrDialog.open()Delete. For a sheet on phones, open a <wr-drawer position="bottom">.Lists files
WrPopoverHarness.isSheet(), WrCommandPaletteHarness.isPresentedAsSheet()DeleteLists files
<wr-editor format>, WrEditorFormatDelete. The value is a WrEditorJson tree, or null when empty.Lists files
[wrTypography] tone="dark", tone="medium"tone="base", tone="muted"Rewrites
.wr-typography--tone-dark, --tone-medium.wr-typography--tone-base, --tone-mutedRewrites
<wr-timeline-item color="medium">, .wr-timeline-item--mediumcolor="neutral", .wr-timeline-item--neutralRewrites
WrSidebarItem badge: 'new', badge: '12'badge: { text: 'New', tone: 'primary' } for a word, count: 12 for a numberLists files
WrSidebarGroup.children holds WrSidebarItemsA child can be a group: check 'children' in child before reading url, disabled or activeOptionsNo
<wr-sidebar> draws folder and caret-forward icons by defaultNo icons by default. Bind defaultGroupIcon and defaultItemIcon to keep them.Lists files
.wr-sidebar__entry, __item, __group-toggle, __item-title, --active and --disabled on the first two.wr-sidebar__row, __title, .wr-sidebar__row--active, .wr-sidebar__row--disabled; .wr-sidebar__row--group for __group-toggleLists files
--wr-sidebar-paddingUnchanged. It was the only sidebar hook v14 published; the others are new.Nothing to do
  • A <wr-sortable-list> that still holds an <ng-template> renders nothing.
  • Bare wrSquircle, showHandle, responsive and format attributes compile and do nothing. So do rules on .wr-overlay-sheet, .wr-overlay-backdrop and .wr-command-palette--sheet.
  • <wr-editor> refuses an HTML or markdown string, and '': convert to WrEditorJson, or null for empty.

v14 to v15: removed entry points

RemovedUse insteadCodemod
ngwr/config: provideWrConfig(), WR_CONFIG, useConfigValue()Bind the input where you want another valueLists files
ngwr/media: WrMedia, provideWrMedia(), WR_BREAKPOINTSBreakpointObserver from @angular/cdk/layoutLists files
ngwr/markdown, @use 'ngwr/markdown' and the markdown.* catalog keys<wr-editor readonly [toolbar]="false">Lists files
ngwr/icon/adapters/feathersvgIcon() from ngwr/icon, with Feather's own SVG filesLists files
provideWrResponsiveOverlays(), WR_RESPONSIVE_OVERLAYS, wrPresentAsSheet, @use 'ngwr/overlay'DeleteLists files
WrVisualViewport, --wr-keyboard-insetwindow.visualViewportLists files
# Breakpoints
- protected readonly isMd = inject(WrMedia).matches('md');
+ protected readonly isMd = toSignal(
+   inject(BreakpointObserver).observe('(min-width: 768px)').pipe(map(s => s.matches)),
+   { initialValue: false },
+ );

# Feather
# needs "loader": { ".svg": "text" } and declare module '*.svg?raw'
- provideWrIcons(featherIcons({ plus: featherSource.plus }));
+ import plusSvg from 'feather-icons/dist/icons/plus.svg?raw';
+ provideWrIcons([svgIcon('plus', plusSvg)]);
  • Deleting provideWrConfig() resets every control it set to md and the standard radius. Bind those inputs first.
  • WrMedia's md is (min-width: 768px). The other widths are $breakpoints in ngwr/breakpoints.
  • A Feather glyph no longer has the wr-icon__svg class. Style .wr-icon svg.
  • A var(--wr-keyboard-inset, 0px) compiles and stays 0px.

v14 to v15: validation messages

ngwr ships no error text: provideWrFormErrors(), WR_FORM_ERRORS, WR_FORM_ERROR_FALLBACKS and the catalogs' validation keys are gone.

# On one rule
+ required(path.email, { message: 'Please fill this in.' });

# Or for every field, date picker included, in your own catalog
+ provideWrI18nStaticLoader({
+   en: {
+     validation: {
+       required: 'Please fill this in.',
+       minlength: 'Minimum length is {{requiredLength}}.',
+       dateFormat: 'Expected format: {{example}}',
+       minDate: 'Choose a later date.',
+       maxDate: 'Choose an earlier date.',
+       dateFilter: 'This date is not available.',
+     },
+   },
+ }),
  • Without your text a field shows no message; only dev mode warns. <wr-form-error key> still works.
  • The codemod lists provideWrFormErrors(). While no catalog has a validation namespace, it also lists every <wr-form-field> without a <wr-form-error>. While none has validation.dateFormat, it lists every date picker.

v14 to v15: dates

The native adapter needs no provider and follows LOCALE_ID.

BeforeAfterCodemod
ngwr/date/adapters/fns, ngwr/date/adapters/luxonThe native adapter, or your own WrDateAdapter<Date> passed as withDates({ adapter })Lists files
date-fns, luxon as optional peersNo longer peers. Keep them only if your own code uses them.No
Your own WrDateAdapter<Date> subclass, undecoratedAdd @Service(), or dev mode warnsLists files
Your own adapter handles six named formatsTen more WrDateFormat keys: the 12h, 24h and Seconds variants of time and shortDateTime, e.g. time24hSeconds. The date picker asks for them from timeFormat and showSeconds.No
ng add ngwr --dateAdapter=…ng add ngwrNo
wrDate with no provider, shortDate: 3/15/263/15/2026No
wrDate with no provider, time: 2:30 PM02:30 PMNo
wrDate with no provider, 'dd.MM.yyyy': falls back to shortDateHonouredNo
  • Native format tokens are yyyy yy MMMM MMM MM M dd d HH H hh h mm ss a. Others, such as date-fns' EEEE or PPP, print as letters.
  • Your own adapter: see Configuration.

v14 to v15: behaviour

Nothing here fails to compile, and migration-v15 neither rewrites nor lists these.

BeforeAfterCodemod
aria-sort on every sorted wr-table columnOnly on the primary sort column. The others say none and show their rank as a number. Tests read getSortIndicator() and getSortOrder() from ngwr/table/testing.No
Table row and group controls named by aria-labelaria-labelledby: an action word plus the row's first cell (the tree column in a tree) or the group's label, e.g. "Select row Ada". selectRowLabel, expandRowLabel, toggleRowLabel, selectGroupLabel and toggleGroupLabel set only the action word.No
Tab moves from option to option in the <wr-cascader> panelThe arrows or a typed letter move, as in a tree, and Tab leaves the panel. getTabIndex() from ngwr/cascader/testing is 0 for one option only.No
WrWindowManager.open() leaves focus where it wasMoves focus into the window and gives it back on close. Pass autoFocus: false for a window that opens in the background.No
A drawer with hasBackdrop off is modalNon-modal: no focus trap, no aria-modal, and the page keeps scrolling.No
Chart numbers: English 5k, the lineChart.thousands catalog keyIntl in LOCALE_ID. Line-chart ticks and bar-chart labels are compact from a thousand up (5K, a bar of 12500 reads 13K) unless the full numbers are shorter; tooltips never are. Drop the key from your catalogs. For another format, pass valueFormat, which every chart takes.No
fill on .wr-line-chart__tick, an SVG <text>HTML beside the plot. Restyle it with color or the --wr-line-chart-tick-* hooks.No
A static flex or grid parent of a wrAffixposition: relative while an affix is in it, through .wr-affix__container. Its absolute children anchor to it and its z-index applies. A position you give it wins.No
  • A test that found a table control by its old shared name, or tabbed through a cascader, needs the change.
  • A drawer that has to stay modal keeps its backdrop. If the dimming was what you turned off, hide it: .wr-drawer-backdrop { background: none; backdrop-filter: none; }.

v14 to v15: looks different

No API change and nothing to search for. Check the pages.

  • The gray scale moved from Tailwind's slate to its gray, so neutral surfaces, text and borders are less blue: --wr-color-gray-500 from #64748b to #6b7280, --wr-color-gray-900 from #0f172a to #111827.
  • Paddings, gaps and margins snapped to the --wr-space-* scale, so many components moved by a pixel or two. wr-alert's padding went from 0.625rem 0.875rem to 0.75rem 1rem.
  • <wr-btn outlined> with no color is transparent with a hover tint. It used to look like a plain button.

v13 to v14

ng update rewrites:

BeforeAfter
<wr-alert closeable>closable
<wr-table [totalItems]>[total]
<wr-pagination [(currentPage)]>, (currentPageChange)[(page)], (pageChange)
WrPaginationHarness.with({ currentPage }){ page }
[isDisabledWhenLoading][disabledWhenLoading]
chromeSize: 'compact' | 'normal', .wr-window--chrome-compact'sm' | 'md', .wr-window--chrome-sm
[wrInput] wrSizesize

By hand (ng update lists the files for most):

BeforeAfter
WrWindowHarness.getChromeSize(): 'compact''sm'
<wr-loading-bar> follows the router on its ownAdd provideWrLoadingBarRouter() from ngwr/loading-bar/router
<wr-tab routerLink>Add wrTabsRouting on <wr-tabs> and WrTabsRouting from ngwr/tabs/router
pagination.of keypagination.range, pagination.compact
<wr-pagination ofLabel>Delete
WR_DATE_LOCALE, WrI18n locales follow the browserFollow LOCALE_ID. DEFAULT_WR_I18N_CONFIG drops defaultLocale, availableLocales.
Named date formats parse through new Date()Parse through Intl and refuse what they cannot read. For the old leniency, pass a token [format].
providers: [
  provideRouter(routes),
+   provideWrLoadingBarRouter(),
]

- <wr-tabs>
+ <wr-tabs wrTabsRouting>

- pagination: { of: 'von' }
+ pagination: {
+   range: '{{from}}–{{to}} von {{total}}',
+   compact: '{{current}} / {{total}}',
+ }
  • Without provideWrLoadingBarRouter() the bar stays at 0% on navigation, with no error.
  • A catalog that still defines pagination.of shows that line in English.
  • Date output changed too: MMMM declines beside a day, a prints the locale's marker, and the calendar is always Gregorian.

v12 to v13

[id] on <wr-checkbox>, <wr-radio> and <wr-switch> lands on the inner <input>, not the host.

- wr-checkbox#agree { margin: 0; }
+ #agree { margin: 0; }                  /* the input */
+ wr-checkbox:has(#agree) { margin: 0; }  /* the whole row */

<wr-checkbox-group> and <wr-radio-group> keep their host id.

v11 to v12

BeforeAfterCodemod
ngwr/date-adapterngwr/dateRewrites
ngwr/date-adapter-fnsngwr/date/adapters/fns (removed in v15)Rewrites
ngwr/date-adapter-luxonngwr/date/adapters/luxon (removed in v15)Rewrites
readI18nText() returns stringSignal<string>: read it as label()No, the compiler finds each

v8 to v9

BeforeAfterCodemod
<wr-checkbox value> in a groupcheckboxValueRewrites
Checkbox stateThe checked model: [(checked)] or [formField]No
lucideIcons({ chevronDown }) registers chevron-downRegisters chevronDownNo
WrColorAdds info. An exhaustive switch needs the case.No
  • A static value left on <wr-checkbox> makes the whole group toggle together.
  • A camelCase Lucide key stops rendering with no error. Quote the key: 'chevron-down': ChevronDown.

v7 to v8

BeforeAfterCodemod
Density compact, default, comfortablesm, md (the default), lg; touch unchangedRewrites
WrPaginationSize xs, xlsm, lgRewrites
wrRevealRemoved, no replacementLists files
<wr-scramble-text><wr-decrypt-text>Lists files

v6 to v7

ng update rewrites the tags, imports and @use paths.

BeforeAfter
wr-autocomplete, wr-chips-inputwr-select with mode
wr-time-picker, wr-date-time-pickerwr-date-picker
wr-tooltipwr-popover
wr-tree-selectwr-tree
wr-bottom-sheetwr-drawer
wr-imagewr-lightbox
ngwr/tagngwr/badge
wr-animated-textwr-typewriter, wr-decrypt-text or wr-split-text, per mode

See also