sercrod

*host

Summary

*host connects an element to a named *shadow template.

The host element keeps its Light DOM children. Sercrod attaches a Shadow DOM to the host when possible, clones the matching shadow template into it, and lets the browser handle standard <slot> assignment.

Sercrod does not reimplement slot behavior. It also does not move Light DOM children into the Shadow DOM.

Key points:

Basic example

A host element connected to a shadow template:

<template *shadow="'messagebox'">
  <section class="message-box">
    <div class="message-box-title">
      <slot name="title"></slot>
    </div>

    <div class="message-box-message">
      <slot name="message"></slot>
    </div>
  </section>
</template>

<message-box *host="'messagebox'">
  <p slot="message">Maintenance will be performed on May 20.</p>
  <h2 slot="title">Notice</h2>
</message-box>

Behavior:

The recommended form is to use an explicit connection name.

The recommended form is to write the connection name as a string literal inside the directive value.

<template *shadow="'messagebox'">
  ...
</template>

<message-box *host="'messagebox'">
  ...
</message-box>

The outer double quotes are the HTML attribute quotes. The inner single quotes are part of the Sercrod expression.

This is important because the directive value is evaluated as an expression. A fixed connection name should therefore be written as a string literal such as 'messagebox'.

Do not use *host="messagebox" as the recommended form in documentation examples. Use *host="'messagebox'" instead.

*shadow="'messagebox'" defines the named shadow template by evaluating the string literal 'messagebox'.

*host="'messagebox'" connects the host element to that named template by using the same string literal.

Explicit names make the relationship between the template and the host easier to read, easier to document, and easier for AI tools to understand correctly.

Aliases

The following host-side directives are equivalent:

*host
*shadow-host
n-host
n-shadow-host

The recommended form is *host.

Most examples should use *host unless there is a specific reason to show an alias.

Where to write *host

*host is written on the host element, not on the template.

Recommended:

<template *shadow="'messagebox'">
  ...
</template>

<message-box *host="'messagebox'">
  ...
</message-box>

Do not use *host on the shadow template itself.

<template *host="'messagebox'">
  ...
</template>

The template side defines the shadow structure with *shadow. The host side connects to that structure with *host.

Connection name

The value of *host is an expression whose result is used as the name of the shadow template to connect.

For a fixed name, write a string literal inside the attribute value.

<template *shadow="'profilecard'">
  ...
</template>

<profile-card *host="'profilecard'">
  ...
</profile-card>

In this example, the expression 'profilecard' evaluates to the connection name profilecard.

Sercrod looks for a matching <template *shadow="'profilecard'">.

Value-less *host shorthand

A value-less *host is allowed as a shorthand, but it is not recommended.

<template *shadow="'MessageBox'">
  ...
</template>

<message-box *host>
  ...
</message-box>

When *host has no value, Sercrod derives a constructor-style connection name from the host element name.

message-box
  ->
MessageBox

Then Sercrod looks for <template *shadow="'MessageBox'">.

This shorthand is allowed for convenience, but explicit names are preferred.

Recommended:

<template *shadow="'messagebox'">
  ...
</template>

<message-box *host="'messagebox'">
  ...
</message-box>

The explicit form avoids hidden name conversion and makes the connection easier to follow.

Use with standard slots

*host works with the browser's standard slot behavior.

<template *shadow="'fieldbox'">
  <div class="field-box">
    <label class="field-box-label">
      <slot name="label"></slot>
    </label>

    <div class="field-box-control">
      <slot name="control"></slot>
    </div>

    <div class="field-box-help">
      <slot name="help"></slot>
    </div>
  </div>
</template>

<field-box *host="'fieldbox'">
  <span slot="label">Email</span>
  <input slot="control" type="email" name="email">
  <small slot="help">Enter a valid email address.</small>
</field-box>

slot="label", slot="control", and slot="help" are assigned to the matching named slots by the browser.

Sercrod only connects the host element to the shadow template.

Default slot

An element without a slot attribute is displayed in the default <slot></slot>.

<template *shadow="'messagebox'">
  <section class="message-box">
    <header>
      <slot name="title"></slot>
    </header>

    <main>
      <slot></slot>
    </main>
  </section>
</template>

<message-box *host="'messagebox'">
  <h2 slot="title">Notice</h2>
  <p>Maintenance will be performed on May 20.</p>
</message-box>

The <h2> is displayed in the named slot. The paragraph has no slot attribute, so it is displayed in the default slot.

Light DOM directives

The host side can still be used as a normal Sercrod scope.

You can write data, *let, *print, *input, and other Light DOM directives on the host side.

<template *shadow="'profilecard'">
  <section class="profile-card">
    <header>
      <slot name="name"></slot>
    </header>

    <main>
      <slot name="body"></slot>
    </main>
  </section>
</template>

<profile-card *host="'profilecard'" data="{ first_name: `Taro`, last_name: `Yamada`, message: `Hello.` }">
  <h2 slot="name" *let="full_name = `${last_name} ${first_name}`" *print="full_name"></h2>
  <p slot="body" *print="message"></p>
</profile-card>

Sercrod processes the Light DOM directives as usual. The processed Light DOM content is then displayed at the slot positions by the browser's standard slot behavior.

CSS scope

When a host is connected to a shadow template, CSS written inside the shadow template is scoped by the browser's standard Shadow DOM behavior.

Outer page CSS does not normally select elements inside the Shadow DOM with ordinary selectors. CSS written inside the shadow template does not leak out to the outer page.

<preview-box *host="'previewbox'">
  <div class="same-box" slot="content">
    <h2 class="same-title">Light DOM article title</h2>
    <p class="same-body">Light DOM article body.</p>
  </div>
</preview-box>

<template *shadow="'previewbox'">
  <style>
    .same-box {
      border: 1px solid #ccc;
      padding: 1rem;
    }

    .same-title {
      font-size: 1.25rem;
    }

    .same-body {
      line-height: 1.7;
    }
  </style>

  <article class="same-box">
    <header class="same-title">Shadow-side title frame</header>

    <main class="same-body">
      <slot name="content"></slot>
    </main>
  </article>
</template>

In this example, the same class names are used on both sides. Ordinary CSS selectors do not directly cross the Shadow DOM boundary.

Sercrod does not implement CSS isolation by itself. Sercrod connects the host element to the shadow template. CSS scoping is handled by the browser as part of standard Shadow DOM behavior.

Important note about slotted content

Slotted elements are still Light DOM nodes.

Even when they appear at a slot position inside the shadow layout, their actual nodes remain on the Light DOM side. Because of that, slotted elements may still be affected by outer page CSS.

If the preview content itself must be separated from the outer page CSS, render that content inside the Shadow DOM instead of passing it as slotted Light DOM content.

Behavior

Conceptually, Sercrod does the following for *host:

  1. Find an element with *host, *shadow-host, n-host, or n-shadow-host.
  2. Resolve the shadow template name from the directive value.
  3. If the directive has no value, derive a constructor-style name from the host element name.
  4. Look for a matching <template *shadow="'name'">.
  5. If a matching template exists and the host has no existing shadowRoot, attach an open Shadow DOM.
  6. Clone the template content into the host's shadow root.
  7. Let the browser handle standard slot assignment.
  8. Process Light DOM Sercrod directives as usual.

Missing template

If the host refers to a shadow template that does not exist, Sercrod should warn when warnings are enabled.

<message-box *host="'messagebox'">
  ...
</message-box>

If there is no matching <template *shadow="'messagebox'">, the intended warning is:

[Sercrod warn] *host requires a matching *shadow template: messagebox

For a value-less *host:

<message-box *host>
  ...
</message-box>

Sercrod derives MessageBox. If there is no matching <template *shadow="'MessageBox'">, the intended warning is:

[Sercrod warn] *host requires a matching *shadow template: MessageBox

Existing shadowRoot

If the host already has a shadowRoot, Sercrod should not overwrite it.

The intended warning is:

[Sercrod warn] shadowRoot already exists: message-box

This avoids breaking external Web Components or user scripts that already created Shadow DOM on the same element.

Shadow mode

The initial specification supports open Shadow DOM only.

attachShadow({ mode: "open" })

Closed Shadow DOM is not part of the initial *host behavior.

The reason is that closed Shadow DOM is harder to inspect, harder to debug, and harder for Sercrod or AI tools to verify. The initial design keeps the bridge visible and inspectable.

HTML order

The host element may appear before or after the matching shadow template in the HTML.

<message-box *host="'messagebox'">
  ...
</message-box>

<template *shadow="'messagebox'">
  ...
</template>

The intended implementation should not depend on declaration order.

A safe implementation can first collect <template *shadow="'...'"> declarations, and then connect *host elements in a second pass.

Relation to customElements.define()

*host is not a replacement for customElements.define().

The *shadow / *host bridge connects a visible shadow template to a host element. It does not need to turn Sercrod into a custom element framework.

Sercrod should be careful not to redefine custom elements that user scripts or external libraries may define. If a custom element name is already registered, Sercrod should not redefine it.

The minimum bridge behavior is to connect the host to the shadow template by using attachShadow({ mode: "open" }) and cloning the template content into the shadow root.

What *host does not do

Best practices

Notes