← All articles

Summarizing the Role of Stencil's Host Element After Reading Ionic Code

What Stencil's virtual Host API does—host element manipulation, top-level DOM wrapper, @Element(), and Shadow DOM :host styling.

Published
Summarizing the Role of Stencil's Host Element After Reading Ionic Code cover image

When I read Ionic code (Stencil), elements rendered as JSX are always wrapped in a Host element. For IonItem, for example, it looks like this:

https://github.com/ionic-team/ionic-framework/blob/master/core/src/components/item/item.tsx#L277-L317

So I assumed Host indicated the Web Components host and was required—but that does not seem to be the case, so I will summarize its role.

The Role of Host

You can read how the host element works in the documentation.

https://stenciljs.com/docs/host-element

Host is a Virtual API and is not rendered as DOM. It is used to handle things at render time and to anchor internal APIs.

Web Components Host Manipulation

Suppose you defined a Web Component like this. Let the tag name be todo-list.

@Prop() open = false;
...
<Host
  aria-hidden={this.open ? 'false' : 'true'}
  class={{
  'todo-list': true,
  'is-open': this.open
  }}
/>

When you use this Web Component, it has an open attribute, so you can use it as <todo-list open="true">. However, the open attribute changes the Host's own area-hidden and class, so it actually renders like this:

<todo-list open="true">

<todo-list class="todo-list is-open" aria-hidden="false"></todo-list>

<todo-list open="false">

<todo-list class="todo-list" aria-hidden="true"></todo-list>

Very useful when you want to change how the Web Component itself renders.

As Top-Level DOM

Web Components are components, so they cannot have multiple top-level DOM nodes. In other words, you cannot do this:

return (
  <h1>Title</h1>
  <p>Message</p>
);

You do not need an extra div that deepens nesting. Use Host.

return (
  <Host>
    <h1>Title</h1>
    <p>Message</p>
  </Host>
);

As In-Component API

When you use Host, you can use the @Element() decorator with the Web Component as the root.

import { Element } from '@stencil/core';

...
export class TodoList {

  @Element() el: HTMLElement;

  getListHeight(): number {
    return this.el.getBoundingClientRect().height;
  }
}

You could argue @Element() works even without Host, but without it you lose stable access to dynamic state updates (prop, and so on), so use Host.

As Shadow DOM Selector

Suppose you applied styling like this:

my-element {
  color: black;
}
my-element div {
  background: blue;
}

With Shadow DOM enabled, the selector root changes, so styling the element itself requires using Host. When you use Host with Shadow DOM enabled, you can rewrite it like this:

:host {
  color: black;
}
div {
  background: blue;
}

Without Host, you cannot style the element itself.

Summary

With a div, the power of Host is hard to see, but when extending existing HTMLElements and the like, implementation can be impossible without Host, so I recommend defaulting to Host when you implement.

See you again.