---
title: "Web Component Custom Element Gotchas - Muhammad Rehan Saeed"
description: "Web components have certain gotchas with relation to custom elements and CSS. This post goes through them all and shows how you can avoid them."
canonical_url: "https://rehansaeed.com/web-component-custom-element-gotchas/"
last_updated: "2021-04-30T12:12:00+00:00"
meta:
  author: "Muhammad Rehan Saeed"
  keywords: "Web Components,HTML,CSS"
  "og:description": "Web components have certain gotchas with relation to custom elements and CSS. This post goes through them all and shows how you can avoid them."
  "og:image": "https://rehansaeed.com/_og/s/o_422f42358e914aea.png"
  "og:image:alt": "Web Component Custom Element Gotchas - Muhammad Rehan Saeed"
  "og:image:height": "630"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "Web Component Custom Element Gotchas"
  "twitter:image": "https://rehansaeed.com/_og/s/o_422f42358e914aea.png"
  "twitter:image:alt": "Web Component Custom Element Gotchas - Muhammad Rehan Saeed"
---

> Index of every page on this site: [llms.txt](https://rehansaeed.com/llms.txt)

# [Web Component Custom Element Gotchas](https://rehansaeed.com/web-component-custom-element-gotchas/)

Posted 30 April 2021 - **4 min read**

![Web Components](https://rehansaeed.com/_ipx/f_png&s_2560x1440/images/hero/Web-Components-1600x900.png)

Update (04 May 2021)

[Chris Holt](https://twitter.com/chrisdholt) from the [FAST UI](https://www.fast.design/docs/fast-element/getting-started) team at Microsoft got in touch with me with an alternative workaround to using a wrapper element when required to use a semantic HTML element like a `section`, so I've updated that section below.

Recently I've been writing web components and found several gotchas that make working with them, that much more difficult. In this post, I'll describe some gotchas you can experience when using web components.

This post is framework agnostic but I've been using a lightweight library called [FAST Element](https://www.fast.design/docs/fast-element/getting-started) built by Microsoft. It is similar to Google's [LitElement](https://lit-element.polymer-project.org/guide) in that it provides a very lightweight wrapper around native web component API's. Overall the experience has been interesting but I'm not sure I'm willing to give up on Vue just yet. This post was written based on my experiences with it.

# Link to this sectionNon-Web Components

When writing a non-web component called `custom-component` using a framework like Vue, React or Angular, you quite often end up with HTML and CSS that looks like this:

```html
<div class="custom-component">
    <h1>Hello</h1>
    <p>World</p>
</div>
```

```css
.custom-component {
    // ...
}
```

The rendered HTML from these frameworks looks exactly the same as above. However, when writing web components, you have an addition custom HTML element rendered into the DOM with the contents of the element being rendered into the shadow DOM which can introduce bugs for the unwary developer.

```html
<custom-component>
    <!-- Shadow DOM -->
    <div class="custom-component">
        <h1>Hello</h1>
        <p>World</p>
    </div>
</custom-component>
```

# Link to this sectionThe Wrapper div

The first gotcha we encounter is that we now have an extra HTML element that we don't actually need. Extra DOM elements, mean higher memory usage and slower performance. As I'll discuss in a moment, it can also mean a more complex layout. The fix for this is simple, we can simply remove the wrapper `div` inside our component, so our code now becomes:

```html
<h1>Hello</h1>
<p>World</p>
```

```css
:host {
    // ...
}
```

We can use the `:host` pseudo-selector to style the `custom-component` HTML element and now when our component is rendered we get the following:

```html
<custom-component>
    <!-- Shadow DOM -->
    <h1>Hello</h1>
    <p>World</p>
</custom-component>
```

# Link to this sectionSemantic HTML

Removing the wrapper `div` is all well and good but what if it's not a `div` but a semantic HTML element like `section` or `article`? Both of these tags have a specific meanings for screen readers and search engines and we must use these tags to support them. Well, in this case we have to bring back our wrapper element like so and encounter our second gotcha:

```html
<section class="custom-component">
    <h1>Hello</h1>
    <p>World</p>
</section>
```

Our HTML will now be rendered as:

```html
<custom-component>
    <section class="custom-component">
        <h1>Hello</h1>
        <p>World</p>
    </section>
</custom-component>
```

Now if we want to style the component we have to target the `.custom-element` class where we can place most of our styles but we also need to target the `:host` to change some defaults.

The default value for `display` in a custom HTML element like `<custom-element>` is actually `inline` which is usually not what you will want (`margin`, `padding`, `border` will not work as you expect), so you'll need to explicitly set your own default. This is our third gotcha! I think it makes sense to be explicit and do this for every web component.

In addition, if the content of your web component does not extend beyond the boundary of the component itself, it's a good idea to add `contain: paint` for a small performance boost (The Mozilla Docs have more on [contain](https://developer.mozilla.org/en-US/docs/Web/CSS/contain)).

```css
:host {
    display: block;

    contain: paint;
}

.custom-component {
    // ...
}
```

# Link to this sectionTemplate Element

One alternative to using a `section` above pointed out by [Chris Holt](https://twitter.com/chrisdholt) is to use a `template` HTML element which gives you the ability to add custom HTML attributes to the `custom-component` element itself.

```html
<template role="section">
    <h1>Hello</h1>
    <p>World</p>
</template>
```

Our HTML will now be rendered as:

```html
<custom-component role="section">
    <h1>Hello</h1>
    <p>World</p>
</custom-component>
```

In the example above, I've added `role="section"` to tell search engines and screen readers to treat the `custom-component` HTML element like a `section` element. With this approach, we no longer have a wrapper HTML element which should help improve performance and lower memory usage (particularly on low powered phones). We also get the advantage of not having to add extra styles for the wrapper element. One downside is that we have to use the `role` attribute.

```css
:host {
    display: block;

    contain: paint;
    // ...
}
```

Another downside of this approach I discovered when trying to write a custom button component is that some CSS pseudo-selectors like `:disabled` will not work:

```html
<custom-button role="button">
    <slot></slot>
</custom-button>
```

```css
:host(:disabled) {
    // This will not work.
    color: red;
}
```

You have to fallback to using a HTML `button` element instead which lights up the `:disabled` pseudo-selector:

```html
<custom-button>
    <button class="custom-button">
        <slot></slot>
    </button>
</custom-button>
```

```css
.custom-button:disabled {
    // This will work.
    color: red;
}
```

# Link to this sectionFinal Thoughts

The promise of web components are that they are lightweight and fast to run. The downside seems to be that there is more to think about when building a web component, as opposed to a standard framework based component using Vue, React or Angular.

## Web Mentions

[What's this?](https://en.wikipedia.org/wiki/Webmention)

### 0 Linked

### 0 Replies

## GitHub Comments

### 0 Comments

[Comment on GitHub](https://github.com/RehanSaeed/rehansaeed.github.io/issues/316)

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://rehansaeed.com/#website","@type":"WebSite","description":"Software Developer at Microsoft, YouTuber, Open Source Contributor and Blogger","inLanguage":"en-GB","name":"Muhammad Rehan Saeed","url":"https://rehansaeed.com/","author":{"@id":"https://rehansaeed.com/#identity"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://rehansaeed.com/?search={search_term_string}"},"query-input":"required name=search_term_string"}],"publisher":{"@id":"https://rehansaeed.com/#publisher"}},{"@id":"https://rehansaeed.com/web-component-custom-element-gotchas/#webpage","@type":"WebPage","dateModified":"2021-04-30T12:12:00+00:00","datePublished":"2021-04-30T12:12:00+00:00","description":"Web components have certain gotchas with relation to custom elements and CSS. This post goes through them all and shows how you can avoid them.","name":"Web Component Custom Element Gotchas","url":"https://rehansaeed.com/web-component-custom-element-gotchas/","breadcrumb":{"@id":"https://rehansaeed.com/web-component-custom-element-gotchas/#breadcrumb"},"isPartOf":{"@id":"https://rehansaeed.com/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://rehansaeed.com/web-component-custom-element-gotchas/"]}]},{"@id":"https://rehansaeed.com/#identity","@type":"Person","name":"Muhammad Rehan Saeed","url":"https://rehansaeed.com/about/","image":{"@id":"https://rehansaeed.com/#/schema/image/1"},"sameAs":["https://github.com/RehanSaeed","https://www.linkedin.com/in/muhammad-rehan-saeed","https://stackoverflow.com/users/1212017/muhammad-rehan-saeed","https://www.youtube.com/c/MuhammadRehanSaeed","https://www.twitch.tv/rehansaeeduk"]},{"@id":"https://rehansaeed.com/#publisher","@type":"Organization","logo":"https://rehansaeed.com/images/schema/Publisher-600x60.png","name":"Muhammad Rehan Saeed","url":"https://rehansaeed.com"},{"@id":"https://rehansaeed.com/web-component-custom-element-gotchas/#article","@type":"Article","dateModified":"2021-04-30T12:12:00+00:00","datePublished":"2021-04-30T12:12:00+00:00","description":"Web components have certain gotchas with relation to custom elements and CSS. This post goes through them all and shows how you can avoid them.","headline":"Web Component Custom Element Gotchas","inLanguage":"en-GB","url":"https://rehansaeed.com/web-component-custom-element-gotchas/","author":{"@id":"https://rehansaeed.com/#identity"},"image":[{"@type":"ImageObject","url":"https://rehansaeed.com/images/hero/Web-Components-1600x900.png","caption":"Web Components","width":"1600","height":"900"}],"isPartOf":{"@id":"https://rehansaeed.com/web-component-custom-element-gotchas/#webpage"},"keywords":["Web Components","HTML","CSS"],"mainEntityOfPage":{"@id":"https://rehansaeed.com/web-component-custom-element-gotchas/#webpage"},"publisher":{"@id":"https://rehansaeed.com/#publisher"}},{"@id":"https://rehansaeed.com/web-component-custom-element-gotchas/#breadcrumb","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","name":"Blog","item":"https://rehansaeed.com/","position":1},{"@type":"ListItem","name":"Web Component Custom Element Gotchas","item":"https://rehansaeed.com/web-component-custom-element-gotchas/","position":2}]},{"@id":"https://rehansaeed.com/#/schema/image/1","@type":"ImageObject","contentUrl":"https://rehansaeed.com/images/author/Muhammad-Rehan-Saeed/Logo-512x512.png","inLanguage":"en-GB","url":"https://rehansaeed.com/images/author/Muhammad-Rehan-Saeed/Logo-512x512.png"}]}
```

## Sitemap

See the full [sitemap](https://rehansaeed.com/sitemap.md) for all pages.
