Repository navigation
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