Substituir os ícones SVG padrão do Discourse por ícones personalizados em um tema

You can replace a Discourse’s default SVG icons individually or as a whole with your own custom SVG and override them within a theme or theme component.

Step 1 - Create an SVG Spritesheet

To get started, you must create an SVG Spritesheet. This can contain anything from a single additional custom SVG icon up to an entire replacement set of hundreds.

The spritesheet should be saved as an SVG file. In principle, you are nesting the <svg> tag contents from the original SVG icon file into <symbol> tags and giving them a nice identifier.

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
  <symbol id="my-theme-icon-1">
    <!--
      Code inside the <svg> tag from the source SVG icon file
      this is typically everything between the <svg> tags
      (but not the SVG tag itself, that's replaced by <symbol> above)
      You can transfer any attributes (i.e. ViewBox="0 0 0 0") to the <symbol> tag
      -->
  </symbol>

  <symbol id="my-theme-icon-2">
    <!-- SVG code here. Add more <symbol> blocks as needed.
      -->
  </symbol>
</svg>
  • Be sure to add a custom ID to each symbol in the spritesheet. It’s probably helpful for your sanity to prefix your IDs with your theme name my-theme-icon.

  • To have the icon color to be dynamic like the existing icons, set the fill to currentColor rather than a hardcoded color (like #333)

  • To scale or correctly centre your icon, utilise a viewBox attribute on the <symbol> tag. See How to Scale SVG | CSS-Tricks for more information.

  • Be on the lookout for style collisions within your SVGs. For example, SVGs will often have an inline style like .st0{fill:#FF0000;} defined. If you have multiple SVGs using the same classes this can cause issues (to fix these issues, edit the classes to be unique to each icon).

  • If you have many icons, there are ways to automate this. svg-sprite-generator - npm is a simple command line tool for combining SVGs into a spritesheet.

Example - single custom icon spritesheet

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
  <symbol id="bat-icon" viewBox="6 6 36 36">
    <path
      fill="currentColor"
      d="M24,18.2c0.7,0,0.9,0.2,0.9,0.2l0.4-1.7c0,0,0.4,1.5,0.4,2.8c0.2,1.1,2.2,0.4,3.9,0C31.4,19.1,32,16,32,16h16c0,0-9.4,3.5-7,10c0,0-14.8-2-17,7l0,0c-2.2-9-17-7-17-7c2.4-6.5-7-10-7-10h16c0,0,0.6,3.1,2.3,3.5c1.7,0.4,3.9,1.1,3.9,0c0.2-1.1,0.4-2.8,0.4-2.8l0.4,1.7C23.1,18.4,23.4,18.2,24,18.2L24,18.2L24,18.2z"
    />
  </symbol>
</svg>

Step 2 - Add the spritesheet to your theme

Once your spritesheet is built, you need to add the SVG file to your component/theme. This is easy via the UI, or you can hard code it into a component/theme.

:information_source: Once it is uploaded to any installed component/theme, it is available throughout your instance using the ID in the <symbol> tag.

Via the UI

Go to the Uploads section of the theme/component settings and add your sprite file with a SCSS var name of icons-sprite:

Hardcode into a Theme / Component

Add the spritesheet file to the Theme’s /assets folder. Then update your assets.json file in the root folder.
For an SVG sprite called my-icons.svg, your about.json should include this:

"assets": {
  "icons-sprite": "/assets/my-icons.svg"
}

Step 3 (optional) - Overriding default icons

Now that your spritesheet is set, you can tell Discourse to replace icons. This is how you do it from an api-initializer:

// {theme}/javascripts/discourse/api-initializers/init-theme.gjs

import { apiInitializer } from "discourse/lib/api";

export default apiInitializer((api) => {
  api.replaceIcon("bars", "my-theme-icon-bars");
  api.replaceIcon("link", "my-theme-icon-link");
  // etc.
});

The first ID, bars, is the default icon ID in Discourse and the second is the ID of your replacement icon. The easiest way to find an ID of one of our icons is to inspect the icon in your browser.

Here the icon name follows the d-icon- prefix. So in this example it’s d-unliked

Most of our icons follow the icon names from https://fontawesome.com/, but there are exceptions (which is why checking the ID in your inspector is the most reliable method). You can see all the exceptions in the const REPLACEMENTS block here on github.

That’s it. You can now style Discourse with your own custom icons!


This document is version controlled - suggest changes on github.

57 curtidas

Como se pode direcionar um ícone específico em um elemento específico? No meu caso, gostaria de substituir o Ícone de Documentos no menu da barra lateral por outro ícone FA.

Eu o ocultaria com CSS e adicionaria um novo botão para ele.

Common / CSS

.sidebar-section-wrapper {
  li[data-list-item-name=docs] {
    display: none !important;
  }
}

Adicione um novo botão em Mais > Personalizar esta seção

3 curtidas

Isso não funciona. Eu tentei fazer um:

<script type="text/discourse-plugin" version="0.8">
    api.replaceIcon("shield-halved", "hat-wizard");
</script>

a partir daqui, mas não parece funcionar. Acho que o método da tag script está quebrado, já que isto não funciona com o link de pré-visualização. Honestamente, não tenho certeza.

funciona para mim :woman_shrugging:t2:

você está colocando na aba head? eu também substituo o robô no meu header:

você pode ter que adicionar o ícone à configuração SVG icon subset do admin.

1 curtida

Sim, na aba head. E na aba header, já que o guia diz isso.

Feito. Agora funciona. Obrigado!

1 curtida

@NateDhaliwal Você poderia me enviar uma mensagem privada por favor? Preciso de ajuda com algo e não vejo uma opção de chat no seu perfil. Obrigado!

1 curtida
Para o menu esquerdo, redefinimos o plano de fundo do elemento com a classe prefix-span dentro da categoria Audi */
.navigation-category [data-category-id="6"] .prefix-span {
  background: url("https://raw.githubusercontent.com/tima4502/car-icons/bb0d0fae3e5b66c512a27a130b219ec0ee342ada/audi.svg") center/contain no-repeat !important;

quando clico na página principal, o ícone quadrado aparece novamente! você pode me dizer o que estou fazendo de errado? e na própria página da categoria funciona

Olá, alguém poderia esclarecer a relação entre o nome de um tema/componente, nome do arquivo, nome da variável SCSS e o ID do símbolo…?

Estou tentando substituir o ícone do moderador shield-halved por um nosso, mas as instruções são um pouco confusas.

Na Etapa 2:

  • A captura de tela “Via a UI” mostra um nome de arquivo baticonsprite.svg com um nome de variável SCSS icons-sprite
  • Mas então em “Hardcode em um Tema”, ele instrui você a codificar diretamente em um tema/componente
  • Mas como? Não vejo um arquivo assets.json no editor. Se eu exportar o componente, vejo um about.json, que mostra o sprite que carreguei via UI
  • Mas este exemplo também mostra um nome de arquivo diferente /assets/my-icons.svg — isso deveria ser o mesmo arquivo que baticonsprite.svg?
  • Essas são duas maneiras alternativas de fazer a mesma coisa, e você só precisa fazer uma OU outra, não ambas…?

Na Etapa 3:

  • Mas então, em api.replaceIcon(), o segundo parâmetro não usa nenhum dos IDs anteriores, nem icons-sprite nem bat-icon nem baticonsprite.svg nem my-icons.svg. Em vez disso, obtemos um my-theme-icon-bars totalmente novo… confuso.
  • O prefixo my-theme é necessário e, em caso afirmativo, de onde vem essa string “nome do tema”? Como se fosse para ser my-theme-bat-icon? E se for um componente, não um tema?
  • E para a parte icon-bars, deveria ser:
    • O ID do símbolo do XML da folha de sprites SVG
    • O nome do arquivo do arquivo SVG
    • O nome da variável SCSS que você dá a ele
    • Alguma combinação dos acima (como icons-sprite-bat-icon?)

E onde você realmente coloca a chamada api.replaceIcon()? Tudo bem colocá-la na aba “JS” de um componente personalizado, que já tem o boilerplate:

import { apiInitializer } from "discourse/lib/api";

export default apiInitializer((api) => {
   // seu código aqui
});

Ou é necessário criar uma tag <script type=”discourse/plugin”> personalizada e colocá-la na aba <head> em vez disso?


Desculpe pela minha confusão aqui.

Tentei várias combinações acima e não consegui fazer meu sprite aparecer, não importa o quê…

Meu XML de sprite se parece com:

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">


<symbol id="my-logo" viewBox="0 0 94.652 95.261"><defs><linearGradient id="a" y1="47.631" x2="94.652" y2="47.631" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ff593d"/><stop offset="1" stop-color="#ff7751"/></linearGradient></defs><title>d_only</title><path d="M47.326,0H0V95.261H47.326c23.67,0,47.326-21.326,47.326-47.624S71,0,47.326,0Zm0,69.274a21.644,21.644,0,1,1,21.65-21.637A21.635,21.635,0,0,1,47.326,69.274Z" fill="url(#a)"/></symbol>

</svg>

O nome do arquivo é my-logo.svg e o nome da variável SCSS também é my-logo

E na aba JS do componente personalizado, eu tenho:

import { apiInitializer } from "discourse/lib/api";

export default apiInitializer((api) => {
    api.replaceIcon("shield-halved", "my-logo")
});

Mas nada parece aparecer. Há alguma etapa que estou perdendo, ou algum tipo de interpolação mágica de string que estou entendendo mal…?