Frameworks

Ícones no SvelteKit

O glyphfox/svelte renderiza no servidor, então as páginas do SvelteKit entregam SVG de verdade no HTML, seja a página renderizada a cada requisição ou pré-renderizada no build.

Instalação

terminal
npm install glyphfox

Não há nada para configurar. O glyphfox/svelte é exposto pela condição de exportação svelte, então o vite-plugin-svelte, que todo projeto SvelteKit usa, o reconhece como uma biblioteca Svelte e compila os componentes dele para o servidor e para o navegador. Os componentes precisam do Svelte 5; o guia do Svelte detalha os componentes e as props.

Registre os nomes de ícones no layout raiz

Componentes como HomeIcon registram os próprios dados. Nomes que só aparecem em strings, em Icon, Fusion, MorphIcon ou morphTo, precisam de registerIcons, e o melhor lugar para isso é um módulo que o layout raiz importa: ele roda no servidor antes da primeira renderização e no navegador antes da hidratação.

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()}

O registro é estado de módulo. No servidor, ele é compartilhado por todas as requisições, o que é seguro porque os dados dos ícones nunca mudam, e é preenchido uma única vez, quando o módulo carrega pela primeira vez.

O que o servidor renderiza

Os componentes de ícone geram o svg completo durante a renderização no servidor. As animações de estado vivo são uma folha de estilo dentro do svg, então um spinner já gira no HTML do servidor, a não ser que o visitante prefira movimento reduzido. Os morphs e os gatilhos de clique e de hover começam depois da hidratação, porque o $effect não roda no servidor.

Para um morph cujo estado final depende de dados, use trigger="state" com active. O servidor renderiza o estado certo do morph, e a animação só toca quando o valor muda no navegador. Com o gatilho padrão (mount), o servidor renderiza o ícone inicial e o morph acontece a cada carregamento 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>

Ler o tema de um cookie em uma função load do servidor dá ao servidor e ao navegador o mesmo valor inicial. Renderize o componente a partir do layout raiz com <ThemeToggle theme={data.theme} />; moon está registrado em src/lib/icons.js.

Arquivos SVG a partir de um endpoint

O toSvg do ponto de entrada principal glyphfox renderiza qualquer spec em uma string, o que permite criar uma rota +server.js que serve ícones como arquivos: um favicon, uma imagem para um template de e-mail ou uma prévia de link. Com entries, os arquivos são pré-renderizados no build.

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" />

Um arquivo SVG avulso não tem um elemento pai de quem herdar a cor, então passe color: o valor é gravado no svg e os traços em currentColor passam a usá-lo. Retorne iconNames.map((name) => ({ name })) em entries para pré-renderizar todos os ícones.

O ponto de entrada principal inclui os dados de todos os ícones. Isso não é problema em arquivos +server.js e +page.server.js, que nunca chegam ao navegador; importado em um componente, o toSvg mandaria o conjunto inteiro para o cliente.

Marcação só do servidor com {@html}

Uma função load em +page.server.js pode retornar strings já renderizadas que a página insere com {@html}. O navegador recebe a marcação no HTML e nos dados serializados da página, mas nenhum código de ícone.

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>

O {@html} é seguro aqui porque as strings vêm do seu próprio código de servidor, nunca de entrada do usuário. Sem um title, os ícones são renderizados com aria-hidden, o que é o certo ao lado de um rótulo visível.

adapter-static e pré-renderização

Com o adapter-static, ou com export const prerender = true em qualquer rota, as páginas são renderizadas no build e os ícones são gravados nos arquivos HTML. As regras da renderização no servidor também valem no build: um nome que o build não consegue resolver é gravado como um svg vazio.

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

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

Para páginas que não precisam de JavaScript, adicione export const csr = false: os ícones continuam como SVG estático e nenhum código de componente é enviado. As animações CSS continuam tocando, enquanto os morphs e os gatilhos de clique ou hover, que precisam de JavaScript, não fazem nada.

Nomes em builds de produção

Em desenvolvimento, o Vite pré-empacota o glyphfox, então qualquer nome parece funcionar. Em um build de produção, o Vite não inclui no bundle o carregador sob demanda que o glyphfox mantém em node_modules, porque ignora imports dinâmicos com variáveis em dependências, e os nomes não registrados ficam vazios. Registre esses nomes ou deixe o Vite incluir o carregador no bundle; ele passa a gerar um chunk pequeno por ícone:

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

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

Esses chunks só ajudam no navegador, depois da hidratação. A renderização no servidor não espera por eles, então o registro continua sendo o jeito de colocar um ícone no HTML.

Props

O SvelteKit usa os componentes de glyphfox/svelte, então as props são as compartilhadas, em camelCase.

PropTipoPadrãoDescrição
sizenumber, string24Largura e altura; números são pixels.
colorstringcurrentColorCor principal.
secondaryColorstringcolorCor da camada duotone e do preenchimento sólido.
secondaryOpacitynumber0.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.
absoluteWeightbooleanfalseManté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.
flipXbooleanfalseEspelha na horizontal.
flipYbooleanfalseEspelha na vertical.
mirrorRtlbooleanfalseEspelha 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.
modifierCornertop-left, top-right, bottom-left, bottom-rightbottom-rightCanto do selo.
morphToname, 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.

Armadilhas comuns

Registrar no onMount ou em código só do navegador
Código em onMount, em $effect ou dentro de if (browser) roda depois que o servidor já renderizou. O HTML fica então com ícones vazios que aparecem de repente depois da hidratação. Registre em um módulo que o layout raiz importa.
Um ícone diferente no servidor e no navegador
O conteúdo do svg é inserido com {@html}, e o Svelte mantém a marcação do servidor de um bloco {@html} quando a primeira renderização no cliente é diferente (em desenvolvimento, ele avisa com hydration_html_changed). Por isso, um ícone escolhido a partir de localStorage ou window pode continuar errado até a próxima mudança. Derive o primeiro valor de dados que o servidor também tem, como um cookie lido em uma função load.
toSvg em um componente compartilhado
Importar toSvg de glyphfox em um arquivo .svelte puxa os dados de todos os ícones para o bundle do cliente. Mantenha-o em arquivos de servidor ou use os componentes.
Um morph pré-renderizado no estado errado
Com o gatilho mount, o HTML pré-renderizado mostra o ícone inicial e o morph acontece depois da hidratação. Use trigger="state" para tudo o que deve ser pré-renderizado já no estado final.

Perguntas frequentes

Posso pré-renderizar todos os ícones como arquivos SVG?

Sim. Retorne iconNames.map((name) => ({ name })) em entries no endpoint acima, e o build grava um arquivo por ícone em /icons/. Cada arquivo tem algumas centenas de bytes.

Os ícones funcionam em páginas com csr = false?

Sim. O servidor grava o svg no HTML, e as animações de estado vivo continuam tocando porque são CSS dentro do svg. Os morphs e os gatilhos de clique e de hover precisam de JavaScript, então ficam parados no primeiro quadro.

Ícones deste guia