- How DevinimJS uses Light DOM (not Shadow DOM) for natural CSS styling
- Use
this.outletto render consumer-provided children at a specific position - Inspect children before render with
prepare(fragment) - Compose PHP-rendered content with reactive component behavior
Light DOM by design
DevinimJS components do not use Shadow DOM. This means:
- Global CSS (CKCSS, site styles) reaches component markup directly
- No CSS encapsulation barriers — components inherit the page's look
- Children placed inside the component tag in HTML are real DOM children
<!-- These children exist in the Light DOM -->
<dv-card>
<h2>Product Name</h2>
<p>Description from the server</p>
</dv-card>
What is this.outlet?
this.outlet is an HtmlString marker. Place ${this.outlet} in your template where you want the consumer-provided Light DOM children to appear:
class DvCard extends BaseComponent {
template() {
return html`
<div class="card-wrapper">
<header>${this.str('title', 'Card')}</header>
<div class="card-content">
${this.outlet} <!-- Children go here -->
</div>
</div>
`;
}
}
define('dv-card', DvCard);
When rendered, ${this.outlet} becomes <dv-outlet> with display: contents. The children are moved inside it, but the <dv-outlet> wrapper is transparent in layout.
Example: Card with server content
<!-- HTML provided by PHP -->
<dv-card data-title="Pricing">
<table>
<tr><td>Basic</td><td>$9/mo</td></tr>
<tr><td>Pro</td><td>$29/mo</td></tr>
</table>
</dv-card>
<!-- The component renders: -->
<!-- <div class="card-wrapper">
<header>Pricing</header>
<div class="card-content">
<dv-outlet style="display:contents">
<table>...</table>
</dv-outlet>
</div>
</div> -->
prepare(fragment) — inspect children before render
Sometimes you need to know what children were provided before rendering. Use prepare():
class DvTabs extends BaseComponent {
prepare(fragment) {
// fragment is a DocumentFragment of Light DOM children
const panels = fragment.querySelectorAll('[data-tab]');
this._tabCount = panels.length;
}
template() {
return html`
<div class="tabs">
<span>${this._tabCount} tabs</span>
${this.outlet}
</div>
`;
}
}
Live demo: Composition in action
- Item 1: Widget — $9.99
- Item 2: Gadget — $24.99
- Item 3: Doodad — $4.99
Total: $39.97
Outlet and morph
The <dv-outlet> element is excluded from the DOM morph engine. When your component re-renders due to a state change, the children inside the outlet are not touched:
template() {
return html`
<div>
<span>Status: ${this.state.loading ? 'Loading...' : 'Ready'}</span>
${this.outlet} <!-- Children never re-render -->
</div>
`;
}
This is critical: server-rendered children (PHP loops, CMS content) survive component re-renders.
PHP integration pattern
The outlet pattern shines when combined with server-rendered content:
<!-- PHP/Laravel Blade -->
<dv-tabs data-label="Product details">
@foreach ($sections as $section)
<section data-tab="{{ $section->title }}">
{!! $section->content !!}
</section>
@endforeach
</dv-tabs>
The PHP template renders the children. DevinimJS adds tab navigation behavior around them. Each owns its domain.
Recap
Light DOM first
No Shadow DOM. CSS reaches component markup naturally.
this.outlet
Place ${this.outlet} where Light DOM children belong.
prepare(fragment)
Inspect children before the first render.
Outlet survives morph
Server content inside the outlet is not touched during re-renders.