Repository navigation
Releases
30.2.0
A feature release. It adds a tile manager theme, a public theme-markers() mixin for declaring the active theme, and theme parameters for tokens the input group and combo schemas already had. The MCP server now resolves components by their selectors and React/Blazor names, and its component theme tool tells agents how to cover both the light and dark variants.
Themes
Tile manager
A new tile-manager-theme() function styles the tile manager and its tiles:
$my-tile-manager-theme: tile-manager-theme(
$background: #eee,
$tile-background: white,
);
@include tokens($my-tile-manager-theme);$tile-background is the primary token. When it's set, the header and content backgrounds follow it, and the title and content text colors are derived from those backgrounds with adaptive-contrast(). The theme also takes the border, hover border, divider, drag-and-drop placeholder, overlay and ghost colors, the border radius, and the resting and drag shadows. The shadows default to the schema's resting-elevation and drag-elevation.
The Tailwind utilities include a tile-manager utility for tile-background. (#667)
Theme markers
The new theme-markers($theme, $variant) mixin declares --ig-theme and --ig-theme-variant in the current scope. The markers inherit, so include the mixin where the active theme is declared, such as :root, or on an element whose subtree uses another theme:
:root {
@include theme-markers(material, light);
}
// Output
:root {
--ig-theme: material;
--ig-theme-variant: light;
}null and values that aren't strings are skipped. An unquoted indigo, which Sass reads as a color keyword, is accepted. tokens($mode: 'scoped') and the Tailwind component utilities emit the markers through this mixin, so their compiled output is unchanged. (#668)
Input group
input-group-theme()takes a$filled-secondary-colorparameter, the label color in the filled state. When it isn't passed and$idle-secondary-coloris, it follows$idle-secondary-color.- The schemas now declare
filled-secondary-colorfor every theme, so the filled label matches the idle label by default:- Material and Bootstrap light: gray 700, was gray 600
- Fluent light: gray 900
- Indigo light: gray 600
- Fluent dark: gray 800
- Indigo dark: gray 50 contrast at 60%
Combo
combo-theme() takes $case-icon-color and $case-icon-color-active, the colors of the case-sensitive icon in the search input. The schema already had these tokens, but the theme couldn't override them.
Color picker
The unused swatch-preview-border-radius token is removed from the color picker schema. No component read it, but --swatch-preview-border-radius is no longer in the generated output.
Documentation
SassDoc now marks the theme parameters that only Ignite UI for Angular uses:
- chip ghost colors and shadow
- expansion panel
$expanded-margin - input success and warning colors, and the success shadow
- tabs ripple colors
- tree
$drop-area-color
It also documents the rating $value-label and $disabled-value-label parameters and fixes the combo-theme() example. (#659)
MCP server
- Component names resolve from selectors. The component theme, component tokens and layout tools accept a component's key, its Angular or Web Components selector, or its React or Blazor name (
nav-drawer,igc-nav-drawer,IgrNavDrawer). The metadata now lists React and Blazor names for all components, and search matches them. - Light and dark guidance.
create_component_themeexplains that each call produces one variant and that the dark variant usually needs different token values. Each response ends with a note on generating the other variant, with an example of the theme's selector scoped under a dark-mode selector. The note says to put that selector onhtmlorbodyso overlay content is included. (#660) - Tile manager. Adds metadata for the tile manager and its tiles: selectors for every platform, aliases (
tiles,dashboard tiles), andresting-shadow/drag-shadowtoken aliases. (#667)
30.1.0
A fix release. It changes how scoped component tokens apply when included inside another selector, makes the spacing and sizing registrations work inside selectors, lets runtime surface overrides reach components in legacy palettes, and makes $scales family maps and the fit_color_scale MCP tool work as documented. It also warns when a deprecated Sass API is used.
Themes
Scoped tokens inside another selector
tokens($theme, $mode: 'scoped') included inside a selector no longer declares the component's tokens on that selector. The tokens go on the component inside it, and on the selector itself only when it is the component:
.promo {
@include tokens(tooltip-theme($background: red), $mode: 'scoped', $scope: '.igx-tooltip');
}
// 30.0.0
.promo, .promo .igx-tooltip { --background: red; … }
// 30.1.0
.promo .igx-tooltip, .promo:where(.igx-tooltip) { --background: red; … }Before, every element inside .promo inherited the tooltip's generic tokens (--background, --foreground, …), so for example every grid toolbar in the section turned red. A class placed on the component itself (<igx-avatar class="vip">) still gets the tokens. :where() keeps its specificity unchanged, so a later .vip { --background: … } rule still wins. A theme map without a selector and with no $scope still targets the parent (&). (IgniteUI/igniteui-angular#17901)
To restyle a component for a whole section, use the default global mode. .promo { @include tokens(tooltip-theme(…)); } declares only --ig-tooltip-* tokens, which no other component reads.
Derived grid inputs
The grid, grid toolbar, paginator, column actions, Excel-style filtering and query builder schemas now declare the background, foreground and accent-color inputs their theme functions accept, with the value initial. These components no longer pick up a generic --background, --foreground or --accent-color declared on an ancestor or on :root.
Spacing and sizing
spacing()andsizing()emit their@propertyrules (--ig-spacing,--ig-size-small|medium|large) at the stylesheet root. When these mixins were included inside a selector or a media query, the rules were nested and the browser dropped them. (#652)- The
pad()function and thesizable()mixin fall back to the initial values (--ig-spacing: 1,--ig-size-medium: 2) when the properties aren't registered.
Tabs
When $tab-ripple-color isn't passed, tabs-theme() now derives it from $item-active-background / $item-background with adaptive-contrast() instead of text-contrast(). Before, when the background was a CSS variable, text-contrast() returned the background itself, so the ripple was the same color as the tab.
Palettes
- Legacy surfaces follow
--ig-surface-500at runtime. In palettes built with thelegacygenerator, which includes all the built-in palettes,--ig-surface-baseis nowvar(--ig-surface-500)instead of a copy of its value. Overriding--ig-surface-500at runtime reaches components again. The compiled colors are the same. Fitted palettes are unchanged; there,--ig-surface-500already points to--ig-surface-base. $scalesmaps keyed by family now reachpalette(). (#634)- A top-level
(anchor: …)is read as one spec for every family. Each family keeps its default range and curve. - A spec without a
rangetakes the family's default range, and also its default curve unless the spec sets one. Arangewithout acurveis still a straight line. - An unquoted
whiteis accepted as ananchor. A color such as#fffis still rejected, with an error that says to quote the keyword. palette()warns about$scaleskeys it ignores: unknown family names, asurfacekey, and extra keys next torange/curve/anchorin a single spec.palette()warns once that thelegacygenerator ignores$scales.
- A top-level
Deprecations
The following APIs now print a deprecation warning, once per compilation:
| API | Use instead |
|---|---|
css-vars($theme) |
tokens($theme) |
text-contrast() |
adaptive-contrast() |
to-opaque() |
CSS color-mix() |
to-hsl() |
Nothing. It will be removed in a future release. |
The library itself no longer calls any of them, and all the SassDoc examples use tokens().
MCP server
fit_color_scaletakes afamilyparameter that sets the key of the returned$scalesmap.nameis now only a label. Iffamilyis omitted andnameis a family name, in any case, it still sets the key. The tool explains the grayanchorand links the scales guidance by itstheming://URI.- Scales schema: family maps are no longer reduced to an empty spec, so per-family scales reach the generated Sass. Unknown family keys, a
surfacekey, and single specs mixed with family keys are rejected, each with its own message at the right path. - Web Components theme template now matches the library (#639):
- The progress counters are registered as
<number>, so fractional progress labels work. - Sizes are registered with
sizing()instead of being set as plain variables. - Adds the tile manager view transitions and the
.ig-scrollbarutility class. The scrollbar variables are emitted for custom palettes too, and the Indigo track uses gray 200. - The
@propertyrules, tile manager transitions and.ig-scrollbarsit outside the generatedtheme()mixin, so including that mixin under a selector doesn't scope them. - The React and Blazor examples are generated from the same template. This fixes the dark examples, which referenced undefined variables.
- The progress counters are registered as
Dependencies
zodis upgraded from 3 to 4 (runtime dependency). In the generated TypeScript types for the JSON palettes, shade maps are nowPartial<Record<Shade, string>>. Shade maps never required every key, and the type now says so.happy-dom21 (preview, dev only),actions/setup-nodeand transitive security updates (body-parser,fast-uri,qs).
30.0.0
This release is about color. The palette() function has a new shade generator that builds a usable, accessible scale from any brand color, including very dark or very light ones that the old generator couldn't handle. Surfaces get named layers instead of numbered shades.
Before you upgrade
If you only use the built-in palettes (Material, Fluent, Bootstrap, Indigo, light or dark), nothing changes visually. They still use the original generator and render the same colors as 29.x. The one thing to check is runtime overrides of --ig-surface-500 (see "Surfaces" below).
If you call palette() with your own colors, your shades will change. That's the main point of the release, but there are three things to know:
-
500is no longer your exact brand color. Shades are now placed by contrast rather than pinned to the seed, sopalette($primary: #0099ff, …)gives--ig-primary-500: rgb(9, 109, 183). The original color is still available as--ig-primary-seed, andseed-lands-on($palette, 'primary')tells you which shade is closest to it ('300'in this example). -
Overriding
--ig-primary-500at runtime no longer recolors the whole family. Fitted shades are emitted as plain values instead of being derived from500withhsl(from var(--ig-primary-500) …). To rebrand at runtime, generate the new palette and swap it in. -
To keep your 29.x colors exactly while you migrate, opt out per palette or globally. Either way the output is identical to 29.x:
// One palette $p: palette(#0099ff, #df1b74, #fff, $generator: 'legacy'); // Everything @use 'igniteui-theming' with ($shade-generator: 'legacy');
New shade generator
The old generator multiplied the seed's lightness by fixed values, so a dark seed produced ten dark shades and a pale seed produced several pure whites. The new "fitted" generator separates a color's character (hue and colorfulness, from the seed) from the shape of its scale (how far shades spread, from a contrast target). For example:
| Seed | 29.x | 30.0 |
|---|---|---|
Dark blue hsl(210 80% 10%) |
50 is #012d5a, so every shade is dark |
50 is #e4ecf8, 900 is #0a1623 |
Pale blue hsl(210 80% 90%) |
50 through 300 are all white |
50 is #e4edf7, 900 is #0b1621 |
The generator works in OKLCH and keeps every shade inside sRGB. Seeds that are almost gray produce neutral families instead of picking up a random hue, and muted brand colors keep their tint.
Predictable contrast. With the default scale, any two shades five steps apart meet WCAG AA (4.5:1) against each other: 50/500, 100/600, 200/700, 300/800, 400/900. So a pairing like background: var(--ig-primary-100); color: var(--ig-primary-600) stays readable whatever the brand color is. This applies to the numbered shades of the chromatic families (primary, secondary, info, success, warn, error).
The gray family is the exception. It keeps the library's original rhythm (material), so existing component styling doesn't shift. Only 3 of those 5 pairings meet AA on white; use $scales: ('gray': 'even') if you want all five.
Custom scales
palette() has a new $scales argument for choosing how shades are distributed:
// A preset for one family
$p: palette(#0099ff, #df1b74, #fff, $scales: ('gray': 'carbon'));
// Your own: a contrast range from 50 to 900, plus an optional cubic-bezier curve
$p: palette(#0099ff, #df1b74, #fff, $scales: (
'primary': (range: 1.1 14, curve: 0.5 0 0.8 0.8)
));- Presets:
even(the default),material(the original grayscale),tailwind(fitted to Tailwind v4 slate) andcarbon(fitted to IBM Carbon gray). They copy those systems' rhythm, not their colors. - A custom range or curve can break the five-step AA rule. That's expected; check your pairings.
- Gray takes an
anchor.'surface'(the default for every scale exceptmaterial) measures contrast from your background, so in dark themesgray-50stays close to the page and higher numbers get lighter.'white'measures from white, which keeps the same ten grays in every theme. Quote the value: unquotedwhiteis a color, and Sass will reject it. - If you ask for more contrast than your background allows (say, a 21:1 gray on a dark gray page), you get the closest reachable shade and a compile warning that tells you how far short it fell.
Surfaces
The surface family now has named layers instead of ten numbered shades:
| Token | What it's for |
|---|---|
--ig-surface-base |
The page background (your surface color) |
--ig-surface-sunken |
Recessed areas |
--ig-surface-raised |
Cards and panels above the page |
--ig-surface-overlay |
Menus, dialogs, popovers |
--ig-surface-container |
A translucent tint layered on top (3% black on light, 3% white on dark) |
Each layer has a matching -contrast token. A layer that has no room to move resolves to base. On pure white, for example, raised and overlay are also white, so let borders or shadows do the separating.
Components now read --ig-surface-base instead of --ig-surface-500. In the built-in palettes the two hold the same color, so nothing changes visually. If you override the surface at runtime, for example with --ig-surface-500: #fafafa, components will no longer pick it up. Set --ig-surface-base instead.
Numbered surface shades are deprecated. --ig-surface-50 through --ig-surface-900 are still emitted, and in fitted palettes --ig-surface-500 points to --ig-surface-base. They'll be removed in a future major, so move your own styles to the roles.
Smaller API changes
color()andcontrast-color()default tobasefor the surface:color($color: 'surface')returnsvar(--ig-surface-base). Other families still default to500.$IPaletteColorsnow lists the surface roles instead of the numbered shades. If you loop over it and need every key a surface emits, use the new$ISurfaceShades.- New public lists:
$INumericShades,$IAccentShades,$ISurfaceRoles,$ISurfaceNumericShades,$ISurfaceShades. - New configurable variables:
$shade-generator,$scales,$family-scales,$surface-roles,$surface-container-alpha,$saturation-floor. - The Tailwind theme exposes both the surface roles and the numbered surface shades.
contrast()andluminance()are unchanged and still available.
MCP server
create_paletteandcreate_themeuse the new generator by default and acceptscalesandgenerator.- New
fit_color_scaletool: give it a color ladder from another system, a brand ramp or a screenshot (lightest first), and it returns ascalesspec that reproduces that rhythm with your own colors. - New
guidance/colors/scalesresource that explains the presets and their trade-offs. create_custom_palettewith explicit surface colors now takes the roles (base,sunken,raised,overlay,container) instead of50–900.- Removed the "this seed is too light or dark for a good palette" warnings, since the new generator handles those seeds.
Full Changelog: v29.1.1...v30.0.0
30.0.0 Beta 2
This release is about color. palette() has a new shade generator that builds a usable, accessible scale from any brand color, including very dark or very light ones that the old generator couldn't handle. Surfaces get named layers instead of numbered shades.
Before you upgrade
If you only use the built-in palettes (Material, Fluent, Bootstrap, Indigo, light or dark), nothing changes visually. They still use the original generator and render the same colors as 29.x. The one thing to check is runtime overrides of --ig-surface-500 (see "Surfaces" below).
If you call palette() with your own colors, your shades will change. That's the main point of the release, but there are three things to know:
-
500is no longer your exact brand color. Shades are now placed by contrast rather than pinned to the seed, sopalette($primary: #0099ff, …)gives--ig-primary-500: rgb(9, 109, 183). The original color is still available as--ig-primary-seed, andseed-lands-on($palette, 'primary')tells you which shade is closest to it ('300'in this example). -
Overriding
--ig-primary-500at runtime no longer recolors the whole family. Fitted shades are emitted as plain values instead of being derived from500withhsl(from var(--ig-primary-500) …). To rebrand at runtime, generate the new palette and swap it in. -
To keep your 29.x colors exactly while you migrate, opt out per palette or globally. Either way the output is identical to 29.x:
// One palette $p: palette(#0099ff, #df1b74, #fff, $generator: 'legacy'); // Everything @use 'igniteui-theming' with ($shade-generator: 'legacy');
New shade generator
The old generator multiplied the seed's lightness by fixed values, so a dark seed produced ten dark shades and a pale seed produced several pure whites. The new "fitted" generator separates a color's character (hue and colorfulness, from the seed) from the shape of its scale (how far shades spread, from a contrast target). For example:
| Seed | 29.x | 30.0 |
|---|---|---|
Dark blue hsl(210 80% 10%) |
50 is #012d5a, so every shade is dark |
50 is #e4ecf8, 900 is #0a1623 |
Pale blue hsl(210 80% 90%) |
50 through 300 are all white |
50 is #e4edf7, 900 is #0b1621 |
The generator works in OKLCH and keeps every shade inside sRGB. Seeds that are almost gray produce neutral families instead of picking up a random hue, and muted brand colors keep their tint.
Predictable contrast. With the default scale, any two shades five steps apart meet WCAG AA (4.5:1) against each other: 50/500, 100/600, 200/700, 300/800, 400/900. So a pairing like background: var(--ig-primary-100); color: var(--ig-primary-600) stays readable whatever the brand color is. This applies to the numbered shades of the chromatic families (primary, secondary, info, success, warn, error).
The gray family is the exception. It keeps the library's original rhythm (material), so existing component styling doesn't shift. Only 3 of those 5 pairings meet AA on white; use $scales: ('gray': 'even') if you want all five.
Custom scales
palette() has a new $scales argument for choosing how shades are distributed:
// A preset for one family
$p: palette(#0099ff, #df1b74, #fff, $scales: ('gray': 'carbon'));
// Your own: a contrast range from 50 to 900, plus an optional cubic-bezier curve
$p: palette(#0099ff, #df1b74, #fff, $scales: (
'primary': (range: 1.1 14, curve: 0.5 0 0.8 0.8)
));- Presets:
even(the default),material(the original grayscale),tailwind(fitted to Tailwind v4 slate) andcarbon(fitted to IBM Carbon gray). They copy those systems' rhythm, not their colors. - A custom range or curve can break the five-step AA rule. That's expected; check your pairings.
- Gray takes an
anchor.'surface'(the default for every scale exceptmaterial) measures contrast from your background, so in dark themesgray-50stays close to the page and higher numbers get lighter.'white'measures from white, which keeps the same ten grays in every theme. Quote the value: unquotedwhiteis a color, and Sass will reject it. - If you ask for more contrast than your background allows (say, a 21:1 gray on a dark gray page), you get the closest reachable shade and a compile warning that tells you how far short it fell.
Surfaces
The surface family now has named layers instead of ten numbered shades:
| Token | What it's for |
|---|---|
--ig-surface-base |
The page background (your surface color) |
--ig-surface-sunken |
Recessed areas |
--ig-surface-raised |
Cards and panels above the page |
--ig-surface-overlay |
Menus, dialogs, popovers |
--ig-surface-container |
A translucent tint layered on top (3% black on light, 3% white on dark) |
Each layer has a matching -contrast token. A layer that has no room to move resolves to base. On pure white, for example, raised and overlay are also white, so let borders or shadows do the separating.
Components now read --ig-surface-base instead of --ig-surface-500. In the built-in palettes the two hold the same color, so nothing changes visually. If you override the surface at runtime, for example with --ig-surface-500: #fafafa, components will no longer pick it up. Set --ig-surface-base instead.
Numbered surface shades are deprecated. --ig-surface-50 through --ig-surface-900 are still emitted, and in fitted palettes --ig-surface-500 points to --ig-surface-base. They'll be removed in a future major, so move your own styles to the roles.
Smaller API changes
color()andcontrast-color()default tobasefor the surface:color($color: 'surface')returnsvar(--ig-surface-base). Other families still default to500.$IPaletteColorsnow lists the surface roles instead of the numbered shades. If you loop over it and need every key a surface emits, use the new$ISurfaceShades.- New public lists:
$INumericShades,$IAccentShades,$ISurfaceRoles,$ISurfaceNumericShades,$ISurfaceShades. - New configurable variables:
$shade-generator,$scales,$family-scales,$surface-roles,$surface-container-alpha,$saturation-floor. - The Tailwind theme exposes both the surface roles and the numbered surface shades.
contrast()andluminance()are unchanged and still available.
MCP server
create_paletteandcreate_themeuse the new generator by default and acceptscalesandgenerator.- New
fit_color_scaletool: give it a color ladder from another system, a brand ramp or a screenshot (lightest first), and it returns ascalesspec that reproduces that rhythm with your own colors. - New
guidance/colors/scalesresource that explains the presets and their trade-offs. create_custom_palettewith explicit surface colors now takes the roles (base,sunken,raised,overlay,container) instead of50–900.- Removed the "this seed is too light or dark for a good palette" warnings, since the new generator handles those seeds.
29.1.1
Sass
Deep Sass imports resolve through exports in Node and Vite.
The package exports map used an invalid subpath pattern (./sass/**/*.*), so plain deep imports such as @use 'igniteui-theming/sass/themes/schemas' failed in Vite with a "not exported" error. Node's own resolver (require.resolve, import.meta.resolve) rejected these paths too. The pattern is now ./sass/*, and these imports work. If you added aliases or relative-path workarounds for this, you can remove them. pkg: imports through Sass's NodePackageImporter already worked and are unaffected. (#626)
Cleaner CSS from sizable() and pad().
When you pass plain numbers, these functions now put the value directly into the generated expression (e.g. var(--is-small, 1) * 2.5rem) instead of wrapping each one in max(2.5rem, -1 * 2.5rem). Values from var() and calc() behave as before. Rendering doesn't change, but the CSS is smaller and easier to read in DevTools. (#629)
Corrected parameter names in the theme docs.
The docs for three themes listed parameters under the wrong names. If you copied one of these from the docs, compilation failed with No parameter named …:
- chip:
$selected-outlined-background(not$outlined-selected-background) - grid:
$body-column-border-color-odd/$body-column-border-color-even(not$body-column-border-color) - input:
$input-suffix-background--filled(not$input-suffix-background-filled)
The docs also note that chips with a variant (primary, info, success, warning, danger) override $background, $text-color and $border-color. (#623)
MCP server
Generated themes compile for Web Components, React and Blazor.
Before this release, Sass from create_theme and create_component_theme could fail to compile outside Angular:
create_themereferenced palette metadata that it never imported. It now writes the design system out directly.create_component_themedidn't import the theme schemas on non-Angular platforms.- Typography output wrapped Sass variables in quotes, so a literal
'$typeface'ended up in your CSS. It also quoted custom font stacks that were already quoted.
Every style-generating tool now has a test that compiles its output, Angular included. (#628)
Clearer errors for invalid colors.
create_palette and create_theme now check color inputs. Palette failures are reported as errors, so your AI assistant sees that the call failed instead of getting broken output. (#628)
Correct CSS variable names for shadow tokens.
Some themes take a *-shadow parameter but emit the CSS variable as *-elevation, for example resting-shadow becomes --ig-button-resting-elevation. Token lookups now return the real variable name, so overrides suggested by the assistant actually apply. This covers badge, bottom nav, button group, contained and FAB buttons, card, carousel, chip, color picker, dialog and others. (#623)
Color Picker, QR Code and Breadcrumbs support.
The MCP server now knows these components, so it can look up their tokens and generate themes for them. (#622)
30.0.0 Beta 1
29.1.0
What's Changed
- feat(tabs): add the
header-backgroundtoken by @desig9stein in #612
Full Changelog: v29.0.0...v29.1.0
29.0.0
What's Changed
- feat(breadcrumbs): add theme and schemas by @adrianptrv in #601
- feat(color-picker): add theme by @adrianptrv in #607
- feat(*): add color-picker, qr-code and breadcrumb tailwind themes by @adrianptrv in #608
Breaking Changes
- refactor(grid-summary): remove potentially harmful grid summary border-width properties by @desig9stein in #605
Full Changelog: v28.1.1...v29.0.0
29.0.0 Beta 2
What's Changed
- feat(color-picker): add theme by @adrianptrv in #607
- feat: add color-picker, qr-code and breadcrumb tailwind themes by @adrianptrv in #608
Full Changelog: v29.0.0-beta.1...v29.0.0-beta.2
29.0.0 Beta 1
What's Changed
- feat(breadcrumbs): additional schema changes by @adrianptrv in #604
- refactor(grid-summary): remove potentially harmful grid summary border-width properties by @desig9stein in #605
Full Changelog: v28.2.0-beta.1...v29.0.0-beta.1