19

Font Awesome is not working in my shadow DOM since I have the following in it to prevent styles from leaking in and out:

:host {
    all: initial; /* 1st rule so subsequent properties are reset. */
    display: block;
    contain: content; /* Boom. CSS containment FTW. */
}

I'm able to use other stylesheets by just inlining them within the :host property, but that doesn't work with Font Awesome since it uses relative paths in its stylesheet.

I found this post and tried it with the scoped CSS I implement, but the icons show as squares, as can be seen in my example.

cryptojesus
  • 375
  • 1
  • 2
  • 9
  • Fetch the file, replace the paths with a regexp, inline it. – wOxxOm Feb 06 '19 at 04:28
  • thanks! this is a good brute-force strategy, but I'll wait to see if someone can help me with linking it first. – cryptojesus Feb 06 '19 at 04:58
  • Well, since this is an extension you're writing, you can probably redirect the incorrect URLs using chrome.webRequest.onBeforeRequest. OTOH they may look just like valid ones from the web page... – wOxxOm Feb 06 '19 at 16:03
  • What about building Font-Awesome yourself and customize the output the way you want? – wOxxOm Feb 06 '19 at 16:06
  • 1
    You forgot to add the in the global scope – Supersharp Feb 06 '19 at 19:24
  • 1
    And the @import() should not be placed in the rule, but directly in the – Supersharp Feb 06 '19 at 19:57
  • 1
    Wow @Supersharp thanks so much! i really don't understand why I need to link it twice, inside and outside the shadowroot. any idea why? – cryptojesus Feb 06 '19 at 20:00
  • 2
    https://codepen.io/anon/pen/NowbXx. Yes the FONT (@font-face) must be declared in the global scope (and is inherited in the Shadow DOM) while the classes (here: fa, fa-search) must me imported into the Shadow DOM to be applied in it – Supersharp Feb 06 '19 at 20:03
  • To use SVG font-awesome icons in shadow dom, see my answer [here](https://stackoverflow.com/a/69239107/5798816) – Harsh Rohila Sep 18 '21 at 23:10

6 Answers6

22

New approaches for Stenciljs --- 2023 update

If you are using Stenciljs and want to ship the fonts directly with your bundle and use it everywhere than one of the two options is for you:

First Option: Use global style sheet. https://stenciljs.com/docs/styling#global-styles. In the global style sheet you can import your fonts like you usually do:

@font-face {
font-family: 'special-font';
src: url('/assets/fonts/special-font.ttf') format('truetype');
}

h1,h2,h3,h4,h5 {
  font-family: 'special-font' !important;
}

This would apply the font to all titles on your page (web-components but also all elements on your normal page). Everything is served through your stencil package. Take care not to accidentally overwrite styles of your normal website.

Second option: Create a new component like <ci-styling></ci-styling> and insert all your styles in this component. Than you just have to set shadow: false in the @Component decorator. With this the component isn't using the shadow dom anymore and the styles are available everywhere like in the first option. With this approach you can always decide per page individually if you want e.g. the fonts or not by simply loading or not loading this component.

@Component({
  tag: 'ci-styles',
  styleUrls: ['ci-styles.scss'],
  shadow: false,
})

Original Answer Below from 2019 (still valid)

Third option: After hours of struggle and the answer from @Intervalia I was able to fix it.

The problem is that the browser doesn't load the font files when they are only included in the shadow dom (your custom web component). This means that the fonts also has to be included in the normal html file (aka light DOM) so that the browser can detect and load them in order to make them available in the shadow dom.

In my case I didn't use Font awesome instead it was a custom font but I tried it a second time with font awesome and a clean Stenciljs project. The solution is always the same doesn't matter which custom font you need.

Step 1: Move the font into your project. I created a seperate "assets" folder inside the "src" folder to have access from all the components. In this example I downloaded font awesome for web environment https://fontawesome.com/download. (I wouldn't recommend "npm install" since you have to use it in the index.html too)

enter image description here

Step 2: Prepare your web component (in this case my-component.tsx). You can import multiple css files using the styleUrls property. Just import the fontawesome css from your assets directory.

import { Component, Prop, h } from '@stencil/core';

@Component({
  tag: 'my-component',
  styleUrls: [
    'my-component.css',
    '../../assets/fontawesome/css/all.css'
],
  shadow: true
})
export class MyComponent {
  @Prop() first: string;


  render() {
    return <div> <i class="fas fa-question-circle"></i> </div>;
  }
}

Step 3 prepare the file where you want to use the component (in this case index.html). The important line is the "link" tag. This includes the "font awesome css" again and force the Browser to really download the fonts.

<!DOCTYPE html>
<html dir="ltr" lang="en">
<head>
  <meta charset="utf-8">
  <title>Stencil Component Starter</title>
  <link rel="stylesheet" type="text/css" href="./assets/fontawesome/css/all.css">
</head>
<body>

  <my-component first="Stencil" last="'Don't call me a framework' JS"></my-component>

</body>
</html>

I know it feels wrong and looks weird but it is not enough to include font awesome only in the index html or in the web component. It must really be included in both files. That doesn't mean the Browser will load it multiple times - it will only be loaded once.

That means that you can't deliver the font with the web component - as far as i know. This isn't a stenciljs bug this is a general problem of the browsers. Please let me know if you have better solutions.

Just for fun here is a screenshot that shows that the browser doesn't load the fonts when it is only included in one file. http://prntscr.com/p2f9tc

Update 05.10.2019:

If you want to use your font inside your web-component the explanation above is correct and still necessary. But you can also use the slot tag inside the web-component. Than you automatically bypass the font from outside (the html) into the web-component. But notice it only works for the stuff you write between the tags of your web component. That means you can use <my-component> <i class="your-font"/> </my-component>. In this case you don't have to import the font into the web components.

Christian Meyer
  • 1,501
  • 1
  • 14
  • 19
  • I have the same problem with Stenciljs too. I've read your answer and I've not still clear how to make Font Awesome work in stencil. I do npm i font-awesome and then whant I need to do? I haven't an asset folder in my project, and what is formate.css that your wrote? – JEricaM Sep 05 '19 at 15:43
  • 1
    In my example I used a different font (not font awesome). But the problem is always the same. To validate the solution, I just tried it again with font awesome and a new stencil project and it worked. The problem is as far as I can see that the browser doesn't load the fonts as soon as they are only embedded in the Shadow Dom. I will update my answer with more details and more generalized way – Christian Meyer Sep 05 '19 at 22:58
  • 1
    Thank you Christian. I've follow your tutorial in a new stencil component project and it all works perfectly!! Thank you so so much for the help. Now I'll try to integrate this in my other stencil project that also use storybook. – JEricaM Sep 06 '19 at 07:24
  • Thanks for the solution and it works **only ** in the stencil component. When I try to use the component which is built(working in stencil) but doesnt show up the icons in ReactJS app. Have you tried to use the component built in ReactJS ? :) – Johnson Samuel Oct 04 '19 at 05:30
  • I'm more an Angular guy I'm sorry ;) I used the components with the explanation above successfully in the newest Angular and AngularJS - so im confident that it works in ReactJS too. The procedure is always the same. The index.html i mentioned above is just for Stencil.js and won't be rendered in any kind into the web-components. The index.html is just for the integrated web-server in StencilJS (localhost:3333). You have to make sure that you import the fonts and CSS in ReactJS directly. I guess ReactJS uses an index.html as starting point too - try to import the css files there directly. – Christian Meyer Oct 05 '19 at 13:03
  • I am using line-awesome and this helped me out. Pretty much the same two steps. 1. include the css in index.html - as link tag 2. include the css in .css for the component as an @import url, or include in the styles array. – Roshan Khandelwal Nov 03 '20 at 07:47
8

One thing I have noticed is that if the page does not load the CSS file then the shadowDOM won't load it either.

I really think that the only problem us that if the font is not defined on the page that it will not work in the component since the rest of the CSS seems to properly apply to the shadowDOM elements.

This example shows just the shadowDOM trying to load the CSS and it does not work:

let template = `
<style>
:host {
  display: block;
}
</style>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.1/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
<header>
  <h1>DreamLine</h1>
  <nav>
    <ul>
      <li><a href="#0">Tour</a></li>
      <li><a href="#0">Blog</a></li>
      <li><a href="#0">Contact</a></li>
      <li><a href="#0">Error</a></li>
      <li><a href="#0"><i class="fa fa-search"></i> Search</a></li>
    </ul>
  </nav>
</header>
`;

class MyEl extends HTMLElement {
  connectedCallback() {
    this.attachShadow({mode: 'open'}).innerHTML = template;
  }
}

customElements.define("blog-header", MyEl);
<i class="fa fa-battery-full" style="font-size: 45px;"></i>
<hr/>
<blog-header></blog-header>
<hr/>

And this example shows both the page and the shadowDOM loading it and it works:

let template = `
<style>
:host {
  display: block;
}
</style>
<header>
  <h1>DreamLine</h1>
  <nav>
    <ul>
      <li><a href="#0">Tour</a></li>
      <li><a href="#0">Blog</a></li>
      <li><a href="#0">Contact</a></li>
      <li><a href="#0">Error</a></li>
      <li><a href="#0"><i class="fa fa-search"></i> Search</a></li>
    </ul>
  </nav>
</header>
`;

class MyEl extends HTMLElement {
  connectedCallback() {
    const styles = document.querySelector('link[href*="fontawesome"]');
    this.attachShadow({mode: 'open'}).innerHTML = template;
    if (styles) {
      this.shadowRoot.appendChild(styles.cloneNode());
    }
  }
}

customElements.define("blog-header", MyEl);
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.1/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">

<i class="fa fa-battery-full" style="font-size: 45px;"></i>
<hr/>
<blog-header></blog-header>
<hr/>

The code I like to use looks for the <link> tag I want in the body and then uses a clone of that tag inside the shadowDOM. This way my component is not out of sync. Yes, this can cause problems if the component was not expecting a change in the CSS but I find it works well for my projects.

Intervalia
  • 10,248
  • 2
  • 30
  • 60
  • This may have changed in the meantime but I now only need to include the `all.min.css` in the host page and it works. Other solutions mention trying to use slots, which sort of worked also but I wanted several instances of an identically named slot in the template, which appears to not work (only the first takes all the inputs of a given name). I tested on FF and Chrome and had the same behaviour - nothing worked except putting the FA css in the host page. (And yes, this component is using the shadow DOM) – AntonOfTheWoods Jan 28 '21 at 13:04
1

If you don't need shadow: true then you can load the all.min.css directly via the index.html or the main application. Even loading the all.min.js file works.

If you need it within the shadow dom, then you need to load the all.min.css in index.html and also load it within shadow root using something like this.

`

componentDidLoad(): void {
  this.hostElement.shadowRoot
    .getElementById("some_Id")
    .insertAdjacentHTML(
      "afterbegin",
      `<link rel="stylesheet" href="${getAssetPath(
        "/fontAssets/fontawesome-free/css/all.min.css"
      )}" />`
    );
}

`

Dhrumil
  • 117
  • 5
  • 13
1

I wanted to share what I did for loading in Font Awesome icons to my stencil components (shadow enabled)...

After several hours into researching this topic, I think I've discovered a solution that will be the most efficient for my components to be bundled in a agnostic way and free of any additional style sheet includes in the HTML header.

My solution was to use the stencil-inline-svg module and then import the svg file directly from the Font Awesome module like this:

// the reference to the svg can be anything (searchIcon name).
// just make sure to import the correct svg from fontawesome node modules.
import searchIcon from 'fontawesome/svgs/regular/search.svg';

@Component({
  tag: 'search-icon',
  styleUrl: 'search-icon.scss',
  shadow: true,
})

export class SearchIconComponent {
  render(){
    return (
      {/* Not ideal to use innerHTML but this renders the full SVG markup */}
      <span class="search-btn" innerHTML={searchIcon}></span>
    )
  }
}

Now, I can set css rules to modify the color and size of my icon like this

    .search-btn {
        width: 40px; // Set SVG size at the parent.

        svg path {
            fill: #fff; // Update svg path color.
        }
    }

Obviously this requires a little bit of Font Awesome icon knowledge so you know which icons to import.

Omar
  • 421
  • 4
  • 10
  • 1
    Excellent solution! I like it because it doesn't pollute the styles of the outer webpage. For those interested, this page shows how to do use this approach in react: https://fontawesome.com/how-to-use/on-the-web/using-with/react. – Christian Fritz Feb 05 '21 at 20:04
0

Shadow Doms's style is scoped. And it does not interfere with outer style

0

FWIW I created a helper method to create a link for font-awesome at the parent page level. Not sure if this is in violation of any custom-elements/Web Components standards but I'll go ahead and post here in hopes that I'll be corrected :) It works for my use case w/ internal web applications for now though.

export const addFontAwesomeStyles = () => {
    injectStylesheet("https://use.fontawesome.com/releases/v5.13.0/css/all.css");
    injectStylesheet("https://use.fontawesome.com/releases/v5.13.0/css/v4-shims.css");
}

export const injectStylesheet = (href: string) => {
    const links = document.head.querySelectorAll("link");

    // Already been injected
    for(let l in links)
        if(links[l].href == href) return;

    const link = document.createElement('link');
    link.rel = "stylesheet";
    link.href = href;
    document.head.appendChild(link)
}

Then in your StencilJS component's construtor you can use it like so:

//...
constructor() {
    addFontAwesomeStyles();
}
LukeP
  • 1,505
  • 1
  • 16
  • 25