Frameworks

Iconos en Astro

Astro renderiza los componentes a HTML en el servidor o en tiempo de compilación, algo que encaja muy bien con los iconos SVG: llama al renderizador en el frontmatter y la página recibe simplemente SVG en línea, sin JavaScript en el cliente. Las islas y el custom element se encargan de los iconos que tienen que reaccionar.

Instalación

Instala el paquete. Los iconos estáticos no necesitan ninguna integración de Astro, porque glyphfox se compone de módulos ES estándar que se ejecutan en el frontmatter. Las islas creadas con glyphfox/react, glyphfox/vue o glyphfox/svelte necesitan la integración de Astro correspondiente.

terminal
npm install glyphfox
# only for islands
npx astro add react

Iconos en el frontmatter

El código del frontmatter se ejecuta en el servidor, o en tiempo de compilación en las páginas estáticas. toSvg devuelve la cadena SVG de cualquier nombre de icono, y set:html la escribe en la página sin escaparla:

src/pages/index.astro
---
import { toSvg } from 'glyphfox';

const search = toSvg('search', { size: 20 });
---
<label class="search">
  <Fragment set:html={search} />
  <input type="search" name="q" aria-label="Search the docs" />
</label>

toSvg acepta la sintaxis completa de nombres, así que las insignias, las fusiones y los estilos caben en una sola cadena: toSvg('file+check@tr'), toSvg('cloud&lock:overlap'), toSvg('heart/solid').

Importar glyphfox carga todos los iconos, pero solo en el servidor o durante la compilación. Nada de eso llega al navegador, porque el frontmatter nunca se ejecuta ahí.

Un componente Icon.astro

Envuelve la llamada en un componente para que las páginas pasen props en lugar de opciones. El SVG va directamente dentro del elemento padre, sin elemento contenedor:

src/components/Icon.astro
---
import { toSvg, type SvgOptions } from 'glyphfox';

interface Props extends SvgOptions {
  name: string;
}

const { name, ...options } = Astro.props;
const svg = toSvg(name, options);
---
<Fragment set:html={svg} />
src/components/Nav.astro
---
import Icon from './Icon.astro';
---
<nav class="nav">
  <a href="/"><Icon name="home" size={20} /> Home</a>
  <a href="/docs/"><Icon name="book-open" size={20} weight={1.5} /> Docs</a>
  <a href="/new/"><Icon name="file+plus" size={20} title="New file" /></a>
</nav>

<style>
  .nav :global(.glyphfox) { color: #6b7280; }
  .nav a:hover :global(.glyphfox) { color: #111827; }
</style>

Cada svg lleva las clases glyphfox y glyphfox-NAME. El marcado insertado con set:html no recibe los atributos que Astro usa para limitar el alcance de los estilos de los componentes, así que selecciónalo con :global() dentro de un selector padre con alcance limitado, como arriba, o deja que herede color a través de currentColor.

El enlace de icono del ejemplo anterior no tiene texto, así que su icono recibe un title: eso añade role="img", aria-label y un <title>, y el enlace toma de ahí su nombre. Los iconos junto a texto visible siguen siendo decorativos. Hay más patrones en la guía de accesibilidad.

Movimiento sin JavaScript

El SVG puede llevar su propio CSS, así que los estados vivos y los morphs al pasar el cursor se reproducen en HTML estático sin ningún script: animate con el trigger="mount" por defecto o con "hover", y morphTo con trigger="hover", que reacciona al svg y a su padre directo.

src/components/Chapter.astro
---
import Icon from './Icon.astro';
---
<a class="next" href="/chapter-2/">
  Next chapter <Icon name="arrow-right" morphTo="chevron-right" trigger="hover" />
</a>

<p class="status">
  <Icon name="loader" animate="spin" title="Building" /> Building the preview
</p>

Como Icon.astro no añade ningún contenedor, el svg es hijo directo del enlace, y pasar el cursor por cualquier parte del enlace hace que la flecha se transforme. Todos los presets se detienen con prefers-reduced-motion; los presets y los disparadores aparecen en iconos animados.

Islas para el estado

Los iconos que siguen un estado, como un botón de menú que se transforma en un icono de cerrar, van en una isla. Usa el paquete del framework de la isla e hidrátala con una directiva de cliente:

src/components/MenuButton.jsx
import { useState } from 'react';
import { MorphIcon, registerIcons } from 'glyphfox/react';
import menu from 'glyphfox/icons/menu';
import x from 'glyphfox/icons/x';

// Names passed to MorphIcon load on demand; registering them
// puts the icon in the server rendered HTML.
registerIcons([menu, x]);

export default function MenuButton() {
  const [open, setOpen] = useState(false);
  return (
    <button
      type="button"
      aria-expanded={open}
      aria-label={open ? 'Close menu' : 'Open menu'}
      onClick={() => setOpen(!open)}
    >
      <MorphIcon name={open ? 'x' : 'menu'} duration={300} />
    </button>
  );
}
src/layouts/Base.astro
---
import MenuButton from '../components/MenuButton.jsx';
---
<header>
  <MenuButton client:load />
</header>

Astro renderiza primero las islas en el servidor. Los componentes de icono como MenuIcon registran su icono cuando tu código los usa, pero los nombres que se pasan a MorphIcon, Icon o morphTo se cargan bajo demanda, así que sin registerIcons el HTML del servidor contiene un svg vacío hasta que la isla se hidrata.

Lo mismo funciona con glyphfox/vue y con glyphfox/svelte, que necesita Svelte 5. Para las islas que quedan por debajo del pliegue, client:visible o client:idle retrasan la descarga hasta que hace falta. Los detalles de cada framework están en las guías de React, Vue y Svelte.

El custom element

Sin framework, el elemento <glyph-fox> se encarga de los clics, el hover y los cambios de nombre. Astro procesa y empaqueta las etiquetas <script> de los componentes, e incluye cada script una sola vez por página, aunque el componente aparezca muchas veces:

src/components/LikeButton.astro
<button type="button" class="like" aria-pressed="false" aria-label="Like">
  <glyph-fox name="heart" morph-to="heart/solid" trigger="state"></glyph-fox>
</button>

<script>
  import { defineGlyphFox } from 'glyphfox/web/element';
  import { registerIcons } from 'glyphfox/core';
  import heart from 'glyphfox/icons/heart';

  registerIcons([heart]);
  defineGlyphFox();

  for (const button of document.querySelectorAll('button.like')) {
    const icon = button.querySelector('glyph-fox');
    button.addEventListener('click', () => {
      const on = button.getAttribute('aria-pressed') !== 'true';
      button.setAttribute('aria-pressed', String(on));
      icon.toggleAttribute('active', on);
    });
  }
</script>

El botón controla el icono mediante trigger="state" y el atributo active. Con trigger="click", el elemento escucharía los eventos sobre sí mismo, y un clic en el padding del botón alternaría aria-pressed sin transformar el icono.

Los custom elements siguen definidos durante la navegación del lado del cliente con el router de transiciones de vista de Astro, así que los iconos de las páginas nuevas se actualizan solos. El código que añade listeners, como el bucle de arriba, se ejecuta una vez; con el router, vuelve a ejecutarlo en el evento astro:page-load.

Clases de Tailwind

Con Tailwind CSS en el proyecto, el plugin de glyphfox dibuja los iconos como máscaras CSS, otra opción sin JavaScript, práctica en plantillas con mucho contenido. La configuración y la sintaxis de las clases están en la guía de Tailwind.

src/components/Hero.astro
<h1 class="flex items-center gap-3">
  <span class="gf-rocket text-4xl text-orange-500" aria-hidden="true"></span>
  Launch faster
</h1>

Props

Icon.astro pasa sus props a toSvg, así que acepta las opciones compartidas con sus nombres en camelCase, además de class y style como cadenas para el svg. A diferencia del renderizador del core, aquí morphTo puede ser un nombre, porque toSvg lo resuelve.

OpciónTipoPor defectoDescripción
sizenumber, string24Ancho y alto; los números son píxeles.
colorstringcurrentColorColor principal.
secondaryColorstringcolorColor de la capa duotone y del relleno sólido.
secondaryOpacitynumber0.3 / 1Opacidad de la capa duotone o del relleno sólido.
weightnumber2Trazo de 1 a 3 en la cuadrícula de 24, con compensación óptica por debajo de 24 px.
absoluteWeightbooleanfalseMantiene el trazo en weight píxeles a cualquier tamaño.
cornersround, sharproundUniones y extremos redondeados o rectos.
variantoutline, duotone, solidoutlineEje de estilo; solid se obtiene de las formas cerradas.
rotatenumber0Rotación en grados.
flipXbooleanfalseRefleja en horizontal.
flipYbooleanfalseRefleja en vertical.
mirrorRtlbooleanfalseRefleja en layouts de derecha a izquierda.
animatedraw, pop, wiggle, spin, pulse, bounce, shakeningunoPreset de estado vivo.
triggermount, hover, click, statemountCuándo se reproduce la animación o el morph.
activebooleanfalseEl estado para el disparador state.
durationnumber (ms)según el presetDuración de la animación o del morph.
delaynumber (ms)0Retraso de la animación o del morph.
loopboolean, numberfalseRepite sin fin o un número de veces (spin y pulse se repiten por defecto).
modifierplus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dotningunoInsignia con hueco de recorte automático.
modifierCornertop-left, top-right, bottom-left, bottom-rightbottom-rightEsquina de la insignia.
morphToname, dataningunoDestino del morph; el disparador decide cuándo se muestra.
titlestringningunoNombre accesible: title, role img y aria-label.
decorativebooleantrue sin titleOculta el icono a las tecnologías de apoyo.

Errores comunes

Estilos con alcance limitado y set:html
Las reglas del bloque de estilos de un componente no se aplican al marcado insertado con set:html, porque le faltan los atributos de alcance. Usa :global(.glyphfox) dentro de un selector con alcance limitado o hereda color.
Nombres en islas
MorphIcon, Icon y morphTo cargan los nombres bajo demanda. Registra sus datos con registerIcons para que el HTML del servidor esté completo.
Disparadores de clic dentro de botones
trigger="click" en glyph-fox escucha en el icono, y los clics en el padding del botón no le llegan. Usa trigger="state" y asigna active desde el botón.
Los nombres desconocidos detienen la compilación
toSvg lanza un error con un nombre que no existe, lo que hace fallar una compilación estática y devuelve un error en modo servidor. Comprueba que los nombres que vienen del contenido estén en la lista de iconos, o captura el error cuando vengan de datos del usuario.
Bundles de servidor en entornos edge
toSvg del punto de entrada principal mete todos los iconos en el bundle del servidor, unos 154 KB minificados. Donde el tamaño de las funciones esté limitado, usa renderToString de glyphfox/core con una importación por icono y pasa datos de iconos a morphTo.
Contenedores y morphs al pasar el cursor
Un morph al pasar el cursor reacciona al svg y a su padre directo. Si envuelves el icono en un elemento adicional, ese elemento pasa a ser el padre, así que pasar el cursor por el enlace que lo rodea ya no inicia el morph.

Preguntas frecuentes

¿Los iconos renderizados en el frontmatter envían algo de JavaScript?

No. El frontmatter se ejecuta en el servidor o en tiempo de compilación, y la página recibe SVG puro. Los presets y los morphs al pasar el cursor son CSS dentro de ese SVG. Solo las islas y el custom element añaden código de cliente.

¿Puedo usar iconos en contenido Markdown o MDX?

En MDX, importa Icon.astro y úsalo como en cualquier componente. El Markdown normal no puede importar componentes, así que renderiza los iconos en el layout que rodea el contenido, o usa las clases de Tailwind en HTML en bruto donde tu Markdown lo permita.

Iconos de esta guía