> ## Documentation Index
> Fetch the complete documentation index at: https://sprincul.sinfullycoded.com/llms.txt
> Use this file to discover all available pages before exploring further.

# HTML Attribute Reference for Sprincul: data-model and More

> Reference for every HTML attribute Sprincul recognizes: data-model, data-bind-*, data-cloaked, and native onclick and oninput event wiring attributes.

Sprincul's entire HTML surface is four kinds of attribute. You mark container elements with `data-model`, wire state updates to elements with `data-bind-*`, hide content until initialization with `data-cloaked`, and attach event handlers with native `on<event>` attributes. Sprincul reads these at `init()` time and does not require any special template syntax or build step.

## `data-model="<Name>"`

Marks an element as the root of a model instance. Sprincul looks for all `[data-model]` elements in the document when `Sprincul.init()` runs and creates one model instance per element.

The value must exactly match a name you passed to `Sprincul.register()` or `Sprincul.registerAll()` — casing is significant. Every `data-bind-*` and `on<event>` attribute inside this container is scoped to this model instance.

```html theme={null}
<div data-model="Counter">
  <button onclick="decrement">-</button>
  <input type="number" data-bind-count="showCount" readonly />
  <button onclick="increment">+</button>
</div>
```

You can use any block-level or sectioning element as the root:

```html theme={null}
<section data-model="Profile">
  <p data-bind-name="showName"></p>
  <p data-bind-email="showEmail"></p>
</section>
```

<Note>
  Nesting one `data-model` container inside another is allowed — each creates its own independent model instance. Bindings and events on an inner container are scoped to the inner model only.
</Note>

***

## `data-bind-<prop>="<callback>"`

Registers a reactive binding between a state property and a DOM element. When `this.state.<prop>` changes on the model, Sprincul calls `<callback>(element)` so your method can update that element directly. You decide exactly what "update" means — set `textContent`, change a class, update a `value`, anything.

`<prop>` is the lowercase state property name. `<callback>` is the name of a method on your model class that accepts an `HTMLElement` as its first argument.

```html theme={null}
<div data-model="Counter">
  <input type="number" data-bind-count="showCount" readonly />
</div>
```

```js theme={null}
import { SprinculModel } from 'sprincul';

export default class Counter extends SprinculModel {
  beforeInit() {
    this.state.count = 0;
  }

  /** @param {HTMLInputElement} el */
  showCount(el) {
    el.value = this.state.count;
  }
}
```

You can bind multiple elements to the same state property, and you can bind multiple different properties within the same model:

```html theme={null}
<section data-model="Profile">
  <h2 data-bind-name="renderName"></h2>
  <p data-bind-email="renderEmail"></p>
  <span data-bind-name="renderNameBadge"></span>
</section>
```

<Warning>
  State property names used in `data-bind-*` attributes must be **lowercase**. Browsers lower-case `data-*` attribute names when parsing HTML, so `data-bind-myProp` becomes `data-bind-myprop` in the DOM. If your state key is `myProp` and your attribute is `data-bind-myProp`, the binding will never fire. Use `this.state.myprop` and `data-bind-myprop` consistently.
</Warning>

***

## `data-cloaked`

Hides an element until Sprincul removes the attribute at the appropriate point in the initialization cycle. You provide the CSS rule that makes the hiding work — Sprincul only removes the attribute.

```html theme={null}
<style>
  [data-cloaked] { display: none; }
</style>
```

The behavior differs depending on where you place the attribute:

**On a `data-model` element** — Sprincul removes `data-cloaked` after that model's `afterInit` hook **completes**, including any async work. Use this for models that fetch data before displaying anything.

```html theme={null}
<div data-model="Profile" data-cloaked>
  <p data-bind-name="showName"></p>
  <p data-bind-email="showEmail"></p>
</div>
```

**On the `<body>` element (or any element without `data-model`)** — Sprincul removes `data-cloaked` immediately after all models' `afterInit` hooks are **called**, without waiting for them to complete. Use this for a simple page-level fade-in when you don't need to wait for async hooks.

```html theme={null}
<body data-cloaked>
  <div data-model="Counter">...</div>
  <div data-model="Profile">...</div>
</body>
```

You can combine both strategies. Model-level cloaking protects individual components that do heavy async work, while page-level cloaking prevents unstyled flashes across the rest of the page:

```html theme={null}
<style>[data-cloaked] { display: none; }</style>

<body data-cloaked>
  <!-- Uncloaked when afterInit resolves (waits for async) -->
  <div data-model="Profile" data-cloaked>
    <p data-bind-name="showName"></p>
  </div>

  <!-- Uncloaked together with the body, no extra waiting -->
  <div data-model="Counter">
    <input data-bind-count="showCount" readonly />
  </div>
</body>
```

<Tip>
  For models that make API calls in `afterInit`, prefer model-level cloaking. It prevents users from briefly seeing placeholder or default state values before real data arrives.
</Tip>

***

## `on<event>="<methodName>"`

Attaches a native DOM event listener to an element. The value is the name of a method on your model class — not inline JavaScript. Sprincul replaces the attribute with a proper `addEventListener` call and passes the native `Event` object to your handler.

Common examples: `onclick`, `oninput`, `onchange`, `onsubmit`, `onkeydown`. Any valid DOM event name works as long as you prefix it with `on`.

```html theme={null}
<div data-model="Counter">
  <button onclick="decrement">-</button>
  <button onclick="increment">+</button>
  <button onclick="resetCounter">Reset</button>
</div>
```

```js theme={null}
import { SprinculModel } from 'sprincul';

export default class Counter extends SprinculModel {
  beforeInit() {
    this.state.count = 0;
  }

  increment() {
    this.state.count++;
  }

  decrement() {
    this.state.count--;
  }

  resetCounter() {
    this.state.count = 0;
  }
}
```

For input and form events, your handler receives the native event object:

```html theme={null}
<div data-model="SearchBox">
  <input type="text" oninput="handleInput" />
  <button onclick="handleSubmit">Search</button>
</div>
```

```js theme={null}
import { SprinculModel } from 'sprincul';

export default class SearchBox extends SprinculModel {
  beforeInit() {
    this.state.query = '';
  }

  /** @param {InputEvent} e */
  handleInput(e) {
    this.state.query = e.target.value;
  }

  /** @param {MouseEvent} e */
  handleSubmit(e) {
    console.log('Searching for:', this.state.query);
  }
}
```

<Note>
  Sprincul removes the `on<event>` attribute from the DOM after wiring the listener, so the raw attribute value is never exposed as executable HTML. Your handler name is private to the model instance.
</Note>
