Frameworks

Iconos en Lit y web components

glyphfox incluye un custom element, <glyph-fox>, que recibe como atributos el nombre del icono y todas las props compartidas. Funciona en cualquier lugar donde funcione HTML y encaja en las plantillas de Lit sin necesidad de ningún wrapper.

Instalación

Instala el paquete y registra el elemento una sola vez, en el módulo de entrada de tu aplicación. Importar glyphfox/web registra todos los iconos y define <glyph-fox> como efecto secundario, así que no hace falta nada más.

terminal
npm install glyphfox
main.js
import 'glyphfox/web';

Para incluir solo los iconos que usas, importa el elemento desde glyphfox/web/element, registra los datos de los iconos desde glyphfox/icons/NAME y define tú mismo el elemento. registerIcons viene de glyphfox/core, que comparte su registro con el elemento.

icons.js
import { defineGlyphFox } from 'glyphfox/web/element';
import { registerIcons } from 'glyphfox/core';
import menu from 'glyphfox/icons/menu';
import x from 'glyphfox/icons/x';
import bell from 'glyphfox/icons/bell';
import search from 'glyphfox/icons/search';

registerIcons([menu, x, bell, search]);
defineGlyphFox();
Punto de entradaQué incluyeTamaño (esbuild, minificado)
glyphfox/webEl elemento, el motor y todos los iconos, registrados al importarlo.unos 209 KB (59 KB con gzip)
glyphfox/web/elementEl elemento y el motor; los iconos los registras tú.unos 77 KB (28 KB con gzip) con un icono, más unos cientos de bytes por icono

Los nombres que no hayas registrado se cargan bajo demanda. webpack y esbuild convierten esas cargas en un chunk pequeño por icono, pero un build de producción de Vite deja el import dentro de node_modules tal como está escrito y el icono se queda vacío (consulta la guía de tree shaking). Registra todos los iconos que uses con glyphfox/web/element; así, además, se dibujan desde el primer fotograma, sin esperar a ninguna petición de red.

Usa el elemento

El elemento renderiza un SVG en línea dentro de su shadow root. name admite la sintaxis de nombres completa, así que las insignias de modificador, las fusiones y los estilos no necesitan ninguna API adicional, y cada prop compartida es un atributo en kebab-case.

index.html
<glyph-fox name="home"></glyph-fox>
<glyph-fox name="file+check" size="32" weight="1.5"></glyph-fox>
<glyph-fox name="cloud&lock" layout="overlap"></glyph-fox>
<glyph-fox name="heart/solid" color="#e11d48"></glyph-fox>
<glyph-fox name="bell" animate="shake" trigger="hover"></glyph-fox>

El elemento observa sus atributos. Cambia uno con setAttribute, o desde cualquier plantilla que escriba atributos, y el icono se vuelve a dibujar con el nuevo valor.

Plantillas de Lit

En Lit, pasa los valores con bindings de atributo. El elemento lee atributos y no tiene propiedades de JavaScript equivalentes, así que un binding de propiedad como .name=${...} asigna un campo que el elemento nunca lee, y el icono se queda como estaba.

app-toolbar.js
import { LitElement, html, css } from 'lit';
import 'glyphfox/web';

export class AppToolbar extends LitElement {
  static properties = {
    open: { type: Boolean },
    unread: { type: Number },
  };

  static styles = css`
    button { display: inline-flex; align-items: center; gap: 8px; color: inherit; }
    glyph-fox { color: var(--toolbar-icon, currentColor); }
  `;

  constructor() {
    super();
    this.open = false;
    this.unread = 0;
  }

  render() {
    return html`
      <button type="button" aria-expanded=${this.open} @click=${() => (this.open = !this.open)}>
        <glyph-fox name=${this.open ? 'x' : 'menu'} morph duration="300"></glyph-fox>
        <span>Menu</span>
      </button>
      <glyph-fox
        name="bell"
        animate="shake"
        trigger="state"
        ?active=${this.unread > 0}
        title=${this.unread ? `${this.unread} new notifications` : 'No new notifications'}
      ></glyph-fox>
    `;
  }
}

customElements.define('app-toolbar', AppToolbar);

Para los flags, usa bindings booleanos: ?morph=${...} y ?active=${...}. El elemento interpreta un atributo vacío o "true" como verdadero, y "false" o la ausencia del atributo como falso, así que un binding booleano se corresponde uno a uno.

Para los valores opcionales, pasa nothing en el binding (o usa la directiva ifDefined) para que el atributo se elimine cuando no haya valor. Un atributo size vacío deja el svg interno con un ancho y un alto vacíos, y el navegador recurre al tamaño predeterminado de los svg.

icon-button.js
import { html, nothing } from 'lit';

export const iconButton = ({ name, size, label, onClick }) => html`
  <button type="button" aria-label=${label} @click=${onClick}>
    <glyph-fox name=${name} size=${size ?? nothing}></glyph-fox>
  </button>
`;

Estilos a través del shadow root

El svg está en el shadow root del elemento, con los trazos en currentColor. El color se hereda a través del límite del shadow DOM, así que define color en el elemento o en cualquier ancestro. El host usa display: inline-flex con line-height: 0 y vertical-align: middle, de modo que se integra en una línea de texto sin CSS adicional.

El tamaño viene del atributo size, que define el ancho y el alto del svg interno. Aplicar width y height con CSS al host no escala el dibujo. Usa size="1em" para seguir el tamaño de fuente, o escribe el atributo a partir de tus propios tokens de diseño.

note.html
<p class="note">
  <glyph-fox name="circle-info" size="1em" weight="2.5"></glyph-fox>
  The icon follows the font size of this paragraph.
</p>
<glyph-fox name="folder" variant="duotone" color="#0f172a" secondary-color="#ff7a1a"></glyph-fox>
toolbar.css
.toolbar glyph-fox { color: #6b7280; }
.toolbar button:hover glyph-fox { color: #111827; }

No hay parts ni slots a los que dar estilo: todo lo que hay dentro se genera a partir de las props. Cambia el dibujo con atributos como weight, corners, variant y secondary-color, no con selectores que intenten llegar al interior del shadow root.

Tu propio nombre de etiqueta

defineGlyphFox(tag) define el elemento con otro nombre y devuelve su clase. Úsalo para adoptar el prefijo de tu sistema de diseño o para evitar un conflicto con otro elemento. Puedes llamarlo más de una vez sin problema: si la etiqueta ya está definida, se devuelve tal cual.

icons.js
import { defineGlyphFox } from 'glyphfox/web/element';
import { registerIcons } from 'glyphfox/core';
import home from 'glyphfox/icons/home';
import settings from 'glyphfox/icons/settings';

registerIcons([home, settings]);
defineGlyphFox('acme-icon');
index.html
<acme-icon name="home" size="20"></acme-icon>
<acme-icon name="settings" size="20" weight="1.5"></acme-icon>

Importar glyphfox/web también define glyph-fox, así que elige un único punto de entrada por aplicación: glyphfox/web para la etiqueta predeterminada con todos los iconos, y glyphfox/web/element cuando eliges tú la etiqueta y la lista de iconos.

Disparadores y morphs

El elemento gestiona su propia interacción. Con trigger="hover" o trigger="click" escucha los eventos sobre sí mismo, y con trigger="state" sigue el atributo active. El atributo morph anima cada cambio de name, mientras que con morph-to el icono se transforma en un segundo icono cada vez que se activa el disparador.

index.html
<glyph-fox name="menu" morph-to="x" trigger="click"></glyph-fox>
<glyph-fox name="sparkles" animate="draw" trigger="hover"></glyph-fox>
<glyph-fox id="visibility" name="eye" morph></glyph-fox>

<script type="module">
  const eye = document.getElementById('visibility');
  eye.addEventListener('click', () => {
    eye.setAttribute('name', eye.getAttribute('name') === 'eye' ? 'eye-off' : 'eye');
  });
</script>

El hover y el clic se detectan en el propio elemento. Los estilos de animación están dentro del shadow root, desde donde no pueden ver al elemento padre, y el clic se escucha en el host. Cuando deba reaccionar todo un botón, controla el icono desde el botón: usa trigger="state" y asigna active desde los manejadores de eventos del botón.

save-button.html
<button type="button" class="save">
  <glyph-fox name="save" animate="pop" trigger="state"></glyph-fox>
  Save
</button>

<script type="module">
  const button = document.querySelector('.save');
  const icon = button.querySelector('glyph-fox');
  button.addEventListener('pointerenter', () => icon.setAttribute('active', ''));
  button.addEventListener('pointerleave', () => icon.removeAttribute('active'));
</script>

Los morphs y los presets respetan prefers-reduced-motion: las transiciones saltan al estado final y las animaciones no se reproducen. Los presets y los disparadores se describen en iconos animados.

SVG estático dentro de plantillas de Lit

Cuando un icono no cambia nunca después del renderizado, puedes prescindir del elemento e insertar el marcado SVG. renderToString de glyphfox/core renderiza los datos del icono con las props compartidas, y Lit inserta la cadena con la directiva unsafeHTML. El renderizador escapa los títulos y los valores de los atributos, así que es seguro insertar el marcado.

saved-badge.js
import { html } from 'lit';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import { renderToString } from 'glyphfox/core';
import circleCheck from 'glyphfox/icons/circle-check';

const check = unsafeHTML(renderToString(circleCheck, { size: 16, color: '#16a34a' }));

export const savedBadge = () => html`<span class="badge">${check} Saved</span>`;

Para la sintaxis de nombres (file+check@tr, cloud&lock), usa toSvg de glyphfox. Resuelve cualquier nombre, lo que significa que incluye todos los iconos; consulta tree shaking antes de usarlo en un bundle de cliente.

Atributos

Cada prop compartida es un atributo en kebab-case. El elemento añade cuatro propios: name para el icono, con la sintaxis de nombres completa; layout y gap para las fusiones; y morph, que anima los cambios de name. Los números se leen a partir de su texto, y los booleanos siguen la regla anterior.

AtributoTipoPor defectoDescripción
namestringningunoLa especificación del icono: un nombre, name+modifier@corner, a&b:layout o name/variant.
layoutoverlap, side, stack, grid, orbit, badge, insideningunoLayout de la fusión cuando el nombre une iconos con &.
gapnumber1Hueco de recorte de una fusión, en unidades de la cuadrícula.
morphbooleanfalseAnima cada cambio de name como un morph.
sizenumber, string24Ancho y alto; los números son píxeles.
colorstringcurrentColorColor principal.
secondary-colorstringcolorColor de la capa duotone y del relleno sólido.
secondary-opacitynumber0.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.
absolute-weightbooleanfalseMantiene 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.
flip-xbooleanfalseRefleja en horizontal.
flip-ybooleanfalseRefleja en vertical.
mirror-rtlbooleanfalseRefleja 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.
modifier-cornertop-left, top-right, bottom-left, bottom-rightbottom-rightEsquina de la insignia.
morph-toname, 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.

Accesibilidad

Sin etiqueta, el icono es decorativo: el svg interno recibe aria-hidden="true". Es lo correcto para un icono junto a texto visible y para un botón de icono que lleva su propio aria-label.

Para darle un nombre al propio icono, usa el atributo title. Añade un <title>, role="img" y aria-label al svg interno. No confíes en aria-label sobre el elemento: la etiqueta se queda en el host, que no tiene rol, mientras que el svg interior se convierte en una imagen sin nombre. title en el host es, además, el atributo global de HTML, así que los navegadores lo muestran como tooltip.

index.html
<!-- decorative: the button carries the name -->
<button type="button" aria-label="Close dialog">
  <glyph-fox name="x"></glyph-fox>
</button>

<!-- meaningful on its own -->
<glyph-fox name="triangle-alert" title="Warning" color="#b45309"></glyph-fox>

Encontrarás patrones para enlaces, iconos de estado y contraste en la guía de accesibilidad.

Renderizado en el servidor y desplazamientos del diseño

El elemento se dibuja cuando el navegador lo inicializa. El HTML renderizado en el servidor solo contiene la etiqueta vacía, y el elemento no usa shadow DOM declarativo, así que el icono aparece después de que se ejecute el script. Reserva el espacio para que el diseño no se mueva cuando aparezca.

icons.css
glyph-fox:not(:defined) {
  display: inline-block;
  width: 24px;
  height: 24px;
  vertical-align: middle;
}
glyph-fox[size="16"]:not(:defined) {
  width: 16px;
  height: 16px;
}

Cuando un icono tenga que estar en el primer pintado, en un correo electrónico o en el HTML para rastreadores, renderiza en su lugar la cadena SVG en el servidor. Las opciones de cada framework están en renderizado en el servidor.

Errores comunes

Etiquetas autocerradas
Escribe <glyph-fox name="home"></glyph-fox>. HTML ignora la barra de <glyph-fox />, así que el elemento queda abierto y el contenido que viene después se convierte en sus hijos, que el shadow root no muestra.
Bindings de propiedad
En Lit, .name=${...} y los demás bindings de propiedad asignan un campo de JavaScript que el elemento no lee. Usa bindings de atributo. Solid necesita attr: por el mismo motivo; consulta la guía de Solid.
Atributos vacíos
Un size vacío renderiza un svg con el ancho y el alto vacíos. Elimina el atributo cuando no haya valor, con nothing o ifDefined en Lit.
Hover y clic en el elemento padre
Los disparadores de hover y clic solo se activan sobre el icono, no sobre el botón que lo rodea. Usa trigger="state" y asigna active desde el elemento padre.
Los cambios de atributos repiten las animaciones de montaje
El elemento reemplaza su svg interno cada vez que cambia el marcado, así que con un preset de animate y el trigger="mount" predeterminado, cambiar color o size vuelve a reproducir la animación. Usa trigger="state" en los iconos cuyos otros atributos cambian a menudo.
Un icono vacío
Un nombre desconocido no renderiza nada y muestra una advertencia glyphfox: en la consola. Un nombre válido que no está registrado se dibuja en cuanto se carga su módulo, así que revisa el panel de red antes de buscar una errata.

Preguntas frecuentes

¿Puedo usar el elemento en React, Vue o Angular?

Sí, es un custom element estándar. Aun así, ahí encajan mejor los paquetes para cada framework: glyphfox/react, glyphfox/vue y glyphfox-angular ofrecen props tipadas y se renderizan en el servidor. Reserva el elemento para Lit, HTML plano y páginas que combinan distintos stacks. En las plantillas de Vue, indica al compilador que glyph-fox es un custom element con compilerOptions.isCustomElement; de lo contrario, Vue intentará resolverlo como un componente.

Iconos de esta guía