Frameworks

Ícones em Lit e web components

O glyphfox traz um custom element, <glyph-fox>, que recebe o nome do ícone e todas as props compartilhadas como atributos. Ele funciona em qualquer lugar onde HTML funciona e se encaixa nos templates do Lit sem wrapper.

Instalação

Instale o pacote e registre o elemento uma única vez, no módulo de entrada da sua aplicação. Importar glyphfox/web registra todos os ícones e define <glyph-fox> como efeito colateral, então não é preciso mais nada.

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

Para incluir no bundle só os ícones que você usa, pegue o elemento de glyphfox/web/element, registre os dados dos ícones a partir de glyphfox/icons/NAME e defina o elemento você mesmo. registerIcons vem de glyphfox/core, que compartilha o registro com o 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();
Ponto de entradaO que é entregueTamanho (esbuild, minificado)
glyphfox/webO elemento, o motor e todos os ícones, registrados na importação.cerca de 209 KB, 59 KB com gzip
glyphfox/web/elementO elemento e o motor; você registra os ícones.cerca de 77 KB, 28 KB com gzip, incluindo um ícone, mais algumas centenas de bytes por ícone

Um nome que você não registrou é carregado sob demanda. O webpack e o esbuild transformam esses carregamentos em um pequeno chunk por ícone, mas o build de produção do Vite deixa o import dentro de node_modules do jeito que foi escrito, e o ícone fica vazio (veja o guia de tree shaking). Registre todos os ícones que você usa com glyphfox/web/element; assim eles também são desenhados já no primeiro frame, sem nenhuma requisição de rede.

Use o elemento

O elemento renderiza um SVG inline dentro do seu shadow root. name aceita a sintaxe de nomes completa, então selos modificadores, fusões e estilos não precisam de nenhuma API extra, e toda prop compartilhada é um atributo em 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>

O elemento observa os próprios atributos. Mude um deles com setAttribute, ou por qualquer template que escreva atributos, e o ícone é redesenhado com o novo valor.

Templates do Lit

No Lit, passe os valores com bindings de atributo. O elemento lê atributos e não tem propriedades JavaScript correspondentes, então um binding de propriedade como .name=${...} define um campo que o elemento nunca lê, e o ícone continua como estava.

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);

Use bindings booleanos para flags: ?morph=${...} e ?active=${...}. O elemento lê um atributo vazio ou "true" como verdadeiro, e "false" ou um atributo ausente como falso, então o binding booleano tem correspondência exata.

Para valores opcionais, faça o binding de nothing (ou use a diretiva ifDefined) para que o atributo seja removido quando não houver valor. Um atributo size vazio deixa o svg interno com largura e altura vazias, e o navegador recorre ao tamanho padrão de 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>
`;

Estilização através do shadow root

O svg fica no shadow root do elemento, com os traços em currentColor. A cor é herdada através do limite do shadow DOM, então defina color no elemento ou em qualquer ancestral. O host usa display: inline-flex com line-height: 0 e vertical-align: middle, então se acomoda numa linha de texto sem CSS extra.

O tamanho vem do atributo size, que define a largura e a altura do svg interno. As propriedades CSS width e height no host não redimensionam o desenho. Use size="1em" para acompanhar o tamanho da fonte, ou preencha o atributo a partir dos seus próprios design tokens.

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; }

Não há parts nem slots para estilizar: tudo o que fica lá dentro é gerado a partir das props. Mude o desenho com atributos como weight, corners, variant e secondary-color, e não com seletores que tentam alcançar o interior do shadow root.

Um nome de tag próprio

defineGlyphFox(tag) define o elemento com outro nome e retorna a classe dele. Use para seguir o prefixo do seu design system ou para evitar conflito com outro elemento. É seguro chamar mais de uma vez: uma tag que já está definida é retornada como está.

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 também define glyph-fox, então escolha um ponto de entrada por aplicação: glyphfox/web para a tag padrão com todos os ícones, glyphfox/web/element quando você escolhe a tag e a lista de ícones.

Gatilhos e morphs

O elemento cuida da própria interação. Com trigger="hover" ou trigger="click", ele escuta os eventos em si mesmo, e com trigger="state", segue o atributo active. O atributo morph anima toda troca de name, enquanto morph-to faz o ícone se transformar em um segundo ícone sempre que o gatilho dispara.

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>

Hover e clique são detectados no próprio elemento. Os estilos da animação ficam dentro do shadow root, onde não enxergam o pai, e o listener de clique fica no host. Quando o botão inteiro deve reagir, controle o ícone a partir do botão: use trigger="state" e defina active nos handlers do botão.

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>

Morphs e presets respeitam prefers-reduced-motion: as transições pulam direto para o fim e as animações não tocam. Os presets e os gatilhos estão descritos em ícones animados.

SVG estático em templates do Lit

Quando um ícone nunca muda depois da renderização, você pode dispensar o elemento e inserir a marcação SVG diretamente. renderToString de glyphfox/core renderiza os dados do ícone com as props compartilhadas, e o Lit insere a string com a diretiva unsafeHTML. O renderizador escapa títulos e valores de atributos, então a marcação pode ser inserida com segurança.

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 a sintaxe de nomes (file+check@tr, cloud&lock), use toSvg de glyphfox. Ele resolve qualquer nome, o que significa que carrega todos os ícones; veja tree shaking antes de usá-lo em um bundle do cliente.

Atributos

Toda prop compartilhada é um atributo em kebab-case. O elemento acrescenta quatro próprios: name para o ícone, com a sintaxe de nomes completa; layout e gap para fusões; e morph, que anima as trocas de name. Números são lidos a partir do texto, e booleanos seguem a regra acima.

AtributoTipoPadrãoDescrição
namestringnenhumA especificação do ícone: um nome, name+modifier@corner, a&b:layout ou name/variant.
layoutoverlap, side, stack, grid, orbit, badge, insidenenhumLayout da fusão quando o nome junta ícones com &.
gapnumber1Espaço de recorte de uma fusão, em unidades da grade.
morphbooleanfalseAnima toda troca de name como um morph.
sizenumber, string24Largura e altura; números são pixels.
colorstringcurrentColorCor principal.
secondary-colorstringcolorCor da camada duotone e do preenchimento sólido.
secondary-opacitynumber0.3 / 1Opacidade da camada duotone ou do preenchimento sólido.
weightnumber2Traço de 1 a 3 na grade de 24, com compensação óptica abaixo de 24 px.
absolute-weightbooleanfalseMantém o traço com weight pixels em qualquer tamanho.
cornersround, sharproundJunções e pontas redondas ou retas.
variantoutline, duotone, solidoutlineEixo de estilo; o sólido vem das formas fechadas.
rotatenumber0Rotação em graus.
flip-xbooleanfalseEspelha na horizontal.
flip-ybooleanfalseEspelha na vertical.
mirror-rtlbooleanfalseEspelha em layouts da direita para a esquerda.
animatedraw, pop, wiggle, spin, pulse, bounce, shakenenhumAnimação de estado vivo.
triggermount, hover, click, statemountQuando a animação ou o morph acontece.
activebooleanfalseO estado para o gatilho state.
durationnumber (ms)por animaçãoDuração da animação ou do morph.
delaynumber (ms)0Atraso da animação ou do morph.
loopboolean, numberfalseRepete para sempre ou um número de vezes (spin e pulse repetem por padrão).
modifierplus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dotnenhumSelo com espaço de recorte automático.
modifier-cornertop-left, top-right, bottom-left, bottom-rightbottom-rightCanto do selo.
morph-toname, datanenhumDestino do morph; o gatilho decide quando aparece.
titlestringnenhumNome acessível: title, role img e aria-label.
decorativebooleantrue sem titleEsconde o ícone das tecnologias assistivas.

Acessibilidade

Sem rótulo, o ícone é decorativo: o svg interno recebe aria-hidden="true". Isso é o correto para um ícone ao lado de texto visível e para um botão de ícone que tem o próprio aria-label.

Para dar um nome ao próprio ícone, use o atributo title. Ele adiciona um <title>, role="img" e aria-label ao svg interno. Não conte com aria-label no elemento: o rótulo fica no host, que não tem role, enquanto o svg de dentro vira uma imagem sem nome. title no host também é o atributo global do HTML, então os navegadores o mostram 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>

Padrões para links, ícones de status e contraste estão no guia de acessibilidade.

Renderização no servidor e deslocamento de layout

O elemento desenha o ícone quando passa pelo upgrade no navegador. O HTML renderizado no servidor contém só a tag vazia, e o elemento não usa declarative shadow DOM, então o ícone aparece depois que o script roda. Reserve o espaço para que o layout não se mova quando isso acontecer.

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;
}

Quando um ícone precisa estar na primeira pintura, em um e-mail ou no HTML para crawlers, renderize a string SVG no servidor em vez disso. As opções para cada framework estão em renderização no servidor.

Armadilhas comuns

Tags autofechadas
Escreva <glyph-fox name="home"></glyph-fox>. O HTML ignora a barra em <glyph-fox />, então o elemento continua aberto e o conteúdo seguinte vira filho dele, e o shadow root não exibe esses filhos.
Bindings de propriedade
.name=${...} no Lit e outros bindings de propriedade definem um campo JavaScript que o elemento não lê. Faça binding de atributos. O Solid precisa de attr: pelo mesmo motivo, veja o guia de Solid.
Atributos vazios
Um size vazio renderiza um svg com largura e altura vazias. Remova o atributo quando não houver valor, com nothing ou ifDefined no Lit.
Hover e clique no elemento pai
Os gatilhos de hover e clique disparam só no ícone, não no botão em volta dele. Use trigger="state" e defina active a partir do pai.
Mudanças de atributo repetem as animações de montagem
O elemento troca o svg interno sempre que a marcação muda, então, com um preset de animate e o trigger="mount" padrão, mudar color ou size toca a animação de novo. Use trigger="state" em ícones cujos outros atributos mudam com frequência.
Um ícone vazio
Um nome desconhecido não renderiza nada e registra um aviso glyphfox: no console. Um nome válido que não foi registrado é desenhado assim que o módulo dele carrega, então confira o painel de rede antes de procurar um erro de digitação.

Perguntas frequentes

Posso usar o elemento em React, Vue ou Angular?

Sim, é um custom element padrão. Mas, nesses casos, os pacotes de cada framework se encaixam melhor: glyphfox/react, glyphfox/vue e glyphfox-angular oferecem props tipadas e renderizam no servidor. Deixe o elemento para Lit, HTML puro e páginas que misturam stacks. Em templates Vue, informe ao compilador, com compilerOptions.isCustomElement, que glyph-fox é um custom element; caso contrário, o Vue tenta resolvê-lo como componente.

Ícones deste guia