When a browser loads a server-rendered page, HTML appears on screen before any JavaScript runs. If your models update visible text, swap classes, or fetch data on startup, users may briefly see placeholder content or the wrong state — a flash of uninitialized UI. Sprincul’s cloaking feature lets you hide that content until initialization is complete.
The problem
Consider a model that fetches a user’s name from an API in afterInit(). Before the fetch resolves, your HTML shows the default value you set in beforeInit() — something like --. That blank placeholder flickers into view and then snaps to the real value. Cloaking hides the element entirely until Sprincul is ready to show the correct state.
The solution: data-cloaked
Add the data-cloaked attribute to any element you want hidden during initialization. Sprincul removes the attribute when the appropriate initialization phase completes. You supply the CSS rule that does the hiding:
<style>
[data-cloaked] { display: none; }
</style>
You control the CSS, so you can use visibility: hidden, opacity: 0, or any other approach that suits your design.
Two scopes
Cloaking works at two levels, and they behave differently.
Model-level cloaking
Add data-cloaked to a data-model container. Sprincul removes the attribute after that model’s afterInit hook completes, including any async work inside it.
<style>
[data-cloaked] { display: none; }
</style>
<div data-model="Profile" data-cloaked>
<p data-bind-name="showName"></p>
<p data-bind-email="showEmail"></p>
</div>
If afterInit() is async and awaits a network request, the element stays hidden until the await resolves. This prevents users from seeing incomplete data during hydration.
Page-level cloaking
Add data-cloaked to the <body> element. Sprincul removes the attribute after all models’ afterInit hooks are called — but does not wait for them to complete.
<body data-cloaked>
<div data-model="Header">…</div>
<div data-model="Sidebar">…</div>
<div data-model="Footer">…</div>
</body>
Page-level cloaking is appropriate when you want to delay the first paint until all models have started initializing, but you do not need to wait for async work to finish.
Page-level cloaking does not wait for async afterInit hooks to complete. If your models fetch data before rendering, use model-level cloaking instead to prevent showing partial results.
Choosing the right scope
| Situation | Recommended scope |
|---|
| Model fetches data from an API before rendering | Model-level |
| Model runs synchronous setup only | Either |
| You want to hide the whole page until all models start | Page-level |
| Different models have different async timings | Model-level per model |
You can mix both scopes on the same page. Apply data-cloaked to the <body> for a fast initial hide, and also to specific heavy models so they stay hidden until their async work finishes.
Full HTML + CSS example
<!DOCTYPE html>
<html>
<head>
<style>
[data-cloaked] { display: none; }
</style>
</head>
<body>
<!-- Model-level: stays hidden until afterInit() resolves -->
<div data-model="Profile" data-cloaked>
<p data-bind-name="showName">--</p>
<p data-bind-email="showEmail">--</p>
</div>
<!-- No cloaking needed: this model initializes synchronously -->
<div data-model="Counter">
<button onclick="decrement">-</button>
<input type="number" data-bind-count="showCount" readonly />
<button onclick="increment">+</button>
</div>
<script type="module" src="./main.js"></script>
</body>
</html>
// Profile.js
import { SprinculModel } from 'sprincul';
export default class Profile extends SprinculModel {
beforeInit() {
this.state.name = '--';
this.state.email = '--';
}
/** @param {HTMLElement} el */
showName(el) {
el.textContent = this.state.name;
}
/** @param {HTMLElement} el */
showEmail(el) {
el.textContent = this.state.email;
}
async afterInit() {
// The element stays hidden until this resolves
const { name, email } = await fetchUserProfile();
this.state.name = name;
this.state.email = email;
}
}
Cloaking in subsequent init() calls
If you call Sprincul.init() again to hydrate dynamically added model markup, cloaking works exactly the same way for newly added elements. Sprincul treats freshly added data-cloaked attributes on new model roots the same as it does on the first pass.
For more on calling Sprincul.init() multiple times to handle dynamic content, see the Dynamic Content guide.