# How the Astryx Icon System and globalIconRegistry Enable Theme-Aware Icon Management

> Discover how the Astryx Icon system and globalIconRegistry manage icons with theme-aware overrides, extensions, and universal rendering for React applications.

- Repository: [Meta/astryx](https://github.com/facebook/astryx)
- Tags: internals
- Published: 2026-08-06

---

**The Astryx Icon system resolves semantic icon names to SVG React components through a globalIconRegistry that supports theme-specific overrides, library extensions, and universal rendering across server and client environments.**

The facebook/astryx repository implements a sophisticated icon management solution that decouples semantic icon names from their visual implementations. At the heart of this system lies the **globalIconRegistry**, a module-level registry that merges default icons, theme overrides, and dynamically registered entries to provide consistent icon resolution. This architecture enables teams to maintain a single icon API while allowing per-theme customization and third-party library extensions.

## Core Architecture of the Astryx Icon System

### The Icon Component

The `Icon` component in [`packages/core/src/Icon/Icon.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Icon/Icon.tsx) serves as the primary interface for consuming icons. It accepts either a semantic string name (such as `"close"`) or a direct React node, ensuring flexibility for both registry-based and ad-hoc icon usage. When receiving a string, the component invokes `getIcon()` to resolve the name against the current theme's registry, then wraps the result in a StyleX-styled `<span>` to apply size and color utilities consistently.

```tsx
// packages/core/src/Icon/Icon.tsx
export function Icon({icon, size = 'md', color = 'primary', ...rest}: IconProps) {
  const theme = useThemeName();
  const resolved = typeof icon === 'string' ? getIcon(icon, theme) : icon;
  // ...render logic with StyleX classes...
}

```

### Type Safety with IconName and ExtendedIconName

Astryx maintains strict type safety through the **`IconName`** and **`ExtendedIconName`** types. `IconName` defines the union of built-in semantic names (e.g., `close`, `chevronDown`), while `ExtendedIconName` widens to `any string`, allowing libraries to register custom keys such as `"richtext:bold"` without modifying core type definitions or triggering TypeScript errors.

## How globalIconRegistry Manages Icon Resolution

### Registry Implementation and API

Located in [`packages/core/src/Icon/globalIconRegistry.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Icon/globalIconRegistry.tsx), the registry maintains a **module-level map** named `globalRegistry` that stores overrides supplied by `registerIcons()`. The implementation exposes four key utilities: `registerIcons` for adding entries, `getIcon` for resolution, `getIconRegistry` for inspection, and `resetIcons` for testing isolation.

```tsx
// packages/core/src/Icon/globalIconRegistry.tsx
export function registerIcons(icons: Partial<Record<ExtendedIconName, ReactNode>>): void {
  warnOnce(...);
  globalRegistry = { ...globalRegistry, ...icons };
}

export function getIcon(name: ExtendedIconName, source?: IconRegistrySource): ReactNode {
  const themeIcons = getThemeIconOverrides(source);
  return themeIcons?.[name as IconName] ?? globalRegistry[name] ?? defaultIcons[name as IconName];
}

```

### Theme-Scoped Icon Registries

Each Astryx theme (e.g., *stone*, *y2k*, *neutral*) exports an **`IconRegistry`** object that maps the same semantic names to Lucide-react components. These registries reside in theme-specific files like [`packages/themes/stone/src/icons.tsx`](https://github.com/facebook/astryx/blob/main/packages/themes/stone/src/icons.tsx). The `getThemeIconOverrides()` function loads the appropriate registry based on the current theme name retrieved via `useThemeName()`.

```tsx
// packages/themes/stone/src/icons.tsx
export const stoneIconRegistry: IconRegistry = {
  close: <X />,
  chevronDown: <ChevronDown />,
  // ...other icons...
};

```

### Resolution Priority and Lookup Order

The `getIcon()` function implements a strict three-tier cascade. When resolving an icon name, it checks **theme overrides** first, then falls back to the **global registry** entries, and finally to the **default icons** defined in [`globalIconRegistry.tsx`](https://github.com/facebook/astryx/blob/main/globalIconRegistry.tsx). This priority ensures that themes can override both built-in defaults and library-registered icons without code changes in consuming components.

## Runtime Resolution Process

The icon resolution flow operates as follows:

1. A component renders `<Icon icon="close" size="sm" color="primary" />`
2. The `Icon` component reads the current theme via `useThemeName()` (e.g., returning `"stone"`)
3. `getIcon("close", "stone")` executes the lookup cascade against `stoneIconRegistry`, then `globalRegistry`, then `defaultIcons`
4. The returned React node (an SVG component) renders inside a styled `<span>` that provides the requested size and color through StyleX classes

This process works identically in React Server Components (RSCs) and client-side environments because the registry exists at the module level and resolves synchronously.

## Extending the Icon System

### Library Registration

Third-party libraries can extend the system by calling `registerIcons()` at initialization, typically in a root layout or provider component. This approach allows packages to contribute icons under namespaced keys like `"mylib:custom"` without conflicting with core definitions.

### Component-Level Overrides

When a component requires a one-off custom icon that should not be globally registered, pass the SVG component directly to the `icon` prop. This **component mode** bypasses the registry entirely, skipping the `getIcon()` lookup while still benefiting from the `Icon` component's size and color styling utilities.

## Summary

- The **globalIconRegistry** in [`packages/core/src/Icon/globalIconRegistry.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Icon/globalIconRegistry.tsx) provides a centralized, module-level store that persists across server and client renders.
- Resolution follows a strict priority: **theme overrides** take precedence, followed by **globally registered icons** (via `registerIcons`), then **default icons**.
- The **Icon** component in [`packages/core/src/Icon/Icon.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Icon/Icon.tsx) transparently handles both semantic name resolution and direct React node rendering.
- **Type safety** is maintained through `IconName` and `ExtendedIconName`, enabling extension without modifying core definitions.
- Each theme exports its own **IconRegistry** (e.g., [`packages/themes/stone/src/icons.tsx`](https://github.com/facebook/astryx/blob/main/packages/themes/stone/src/icons.tsx)) to override visual implementations while preserving semantic names.

## Frequently Asked Questions

### How do I register custom icons in Astryx?

Import `registerIcons` from [`packages/core/src/Icon/globalIconRegistry.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Icon/globalIconRegistry.tsx) and call it with an object mapping your custom `ExtendedIconName` keys to React nodes. Execute this registration at your application's entry point or root layout. The icons become available globally and persist for the lifetime of the application module.

### What is the lookup priority for icons in globalIconRegistry?

The resolution order is: first **theme-specific overrides** (defined via `defineTheme({icons})`), then **globally registered icons** (added via `registerIcons()`), and finally the **built-in default icons**. This cascade allows themes to override both core defaults and third-party library registrations.

### How does Astryx handle server-side rendering with icons?

The registry operates at the module level, making icon mappings immediately available in React Server Components without requiring client-side hydration. The `getIcon()` function executes synchronously during server rendering, returning the React node directly to the SSR stream.

### Can I use icons without registering them in globalIconRegistry?

Yes. The `Icon` component accepts React nodes directly via the `icon` prop, bypassing the registry lookup entirely. This component mode is useful for one-off custom icons or when you need to avoid polluting the global namespace while still leveraging the component's size and color styling utilities.