# Replace Discourse's default SVG icons with custom icons in a theme

**URL:** https://meta.discourse.org/t/replace-discourses-default-svg-icons-with-custom-icons-in-a-theme/115736
**Category:** Developer Guides
**Tags:** how-to, theme-guides
**Created:** [4월 23, 2019, 4:43오후 UTC](https://meta.discourse.org/t/replace-discourses-default-svg-icons-with-custom-icons-in-a-theme/115736 "2019-04-23T16:43:06Z")
**Posts on this page:** 11
**Page:** 1

<div class="post-metadata">

### Author: ![Discourse](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/discourse/32/148734_2.png) [@Discourse](https://meta.discourse.org/u/Discourse)
#### Post date: [4월 23, 2019, 4:43오후 UTC](https://meta.discourse.org/t/replace-discourses-default-svg-icons-with-custom-icons-in-a-theme/115736/1 "2019-04-23T16:43:06Z")

</div>

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.](https://meta.discourse.org/t/beginners-guide-to-using-discourse-themes/91966#create-new-themes-and-theme-components-7)

# 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
<?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](https://css-tricks.com/scale-svg/#:~:text=The%20viewBox%20is%20an%20attribute,%2C%20y%2C%20width%2C%20height.&text=Likewise%2C%20the%20height%20is%20the,to%20fill%20the%20available%20height) 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. [https://www.npmjs.com/package/svg-sprite-generator](https://www.npmjs.com/package/svg-sprite-generator) is a simple command line tool for combining SVGs into a spritesheet.

### Example - single custom icon spritesheet

```xml
<?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.

> ℹ 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`:

> ![image](https://global.discourse-cdn.com/meta/original/4X/d/1/2/d127176d485a2d6ad8a0ba7cabd313a631a3bafa.png)

### 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:

```json
"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:

```gjs
// {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`

 ![43%20PM](https://global.discourse-cdn.com/meta/original/4X/a/8/5/a85f8e1c5f910d1f0fa92e359066ca9b022a15c0.png)

Most of our icons follow the icon names from [https://fontawesome.com/](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](https://github.com/discourse/discourse/blob/0b5d5b0d40ecf4b1588a442598410ea64d7869d5/app/assets/javascripts/discourse-common/addon/lib/icon-library.js#L14).

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

* * *

This document is version controlled - suggest changes [on github](https://github.com/discourse/discourse/blob/main/docs/developer-guides/docs/05-themes-components/19-custom-icons.md).

---

<div class="post-metadata">

### Author: ![jrgong](https://avatars.discourse-cdn.com/v4/letter/j/c57346/32.png) [@jrgong](https://meta.discourse.org/u/jrgong)
#### Post date: [8월 23, 2024, 7:35오전 UTC](https://meta.discourse.org/t/replace-discourses-default-svg-icons-with-custom-icons-in-a-theme/115736/37 "2024-08-23T07:35:50Z")

</div>

특정 요소 안의 특정 아이콘을 어떻게 표적화하나요? 제 경우, 사이드바 메뉴의 Docs 아이콘을 다른 FA 아이콘으로 교체하고 싶습니다.

---

<div class="post-metadata">

### Author: ![Don](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/don/32/228726_2.png) [@Don](https://meta.discourse.org/u/Don)
#### Post date: [8월 23, 2024, 7:48오전 UTC](https://meta.discourse.org/t/replace-discourses-default-svg-icons-with-custom-icons-in-a-theme/115736/38 "2024-08-23T07:48:21Z")

</div>

CSS로 이를 숨기고 새로운 버튼을 추가하겠습니다.

Common / CSS

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

```

더 보기 \> 이 섹션 사용자 지정에서 새 버튼을 추가하세요.

 ![Screenshot 2024-08-23 at 9.45.46](https://global.discourse-cdn.com/meta/original/4X/d/4/5/d453d1062d42a5b86626c871a26cb2a0b6506199.png)

---

<div class="post-metadata">

### Author: ![NateDhaliwal](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/natedhaliwal/32/313494_2.png) [@NateDhaliwal](https://meta.discourse.org/u/NateDhaliwal)
#### Post date: [1월 18, 2025, 3:59오전 UTC](https://meta.discourse.org/t/replace-discourses-default-svg-icons-with-custom-icons-in-a-theme/115736/39 "2025-01-18T03:59:57Z")

</div>

> [@Discourse](#):
>
> ```plaintext
> <script type="text/discourse-plugin" version="0.8">
> api.replaceIcon('bars', 'mytheme-icon-bars');
> api.replaceIcon('link', 'mytheme-icon-link');
> <!-- etc -->
> </script>
> 
> ```

이 방법은 작동하지 않습니다. 다음을 시도해 보았지만:

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

```

[여기](https://meta.discourse.org/t/differentiate-admin-and-moderator-shield-icons/106114/40?u=natedhaliwal)에서 가져온 내용인데, 여전히 작동하지 않는 것 같습니다. [이것](https://discourse.theme-creator.io/theme/Discourse/custom-topic-button)도 미리보기 링크에서 작동하지 않기 때문에, 스크립트 태그 방식이 깨진 것 같습니다. 솔직히 확신이 서지 않습니다.

---

<div class="post-metadata">

### Author: ![Lilly](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/lilly/32/575047_2.png) [@Lilly](https://meta.discourse.org/u/Lilly)
#### Post date: [1월 18, 2025, 4:34오전 UTC](https://meta.discourse.org/t/replace-discourses-default-svg-icons-with-custom-icons-in-a-theme/115736/40 "2025-01-18T04:34:02Z")

</div>

제겐 잘 작동해요 🤷🏻‍♀️

 ![IMG_2068](https://global.discourse-cdn.com/meta/original/4X/1/e/2/1e2f1c2a6ac83488ec97b4f11905dfc50c82182a.jpeg)

head 탭에 넣으시는 건가요? 저는 헤더에서 robot을 교체하기도 해요:

 ![IMG_2070](https://global.discourse-cdn.com/meta/original/4X/d/0/7/d07d0a9878e1abae1bd11c440f3b875fbf0717d5.jpeg)

관리자 `SVG icon subset` 설정에 아이콘을 추가해야 할 수도 있습니다.

---

<div class="post-metadata">

### Author: ![NateDhaliwal](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/natedhaliwal/32/313494_2.png) [@NateDhaliwal](https://meta.discourse.org/u/NateDhaliwal)
#### Post date: [1월 18, 2025, 5:00오전 UTC](https://meta.discourse.org/t/replace-discourses-default-svg-icons-with-custom-icons-in-a-theme/115736/41 "2025-01-18T05:00:05Z")

</div>

> [@Lilly](#):
>
> head 탭에 넣는 거죠?

네, head 탭이요. 그리고 가이드에 그렇게 되어 있어서 header 탭에도 넣었습니다.

> [@Lilly](#):
>
> `SVG icon subset`

완료했습니다. 이제 작동합니다. 감사합니다!

---

<div class="post-metadata">

### Author: ![Wasay\_Ahmed](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/wasay_ahmed/32/482084_2.png) [@Wasay\_Ahmed](https://meta.discourse.org/u/Wasay_Ahmed)
#### Post date: [1월 18, 2025, 7:49오전 UTC](https://meta.discourse.org/t/replace-discourses-default-svg-icons-with-custom-icons-in-a-theme/115736/42 "2025-01-18T07:49:24Z")

</div>

@NateDhaliwal 개인 메시지를 보내주실 수 있을까요? 도움이 필요한 게 있는데, 프로필에서 채팅 옵션을 찾을 수가 없어요. 감사합니다!

---

<div class="post-metadata">

### Author: ![Adi1](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/adi1/32/485503_2.png) [@Adi1](https://meta.discourse.org/u/Adi1)
#### Post date: [3월 7, 2025, 7:42오후 UTC](https://meta.discourse.org/t/replace-discourses-default-svg-icons-with-custom-icons-in-a-theme/115736/43 "2025-03-07T19:42:44Z")

</div>

```plaintext
왼쪽 메뉴에서 Audi 카테고리 안의 prefix-span 클래스가 있는 요소의 배경을 다시 정의합니다 */
.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;

```

메인 페이지를 클릭하면 사각형 아이콘이 다시 나타납니다! 제가 무엇을 잘못하고 있는지 알려줄 수 있나요? 그리고 카테고리 페이지 자체에서는 정상적으로 동작합니다.

---

<div class="post-metadata">

### Author: ![confused-discourse-user](https://avatars.discourse-cdn.com/v4/letter/c/a88e57/32.png) [@confused-discourse-user](https://meta.discourse.org/u/confused-discourse-user)
#### Post date: [9월 17, 2025, 6:15오후 UTC](https://meta.discourse.org/t/replace-discourses-default-svg-icons-with-custom-icons-in-a-theme/115736/44 "2025-09-17T18:15:31Z")

</div>

안녕하세요, 누군가 주제/컴포넌트 이름, 파일명, SCSS 변수 이름, 그리고 심볼 ID 사이의 관계를 설명해 주실 수 있을까요?

모더레이터의 `shield-halved` 아이콘을 자체 아이콘으로 교체하려고 하는데, 설명이 다소 불분명합니다.

2단계에서:

- “[UI를 통해](https://meta.discourse.org/t/replace-discourses-default-svg-icons-with-custom-icons-in-a-theme/115736#p-568329-via-the-ui-4)” 스크린샷에는 파일명이 `baticonsprite.svg`이고 SCSS 변수 이름이 `icons-sprite`로 표시되어 있습니다.
- 하지만 “[테마에 하드코딩](https://meta.discourse.org/t/replace-discourses-default-svg-icons-with-custom-icons-in-a-theme/115736#p-568329-hardcode-into-a-theme-component-5)” 부분에서는 테마/컴포넌트에 하드코딩하라고 안내합니다.
  - 그런데 어떻게 해야 하는 건가요? 편집기에서 `assets.json` 파일을 찾을 수 없습니다. 컴포넌트를 내보내면 `about.json` 파일이 보이며, 여기에는 UI를 통해 업로드한 스프라이트가 표시되어 있습니다.
  - 하지만 이 예시에는 `/assets/my-icons.svg`라는 다른 파일명이 나와 있습니다. 이는 `baticonsprite.svg`와 동일한 파일을 의미하는 건가요?
  - 이 두 가지 방법은 동일한 작업을 수행하는 _대안_이고, 둘 중 하나만 수행하면 되는 건가요?

3단계에서:

- 그런데 이제 `api.replaceIcon()`에서 두 번째 매개변수는 이전의 어떤 ID도 사용하지 않습니다. `icons-sprite`, `bat-icon`, `baticonsprite.svg`, `my-icons.svg`도 아닙니다. 대신 완전히 새로운 `my-theme-icon-bars`가 나옵니다… 혼란스럽습니다.
  - `my-theme` 접두사는 필수적인가요? 그렇다면 그 “테마 이름” 문자열은 어디에서 오는 건가요. `my-theme-bat-icon`이어야 하나요? 그리고 만약 컴포넌트인 경우 테마가 아닌 경우엔 어떻게 되나요?
  - 그리고 `icon-bars` 부분은 다음 중 무엇이어야 하나요?
    - SVG 스프라이트 시트 XML의 심볼 ID
    - SVG 파일의 파일명
    - 부여하는 SCSS 변수 이름
    - 위의 조합 (예: `icons-sprite-bat-icon`?)

그리고 실제로 `api.replaceIcon()` 호출은 어디에 넣어야 하나요? 이미 다음 보일러플레이트가 있는 커스텀 컴포넌트의 “JS” 탭에 넣는 것이 괜찮나요?

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

export default apiInitializer((api) => {
   // your code here
});

```

아니면 커스텀 `<script type=”discourse/plugin”>` 태그를 만들어 `<head>` 탭에 넣어야 하나요?

* * *

혼란을 드려 죄송합니다.

위 조합을 여러 번 시도해 보았지만, 어떤 경우에도 제 스프라이트가 표시되지 않았습니다…

제 스프라이트 XML은 다음과 같습니다:

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

```

파일명은 `my-logo.svg`이고, SCSS 변수 이름도 `my-logo`입니다.

 ![DatoCMS forum usability tweaks (from Roger) - Admin - DatoCMS community - 2025-09-17 11-12-21 AM](https://global.discourse-cdn.com/meta/original/4X/9/d/4/9d4434f2563adbb0a3d64a5d26a1dd9bae64d9e7.png)

그리고 커스텀 컴포넌트의 `JS` 탭에는 다음이 있습니다:

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

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

```

하지만 아무것도 표시되지 않습니다. 놓치고 있는 단계가 있거나, 제가 오해하고 있는 마법 같은 문자열 보간법이 있는 건가요…?

---

<div class="post-metadata">

### Author: ![confused-discourse-user](https://avatars.discourse-cdn.com/v4/letter/c/a88e57/32.png) [@confused-discourse-user](https://meta.discourse.org/u/confused-discourse-user)
#### Post date: [2월 24, 2026, 9:10오후 UTC](https://meta.discourse.org/t/replace-discourses-default-svg-icons-with-custom-icons-in-a-theme/115736/45 "2026-02-24T21:10:02Z")

</div>

이거 어떻게 해결할 수 있는 분 계신가요? 아직도 이 문제로 고생하고 있습니다…

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

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

```

UI를 통해 파일을 업로드할 경우, 두 번째 ID(`my-logo`)가 어디에서 오는지 잘 모르겠습니다:

 ![DatoCMS forum usability tweaks (from Roger) - Admin - DatoCMS community - 2026-02-24 01-08-47 PM](https://global.discourse-cdn.com/meta/original/4X/0/6/5/06509d3ea3913e39ede7dd7ba20bbd3b111a5af1.png)

$my-logo, $test, my-logo.svg, 디스크에 있는 SVG의 절대 URL 등 아무것도 동작하지 않습니다. 방패 아이콘은 대체되긴 하지만, 아무것도 표시되지 않습니다. SVG가 내용 없이 빈 `<use href="#my-logo">` 태그만 남게 됩니다.

---

<div class="post-metadata">

### Author: ![confused-discourse-user](https://avatars.discourse-cdn.com/v4/letter/c/a88e57/32.png) [@confused-discourse-user](https://meta.discourse.org/u/confused-discourse-user)
#### Post date: [2월 24, 2026, 9:24오후 UTC](https://meta.discourse.org/t/replace-discourses-default-svg-icons-with-custom-icons-in-a-theme/115736/47 "2026-02-24T21:24:39Z")

</div>

드디어 해결했습니다 (클로드에게 감사드립니다)!

즉:

1. SVG 파일명은 중요하지 않습니다.

2. SVG 스프라이트 시트 내부에서는 심볼 `id`가 최종 아이콘 이름을 결정합니다. 예를 들어 `<symbol id=”my-logo” …>`

3. 하지만 SCSS 변수 이름은 **반드시** `icons-sprite`여야 합니다. 최종 스프라이트 시트 ID와 관련이 있는 이름으로는 절대 안 됩니다:

4. 업로드 후 이 모습이 됩니다:

5. 마지막으로, 테마의 “JS” 탭(`<head>`가 아님)에서 1단계의 스프라이트 시트 ID를 사용합니다:

즉, SCSS 변수 이름은 반드시 `icons-sprite`여야 하고, 파일명은 중요하지 않으며, API 아이콘 이름은 스프라이트 시트 내부의 심볼 ID에 의해 결정됩니다.
