Tópicos

Sprites SVG

Um sprite é um único arquivo SVG com um símbolo para cada ícone. As páginas referenciam os símbolos com <use>, o navegador baixa o arquivo e o guarda em cache uma única vez, e nenhum JavaScript é executado.

Obtenha o sprite

O pacote traz um sprite com todos os ícones em glyphfox/sprite.svg. Cada ícone é um <symbol> com o id gf- mais o nome do ícone, o próprio viewBox e as configurações de traço do conjunto.

terminal
npm install glyphfox
cp node_modules/glyphfox/dist/sprite.svg public/icons/sprite.svg
sprite.svg
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="gf-home" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">...</symbol>
<symbol id="gf-search" viewBox="0 0 24 24" ...>...</symbol>
...
</svg>

Com um bundler, você pode deixar que ele copie o arquivo e aplique o fingerprint por você. No Vite, o sufixo ?url retorna a URL final do asset:

icons.js
import spriteUrl from 'glyphfox/sprite.svg?url';

export const icon = (name) =>
  `<svg class="icon" width="24" height="24" aria-hidden="true"><use href="${spriteUrl}#gf-${name}"></use></svg>`;

O sprite completo reúne os 419 ícones em 117,9 KB, ou 15,7 KB com gzip. Um sprite com os 30 ícones que um app shell típico usa tem 7,5 KB, ou 1,3 KB com gzip; a CLI gera um para você, como mostrado abaixo.

Referencie um símbolo

Escreva um svg externo com o tamanho que você quiser e um elemento use que aponte para o símbolo. O símbolo traz o próprio viewBox, então o elemento externo só precisa de largura e altura.

index.html
<button type="button" class="btn">
  <svg class="icon" width="20" height="20" aria-hidden="true">
    <use href="/icons/sprite.svg#gf-download"></use>
  </svg>
  Download
</button>
app.css
.icon {
  width: 1.25em;
  height: 1.25em;
  flex-shrink: 0;
  vertical-align: -0.2em;
}
.btn:hover .icon {
  color: #ff7a1a;
}

Os símbolos desenham com stroke="currentColor", e color é herdado pelo conteúdo do use, então o ícone acompanha a cor do texto do elemento pai e qualquer color que você definir no svg externo. Largura e altura definidas no CSS sobrescrevem os atributos, o que permite que o tamanho acompanhe o tamanho da fonte.

Somente na mesma origem

Os navegadores só seguem uma referência use para um arquivo na mesma origem da página. Um sprite em uma CDN como o jsDelivr, ou em outro subdomínio do seu site, não renderiza nada, e cabeçalhos CORS não mudam isso. Sirva o sprite a partir da sua própria origem, ou busque o arquivo uma vez e insira o conteúdo na página:

inline-sprite.js
// jsDelivr sends CORS headers, so fetch works where <use> across origins does not.
const res = await fetch('https://cdn.jsdelivr.net/npm/glyphfox@1/dist/sprite.svg');
const holder = document.createElement('div');
holder.setAttribute('aria-hidden', 'true');
holder.style.cssText = 'position:absolute;width:0;height:0;overflow:hidden';
holder.innerHTML = await res.text();
document.body.prepend(holder);

// now <use href="#gf-home"></use> works anywhere on the page

Coloque o sprite inline na página

Um sprite inline é formado pelos mesmos símbolos, colocados uma única vez no HTML, normalmente logo depois de <body>. Assim, as referências usam só o fragmento, #gf-home, não há requisição extra e os ícones aparecem já na primeira pintura. O custo é que cada página carrega os símbolos de novo, então coloque inline um subconjunto pequeno, e não o conjunto completo.

layout.html
<body>
  <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true"
       style="position:absolute;width:0;height:0;overflow:hidden">
    <symbol id="gf-home" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">...</symbol>
    <symbol id="gf-search" viewBox="0 0 24 24" ...>...</symbol>
  </svg>

  <a href="/"><svg class="icon" aria-hidden="true"><use href="#gf-home"></use></svg> Home</a>
</body>

Esconda o sprite com tamanho zero e posicionamento absoluto, não com display:none. Alguns navegadores deixam de renderizar máscaras que ficam dentro de um SVG oculto, e o estilo sólido usa máscaras para recortar as linhas internas.

Gere um subconjunto com a CLI

O comando glyphfox build gera um sprite só com os ícones que você listar, renderizados com as props que você passar. Os nomes podem vir da linha de comando ou de um arquivo com um nome por linha; o arquivo aceita espaços, vírgulas e comentários com #.

terminal
npx glyphfox build home search settings bell user --type sprite --out public/icons
npx glyphfox build --from icons.txt --type sprite --weight 1.5 --out public/icons
npx glyphfox build --from icons.txt --type sprite --variant solid --out public/icons/solid
icons.txt
# app shell
home, search, settings, bell, user
menu x chevron-down

# badges and fusions work too
file+check
cloud&lock:overlap

Nomes simples mantêm o id: home vira gf-home. Specs com selo ou fusão recebem um id com os caracteres especiais substituídos: file+check vira gf-file_check e cloud&lock:overlap vira gf-cloud__lock-overlap. Abra o arquivo gerado uma vez para copiar os ids de que você precisa.

Gerar o sprite no tamanho em que os ícones serão exibidos também traz a compensação óptica dos componentes: --size 16 grava o traço um pouco mais grosso que o glyphfox usa em ícones de 16 pixels, então um sprite para tabelas densas tem a mesma presença do resto da interface.

O mesmo comando com --type css gera, em vez disso, uma folha de estilo com classes de máscara (<span class="gf gf-home">), para sites que querem classes de ícone sem o Tailwind CSS. O guia do Tailwind CSS explica como os ícones de máscara se comportam.

Opções de build

Um sprite é estático, então as props compartilhadas viram flags de build. Estas são as que importam para um sprite; cada flag tem o mesmo significado da prop de mesmo nome na tabela de props.

FlagEfeito nos símbolos
--type spritegera sprite.svg (o tipo padrão); css e package são os outros tipos
--out DIRdiretório de saída; por padrão, o diretório atual
--from FILElê os nomes de um arquivo
--weight Npeso do traço de 1 a 3, gravado em cada símbolo
--size No tamanho em pixels em que você exibe os ícones, para a compensação óptica do traço abaixo de 24
--corners sharpjunções e pontas retas
--variant duotone, --variant solido estilo de todos os símbolos
--color C, --secondary-color Ccores fixas em vez de currentColor
--secondary-opacity Nopacidade da camada duotone ou do preenchimento sólido
--rotate DEG, --flip-x, --flip-yembute uma rotação ou um espelhamento nos símbolos

O que o CSS pode e não pode mudar

O que o use desenha é uma cópia do símbolo. O CSS aplicado ao svg externo ou ao elemento use só chega a essa cópia por herança, e é por isso que color funciona. Já as propriedades que o próprio símbolo define, não: cada símbolo traz stroke-width, stroke-linecap e stroke-linejoin como atributos, e eles prevalecem sobre qualquer valor herdado de fora. Definir stroke-width em .icon não tem efeito.

Defina peso, cantos e estilo ao gerar o sprite, e gere dois sprites se precisar de dois pesos. Quando precisar controlar o peso, uma segunda cor ou o movimento em cada instância, renderize SVG inline com um componente ou com toSvg no lugar do sprite.

Animações do ícone inteiro continuam funcionando, porque agem sobre o elemento externo: .icon.spin { animation: spin 1s linear infinite } gira um ícone de sprite como qualquer outro elemento.

Acessibilidade

Um ícone de sprite é um svg externo escrito por você, então cabe a você definir a semântica dele. Esconda ícones decorativos com aria-hidden="true". Para um ícone que tem significado por si só, transforme o elemento externo em uma imagem com nome:

status.html
<svg class="icon" role="img" aria-label="Payment failed">
  <use href="/icons/sprite.svg#gf-circle-alert"></use>
</svg>

Botões só com ícone precisam do rótulo no próprio botão, como mostra o guia de acessibilidade.

Cache

Um sprite externo é uma única requisição compartilhada por todas as páginas. Sirva o arquivo com um tempo de cache longo e um fingerprint no nome, como sprite.3f9a1c.svg, para que um build novo mude a URL em vez de esperar os caches expirarem. Os bundlers fazem isso por você quando você importa o arquivo. Cada URL de sprite diferente é baixada uma vez por página, não importa quantos ícones a referenciem.

Armadilhas comuns

Sprite em outra origem
Um use externo que aponta para uma CDN ou outro subdomínio renderiza uma caixa vazia; o navegador só informa a requisição bloqueada no console. Hospede o arquivo na origem da página ou coloque o sprite inline.
Camadas duotone no sprite completo
Os 42 ícones que têm uma camada duotone desenhada à mão mantêm essa camada em glyphfox/sprite.svg, como um preenchimento translúcido na cor atual. Se você quer só contornos, gere um subconjunto: o estilo padrão outline deixa a camada de fora, e --variant duotone a mantém.
Máscaras em sprites ocultos ou externos
Os símbolos sólidos usam uma mask para recortar as linhas internas. As máscaras podem não renderizar quando o sprite está escondido com display:none, e o suporte a máscaras dentro de um arquivo de sprite externo varia entre navegadores. Teste o estilo sólido nos navegadores que você precisa suportar, ou coloque esse sprite inline.
Estados vivos em um sprite
Presets como spin ou draw têm escopo em uma classe no svg raiz, que o símbolo não preserva, então --animate não tem efeito visível em um sprite. Anime o elemento externo com o seu próprio CSS, ou use SVG inline.
Ids em conflito
Um sprite inline coloca ids como gf-home na página. Dois sprites inline com o mesmo ícone, ou um elemento seu com o mesmo id, deixam as referências ambíguas. Coloque um único sprite inline por página.

Perguntas frequentes

Quando um sprite é melhor do que componentes?

Em sites estáticos ou renderizados no servidor sem framework JavaScript, e em páginas que repetem muitos ícones: os símbolos são baixados uma vez e cada uso custa poucos bytes de markup. Os componentes levam vantagem quando os ícones mudam com o estado, se transformam, são animados ou precisam de uma segunda cor, porque um sprite fica fixo no momento do build.

Posso referenciar o sprite direto no jsDelivr?

Não com use, porque os navegadores bloqueiam referências externas para outras origens. Busque o arquivo e insira o conteúdo na página, como mostrado acima, ou copie o arquivo para o seu próprio servidor.

Ícones deste guia