Reemplazar los iconos SVG predeterminados de Discourse con iconos personalizados en un 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 Me gusta

¿Cómo se puede seleccionar un icono específico dentro de un elemento específico? En mi caso, me gustaría reemplazar el icono de Documentos en el menú lateral con otro icono de FA.

Lo ocultaría con CSS y añadiría un nuevo botón para ello.

Común / CSS

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

Añade un nuevo botón en Más > Personalizar esta sección

3 Me gusta

Esto no funciona. Intenté hacer un:

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

desde aquí, pero no parece funcionar. Creo que el método de la etiqueta script está roto, ya que esto no funciona con el enlace de vista previa. Sinceramente, no estoy seguro.

me funciona :woman_shrugging:t2:

¿lo estás poniendo en la pestaña head? también reemplazo el robot en mi encabezado:

puede que tengas que añadir el icono a la configuración SVG icon subset del administrador.

1 me gusta

[cita=“Lilly, post:40, topic:115736”]
¿Lo estás poniendo en la pestaña head?
[/quote]

Sí, en la pestaña head. Y en la pestaña header, ya que la guía dice eso.

[cita=“Lilly, post:40, topic:115736”]
SVG icon subset
[/quote]
Hecho. Ahora funciona. ¡Gracias!

1 me gusta

@NateDhaliwal ¿Podrías enviarme un mensaje privado por favor? Necesito ayuda con algo y no veo una opción de chat en tu perfil. ¡Gracias!

1 me gusta
/* Para el menú de la izquierda, redefinimos el fondo del elemento con la clase prefix-span dentro de la categoría 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;

¡Cuando hago clic en la página principal, el icono cuadrado vuelve a aparecer! ¿Puedes decirme qué estoy haciendo mal? Y en la propia página de categoría funciona.

Hola, ¿alguien podría aclarar la relación entre el nombre de un tema/componente, el nombre del archivo, el nombre de la variable SCSS y el ID del símbolo…?

Estoy intentando reemplazar el icono del moderador shield-halved por uno propio, pero las instrucciones no son muy claras.

En el Paso 2:

  • La captura de pantalla “A través de la UI” muestra un nombre de archivo de baticonsprite.svg con un nombre de variable SCSS de icons-sprite.
  • Pero luego, en “Codificar directamente en un tema”, te indica que lo codifiques directamente en un tema/componente.
  • ¿Pero cómo? No veo ningún archivo assets.json en el editor. Si exporto el componente, veo un about.json, que sí muestra el sprite que subí a través de la UI.
  • Pero este ejemplo también muestra un nombre de archivo diferente de /assets/my-icons.svg, ¿se supone que es el mismo archivo que baticonsprite.svg?
  • ¿Son estas dos formas alternativas de hacer lo mismo, y solo necesitas hacer una U OTRA, no ambas…?

En el Paso 3:

  • Pero entonces, en api.replaceIcon(), el segundo parámetro no utiliza ninguno de los IDs anteriores, ni icons-sprite, ni bat-icon, ni baticonsprite.svg, ni my-icons.svg. En cambio, obtenemos un my-theme-icon-bars completamente nuevo… confundido.
  • ¿Es necesario el prefijo my-theme y, si es así, de dónde proviene esa cadena “nombre del tema”? ¿Como si se supusiera que es my-theme-bat-icon? ¿Y qué pasa si es un componente, no un tema?
  • Y para la parte icon-bars, ¿se supone que es:
    • El ID del símbolo del XML de la hoja de sprites SVG
    • El nombre del archivo del archivo SVG
    • El nombre de la variable SCSS que le das
    • ¿Alguna combinación de lo anterior (como icons-sprite-bat-icon)?

¿Y dónde pones realmente la llamada api.replaceIcon()? ¿Está bien ponerla en la pestaña “JS” de un componente personalizado, que ya tiene el código base:

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

export default apiInitializer((api) => {
   // tu código aquí
});

¿O es necesario crear una etiqueta personalizada <script type=”discourse/plugin”> y ponerla en la pestaña <head> en su lugar?


Disculpen mi confusión.

He intentado varias combinaciones de lo anterior y no he podido hacer que mi sprite aparezca sin importar qué…

Mi XML de sprite se ve así:

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

El nombre del archivo es my-logo.svg y el nombre de la variable SCSS también es my-logo.

Y en la pestaña JS del componente personalizado, tengo:

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

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

Pero nada parece aparecer. ¿Me falta algún paso, o hay alguna interpolación mágica de cadenas que no estoy entendiendo…?