Skip to content

feat: add illustration theming via applyTheme prop#672

Open
adrienzheng-cb wants to merge 8 commits into
illustrations/05-10-2026from
adrien/illustration-theming
Open

feat: add illustration theming via applyTheme prop#672
adrienzheng-cb wants to merge 8 commits into
illustrations/05-10-2026from
adrien/illustration-theming

Conversation

@adrienzheng-cb
Copy link
Copy Markdown
Contributor

@adrienzheng-cb adrienzheng-cb commented May 12, 2026

Here's a more detailed PR description:


Summary

Adds opt-in illustration theming to all five illustration components (HeroSquare, SpotSquare, SpotIcon, SpotRectangle, Pictogram) on both web and mobile via a new applyTheme boolean prop. Without applyTheme, behavior is completely unchanged — making this a non-breaking, additive change.

How it works

Web

When applyTheme is set, createIllustration asynchronously loads the illustration's "themeable" SVG variant (via a lazy ESM import), inlines the markup into the DOM using dangerouslySetInnerHTML, and lets the browser resolve var(--illustration-*) CSS variables through normal cascade. While loading or when applyTheme is absent, the component falls back to the existing <img> CDN path with no behavioral change.

The new --illustration-* CSS variables are emitted by createThemeCssVars and injected by ThemeProvider alongside the existing design token variables. Token names with numeric suffixes are normalized to kebab-case (e.g. accent1--illustration-accent-1) to match the variable names embedded in the SVG files.

Mobile

react-native-svg cannot resolve CSS variables at runtime, so a different approach is used. When applyTheme is set and the active theme provides an illustration palette, the themeable SVG string is loaded synchronously and the new convertThemedSvgToHex utility (in packages/common) performs a single-pass string substitution — replacing every var(--illustration-*) token with its resolved hex value before passing the string to SvgXml. Hyphenated tokens (accent-1) are normalized back to camelCase keys (accent1) to look up values in the palette. If applyTheme is absent, the theme provides no palette, or no themeable variant exists, the component falls back to the existing light/dark SVG.

Theme system

ThemeConfig (on both platforms) gains two new optional fields: lightIllustrationColor and darkIllustrationColor. ThemeProvider derives the active illustrationColor from these based on the current color scheme and exposes it via useTheme(). All four built-in themes (Default, Coinbase, Default High Contrast, Coinbase High Contrast) ship with complete 14-token palettes covering primary, black, white, gray, gray2, gray3, positive, negative, accent1–4, invert, and invert2.

Files changed

Area Change
packages/common/src/utils/convertThemedSvgToHex.ts New utility — replaces var(--illustration-*) tokens in SVG strings with hex values
packages/web/src/illustrations/createIllustration.tsx Adds applyTheme prop; async ESM inline SVG path
packages/mobile/src/illustrations/createIllustration.tsx Adds applyTheme prop; sync hex-substitution path
packages/web/src/core/createThemeCssVars.ts Emits --illustration-* CSS vars from illustrationColor
packages/web/src/system/ThemeProvider.tsx Derives and exposes illustrationColor
packages/mobile/src/system/ThemeProvider.tsx Derives and exposes illustrationColor
packages/*/src/core/theme.ts Extends ThemeConfig and Theme types
packages/*/src/themes/*.ts Adds light/darkIllustrationColor palettes to all four built-in themes
packages/web/src/illustrations/__stories__/ New ThemedIllustrations story with interactive theme picker + Default vs Garish comparison
packages/mobile/src/illustrations/__stories__/ Same as above for mobile
apps/docs/docs/components/media/* Docs updated for all five illustration types on both platforms
packages/web/jest/svgEsmMapMock.js + jest.config.js Jest mock for ESM SVG modules
packages/common + packages/web Unit tests for convertThemedSvgToHex and createThemeCssVars
iOS Old iOS New
old screenshot new screenshot
Android Old Android New
old screenshot new screenshot
Web Old Web New
old screenshot new screenshot

Testing

How has it been tested?

  • Unit tests
  • Interaction tests
  • Pseudo State tests
  • Manual - Web
  • Manual - Android (Emulator / Device)
  • Manual - iOS (Emulator / Device)

Testing instructions

Illustrations/Icons Checklist

Required if this PR changes files under packages/illustrations/** or packages/icons/**

  • verified visreg changes with Terran (include link to visreg run/approval)
  • all illustration/icons names have been reviewed by Dom and/or Terran

Change management

type=routine
risk=low
impact=sev5

automerge=false

sverg-cb and others added 7 commits May 12, 2026 13:46
* feat(IconButton): expose icon glyph styles via styles and classNames props
* feat(IconButton): add static classNames, StylesAndClassNames type, and docs styles tab

- Introduce iconButtonClassNames with root and icon selectors
- Replace manual styles/classNames types with StylesAndClassNames utility
- Apply static class names to Pressable root and Icon glyph
- Add static classNames test
- Add _webStyles.mdx and _mobileStyles.mdx doc pages
- Wire styles tabs into index.mdx

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat: Publish illustrations 2026-05-12

* format
* feat: support key Tag props in component configs

* Bump version

* Rename vars
@adrienzheng-cb adrienzheng-cb force-pushed the adrien/illustration-theming branch from 5e9b94a to 4699062 Compare May 12, 2026 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants