skip to Main Content

so I’m creating custom web components using Vue 3 to be used in any web application and I’m running into a problem when it comes to importing fonts and Google material icons because of the shadow-root. My current approach is to add the stylesheet link tag to the application header when the web components are registered, it works however I feel like it defeats the encapsulating idea of web components and I feel like this can affect the styles of the application that is using my custom web components.

Here is my current implementation.

import { defineCustomElement as VueDefineCustomElement, h, getCurrentInstance } from 'vue';

export function register() {
  // Icons
  loadStylesheet(
    'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200'
  );
  // Fonts
  loadStylesheet('https://fonts.googleapis.com/css2?family=Lato:wght@400;500;600;700&display=swap');

  // components
  customElements.define('comp', defineCustomElement(comp1));

  ...
}

export const defineCustomElement = (component) => {
  ...

  return VueDefineCustomElement({
    setup() {
      ... // some data injection
      return () => h(component);
    },
  });
};

// Function to add the stylesheets to applications head tag
function loadStylesheet(href) {
  let existingNode = null; // get existing stylesheet node if it already exists:
  for (let i = 0; i < document.styleSheets.length; i++) {
    if (document.styleSheets[i].href && document.styleSheets[i].href.indexOf(href) > -1) {
      existingNode = document.styleSheets[i].ownerNode;
      break;
    }
  }
  if (existingNode) {
    return;
  } // already exists so don't need to add again

  let linkTag = document.createElement('link');
  linkTag.rel = 'stylesheet';
  linkTag.href = href;
  document.getElementsByTagName('head')[0].appendChild(linkTag);
}

And in the root component I did the following

@import url('../index.css');
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200');
:host {
  font-family: Lato, Arial, Helvetica, sans-serif;
}

I have thought about download the font and using that and replacing icons with svg. However I feel like this will increase the bundle size.

2

Answers


  1. To add icons go to font awesome sign UP and get your embeded kit code , insert it in thé HTML header part and you Can search in thé website for icons it IS easy to insert them

    Login or Signup to reply.
  2. When creating web components with shadow DOM, you indeed face the challenge of how to include styles such as fonts and icons without affecting the host page’s styles. Here are several strategies you could consider:

    Inline Fonts and Icons:
    Convert fonts and icons to data URIs and include them directly in your component’s styles. This keeps the encapsulation but increases the size of your component.

    External Stylesheets with CSS @import:
    Within your shadow DOM, you can use CSS @import to include external stylesheets. This is similar to adding links in the head, but it keeps the styles scoped to your component:

    /* Inside your component's styles */
    @import url('https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap');
    @import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined...');
    

    This approach still requests the external resources but does not affect the global document styles.

    Bundling Fonts and Icons:
    If you have a limited set of icons, you could bundle SVG versions of them with your component. For fonts, consider subsetting them to include only the characters and weights you need.

    Dynamic Injection into Shadow DOM:
    You could inject a style or link element into your shadow DOM at runtime. This encapsulates the styles but requires each instance of your component to fetch its own copy of the styles.

    CSS Variables and Host Page Cooperation:
    Define CSS variables for fonts and icons in your component’s styles, and expect the host page to provide values for these variables. This requires cooperation from the consumer of your component but keeps styles flexible and encapsulated.

    Font Face Inside Shadow DOM:
    You could include @font-face declarations directly inside your component’s styles, scoped to the shadow DOM. This still requires fetching the font files, but it won’t affect the host page.

    Documentation and Best Practices:
    Sometimes, the best approach is to provide clear documentation on how to include necessary styles in the host application. You can provide the links to the required stylesheets and suggest where to include them.

    Given that you’re concerned about encapsulation and not bloating your component’s size, you might find a combination of these strategies to be the best approach. For example, you could include SVGs for the most common icons directly and use @import for fonts within your shadow DOM. Or, you could document how to include the fonts and icons, allowing the consumer to decide the best approach for their application.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search