Frameworks

Ícones no Qwik

A maioria dos ícones nunca muda depois que a página carrega. No Qwik, eles podem ser SVG puro escrito no servidor, sem nenhum código de componente enviado ao navegador. Para os poucos que se transformam ou reagem, o elemento <glyph-fox> cuida da interação sozinho.

Instalação

Instale o pacote principal. A abordagem no servidor usa glyphfox/core e um módulo por ícone de glyphfox/icons/NAME; o elemento vem de glyphfox/web. Não é preciso configurar nada no Vite nem no Qwik.

terminal
npm install glyphfox

Os exemplos importam de @builder.io/qwik. No Qwik 2, esses imports passam para @qwik.dev/core; a parte do glyphfox continua igual.

Ícones renderizados no servidor

O renderToString retorna a marcação SVG a partir dos dados do ícone e das props compartilhadas. O Qwik insere marcação bruta com dangerouslySetInnerHTML, que no Qwik recebe a própria string, e não um objeto como no React.

src/components/icon/icon.tsx
import type { IconData, SvgOptions } from 'glyphfox';
import { renderToString } from 'glyphfox/core';

type Props = SvgOptions & {
  icon: IconData;
  class?: string;
};

export const Icon = ({ icon, class: className, ...options }: Props) => (
  <span
    class={className}
    style="display:inline-flex;line-height:0"
    dangerouslySetInnerHTML={renderToString(icon, options)}
  />
);

Icon é uma função comum, um componente inline na terminologia do Qwik. Ele renderiza como parte do pai e não tem uma fronteira de lazy loading própria. No servidor, o pai o escreve no HTML e, desde que o pai não renderize de novo no navegador, nenhum código dele chega a ser baixado.

src/routes/layout.tsx
import { component$, Slot } from '@builder.io/qwik';
import home from 'glyphfox/icons/home';
import settings from 'glyphfox/icons/settings';
import bookOpen from 'glyphfox/icons/book-open';
import { Icon } from '~/components/icon/icon';

export default component$(() => (
  <div class="shell">
    <nav class="sidebar">
      <a href="/"><Icon icon={home} size={20} /> Home</a>
      <a href="/docs/"><Icon icon={bookOpen} size={20} /> Docs</a>
      <a href="/settings/"><Icon icon={settings} size={20} weight={1.5} /> Settings</a>
    </nav>
    <main>
      <Slot />
    </main>
  </div>
));

Se um componente renderiza de novo no cliente, por exemplo porque lê um signal que muda, o Qwik baixa a função de renderização dele e os módulos que ele importa, incluindo o renderizador de glyphfox/core. Mantenha os ícones em componentes que renderizam uma vez só e leve as partes que mudam para componentes próprios.

Movimento sem JavaScript

A marcação do renderToString traz o próprio CSS. Com animate, ela toca um preset, e com morphTo e trigger: 'hover', ela se transforma enquanto o ponteiro está sobre o svg ou sobre o pai direto dele. Nenhum dos dois precisa de script, e ambos param com prefers-reduced-motion.

src/components/status.tsx
import arrowRight from 'glyphfox/icons/arrow-right';
import chevronRight from 'glyphfox/icons/chevron-right';
import loader from 'glyphfox/icons/loader';
import { Icon } from '~/components/icon/icon';

export const Next = () => (
  <a href="/next/" class="next">
    Next <Icon icon={arrowRight} morphTo={chevronRight} trigger="hover" />
  </a>
);

export const Saving = () => <Icon icon={loader} animate="spin" title="Saving" />;

Com o core, morphTo recebe dados de ícone, não um nome: importe o ícone de destino como acima. Uma string é ignorada, e o ícone fica parado.

Para um estado como aberto ou selecionado, renderize com trigger: 'state': o svg então se transforma quando recebe a classe gf-on. Alternar uma classe não exige nova renderização, então o único código que o navegador baixa é o manipulador de clique, e só no primeiro clique.

src/components/menu-toggle.tsx
import { component$ } from '@builder.io/qwik';
import menu from 'glyphfox/icons/menu';
import x from 'glyphfox/icons/x';
import { Icon } from '~/components/icon/icon';

export const MenuToggle = component$(() => (
  <button
    type="button"
    aria-label="Menu"
    aria-expanded="false"
    onClick$={(_, button) => {
      const open = button.getAttribute('aria-expanded') !== 'true';
      button.setAttribute('aria-expanded', String(open));
      button.querySelector('svg')?.classList.toggle('gf-on', open);
    }}
  >
    <Icon icon={menu} morphTo={x} trigger="state" />
  </button>
));

Ícones interativos com o elemento

Quando um ícone precisa alternar entre nomes, se transformar a cada mudança ou reagir sozinho a cliques, use o elemento <glyph-fox>. Carregue-o a partir de uma visible task no componente que o usa, para que o navegador só o baixe quando um ícone desses estiver na tela.

src/components/like-button.tsx
import { component$, useVisibleTask$ } from '@builder.io/qwik';

export const LikeButton = component$(() => {
  // eslint-disable-next-line qwik/no-use-visible-task
  useVisibleTask$(() => {
    import('glyphfox/web');
  });

  return (
    <button type="button" class="like" aria-label="Like">
      <glyph-fox name="heart" morph-to="heart/solid" trigger="click"></glyph-fox>
    </button>
  );
});

Escreva as props do elemento como atributos de string, como no HTML: name, size, morph, morph-to, trigger. Com um gatilho de clique ou hover, o elemento guarda o próprio estado, então o componente nunca renderiza de novo e o Qwik não tem nada para retomar nele.

O glyphfox/web traz todos os ícones. Para carregar menos, importe, a partir da task, um módulo seu que registre um subconjunto com defineGlyphFox e registerIcons, como no guia de web components. Se a sua configuração de TypeScript rejeitar a tag desconhecida, declare glyph-fox entre os elementos intrínsecos do JSX em um arquivo de declaração.

O elemento só desenha depois do upgrade, então o HTML do servidor fica com uma tag vazia. Reserve o tamanho dele com CSS para o layout não se mexer:

src/global.css
glyph-fox:not(:defined) {
  display: inline-block;
  width: 24px;
  height: 24px;
}

Qwik City

Layouts e rotas renderizam no servidor, então o componente Icon funciona em src/routes/layout.tsx, nas páginas e em todo componente que elas renderizam. A geração de site estático grava a mesma marcação em arquivos HTML.

Quando os nomes dos ícones vêm de dados, resolva-os no servidor. Em um routeLoader$, o toSvg de glyphfox aceita qualquer nome e a sintaxe de nomes completa; ele traz todos os ícones, o que não é problema em código só de servidor.

src/routes/features/index.tsx
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
import { toSvg } from 'glyphfox';

const FEATURES = [
  { icon: 'shield-check', label: 'Audited' },
  { icon: 'cloud&lock:overlap', label: 'Encrypted backups' },
  { icon: 'file+check@tr', label: 'Signed releases' },
];

export const useFeatures = routeLoader$(() =>
  FEATURES.map((f) => ({ ...f, svg: toSvg(f.icon, { size: 28, weight: 1.75 }) })),
);

export default component$(() => {
  const features = useFeatures();
  return (
    <ul class="features">
      {features.value.map((f) => (
        <li key={f.label}>
          <span dangerouslySetInnerHTML={f.svg} />
          {f.label}
        </li>
      ))}
    </ul>
  );
});

Se preferir ter o elemento em todas as páginas, adicione o build de arquivo único da CDN ao head em src/root.tsx com defer, como descrito em HTML e CDN. Com isso, você paga um download de 62 KB gzip em cada página para não precisar controlar onde o elemento é usado.

Props

O componente Icon repassa as props para o renderToString, que aceita as opções compartilhadas com os nomes em camelCase, além das strings class e style para o svg. No elemento, as mesmas props são atributos em kebab-case.

OpçãoTipoPadrã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

dangerouslySetInnerHTML recebe uma string
Passe a própria marcação. A forma do React, com __html, não funciona no Qwik.
Componentes pais que renderizam de novo
Um ícone dentro de um componente que renderiza de novo no navegador leva junto o glyphfox/core, cerca de 37 KB minificados. Mantenha as partes com estado em componentes pequenos e separados.
Nomes em morphTo
O core precisa de dados de ícone em morphTo. Uma string é ignorada, e o ícone não se transforma.
O hover só alcança o pai direto
O morph CSS no hover reage ao svg e ao pai direto dele, que é o span que o envolve. Passar o mouse sobre um botão maior em volta dele não inicia o morph; para isso, alterne gf-on com trigger: 'state'.
toSvg em código do cliente
import { toSvg } from 'glyphfox' traz todos os ícones. O lugar dele é em código só de servidor, como routeLoader$; em componentes que podem rodar no navegador, use o core com um módulo por ícone.
O elemento antes do upgrade
O HTML do servidor fica com um <glyph-fox> vazio. Reserve o espaço dele com CSS ou renderize com o componente Icon os ícones que precisam aparecer de imediato.

Perguntas frequentes

Os ícones renderizados no servidor realmente não enviam JavaScript?

Sim, desde que o componente que os renderiza não renderize de novo no navegador. O HTML traz SVG puro, e presets e morphs no hover são CSS dentro desse SVG. Só os componentes que renderizam de novo e o elemento <glyph-fox> levam código para o navegador.

Posso usar os componentes React com qwikify$?

Eles se comportam como qualquer componente React dentro de qwikify$, mas essa ilha passa a enviar o React e o runtime React do glyphfox. Para ícones, a marcação renderizada no servidor ou o elemento custam bem menos.

Ícones deste guia