Skip to content

fix(react-renderer): fix icon rendering in v0.9 basic catalog#972

Open
jacobsimionato wants to merge 1 commit intogoogle:mainfrom
jacobsimionato:react_icons
Open

fix(react-renderer): fix icon rendering in v0.9 basic catalog#972
jacobsimionato wants to merge 1 commit intogoogle:mainfrom
jacobsimionato:react_icons

Conversation

@jacobsimionato
Copy link
Collaborator

@jacobsimionato jacobsimionato commented Mar 25, 2026

Description of Changes

  • Automatic Font Loading: Added a side-effect to Icon.tsx that programmatically injects the Google Fonts stylesheet for "Material Symbols Outlined" if it's not already detected in the document. This makes the Icon component self-contained.
  • Icon Name Mapping: Implemented an ICON_MAPPING dictionary to bridge the gap between A2UI specification names (e.g., play, rewind, starOff) and the actual ligatures required by Material Symbols (e.g., play_arrow, fast_rewind, star with outlined setting).
  • CamelCase to Snake_case Conversion: Added a regex transformer to convert standard A2UI camelCase icon names into the snake_case format required for font ligatures.
  • Variable Font Support: Added support for the FILL variation axis. This allows icons like star and favorite to correctly render as filled when requested, while their "off" variants render as outlined.

Rationale

The v0.9 React renderer's Icon component was previously just rendering the raw icon name string. This failed for two reasons:

  1. The font stylesheet wasn't being loaded automatically.
  2. Many icon names in the A2UI spec (like play) don't have exact string matches in the Material Symbols font family (which uses play_arrow).

These changes ensure that icons rendered via the React v0.9 catalog match the visual behavior of the v0.8 Lit/Angular renderers and follow the design requirements of the Material Symbols variable font.

Testing/Running Instructions

  1. Run the A2UI Explorer or a React application using the v0.9 renderer.
  2. Load a surface that uses icons, such as the "Track List" example (18_track-list.json).
  3. Verify that the play icon and other system icons (like star, favorite) render correctly as glyphs rather than raw text.
  4. Verify that the "Material Symbols Outlined" font stylesheet is added to the <head> of the document upon the first Icon component mount.

Fixes #946

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces programmatic loading of the Material Symbols font, adds icon name mapping, and converts camelCase names to snake_case for the Icon component, also applying fill settings for certain icons. Feedback suggests reconsidering the programmatic font loading for better control by the application, adding unit tests for the new icon logic as per the style guide, and fixing a potential bug in the camelCase to snake_case conversion regex that could lead to leading underscores.

Comment on lines +22 to +33
// Programmatically load the material symbols font if the application hasn't already.
if (typeof document !== 'undefined') {
const isLoaded = Array.from(document.querySelectorAll('link')).some(
(link) => link.href.includes('Material+Symbols+Outlined')
);
if (!isLoaded) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,1';
document.head.appendChild(link);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

While automatically loading the font makes the component self-contained, injecting a <link> tag as a module-level side effect is generally discouraged in React. It can have unintended consequences in different environments (like SSR), can be hard to test, and takes control away from the application developer who might want to manage font loading differently (e.g., self-hosting).

A more robust approach for a component library is to remove this logic and instead document that the consuming application is responsible for including the 'Material Symbols Outlined' font. This gives developers more control and makes the component's behavior more predictable.


const FILLED_ICONS = ['star', 'starHalf', 'favorite'];

export const Icon = createReactComponent(IconApi, ({props}) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The changes in this file introduce new logic for icon name mapping and font loading, but there are no corresponding tests to verify this new functionality for the v0.9 Icon component. According to the repository's style guide, code changes should be accompanied by tests.

Please add unit tests to cover the new logic, including:

  • Correct mapping of icon names from ICON_MAPPING.
  • Correct conversion of camelCase names to snake_case.
  • Correct application of the fontVariationSettings for filled icons.
References
  1. Code changes should have tests. (link)

if (ICON_MAPPING[rawIconName]) {
iconName = ICON_MAPPING[rawIconName];
} else {
iconName = rawIconName.replace(/([A-Z])/gm, "_$1").toLowerCase();
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The regular expression used to convert camelCase to snake_case can produce a leading underscore for PascalCase icon names (e.g., MyIcon becomes _my_icon). While the current icon set might not use PascalCase, this could be a source of bugs for custom icons.

A small adjustment can make this more robust by removing the potential leading underscore.

Suggested change
iconName = rawIconName.replace(/([A-Z])/gm, "_$1").toLowerCase();
iconName = rawIconName.replace(/([A-Z])/gm, "_$1").toLowerCase().replace(/^_/, '');

Copy link
Collaborator

@ava-cassiopeia ava-cassiopeia left a comment

Choose a reason for hiding this comment

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

Is this matching behavior seen in other, competing frameworks?

I ask because unless we're trying to match functionality, I'd actually strongly recommend against this.

This creates an opinionated link to Material Symbols, when downstream users may want to use a different icon library. We probably ought to provide a way to override the font library used (even the MatIcon, which is more Material-opinionated, provides an option for this).

I'm also wary of importing a whole icon library due to weight, as most icon libraries offer incremental loading and can be quite heavy if imported in full. Then again, I suppose the AI could produce any icon name...and speaking of, do we tell the agent what icons are available to it..? That seems like something we need if we're gonna choose the library here.

// Programmatically load the material symbols font if the application hasn't already.
if (typeof document !== 'undefined') {
const isLoaded = Array.from(document.querySelectorAll('link')).some(
(link) => link.href.includes('Material+Symbols+Outlined')
Copy link
Collaborator

Choose a reason for hiding this comment

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

This will in some edge cases end up burning you. Material Symbols offers dynamic icon loading from their CDN, meaning that users can provide Material Symbols URLs that look like:

<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=close" />

Note the icon_names=close, which will cause that to only load the close icon and nothing else, saving significantly on weight.

This code will match the above href, even though it doesn't pull in the full library, which I guess we probably need for the sake of the AI choosing any icon? More on that in my other comment.

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

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

[React] Fix icon rendering

2 participants