Skip to content

30.0.0

Latest

Choose a tag to compare

@simeonoff simeonoff released this 22 Oct 10:21
· 3 commits to master since this release
2b96f3a

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:

  • 500 is no longer your exact brand color. Shades are now placed by contrast rather than pinned to the seed, so palette($primary: #0099ff, …) gives --ig-primary-500: rgb(9, 109, 183). The original color is still available as --ig-primary-seed, and seed-lands-on($palette, 'primary') tells you which shade is closest to it ('300' in this example).

  • Overriding --ig-primary-500 at runtime no longer recolors the whole family. Fitted shades are emitted as plain values instead of being derived from 500 with hsl(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) and carbon (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 except material) measures contrast from your background, so in dark themes gray-50 stays 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: unquoted white is 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() and contrast-color() default to base for the surface: color($color: 'surface') returns var(--ig-surface-base). Other families still default to 500.
  • $IPaletteColors now 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() and luminance() are unchanged and still available.

MCP server

  • create_palette and create_theme use the new generator by default and accept scales and generator.
  • New fit_color_scale tool: give it a color ladder from another system, a brand ramp or a screenshot (lightest first), and it returns a scales spec that reproduces that rhythm with your own colors.
  • New guidance/colors/scales resource that explains the presets and their trade-offs.
  • create_custom_palette with explicit surface colors now takes the roles (base, sunken, raised, overlay, container) instead of 50–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