Slots

Slots as defined in the Web Component standard enable flexible templates that allow substitution of internals while maintaining core functionality.

Server render

Enhance renders <slot> elements server side, replacing their contents in a way that aligns with the standard so that they work both with Custom Elements and Shadow DOM if needed. The slot standard defines two use cases: named slots and unnamed slots. Enhance supports both as the spec outlines.

Named slots

Named slots are slot elements with a name attribute — for example:

export default function MyLayout({ html }) {
  return html`
    <main>
      <slot name="header"></slot>
      <!-- More content… -->
    </main>
  `
}

To place content into a named slot, declare a custom element with a matching slot attribute as a direct descendant of that element, like so:

<my-layout>
  <header slot="header">
    <h1>I'm in the header slot</h1>
  </header>
</my-layout>

Unnamed slots

Unnamed slots are a catchall container that collects all child nodes without a slot attribute. These allow your custom elements to accept child content just like native browser elements. For example, for a button:

export default function MyButton({ html }) {
  return html`
    <button class="my-fancy-button">
      <slot></slot>
    </button>
  `
}

Place your child content directly within the opening and closing tags of your custom element:

<my-button>
  Click Me!
</my-button>

Layouts

Slots are extremely useful when authoring layout elements. You can set up layout constraints, then slot in content to make the layout reusable across pages. For example, here’s a layout that uses both named and unnamed slots:

export default function MyLayout({ html }) {
  return html`
    <style>
      :host {
        min-height: 100vh;
        display: block;
      }

      ::slotted([slot="header"]) {
        position: sticky;
        top: 0;
      }
    </style>

    <slot name="header"></slot>

    <main>
      <slot></slot>
    </main>

    <slot name="footer"></slot>
  `
}

You can then use this layout in each of your app’s pages:

<my-layout>
  <my-header slot="header">
    <h1 class="font-medium">
      Enhanced!
    </h1>
  </my-header>

  <!-- This content is not targeting a named slot. -->
  <!-- Therefore, it will be collected and placed into the layout’s unnamed slot. -->
  <section class="p1">
    Main content
  </section>

  <my-footer slot="footer">
    <p>
      Copyright &copy; ${new Date().getFullYear()}
    </p>
  </my-footer>
</my-layout>

Special handling

As they’re defined in the web components spec, slots are only available when using the shadow DOM. However, Enhance enables the same behavior without the shadow DOM by expanding your elements on the server and emitting the resultant markup. That means your pages will deliver fully compliant HTML, allowing you to decide how you want to progressively enhance that HTML in the browser.

In order to not litter your HTML with <slot> tags, Enhance will convert any unused slots into <span> tags which have the same characteristics as a <slot> tag.

as attribute

One feature Enhance adds to slots is you can use an as attribute to specify what type of element you would like Enhance to replace the unused slot with.

To use the as attribute, supply the attribute with a valid element name:

export default function MyUnusedSlot({ html }) {
  return html`<slot name="lonely" as="div"></slot>`
}

In the case that the slot is not used, the output will reflect your as attribute so you can progressively enhance it how you like.

<div slot="lonely"></div>

That’s it

Go forth and make some reusable elements with slots!


Community Resources

GitHub
Visit Enhance on GitHub.
Discord
Join our Discord server to chat about development and get help from the community.
Mastodon
Follow Enhance in the Fediverse. We're huge fans of the IndieWeb!