Custom elements
Since 2.10, Micra.define("dropdown", { ... }) also registers a
<micra-dropdown> tag. The element mounts itself when it enters the document
and tears itself down when it leaves, so you don’t call Micra.start() or
Micra.autoCleanup() for it.
<micra-counter>
<span data-text="count"></span>
<button @click="increment">+</button>
</micra-counter>
<script src="https://cdn.jsdelivr.net/npm/micra.js@2/dist/micra.min.js"></script>
<script>
Micra.define("counter", {
state: { count: 0 },
increment() {
this.state.count++;
},
});
</script>
Directives, props, this.$el, this.refs and lifecycle hooks all work the same
as with data-component. The element sets data-component on itself, so
Micra.start(), Micra.destroy(), Micra.instances() and selectors like
[data-component="counter"] see it as a regular component once it has connected.
Markup that arrives later
The tag doesn’t care how it got into the page. htmx, Turbo Drive, Turbo Frames, a
raw innerHTML assignment, or a CMS block all work without a bridge:
<button hx-get="/cart" hx-target="#slot">Show cart</button>
<div id="slot"></div>
<!-- /cart responds with <micra-cart>…</micra-cart>: it mounts on arrival,
and the previous content is destroyed when it is swapped out -->
History snapshots are the one exception. Turbo’s page cache and htmx’s history hold
rendered rows, and mounting on a restored snapshot duplicates them. For Turbo, add
document.addEventListener("turbo:before-cache", () => Micra.destroy()). For htmx
with hx-push-url, put hx-history="false" on the page.
What counts as leaving the document:
- Removed: the component is destroyed and
onDestroyruns. - Moved in one go, like a keyed
data-eachreorder: nothing happens, state stays. - Hidden by a
data-ifon an ancestor: the instance and its state stay until the element comes back.
Which form to use
Use the tag by default. Both forms are supported, and they can sit on the same page.
| Use | When |
|---|---|
<micra-name> |
The root is a plain container, and the markup may arrive at any time |
<div data-component="name"> |
You mount explicitly and want to control when |
<form data-component="name">, <nav …>, <section …>, <ul …> |
The root’s own element matters: a custom element can’t be a form or a landmark |
For the last case you can also wrap the semantic element in the tag and keep the
<form> inside it.
Things to know
The tag is inline by default. Browsers give unknown elements display: inline.
A root holding block content needs one CSS rule, and a class with its own display
overrides it:
micra-counter {
display: block;
}
Timing while the page loads. While the HTML parser is still running, the element
waits for DOMContentLoaded before mounting, so its children are complete. Code
that reads an instance immediately after the script needs to wait as well, or call
Micra.start(), which mounts right away and is safe alongside the tags:
Micra.define("dropdown", { /* … */ });
document.addEventListener("DOMContentLoaded", () => {
const dropdown = Micra.instances().get(document.querySelector("micra-dropdown"));
dropdown.state.items = items;
});
Markup inserted after the page has loaded mounts in a microtask.
Names. A tag is registered only for names of lowercase letters, digits and
hyphens, starting with a letter. Micra.define("userCard", …) still works with
data-component, but gets no tag; use "user-card". A micra-* element is always
treated as a separate component by its parent, so don’t use that prefix for your
own elements.
Fill it right away. The element mounts on the first connection. Inserting an
empty tag and filling it in the same task works; filling it later, after a fetch,
is too late for directives in that content.
Data from a parent. As with data-component, a nested component has its own
scope: it doesn’t see the item of a surrounding data-each, and the parent
doesn’t apply directives to it. Pass data through data-* attributes rendered by
the server, or over the event bus.
WordPress. Post content is filtered for users without the unfiltered_html
capability, and unknown tags are stripped there. Blocks and shortcodes rendered by
PHP aren’t affected.