Frameworks

Iconos en SvelteKit

glyphfox/svelte se renderiza en el servidor, así que las páginas de SvelteKit envían SVG real en el HTML, tanto si la página se renderiza en cada solicitud como si se prerenderiza en tiempo de compilación.

Instalación

terminal
npm install glyphfox

No hay nada que configurar. glyphfox/svelte se expone bajo la condición de exportación svelte, así que vite-plugin-svelte, que usan todos los proyectos de SvelteKit, lo reconoce como una biblioteca de Svelte y compila sus componentes para el servidor y el navegador. Los componentes necesitan Svelte 5; la guía de Svelte explica en detalle los componentes y las props.

Registra los nombres de iconos en el layout raíz

Los componentes como HomeIcon registran sus propios datos. Los nombres que solo aparecen en cadenas, en Icon, Fusion, MorphIcon o morphTo, necesitan registerIcons, y el mejor sitio para ello es un módulo importado por el layout raíz: se ejecuta en el servidor antes del primer renderizado y en el navegador antes de la hidratación.

src/lib/icons.js
import { registerIcons } from 'glyphfox/svelte';
import menu from 'glyphfox/icons/menu';
import x from 'glyphfox/icons/x';
import moon from 'glyphfox/icons/moon';
import file from 'glyphfox/icons/file';

registerIcons([menu, x, moon, file]);
src/routes/+layout.svelte
<script>
  import '$lib/icons.js';
  import { MorphIcon } from 'glyphfox/svelte';

  let { children } = $props();
  let open = $state(false);
</script>

<header>
  <button type="button" aria-label="Menu" aria-expanded={open} onclick={() => (open = !open)}>
    <MorphIcon name={open ? 'x' : 'menu'} />
  </button>
</header>

{@render children()}

El registro es estado del módulo. En el servidor lo comparten todas las solicitudes, lo cual es seguro porque los datos de los iconos nunca cambian, y se llena una sola vez, cuando el módulo se carga por primera vez.

Qué renderiza el servidor

Los componentes de icono generan su svg completo durante el renderizado en el servidor. Las animaciones de los estados vivos son una hoja de estilos dentro del svg, así que un spinner ya gira en el HTML del servidor, salvo que el visitante prefiera movimiento reducido. Los morphs y los disparadores de clic y hover empiezan después de la hidratación, porque $effect no se ejecuta en el servidor.

Para un morph cuyo extremo depende de los datos, usa trigger="state" con active. El servidor renderiza el extremo correcto del morph, y la animación solo se reproduce cuando el valor cambia en el navegador. Con el disparador por defecto (mount), el servidor renderiza el icono inicial y el morph se reproduce en cada carga de página.

src/routes/+layout.server.js
export function load({ cookies }) {
  return { theme: cookies.get('theme') ?? 'light' };
}
src/lib/ThemeToggle.svelte
<script>
  import { SunIcon } from 'glyphfox/svelte';

  let { theme } = $props();
  let dark = $state(theme === 'dark');

  function toggle() {
    dark = !dark;
    document.cookie = `theme=${dark ? 'dark' : 'light'}; path=/; max-age=31536000`;
  }
</script>

<button type="button" aria-label="Dark theme" aria-pressed={dark} onclick={toggle}>
  <SunIcon morphTo="moon" trigger="state" active={dark} />
</button>

Leer el tema desde una cookie en una función load del servidor da al servidor y al navegador el mismo valor inicial. Renderiza el componente desde el layout raíz con <ThemeToggle theme={data.theme} />; moon está registrado en src/lib/icons.js.

Archivos SVG desde un endpoint

toSvg, del punto de entrada principal glyphfox, renderiza cualquier especificación en una cadena, lo que permite crear una ruta +server.js que sirva iconos como archivos: un favicon, una imagen para una plantilla de correo o una vista previa de enlace. Con entries, los archivos se prerenderizan en tiempo de compilación.

src/routes/icons/[name].svg/+server.js
import { error } from '@sveltejs/kit';
import { iconNames, toSvg } from 'glyphfox';

export const prerender = true;

export function entries() {
  return ['fox', 'home', 'search'].map((name) => ({ name }));
}

export function GET({ params }) {
  if (!iconNames.includes(params.name)) error(404, 'Unknown icon');
  const svg = toSvg(params.name, { size: 32, color: '#ff7a1a', variant: 'duotone' });
  return new Response(svg, { headers: { 'content-type': 'image/svg+xml' } });
}
src/app.html
<link rel="icon" href="/icons/fox.svg" type="image/svg+xml" />

Un archivo SVG independiente no tiene un elemento padre del que heredar un color, así que pasa color: se escribe en el svg y los trazos con currentColor lo toman. Devuelve iconNames.map((name) => ({ name })) desde entries para prerenderizar todos los iconos.

El punto de entrada principal incluye los datos de todos los iconos. Eso no es problema en los archivos +server.js y +page.server.js, que nunca llegan al navegador; si lo importas en un componente, toSvg enviaría el conjunto completo al cliente.

Marcado solo de servidor con {@html}

Una función load en +page.server.js puede devolver cadenas ya renderizadas que la página inserta con {@html}. El navegador recibe el marcado en el HTML y en los datos serializados de la página, pero ningún código de iconos.

src/routes/+page.server.js
import { toSvg } from 'glyphfox';

const features = [
  { icon: 'zap', title: 'Fast' },
  { icon: 'shield-check', title: 'Safe' },
  { icon: 'accessibility', title: 'Accessible' },
];

export function load() {
  return {
    features: features.map((f) => ({ ...f, svg: toSvg(f.icon, { size: 28, weight: 1.5 }) })),
  };
}
src/routes/+page.svelte
<script>
  let { data } = $props();
</script>

<ul class="features">
  {#each data.features as feature (feature.icon)}
    <li>{@html feature.svg} {feature.title}</li>
  {/each}
</ul>

{@html} es seguro aquí porque las cadenas vienen de tu propio código de servidor, nunca de datos introducidos por el usuario. Sin title, los iconos se renderizan con aria-hidden, que es lo correcto junto a una etiqueta visible.

adapter-static y prerenderizado

Con adapter-static, o con export const prerender = true en cualquier ruta, las páginas se renderizan en tiempo de compilación y los iconos se escriben en los archivos HTML. Las reglas del renderizado en el servidor también se aplican en la compilación: un nombre que la compilación no puede resolver se escribe como un svg vacío.

svelte.config.js
import adapter from '@sveltejs/adapter-static';

export default {
  kit: { adapter: adapter() },
};
src/routes/+layout.js
export const prerender = true;

Para las páginas que no necesitan JavaScript, añade export const csr = false: los iconos siguen siendo SVG estático y no se envía código de componentes. Las animaciones CSS se siguen reproduciendo, mientras que los morphs y los disparadores de clic o hover, que necesitan JavaScript, no hacen nada.

Nombres en compilaciones de producción

En desarrollo, Vite pre-empaqueta la dependencia, así que cualquier nombre parece funcionar. En una compilación de producción, Vite no empaqueta el cargador bajo demanda que glyphfox tiene en node_modules, porque omite las importaciones dinámicas con variables en las dependencias, y los nombres sin registrar se quedan vacíos. Regístralos o deja que Vite empaquete el cargador, que entonces genera un pequeño chunk por icono:

vite.config.js
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [sveltekit()],
  build: {
    dynamicImportVarsOptions: { exclude: [/node_modules\/(?!glyphfox\/)/] },
  },
});

Esos chunks solo sirven en el navegador, después de la hidratación. El renderizado en el servidor no los espera, así que registrar el icono sigue siendo la forma de que aparezca en el HTML.

Props

SvelteKit usa los componentes de glyphfox/svelte, así que las props son las compartidas, en camelCase.

PropTipoPor 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

Registrar en onMount o en código solo de navegador
El código en onMount, en $effect o dentro de if (browser) se ejecuta después de que el servidor haya renderizado. Entonces el HTML tiene iconos vacíos que aparecen de golpe tras la hidratación. Haz el registro en un módulo importado por el layout raíz.
Un icono distinto en el servidor y en el navegador
El contenido del svg se inserta con {@html}, y Svelte conserva el marcado del servidor de un bloque {@html} cuando el primer renderizado del cliente es distinto (en desarrollo avisa con hydration_html_changed). Por eso, un icono elegido a partir de localStorage o window puede quedarse mal hasta el siguiente cambio. Obtén el primer valor de datos que el servidor también tenga, como una cookie leída en una función load.
toSvg en un componente compartido
Importar toSvg de glyphfox en un archivo .svelte mete los datos de todos los iconos en el bundle del cliente. Mantenlo en los archivos de servidor o usa los componentes.
Un morph prerenderizado en el estado equivocado
Con el disparador mount, el HTML prerenderizado muestra el icono inicial y el morph se reproduce tras la hidratación. Usa trigger="state" para todo lo que deba prerenderizarse en su estado final.

Preguntas frecuentes

¿Puedo prerenderizar cada icono como un archivo SVG?

Sí. Devuelve iconNames.map((name) => ({ name })) desde entries en el endpoint anterior, y la compilación escribe un archivo por icono en /icons/. Cada archivo ocupa unos cientos de bytes.

¿Funcionan los iconos en páginas con csr = false?

Sí. El servidor escribe el svg en el HTML, y las animaciones de los estados vivos se siguen reproduciendo porque son CSS dentro del svg. Los morphs y los disparadores de clic y hover necesitan JavaScript, así que se quedan en su primer fotograma.

Iconos de esta guía