Remplacer les icônes SVG par défaut de Discourse par des icônes personnalisées dans un thème

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 « J'aime »

Comment cibler une icône spécifique dans un élément spécifique ? Dans mon cas, j’aimerais remplacer l’icône Docs dans le menu de la barre latérale par une autre icône FA.

Je le cacherais avec CSS et j’ajouterais un nouveau bouton pour cela.

Commun / CSS

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

Ajoutez un nouveau bouton dans Plus > Personnaliser cette section

3 « J'aime »

Cela ne fonctionne pas. J’ai essayé de faire :

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

d’ici, mais cela ne semble pas fonctionner. Je pense que la méthode de la balise script est cassée, car ceci ne fonctionne pas avec le lien de prévisualisation. Honnêtement, je ne suis pas sûr.

ça marche pour moi :woman_shrugging:t2:

le mettez-vous dans l’onglet head ? j’ai aussi remplacé le robot dans mon en-tête :

vous devrez peut-être ajouter l’icône au paramètre SVG icon subset de l’administrateur.

1 « J'aime »

[citation=“Lilly, post:40, topic:115736”]
êtes-vous en train de le mettre dans l’onglet d’en-tête ?
[/citation]

Oui, l’onglet d’en-tête. Et l’onglet d’entête (header), puisque le guide dit ça.

[citation=“Lilly, post:40, topic:115736”]
SVG icon subset
[/citation]
C’est fait. Ça marche maintenant. Merci !

1 « J'aime »

@NateDhaliwal Pourriez-vous m’envoyer un message privé s’il vous plaît ? J’ai besoin d’aide pour quelque chose et je ne vois pas d’option de chat sur votre profil. Merci !

1 « J'aime »
Pour le menu de gauche, nous redéfinissons l'arrière-plan de l'élément avec la classe prefix-span dans la catégorie 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;

lorsque je clique sur la page principale, l’icône carrée réapparaît ! Pouvez-vous me dire ce que je fais de mal ? et sur la page de catégorie elle-même, cela fonctionne.

Bonjour, quelqu’un pourrait-il clarifier la relation entre un nom de thème/composant, un nom de fichier, un nom de variable SCSS et l’ID du symbole… ?

J’essaie de remplacer l’icône de modérateur shield-halved par la nôtre, mais les instructions sont un peu confuses.

Dans l’étape 2 :

  • La capture d’écran “Via l’interface utilisateur” montre un nom de fichier baticonsprite.svg avec un nom de variable SCSS icons-sprite.
  • Mais ensuite, dans “Codage en dur dans un thème”, il vous est demandé de le coder en dur dans un thème/composant.
  • Mais comment ? Je ne vois pas de fichier assets.json dans l’éditeur. Si j’exporte le composant, je vois un about.json, qui montre le sprite que j’ai téléchargé via l’interface utilisateur.
  • Mais cet exemple montre également un nom de fichier différent /assets/my-icons.svg — est-ce censé être le même fichier que baticonsprite.svg ?
  • S’agit-il de deux façons alternatives de faire la même chose, et vous n’avez besoin d’en faire qu’une OU l’autre, pas les deux… ?

Dans l’étape 3 :

  • Mais alors maintenant, dans api.replaceIcon(), le deuxième paramètre n’utilise aucun des ID précédents, ni icons-sprite, ni bat-icon, ni baticonsprite.svg, ni my-icons.svg. Au lieu de cela, nous obtenons un tout nouvel my-theme-icon-bars… confus.
  • Le préfixe my-theme est-il requis, et si oui, d’où vient cette chaîne “nom du thème” ? Est-ce censé être my-theme-bat-icon ? Et qu’en est-il si c’est un composant, pas un thème ?
  • Et pour la partie icon-bars, est-ce censé être :
    • L’ID du symbole du XML de la spritesheet SVG
    • Le nom du fichier du fichier SVG
    • Le nom de la variable SCSS que vous lui donnez
    • Une combinaison des éléments ci-dessus (comme icons-sprite-bat-icon) ?

Et où placez-vous réellement l’appel api.replaceIcon() ? Est-il acceptable de le placer dans l’onglet “JS” d’un composant personnalisé, qui contient déjà le boilerplate :

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

export default apiInitializer((api) => {
   // votre code ici
});

Ou est-il nécessaire de créer une balise personnalisée <script type=”discourse/plugin”> et de la placer dans l’onglet <head> à la place ?


Désolé pour ma confusion ici.

J’ai essayé plusieurs combinaisons des éléments ci-dessus et je n’ai pas réussi à faire apparaître mon sprite, quoi que j’aie fait…

Mon XML de sprite ressemble à ceci :

<?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>

Le nom du fichier est my-logo.svg et le nom de la variable SCSS est également my-logo.

Et dans l’onglet JS du composant personnalisé, j’ai :

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

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

Mais rien ne semble apparaître. Y a-t-il une étape que j’ai manquée, ou une sorte d’interpolation magique de chaîne que je ne comprends pas… ?