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.
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:
--- 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:
--- 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} />
--- 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.
--- 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:
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> ); }
--- 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:
<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.
<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ón | Tipo | Por defecto | Descripción |
|---|---|---|---|
size | number, string | 24 | Ancho y alto; los números son píxeles. |
color | string | currentColor | Color principal. |
secondaryColor | string | color | Color de la capa duotone y del relleno sólido. |
secondaryOpacity | number | 0.3 / 1 | Opacidad de la capa duotone o del relleno sólido. |
weight | number | 2 | Trazo de 1 a 3 en la cuadrícula de 24, con compensación óptica por debajo de 24 px. |
absoluteWeight | boolean | false | Mantiene el trazo en weight píxeles a cualquier tamaño. |
corners | round, sharp | round | Uniones y extremos redondeados o rectos. |
variant | outline, duotone, solid | outline | Eje de estilo; solid se obtiene de las formas cerradas. |
rotate | number | 0 | Rotación en grados. |
flipX | boolean | false | Refleja en horizontal. |
flipY | boolean | false | Refleja en vertical. |
mirrorRtl | boolean | false | Refleja en layouts de derecha a izquierda. |
animate | draw, pop, wiggle, spin, pulse, bounce, shake | ninguno | Preset de estado vivo. |
trigger | mount, hover, click, state | mount | Cuándo se reproduce la animación o el morph. |
active | boolean | false | El estado para el disparador state. |
duration | number (ms) | según el preset | Duración de la animación o del morph. |
delay | number (ms) | 0 | Retraso de la animación o del morph. |
loop | boolean, number | false | Repite sin fin o un número de veces (spin y pulse se repiten por defecto). |
modifier | plus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dot | ninguno | Insignia con hueco de recorte automático. |
modifierCorner | top-left, top-right, bottom-left, bottom-right | bottom-right | Esquina de la insignia. |
morphTo | name, data | ninguno | Destino del morph; el disparador decide cuándo se muestra. |
title | string | ninguno | Nombre accesible: title, role img y aria-label. |
decorative | boolean | true sin title | Oculta 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 heredacolor. - Nombres en islas
MorphIcon,IconymorphTocargan los nombres bajo demanda. Registra sus datos conregisterIconspara que el HTML del servidor esté completo.- Disparadores de clic dentro de botones
trigger="click"englyph-foxescucha en el icono, y los clics en el padding del botón no le llegan. Usatrigger="state"y asignaactivedesde el botón.- Los nombres desconocidos detienen la compilación
toSvglanza 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
toSvgdel 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, usarenderToStringdeglyphfox/corecon una importación por icono y pasa datos de iconos amorphTo.- 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.