Icons
Material Symbols Variable Font integration with customizable axes and hardware-accelerated animations.
The <Icon /> component provides a highly performant wrapper around Google's Material Symbols variable font. Instead of shipping static SVG files, it leverages a single variable font file capable of infinite interpolation across 4 distinct typography axes.
Introduction
Material Symbols are the next generation of Material Icons, designed to be more flexible and expressive. By using a variable font, the <Icon /> component can morph between different weights, fills, and styles without loading multiple assets. This ensures faster load times and smoother animations, especially for state transitions like "liking" or "favoriting" an item.
Anatomy
- Glyph: The visual representation of the icon, defined by its name (ligature).
- Axes: Four parameters (Fill, Weight, Grade, Optical Size) that control the font's geometry.
- Bounding Box: The 24x24dp (default) area that contains the icon, ensuring consistent alignment with other UI elements.
Variants
Stylistic Variants
Material Symbols offers three distinct stylistic variations, each providing a different aesthetic for your application:
- Rounded (Default): Softer, friendlier geometric forms.
- Outlined: Modern, clean lines.
- Sharp: Direct, high-precision geometry.
Features
Variable Font Axes
The <Icon /> component exposes four axes for fine-tuned control:
- Fill: Toggle between outlined (
0) and solid (1) styles. - Weight: Adjust the thickness of the icon strokes (100–700).
- Grade: Fine-tune the stroke weight without affecting the icon's dimensions (-50 to 200).
- Optical Size: Optimize the icon's rendering for specific display sizes (20, 24, 40, 48).
Expressive Animations
By setting animateFill={true}, the icon can smoothly transition between its outlined and filled states. This is powered by motion/react and provides a premium feel to interactive icons.
Setup & Font Loading
No configuration is required for the default CDN mode in React, Vite, or
Next.js. <Icon> automatically hoists the Google Fonts stylesheet for each
variant used, reserves its size while loading, and only reveals the glyph after
the font is ready. If the request fails, the slot remains hidden at the same
size; the ligature name is never exposed in the UI. This also applies when the
icon is passed through Button, IconButton, FAB, AppBar, Navigation, or any
other ReactNode icon slot.
Local generated subset
For offline delivery and to ship only the symbols your app uses, add
@bug-on/m3-fonts and create m3-symbols.config.json:
{
"sources": ["app/**/*.tsx", "components/**/*.tsx"],
"include": { "outlined": [], "rounded": ["home"], "sharp": [] },
"outDir": "app/_generated/material-symbols"
}
Install the pinned generator tooling with
python3 -m pip install -r node_modules/@bug-on/m3-fonts/tools/requirements.txt,
run pnpm exec m3-symbols before dev/build, then import the generated CSS and
wrap the application with the local source provider:
import "./_generated/material-symbols/material-symbols.css";
import { MaterialSymbolsProvider } from "@bug-on/m3-expressive/core";
<MaterialSymbolsProvider source="local">{children}</MaterialSymbolsProvider>
Names from data or other runtime values must be declared in include; the
generator fails with their source location if the manifest is incomplete.
Usage
Basic Usage
import { Icon } from "@bug-on/m3-expressive/core";
<Icon name="favorite" />
Advanced Customization
<Icon
name="settings"
weight={600}
fill={1}
grade={100}
opticalSize={48}
/>
Animated Toggle
const [isFavorite, setIsFavorite] = useState(false);
<Icon
name="favorite"
fill={isFavorite ? 1 : 0}
animateFill
onClick={() => setIsFavorite(!isFavorite)}
/>
Best Practices
Do
- Use consistent icon styles (e.g., all Rounded) throughout your application.
- Use Optical Size that matches the icon's actual display size for the clearest rendering.
- Animate the Fill axis for toggle actions like "Like" or "Bookmark" for better feedback.
- Pair icons with text labels or tooltips to ensure their meaning is clear.
Don't
- Don't use different stylistic variants (e.g., Outlined and Sharp) on the same screen.
- Avoid using very light weights (100) on low-contrast backgrounds as they may be hard to see.
- Don't use complex icons at small sizes where they may lose detail.
Accessibility
- Decorative Icons: By default, icons are marked with
aria-hidden="true"as they are usually decorative. - Semantic Icons: If an icon conveys meaning on its own, wrap it in an
IconButtonor provide anaria-labelon a parent element. - Contrast: Ensure the icon color meets WCAG contrast requirements against its background.
API Reference
MaterialSymbolsProvider
The default source is automatic CDN delivery. Use the provider only to select
the advanced local path; source="local" prevents any Google Fonts request.
import { MaterialSymbolsProvider } from "@bug-on/m3-expressive/core";
<MaterialSymbolsProvider source="local">{children}</MaterialSymbolsProvider>
MaterialSymbolsPreconnect and MaterialSymbolsLoader are retained as legacy
helpers for applications that manually control resource loading.
Icon
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | — | Required. Ligature name (e.g., "search"). |
variant | "outlined" | "rounded" | "sharp" | "rounded" | Stylistic variation. |
fill | 0 | 1 | 0 | Outline or filled glyph. |
weight | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 400 | Stroke thickness. |
grade | -50 | -25 | 0 | 100 | 200 | 0 | Emphasis modifier without changing layout. |
opticalSize | 20 | 24 | 40 | 48 | 24 | Rendering optimization size. |
size | number | "inherit" | 24 | Visual font-size in pixels or inherited size. |
animateFill | boolean | false | Enables spring animation for fill changes. |
className | string | — | Custom classes. |