Menu

Basic Features

You don't need to learn or know Custom Elements beforehand to use Sloth, nor your knowledge of Custom Elements will be useless.

Most concepts introduced in Sloth is a native web platform features. Of course, there will be something specific in sloth to fill missing platform capabilities, but we'll still be using HTML.

Templates

Templates are basic building blocks in Sloth. Sloth leverages native HTML template, so if you're already familiar with it, you'll feel at home. Templates in Sloth must have a uniquely-identified id. This id will be used as Custom Elements name in dev, and as a prefix in prod to simulate scoped styles.

Template can contains any HTML element that you typically find inside body tag. Elements inside a template can reference any global styles defined in root index.html. You shouldn't add style or script tag inside a template as it's not supported.

We'll see later how we can enhance our templates with custom style and script.

<template id="hello-world">
  <h1 class="heading">It works!</h1>
</template>

Tips for naming template id:

  1. Prefix with feature / page name for specific elements, e.g: docs-header.
  2. Use x- (or any other short letter) prefix for shared elements, e.g: x-header.

Using Custom Elements

You use template by referencing its id

<hello-world></hello-world>

In dev, this will be rendered to actual Custom Elements. In build time, this will be compiled statically to native HTML elements so you don't have to worry about browser support or polyfill. All sloth build output behaves like regular HTML elements without Shadow DOM.

<div data-template="hello-world">
  <h1 class="heading">It works!</h1>
</div>

Slot

Slot is a placeholder element that you can replace when using a custom element. It is similar to children props in React.

First, a template must define a slot that user can fill later. Template can have multiple slot, each uniquely identifiable by its name attribute.

<template id="hello-world">
  <h1 class="heading">
    Hello, <slot name="name">{name}</slot>
  </h1>
  <div>Good <slot name="time"></div>
</template>

You don't have to add children (such as {name}) inside slot element, but it can be useful in case you forgot to fill the slot as it will be visible in the screen.

You fill slot by using any HTML element with slot attribute matching the slot name. Usually a span element is used because it's an inline element, but you can use any element.

<hello-world>
  <span slot="name">Sloth</span>
  <div slot="time">night</div>
</hello-world>

This will be compiled in build time into

<div data-template="hello-world">
  <h1 class="heading">Hello, <span>Sloth</span></h1>
  <div>Good <span>night</span></div>
</div>

Variables

There's no concept of variables (or props) in HTML templates and Custom Elements, only attributes.

To help with attributes assignment and forwarding, Sloth defines a convention by using data attributes with a var- prefix.

Template can define variable placeholder by writing data attribute in any element that needs it.

<template id="hello-world">
  <h1>
    <a data-var-href>It works!</a>
  </h1>
</template>

To pass the variable, you use the attribute that's attached to var- prefix

<hello-world href="https://github.com/pveyes/vite-plugin-sloth"></hello-world>

By default Sloth will automatically fill the attribute with the same name with its data-var equivalent. For example, data-var-href will set href attribute. So the above example compiles into:

<div data-template="hello-world">
  <h1>
    <a href="https://github.com/pveyes/vite-plugin-sloth">It works!</a>
  </h1>
</div>

You can also mark data as optional by providing fallback value using the same attribute.

<!-- template -->
<template id="hello-world">
  <h1>
    <a data-var-href href="http://default">It works!</a>
  </h1>
</template>

<!-- usage: no need to pass `href` because it's optional -->
<hello-world></hello-world>

<!-- result -->
<div data-template="hello-world">
  <h1>
    <a href="http://default">It works!</a>
  </h1>
</div>

Named Variables

If you want to use different name for your variables, pass attribute name as the value in the data attribute.

For example, data-var-link="href" means the component read link attribute from parent element, but the value will be passed to href attribute.

<!-- template -->
<template id="hello-world">
  <h1>
    <a data-var-link="href">It works!</a>
  </h1>
</template>

<!-- usage: pass `link` attribute -->
<hello-world link="https://github.com/pveyes"></hello-world>

<!-- result -->
<div data-template="hello-world">
  <h1>
    <a href="http://github.com/pveyes">It works!</a>
  </h1>
</div>

Attribute Forwarding

If you want to forward data attribute to another custom element, use the same data attribute.

<hello-world link="https://github.com/pveyes/vite-plugin-sloth"></hello-world>

<!-- template -->
<template id="hello-world">
  <h1>
    <!-- this will forward `link` from <hello-world /> to <link-component /> -->
    <link-component data-var-link></link-component>
  </h1>
</template>

<template id="link-component">
  <!-- use `link` from <hello-world /> -->
  <a data-var-link="href"> It works! </a>
</template>

External Templates

You can reference external templates using HTML Imports. Yes, this is deprecated, which means we can abuse it.

Quick note: Due to how Vite handles href in link tag, you need to put your template files inside public directory and reference top-level import using absolute path.

<html>
  <head>
    <link rel="import" href="/templates/hello-world.html" />
  </head>

  <body>
    <hello-world></hello-world>
  </body>
</html>

This is the result

<html>
  <head> </head>

  <body>
    <div data-template="hello-world">
      <h1>It works</h1>
    </div>
  </body>
</html>

As you see, the template is rendered inline and HTML imports are removed from the output.

Template Dependencies

You can also import other templates in an external template. Here you can use relative path, but you still need to put them inside public directory so it's still accessible by Vite dev server.

<link rel="import" href="./heading-text.html" />
<link rel="import" href="../another-component.html" />

<template id="hello-world">
  <heading-text>It works!</heading-text>
  <another-component></another-component>
</template>

Scoped Styles

Using external templates allows you to define scoped style that only applies to elements inside the template. To use scoped style, add style tag in your template file.

<style>
  h1 {
    font-size: max(10vw, 10vh);
  }
</style>

<template id="hello-world">
  <h1>It works!</h1>
</template>

In build-time, the style will be hoisted to root, and all of its selectors will be scoped by template id.

Element Extensions

If you want to add a sprinkle of JS to make your components more interactive, you can create script tag, and export a class that extends HTMLElement, same as creating Custom Elements class.

Sloth will guarantee that any Slot, Variables, and Scoped Styles will be initialized before connectedCallback is executed.

<template id="hello-world">
  <h1>It works!</h1>
</template>

<script type="module">
  export default class HelloWorld extends HTMLElement {
    connectedCallback() {
      alert("HA!");
    }
  }
</script>

Query Element

To query element inside Custom Elements, you can use this.root instead of document:

<template id="hello-world">
  <h1>It works!</h1>
</template>

<script type="module">
  export default class HelloWorld extends HTMLElement {
    connectedCallback() {
      this.root.querySelector("h1").classList.add("mounted");
    }
  }
</script>

You can also query element inside another shadow DOM by using this. For example, here we want to query a span inside another-element

<link rel="import" href="../another-component.html" />

<template id="hello-world">
  <h1>It works!</h1>
  <another-element></another-element>
</template>

<script type="module">
  export default class HelloWorld extends HTMLElement {
    connectedCallback() {
      this.querySelector("span").style.display = "none";
    }
  }
</script>

3rd-party dependencies

You can also import external dependencies using Skypack.

<template id="hello-world">
  <h1>It works!</h1>
</template>

<script type="module">
  import confetti from "https://cdn.skypack.dev/canvas-confetti";

  export default class HelloWorld extends HTMLElement {
    connectedCallback() {
      confetti();
    }
  }
</script>

Lifecycle Cleanup

If you have event listeners or other cancellable operations, put the cleanup logic in disconnectedCallback method.

<script type="module">
  export default class HelloWorld extends HTMLElement {
    connectedCallback() {
      window.addEventListener("scroll", this.handleScroll);
    }

    disconnectedCallback() {
      window.removeEventListener("scroll", this.handleScroll);
    }

    handleScroll = () => {};
  }
</script>

This will only be used in HMR cleanup phase, as there's no concept of disconnect in static build.

Previous Page

Next Page