Intro to Web Components

Creating Custom Elements

Building Web Components

Build Tools

Stencil.js

LitElement

Installing Stencil

npm install @stencil/core

Stencil Init

npm init stencil

Stencil Decorators

@Prop()

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

@Component({
  tag: 'say-hello'
})
export class SayHello {
  @Prop() name: string;

  render() {
    return <p>Hello, World! I'm {this.name}.</p>;
  }
}
<say-hello name="Carla"></say-hello>
<say-hello name="Carla">
  <p>Hello, World! I'm Carla.</p>
</say-hello>

@State()

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

@Component({
  tag: 'click-counter'
})
export class ClickCounter {
  //...
  //...
  @State() count = 0;

  private incrementCount() {
    this.count++;
  }
  //...
  //...
  render() {
    return (
      <div>
        <p>{this.count}</p>
        <button
          type="button"
          onClick={() => this.incrementCount()}
        >Plus 1</button>
      </div>
    );
  }
}

@Watch()

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

@Component({
  tag: 'say-hello'
})
export class SayHello {
  @Prop() name: string;

  @Watch('name')
  updateName(newValue, oldValue) {
    // Fire name-related event, perhaps...
  }
  //...
  //...
  render() {
    return <p>Hello, World! I'm {this.name}.</p>;
  }
}

@Event()
@Listen()

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

@Component({
  tag: 'modal-trigger'
})
export class ModalTrigger {
  // ..
  //...
  @Event() openModal!: EventEmitter;

  private triggerClick() {
    this.openModal.emit();
  }
  //...
  //...
  render() {
    return (
      <button onClick={() => this.triggerClick()}>
        <slot />
      </button>
    );
  }
}
import { Component, h, Listen, State } from '@stencil/core';

@Component({
  tag: 'modal-window'
})
export class ModalWindow {
  // ..
  //...
  @State() isOpen = false;

  @Listen('openModal')
  openModalWindow() {
    this.isOpen = true;
  }
  //...
  //...
  render() {
    const modalClasses = {
      modal: true,
      'modal--open': this.isOpen
    };

    return (
      <section class={modalClasses}>
        <slot />
      </section>
    );
  }
}
<modal-trigger>
  Log In
</modal-trigger>

<modal-window>
  <form></form>
</modal-window>

Component Lifecycle Hooks

connectedCallback()

Every time it’s con­nected to DOM, be­fore componentWillLoad()

disconnectedCallback()

Every time it’s dis­con­nected from DOM

componentWillLoad()

Runs 1x af­ter com­po­nent 1st con­nects to DOM

componentDidLoad()

Called once af­ter fully loaded & 1st render()

componentWillRender()

Before every render()

componentDidRender()

After every render()

componentWillUpdate()

When @Prop() or @State() change (not 1st render())

componentDidUpdate()

After up­date render() (but not 1st render())

Styling Web Components

Shadow DOM

Completely scoped styles:

Outer styles can’t leak in;
in­ner styles can’t leak out.

Shadow DOM Selectors

  • :host, :host()
  • :host-context()
  • ::slotted()
  • ::part() *
:host {
  display: block;
}

:host(.is-blue) {
  color: blue;
}

:host([aria-hidden="true"]) {
  opacity: 0;
}
:host-context(article) {
  width: 50%;
}

:host-context(aside) {
  width: 100%;
}
::slotted(svg) {
  margin-right: 8px;
  width: 22px;
}

::slotted(svg *) {
  fill: var(--button-fg, #FFF);
}
render() {
  return (<div>
    <button part="trigger">Click Me</button>
  </div>);
}
/* Outer CSS allowed through the Shadow DOM */
custom-element::part(trigger) {
  color: dodgerblue;
}

* Implemented in Firefox/Chrome, not in Spec

Custom Props

CSS Custom Properties are the only things al­lowed to af­fect Shadow DOM elements.

export class CustomButton {
  render() {
    return (
      <button class="button">
        <slot />
      </button>
    );
  }
}
/* Attached to Shadow DOM */
.button {
  color: var(--button-fg, #FFF);
  background: var(--button-bg, #1976D2);
}

.button:hover {
  color: var(--button-fg-active, #FFF);
  background: var(--button-bg-active, #1565C0);
}
/* Outer CSS: inherited by Shadow DOM */
.button-container--red {
  --button-bg: #D32F2F;
  --button-bg-active: #B71C1C;
}

Deploying Web Components

npm run build

Get /dist/* onto your server
or into your site build process.

Framework Integration

Instructions in Stencil Docs:

Angular | React | Vue | Ember

James Steinbach

Senior UX Developer
at DockYard

jdsteinbach

Twitter | Github | Blog

jds.li/components