# Font glyph icons

ASIcon glyph rendering is available since v2.18.0. The `createGlyphIcon` factory described below is new source work and requires the next package release.

## Quick start: define your app icons once

`createGlyphIcon` returns a React component backed by ASIcon's glyph renderer. Put definitions at module scope in a shared file, then import the icons wherever they are needed:

```tsx
// icons.ts
import { createGlyphIcon } from '@eleventheye/asui';

const fontFamily = 'Material Symbols Rounded';

export const RefreshIcon = createGlyphIcon({
  glyph: 'refresh',
  fontFamily,
  weight: 300,
});

export const EditIcon = createGlyphIcon({ glyph: 'edit', fontFamily });
```

```tsx
// ProjectToolbar.tsx
import { ASIconButton } from '@eleventheye/asui';
import { RefreshIcon } from './icons';

export const ProjectToolbar = ({ onRefresh }: { onRefresh: () => void }) => (
  <ASIconButton
    as="button"
    icon={RefreshIcon}
    aria-label="Refresh projects"
    onClick={onRefresh}
  />
);
```

Use the same icon directly: `<RefreshIcon iconSize={32} weight={500} aria-label="Refresh" />`.
ASUI owns the factory; your app owns its icon collection. Each factory call creates a new component, so do not call it inside a component render. Module-scope definitions keep identity stable without a cache.

## Load the font in your application

The factory does not download or register fonts. Load a font containing the chosen glyphs, using your existing font stylesheet or a self-hosted file. For example, if your app serves the matching variable font at this path:

```css
@font-face {
  font-family: 'Material Symbols Rounded';
  src: url('/fonts/material-symbols-rounded.woff2') format('woff2');
  font-style: normal;
  font-weight: 100 700;
  font-display: block;
}
```

Use your actual font file and supported weight range. The definition's `fontFamily` must match the loaded family. Missing fonts or unavailable glyphs can display literal ligature text such as "refresh" or a missing-character symbol. Other icon fonts and character glyphs work too; the factory is not tied to Google fonts.

## Factory API

`createGlyphIcon(definition: GlyphIconDefinition)` returns a React component accepting `GlyphIconProps`. Both types are exported from `@eleventheye/asui`.

| Definition option       | Default                            | Instance override |
| ----------------------- | ---------------------------------- | ----------------- |
| `glyph`                 | Required ligature or character     | Fixed             |
| `fontFamily`            | Required loaded font family        | Fixed             |
| `iconSize`              | 24 pixels                          | Yes               |
| `color`                 | currentColor                       | Yes               |
| `fillColor`             | Unset; takes precedence over color | Yes               |
| `weight`                | 300                                | Yes               |
| `fill`                  | 0 (outline); 1 means filled        | Yes               |
| `grade`                 | 0                                  | Yes               |
| `opticalSize`           | 48, independent of rendered size   | Yes               |
| `fontVariationSettings` | Generated from the four axes       | Yes               |

A defined instance value overrides the factory default. An omitted or `undefined` value retains it; `0` is a real override. Font-axis values must be supported by your font. A custom `fontVariationSettings` string replaces the generated axes, so changing individual axes has no effect on that string.

The returned icon also accepts span attributes and event handlers, `className`, `style`, and a React 19 `ref` targeting `HTMLSpanElement`. Inline `style` is the final CSS override. These instance props are not factory definition options.

An `ASIconButton` supplies its resolved size and colors to the icon. Those values override definition defaults, keeping enabled and disabled states consistent with the button theme. SVG-only decoration props supplied by ASUI controls are ignored: circle settings, stroke width, fill opacity, and wrapperStyle are not glyph features and are not forwarded to the DOM.

### Accessibility and refs

Standalone icons are decorative by default. Give a meaningful standalone icon `aria-label` or `aria-labelledby` to expose it as an image. Inside an icon button, name the button and leave the glyph decorative.

```tsx
import { useRef } from 'react';
import { RefreshIcon } from './icons';

export const RefreshStatus = () => {
  const iconRef = useRef<HTMLSpanElement>(null);
  return <RefreshIcon ref={iconRef} aria-label="Refresh available" />;
};
```

The rendered span is not a button. Use `ASIconButton` for an action.

## Direct glyph rendering

`ASIcon as="glyph"` renders an inline span using an application-loaded icon font. Omitting `as` preserves the existing SVG wrapper and circle behavior.

```tsx
<ASIcon
  as="glyph"
  glyph="edit"
  fontFamily="Material Symbols Rounded"
  iconSize={24}
  weight={300}
  fill={0}
  grade={0}
  opticalSize={48}
  aria-label="Edit"
/>
```

`glyph` and `fontFamily` are required. The consumer loads the font; ASUI adds no Google request or runtime font dependency. Ligatures and character glyphs are supported. The rendered size defaults to 24px, independently of optical size.

| Option                  | Default                         | Purpose                                                      |
| ----------------------- | ------------------------------- | ------------------------------------------------------------ |
| `weight`                | 300                             | CSS font weight and `wght` axis                              |
| `fill`                  | 0                               | `FILL` axis: 0 outlined, 1 filled                            |
| `grade`                 | 0                               | `GRAD` axis; Material Symbols supports -50 to 200            |
| `opticalSize`           | 48                              | `opsz` axis; Material Symbols supports 20 to 48              |
| `fontVariationSettings` | Generated from the four options | Custom axis string for other icon fonts                      |
| `color`                 | currentColor                    | Inherits surrounding text colour                             |
| `fillColor`             | Unset                           | Overrides colour for compatibility with icon-button adapters |

Use axis values supported by the loaded font. `style` can override generated inline styles. SVG-only options such as circle decoration and stroke width are not part of glyph mode.

Glyphs are decorative by default (`aria-hidden`). Supplying `aria-label` or `aria-labelledby` gives the span an image role; explicit ARIA attributes and role remain available. Name an enclosing icon button instead of naming its decorative glyph. A React 19 `ref` resolves to the span; use a real button or link for interactive actions.

## Icon buttons

Pass a factory-created icon directly to `ASIconButton`. All modes support `GlyphIcons`.

```tsx
const EditIcon = createGlyphIcon({
  glyph: 'edit',
  fontFamily: 'Material Symbols Rounded',
});

<ASIconButton as="button" icon={EditIcon} aria-label="Edit" onClick={openEditor} />
```

The Material Symbols Storybook examples use the public factory. **ASIcons → Font Glyphs** demonstrates standalone glyphs, factory-created icons, native icon-button use, and span ref focus. Existing SVG examples and default rendering remain supported.

## Migration from local factories

Replace local `createMaterialSymbol('refresh', { weight: 300 })` calls with `createGlyphIcon({ glyph: 'refresh', fontFamily: 'Material Symbols Rounded', weight: 300 })`. Keep the resulting named exports in your app's icon file. Consumers must install an ASUI release containing the factory before removing their local implementation.
