How to use web components :host selector?
Published: 2023-05-25 04:16:09

If you're new to writing vanilla web components, the `:host` selector in web components can be a bit tricky and confusing. However, fear not! In this post, I will cover all the different scenarios where you might use the `:host` selector, so you can become a pro at using it in your web components.

Let's start by examining a web component like this one shown below.

```html
<fancy-button>
  #shadow-root(open)
    <button><slot></slot></button>
    <just-a-spinner></just-a-spinner>
    <style>/* will add css rules here to style this component */</style>
</fancy-button>
```

The `slot` element in this example allows us to use this custom element in our web page like this.

```html
<fancy-button>Click me</fancy-button>
```

Once the browser has defined this custom element, the text "Click me" will be treated as the `textContent` of our `slot` element. By the way, the `slot` element is `display: contents` by default, so you can treat the `button` as the parent wrapper of the text "Click me". I hope that make sense.

Now, let's move on to the main event: the :host selector.

## The basics

```css
/* valid rule */
:host {
  display: flex;
}

/* invalid rule */
fancy-button {
  display: flex;
}
```

Inside the shadow root of our custom element `fancy-button`, we can't use the `fancy-button` CSS tag selector to target our custom element, as styles inside the shadow root are encapsulated. This is where the `:host` selector comes to the rescue.

One thing to note is that a custom element is an inline element by default. However, I've found that most of the time, the default inline behavior is not really what we want.

## :host with class
If we want to target `fancy-button` that has a `visible` class, we need to add the `visible` class inside parentheses, like this. This will ensure that the `visible` class is properly applied to the `fancy-button` element.

```html
<fancy-button class="visible">Click me</fancy-button>
```

```css
/* hide initially */
:host {
  display: none;
}

/* if it has a 'visible' class, show this button */
:host(.visible) {
  display: block;
}
```

## :host with attributes

Just like with classes, attributes also need to be enclosed in parentheses when using them as selectors. For example, if we want to select all `fancy-button` elements with a `theme="round"` attribute.

```html
<fancy-button theme="round">Click me</fancy-button>
```

```css
:host([theme="round"]) {
  border-radius: 2em;
}
```

In some cases, we may want users to be able to consume our button components with multiple theme attributes.

```html
<fancy-button theme="round large">Click me</fancy-button>
```

As a component author, we can use the handy `~` CSS operator to target specific theme attributes. This operator selects elements that have the specified attribute with a value that contains a given word, delimited by spaces.

```css
:host([theme~="round"]) {
  border-radius: 2em;
}

:host([theme~="large"]) {
  font-size: 1.2rem;
}

/* or want to styles button differently */
:host([theme~="round"]) button {
  /* styles here */
}

:host([theme~="large"]) button {
  /* styles here */
}
```

## How to work with :hover :focus :focus-within state?

For element state like `:hover`, `:focus` and `:focus-within`, they also need to be enclosed in parentheses when used as selectors.

```css
:host(:hover) {
  background: white;
}

:host([theme="purple"]:hover) {
  background: purple;
}

:host(:focus-within) {
  border: 1px solid white;
}

:host([theme="purple"]:focus-within) {
  border-color: purple;
}
```

## 5. What about ::before and ::after suedo-elements?

For `::before` and `::after` suedo-elements, they need to be outside of parentheses when used as selectors.

```css
:host::before {
  content: attr(data-content);
}

:host([theme="purple"])::before {
  border: 1px solid purple;
}
```