Skip to content

[EuiIllustration] Add adaptive light-dark illustration variant - #9797

Open
clintandrewhall wants to merge 3 commits into
mainfrom
adaptive-illustrations
Open

[EuiIllustration] Add adaptive light-dark illustration variant#9797
clintandrewhall wants to merge 3 commits into
mainfrom
adaptive-illustrations

Conversation

@clintandrewhall

@clintandrewhall clintandrewhall commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Note

EUI implementation of elastic/kibana#221938

Ideally, these would not be hard-coded values, but rather CSS variables that are shared throughout, defined by EuiProvider. But as EUI doesn't currently define them, we'll work with hardcoded for now.

One of the purposes of this approach was to prevent having to maintain a .light.svg and .dark.svg, but since we're maintaining two anyway, we might not need this as much as before. Open to discussion.

Summary

Adds a color-mode adaptive variant to @elastic/eui-illustrations and wires EuiIllustration to use it, so a single SVG string recolors between light and dark via CSS light-dark() — no runtime SVG swap.

Light System, Light Theme

image

Light System, Dark Theme

image

Dark System, Light Theme

image

Dark System, Dark Theme

image

  • Generation (packages/illustrations/scripts/generate.js): for each asset, merge the light/dark pair by pairing elements and rewriting differing color properties (fill, stroke, stop-color, etc.) into light-dark(<light>, <dark>) inline styles, producing a single adaptive SVG. The merge runs as a dedicated single pass (with an idempotency guard) so light-dark() values are never nested, and minifyStyles is disabled so the functions survive optimization. Assets whose light/dark markup is not structurally identical are skipped and keep light/dark only.
  • Distribution: the adaptive SVG is stored on the asset (EuiIllustrationSource.adaptive, optional) and also emitted as @elastic/eui-illustrations/svgs/<name>.adaptive.svg (with color-scheme: light dark on the root) for <img>/CSS consumers. A build:svgs step copies these into lib/svgs.
  • EuiIllustration: renders the adaptive SVG when present and supported (feature-detected via CSS.supports('color', 'light-dark(...)'), defaulting on for SSR), setting color-scheme from the active EUI theme. Falls back to the discrete light/dark markup otherwise.
  • Docs & Storybook: new Adaptive story showing one string under color-scheme: light, dark, following EuiProvider, and following the OS (light dark), plus a matching website demo and README updates.

Test plan

  • yarn workspace @elastic/eui-illustrations build regenerates adaptive SVGs without nested light-dark().
  • Unit tests in illustration.test.tsx pass (adaptive preferred when supported; discrete fallback otherwise; a11y labelling).
  • Storybook Display/EuiIllustration/Adaptive: light/dark pin correctly, EuiProvider follows the theme toggle, and system follows the OS/browser prefers-color-scheme.

Generate a single `adaptive` SVG per asset by merging the `light`/`dark` pair, rewriting differing colors into CSS `light-dark()` so a single string recolors via the ancestor `color-scheme` with no runtime SVG swap. Assets whose `light`/`dark` markup is not structurally identical keep `light`/`dark` only. The adaptive SVG is also emitted as `@elastic/eui-illustrations/svgs/<name>.adaptive.svg` for `<img>`/CSS consumers.

`EuiIllustration` now renders the adaptive SVG when present, setting `color-scheme` from the active EUI theme, and falls back to the discrete `light`/`dark` markup otherwise.
@clintandrewhall
clintandrewhall requested a review from a team as a code owner July 9, 2026 21:10
Copilot AI review requested due to automatic review settings July 9, 2026 21:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a CSS light-dark()-driven “adaptive” illustration variant to @elastic/eui-illustrations, and updates EuiIllustration + docs/stories so illustrations can recolor between light/dark without swapping SVG markup at runtime.

Changes:

  • Extend illustration assets to optionally include a single adaptive SVG (generated by merging light/dark color differences into light-dark()).
  • Emit .adaptive.svg files for <img>/CSS consumers and export them via @elastic/eui-illustrations/svgs/*.
  • Prefer adaptive in EuiIllustration (with feature detection + color-scheme wiring), plus docs/storybook/test updates.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/website/docs/components/display/illustrations/index.mdx Updates website docs and adds an “Adaptive color modes” demo snippet.
packages/illustrations/src/types.ts Adds adaptive?: string to the illustrations package type contract.
packages/illustrations/scripts/generate.js Generates merged adaptive SVG markup and writes src/generated/svgs/*.adaptive.svg.
packages/illustrations/README.md Documents the new adaptive variant and consumption patterns.
packages/illustrations/package.json Adds ./svgs/* export and a build:svgs copy step.
packages/illustrations/changelogs/upcoming/adaptive_illustrations.md Changelog entry for adaptive variants + emitted .adaptive.svg files.
packages/eui/src/components/illustration/illustration.tsx Prefers adaptive markup when supported and pins color-scheme from EUI theme.
packages/eui/src/components/illustration/illustration.test.tsx Adds tests covering adaptive preference vs fallback behavior.
packages/eui/src/components/illustration/illustration.stories.tsx Adds an Adaptive story demonstrating color-scheme behaviors.
packages/eui/changelogs/upcoming/adaptive_illustrations.md Changelog entry for EuiIllustration adaptive rendering behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/eui/src/components/illustration/illustration.tsx Outdated
Comment thread packages/eui/src/components/illustration/illustration.stories.tsx Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@infra-vault-gh-plugin-prod

Copy link
Copy Markdown

💚 Build Succeeded

History

@infra-vault-gh-plugin-prod

Copy link
Copy Markdown

💚 Build Succeeded

History

@weronikaolejniczak weronikaolejniczak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only have a bunch of nits and a couple of doubts.

But it's working great! ✨

Image

Comment on lines +199 to +201
// Sentinels resolved to real CSS in `AdaptiveExample`: `EuiProvider` to the
// live theme color mode (a module-level const can't read `useEuiTheme()`), and
// `system` to `light dark` (the value that follows `prefers-color-scheme`).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

I think this comment is redundant. It's normal we cannot use hooks at a module level, we see PROVIDER_SCHEME set to EuiProvider and SYSTEM_SCHEME to system. If SYSTEM_SCHEME needs more explanation, we can add a JSDoc comment there that explains the value can be system | light | dark (prefers-color-scheme).

const svg = colorMode === 'DARK' ? type.dark : type.light;
const isDark = colorMode === 'DARK';
const useAdaptive = type.adaptive != null && supportsLightDark();
const svg = useAdaptive ? type.adaptive! : isDark ? type.dark : type.light;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
const svg = useAdaptive ? type.adaptive! : isDark ? type.dark : type.light;
const svg = useAdaptive ? type.adaptive : isDark ? type.dark : type.light;

Comment on lines +28 to +33
/**
* A single optimized SVG whose colors resolve at runtime via CSS
* `light-dark()`, driven by the active `color-scheme`. Absent when the
* `light`/`dark` files are not structurally identical and cannot be safely
* auto-merged; consumers must fall back to {@link light}/{@link dark}.
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Just an opinionated rewrite to follow the wording of other JSDoc comments:

Suggested change
/**
* A single optimized SVG whose colors resolve at runtime via CSS
* `light-dark()`, driven by the active `color-scheme`. Absent when the
* `light`/`dark` files are not structurally identical and cannot be safely
* auto-merged; consumers must fall back to {@link light}/{@link dark}.
*/
/**
* Optimized SVG markup for the color-adaptive mode.
*
* Colors resolve at runtime via CSS `light-dark()`, driven by the active
* `color-scheme`. Absent when the `light`/`dark` files are not structurally
* identical and cannot be safely auto-merged;
* consumers must fall back to {@link light}/{@link dark}.
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking thought:

We should probably add tests for this script. Not necessarily on this PR.

Comment on lines +235 to +236
padding: 8px;
border-radius: 4px;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion:

Suggested change
padding: 8px;
border-radius: 4px;
padding: ${euiTheme.size.s};
border-radius: ${euiTheme.border.radius.medium};

Comment on lines 22 to 26
* @remarks
* The `light`/`dark` markup is inlined via `dangerouslySetInnerHTML`. Only pass
* trusted, package-authored SVGs — never untrusted or user-supplied markup, as
* it would be rendered as-is and could introduce an XSS vulnerability.
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion:

We should mention adaptive here as well.

Suggested change
* @remarks
* The `light`/`dark`/`adaptive` markup is inlined via `dangerouslySetInnerHTML`. Only pass
* trusted, package-authored SVGs never untrusted or user-supplied markup, as
* it would be rendered as-is and could introduce an XSS vulnerability.
*/

// Pins `color-scheme` so the adaptive SVG's `light-dark()` colors follow the
// EUI color mode rather than the OS preference.
const inlineStyle = useAdaptive
? { colorScheme: isDark ? 'dark' : 'light', ...style }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking suggestion:

Do we want to reverse to avoid consumers overriding it? Unlikely scenario.

Suggested change
? { colorScheme: isDark ? 'dark' : 'light', ...style }
? { ...style, colorScheme: isDark ? 'dark' : 'light' }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

We should disable controls for Adaptive story. We don't need them there and they don't work anyway.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking question:

The point of stories for the components is to showcase the component usage. But here we are showcasing @elastic/eui-illustration usage. So maybe we can rely on the documentation website entry instead?

Comment on lines +156 to +163
/**
* Most assets ship a single \`adaptive\` SVG whose colors resolve via CSS
* \`light-dark()\`. \`EuiIllustration\` sets \`color-scheme\` from the EUI theme;
* this story sets it manually so the same string renders pinned \`light\`,
* pinned \`dark\`, following \`EuiProvider\`, and following the OS
* (\`light dark\`, via \`prefers-color-scheme\`) at once.
* \`aerospace\` has no \`adaptive\` variant and cannot adapt.
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Do we even need this comment? It's verbose, the escaping makes it hard to read and the story is self-explanatory?

* falls back to {@link light}/{@link dark}. Inlined verbatim — see the
* interface's security note.
*/
readonly adaptive?: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Little too verbose for my taste as well:

Suggested change
readonly adaptive?: string;
/** Trusted SVG markup for both color modes via CSS `light-dark()`. Inlined verbatim — see the interface's security note. */
readonly adaptive?: string;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants