Introducing Font Awesome 5 and SVG icons

We will shortly merge into master a branch that upgrades Discourse to Font Awesome 5.5.0 (the free version) and switches to using SVG icons instead of an icon font. This is a substantial change, with lots of benefits, and one significant change for developers.

Here is a quick rundown of the changes:

  • using SVG icons will provide crisper icons, better for accessibility and easier to customize, see this Github article for more details
  • since the Font Awesome icon set has grown to 1300+ icons in version 5, we have built an internal API that delivers to clients a subset of all FA icons, that is, only those icons used by that Discourse instance
  • the subset thankfully has a smaller size footprint: itā€™s already running here in Meta, and it is only 27.5 kb vs the 75.7 kb of the FA 4.7 icon font
  • plugins and themes (including theme components) can add additional FA icons to the set
  • group flair and badge icons will automatically be included in the set, and site administrators can also use a new site setting called svg icon subset to register their chosen icons and add them to their siteā€™s subset
  • breaking change: plugin and theme developers can no longer use <i class="fa fa-icon"></i> or override the :before pseudo selectors to reference/replace icons, these should now be replaced by using Discourse functions that inject SVGs in the page

Below, you can find instructions on how to update plugins and themes to use icons from the new set.

Whatā€™s new in Font Awesome 5

Font Awesome 5 has introduced many new icons but also some naming changes. For a full discussion of the changes please see the Font Awesome upgrade documentation. The main change is that icons in FA come in separate styles now. There are three styles:

  1. solid (default) ā€“ fas
  2. regular ā€“ far
  3. brands ā€“ fab

For the regular or brands styles, FA 5 introduces new class prefixes, ā€œfarā€ and ā€œfabā€ respectively. To use an icon from the regular or brands styles, then, we need to use this new naming convention: "far fa-address-book" or "fab fa-facebook". (Solid icons can simply be referenced as before ā€œfa-icon-nameā€).

To allow bundling the three styles into one SVG sprite, icons in the regular and brand styles in Discourse are converted to far-icon-name and fab-icon-name internally. Plugins, themes, group flair and badges can use the standard FontAwesome 5 naming convention. Site admins adding icons to the set via the svg icon subset site setting must use the internal naming convention.

Developers: how to use or add an SVG icon to your plugin or theme

  1. Adding new icons

    plugins:

    register the icon in your pluginā€™s plugin.rb file:

    register_svg_icon "user-times" if respond_to?(:register_svg_icon)
    

    (Note: you need to restart your Rails server in your development environment for this change to take effect.)

    themes or components:

    add a string or list setting with a name containing _icon, for example:

    svg_icons: 
      default: 'question-circle|wallet'
      type: 'list'
      list_type: 'compact'
    

    and Discourse will include the icon(s) defined in that theme setting to the subset.

  2. Using icons in your javascript

    plugins:

    import { iconNode } from "discourse-common/lib/icon-library";
    ...
    let icon = iconNode('user-times');
    

    or use the iconHTML helper

    import { iconHTML } from "discourse-common/lib/icon-library";
    ...
    let icon = iconHTML('user-times');
    

    themes or components:

    const { iconNode } = require("discourse-common/lib/icon-library");
    ...
    let icon = iconNode('user-times');
    

    or use the iconHTML helper

    const { iconHTML } = require("discourse-common/lib/icon-library");
    ...
    let icon = iconHTML('user-times');
    

    the helpers can also take a second parameter with properties like title or class. This works in plugins and themes / components in the same way, for example

    iconHTML('user-times', { class: "reply-to-glyph" })
    
  3. Using icons in your handlebars templates
    In Handlebars templates, you can use d-icon, like this:

    {{d-icon 'user-times'}}
    

    this also works in the same way for plugins and themes / components

Adding custom icons

If you would like to have more icons than whatā€™s available in FontAwesome, you can add your own SVG icons in a plugin or a theme. See this SVG sprite for an example of how to structure your sprite. (Itā€™s essentially a list of <symbol> elements, each with its own unique ID.)

In themes and components: add the SVG sprite in the /assets folder, and reference it in about.json using the variable name icons-sprite. For a sprite called my-icons.svg, your assets.json should include this:

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

You can also add the SVG sprite to a theme or component via the UI, when doing so, make sure the SCSS var name is set to icons-sprite. Screenshot:

In plugins: simply include an SVG sprite file in the plugins/your-plugin-name/svg-icons folder. Restart your server (if in dev) or rebuild the site if in a Docker container and your custom icons should be automatically available.

To avoid any potential clashes with FontAwesome icon ids, you should prefix the ids of the custom icons in your plugin or theme.

79 Likes

Could someone elaborate on how to update a theme component? Iā€™m very green at this and havenā€™t been able to make sense of all this. Iā€™m currently using as such:

<a href="javascript:history.back()" class="app-go-back"><i class="fas fa-arrow-left" aria-hidden="true"></i></a>

This may be unrelated, but the following css has broken since the latest build:

.b-header .nav-pills > li:nth-child(3) a::before{
	content: "\f1ea";
}

As you can see here, only this one icon has broken, I double checked and f1ea is still valid in FA5. Is there a better way to achieve this with the new changes?

2 Likes

From what I can see all of the icons are broken:

2 Likes

Hmm interesting, they must be cached on my side. Is the option of using this gone now @pmusaraj?

2 Likes

For HTML code directly, you can replace:

<i class="fas fa-arrow-left" aria-hidden="true"></i>

with:

<svg class="fa d-icon d-icon-arrow-left svg-icon svg-node" aria-hidden="true"><use xlink:href="#arrow-left"></use></svg>

Note that ā€œarrow leftā€ is in two places, in the class and in the <use> tag. Also, this icon is in the solid style in FA5, but for icons in regular or brands, you need to use the prefixes far- and fab-, respectively.

In your header links, you canā€™t use :before anymore, because SVG sprites canā€™t be added to pseudo selectors. But you can use this component: Header submenus (itā€™s been updated recently, and is FA5-compatible).

15 Likes

Good job. :ok_hand:

How can I change this code to work with Font Awesome 5?

 a[href="/new"]:before {
      display: inline-block;
      font-family: FontAwesome;
      font-style: normal;
      font-weight: normal;
      line-height: 1;
      -moz-osx-font-smoothing: grayscale;
      content: "\f0ca";
      margin-right: 3px
    }
2 Likes

What is creating this a[href="/new"] element? If you are adding it in your theme, via JS, then itā€™s easier to add the icon there, instead of using the CSS pseudo selector. One of iconHTML or iconNode above should do the trick.

5 Likes

Iā€™m very confused. I tried this and it worked:

But when I switched ā€œleftā€ to ā€œrightā€ in both places, it didnā€™t work. Am I missing something? Hereā€™s the code I tried:

<svg class="fa d-icon d-icon-arrow-right svg-icon svg-node" aria-hidden="true"><use xlink:href="#arrow-right"></use></svg>

(I was actually trying to get the church icon to work, so if thatā€™s going to require something else, let me know.)

1 Like

The arrow-right icon is not included by default (because it isnā€™t used elsewhere in Discourse), so you will need to add it to the svg icons subset site setting. Same for the church icon. (FA5 comes with thousands of icons, so we use a subset to avoid loading all the icons all the time. It saves precious bytes :slight_smile:)

17 Likes

Makes complete sense. Thanks much.

2 Likes

I was banging my head against the wall trying to figure out why the right arrow wasnā€™t displaying last night! FYI I donā€™t if this is because it is a work in progress, but the instructions say to use far but this did not display the icon, I had to add it as fa-right-arrow for it to display.

3 Likes

Sorry about that, I have updated the description text for that setting to include: ā€œUse prefix ā€˜fa-ā€™ for solid icons, ā€˜far-ā€™ for regular icons and ā€˜fab-ā€™ for brand icons.ā€

8 Likes

I want to add icons to the navigation bar ā€“ and I used a[href="/new"] for meta.discourse.org/new or a[href="/categories"] for meta.discourse.org/categories

// Add Font Awesome 5 Icons to the navigation bar

a[href="/new"]:before {
  font-family: "Font Awesome 5 Free";
  font-weight: 900;
  content: "\f007";
  display: inline-block;
  font-style: normal;
  font-variant: normal;
  text-rendering: auto;
  line-height: 1;
  margin-right: 3px;
  -webkit-font-smoothing: antialiased;
}

But Iā€™m doing something wrong and itā€™s not working.

1 Like

We are no longer using Font Awesome as a font, so using the old method of pseudo selectors in CSS will not work.

If you donā€™t want to touch javascript and want a CSS-only solution, you can use an SVG as an image:

a[href="/new"]:before {
   content: url(/link-to-file.svg);
  // display inline-block, etc still needed
}

or you can inline the SVGā€™s code (which I believe has some compatibility issues with older browsers)

a[href="/new"]:before {
   content: url('data:image/svg+xml; utf8, <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" data-prefix="fas" data-icon="grin-tongue-wink" class="svg-inline--fa fa-grin-tongue-wink fa-w-16" role="img"  height="25" width="25" viewBox="0 0 496 512"><path fill="white" d="M344 184c-13.3 0-24 10.7-24 24s10.7 24 24 24 24-10.7 24-24-10.7-24-24-24zM248 8C111 8 0 119 0 256c0 106.3 67 196.7 161 232-5.6-12.2-9-25.7-9-40v-45.5c-24.7-16.2-43.5-38.1-47.8-63.8-2-11.8 9.3-21.5 20.7-17.9C155.1 330.5 200 336 248 336s92.9-5.5 123.1-15.2c11.5-3.7 22.6 6.1 20.7 17.9-4.3 25.7-23.1 47.6-47.8 63.8V448c0 14.3-3.4 27.8-9 40 94-35.3 161-125.7 161-232C496 119 385 8 248 8zm-56 225l-9.5-8.5c-14.8-13.2-46.2-13.2-61 0L112 233c-8.5 7.4-21.6.3-19.8-10.8 4-25.2 34.2-42.1 59.9-42.1S208 197 212 222.2c1.6 11.1-11.6 18.2-20 10.8zm152 39c-35.3 0-64-28.7-64-64s28.7-64 64-64 64 28.7 64 64-28.7 64-64 64zm-50.9 102.6c-14.4-6.5-31.1 2.2-34.6 17.6l-1.8 7.8c-2.1 9.2-15.2 9.2-17.3 0l-1.8-7.8c-3.5-15.4-20.2-24.1-34.6-17.6-.9.4.3-.2-18.9 9.4v63c0 35.2 28 64.5 63.1 64.9 35.7.5 64.9-28.4 64.9-64v-64c-19.5-9.6-18.2-8.9-19-9.3z"></path></svg>');
}
8 Likes

Iā€™ve hacked my way through this and Iā€™ve got everything working again except the right arrow on my mobile navigation component for the app, I canā€™t get it to right align to save my life. I tried using flex with flex-end to no avail. Please forgive my horrible attempt at thisā€¦

Here is my components code:
/body

/body
<div id="mobilenav">
<a href="javascript:history.back()" class="app-go-back">Back</a>
<a href="javascript:history.forward()" class="app-go-forward">Forward</a>
		<div id="mobilenavleft">
			<svg class="fa d-icon d-icon-arrow-left svg-icon svg-node" aria-hidden="true">
			<use xlink:href="#arrow-left"></use>
		</svg>
	</div>
		<div id="mobilenavright">
			<svg class="fa d-icon d-icon-arrow-right svg-icon svg-node" aria-hidden="true">
			<use xlink:href="#arrow-right"></use>
	</svg>
</div>
CSS
@media only screen and (min-width:1024px) {
div#mobilenav {
            display: none !important;
        }
}

/* move up compose window on mobile */
@media only screen and (max-width:1024px) {
#reply-control.open.edit-title {
            margin-bottom: 29px;
            height: 85%;
            margin-top: -29px;
        }

.timeline-container.timeline-fullscreen.show {
            margin-bottom: 29px;
        }
#reply-control.open {
            margin-bottom: 29px;
        }
.docked-composer .docked-editor {
    margin-bottom: 29px;
}
#topic-progress {
    margin-bottom: 33px;
}
.sticky-footer {
    margin-bottom: 33px;
}
}

/* display on ipad in landscape orientation */
@media only screen 
and (min-device-width : 768px) 
and (max-device-width : 1024px) 
and (orientation : landscape) {
div#mobilenav {
            display: block;
        }
}

div#mobilenav {
box-shadow: 0px -1px 5px 0px rgba(0,0,0,0.15);
position: fixed;
bottom: 0px;
width: 100%;
height: auto;
border: none;
z-index: 99999999999;
background-color: #2A2B2F;
}

.app-go-forward {
text-align: right;
padding: 5px 3%;
width: 44%;
float: right;
display: inline-block;
}

.app-go-back {
text-align: left;
padding: 5px 7%;
width: 44%;
float: left;
display: inline-block;
}

div#mobilenav {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
}

div#mobilenavleft {
box-shadow: 0px -1px 5px 0px rgba(0,0,0,0.15);
position: fixed;
bottom: 0px;
width: 1%;
height: auto;
border: none;
z-index: 99999999999;
padding-bottom: 4px;
padding-left: 5px;
}

div#mobilenavright {
box-shadow: 0px -1px 5px 0px rgba(0,0,0,0.15);
position: fixed;
bottom: 0px;
width: 1%;
height: auto;
border: none;
z-index: 99999999999;
padding-bottom: 4px;
padding-right: 5px;
justify-content: flex-end;
}

If anyone wants to help, my site is here and this component only displays below 1024px obviously. The left arrow looks perfect and the right arrow should mirror this.

1 Like

Itā€™s because youā€™re using position: fixed on the arrow, if you add

div#mobilenavright {
  right: 5px;
}

Then it should be where you want it.

Sidenote: You donā€™t really need to use position: fixed for those arrows at all because theyā€™re already within a fixed container, if you put the arrows inside of your a tags containing the ā€œforwardā€ and ā€œbackā€ text the layout might be a bit easier to manage in general.

10 Likes

Iā€™m trying to use this with the Brand Header Theme Component but not having any success with this:

.b-header .nav-pills > li:nth-child(1) a::before{
content: url(https://npn.sfo2.cdn.digitaloceanspaces.com/misc/home-solid.svg);
display: inline-block;
width: 20px;
height: 20px;}

Iā€™ve also tried:

.b-header .nav-pills > li:nth-child(1) a::before {
display: block;
  content: ' ';
  background-image: url('https://npn.sfo2.cdn.digitaloceanspaces.com/misc/home-solid.svg');
  background-size: 20px 20px;
  height: 20px;
  width: 20px;
}

Anyone have ideas?

2 Likes

Something is wrong with your SVG image. The screenshot below works with the Discourse logo, but not with that SVG file:

2 Likes

It might be because the SVG doesnā€™t have any height/width defined (in the SVG markup itself, not the CSS)

Can you right click this one below, save it, and try againā€¦ Iā€™ve added some dimensions to it.

home-solid

8 Likes

This does seem to work, dare I ask how? Also, fill: does not seem to work on this to change the color, it just displays as black.

For reference, my code:

.b-header .nav-pills > li:nth-child(1) a::before {
display: inline-block;
content: ' ';
background: url('https://d11a6trkgmumsb.cloudfront.net/original/3X/a/6/a61b08e7f318170faee755cb6dcd48d6f6d7413d.svg');
background-size: contain;
height: 20px;
width: 20px;
border: 1px solid blue;
fill: blue;

}

2 Likes