Directives
Create custom attributes for <uizy-box> elements with reusable logic.
Overview
Directives are custom attributes that extend <uizy-box> functionality. They receive the element and a context object with utilities for reactive effects and cleanup.
// Register a directive
uizy.directive("highlight", (el, { value }) => {
el.style.backgroundColor = value || "yellow";
});
<!-- Use it -->
<uizy-box u-highlight="red">Highlighted text</uizy-box>
Syntax
u- prefixAlways use the u- prefix for better compatibility across build tools and frameworks. Some TSX/JSX transformers struggle with : prefixed attributes.
| Prefix | Example | Notes |
|---|---|---|
u- | u-foo:arg="value" | Recommended - Works everywhere |
: | :foo:arg="value" | May cause issues with some transformers |
Modifiers
Split multiple modifiers into separate attributes for clarity:
<!-- ✅ Recommended -->
<uizy-box u-tooltip:top u-tooltip:arrow u-tooltip:delay="500">
Hover me
</uizy-box>
<!-- ⚠️ May cause parsing issues -->
<uizy-box u-tooltip:top:arrow:delay="500">Hover me</uizy-box>
Registration
Via start()
uizy.start({
directives: {
// Simple directive
uppercase: (el) => {
el.style.textTransform = "uppercase";
},
// With value
color: (el, { value }) => {
el.style.color = value || "inherit";
},
// Reactive directive
bind: (el, { value, effect }) => {
effect(() => {
const store = uizy.$(value, { raw: true });
return store?.subscribe((v) => {
el.textContent = String(v);
});
});
},
},
});
Via uizy.directive()
uizy.directive("tooltip", (el, { value, modifiers }) => {
const position = modifiers.includes("top") ? "top" : "bottom";
// Setup tooltip...
});
Context Object
The second argument provides utilities:
uizy.directive("example", (el, ctx) => {
ctx.value; // Attribute value (string)
ctx.modifiers; // Array of modifier args
ctx.bindings; // All bindings for this directive
ctx.effect; // Register reactive effect (auto-cleanup)
ctx.cleanup; // Register cleanup function
});
value
The attribute value as a string:
<uizy-box u-example="hello world"></uizy-box>
uizy.directive("example", (el, { value }) => {
console.log(value); // "hello world"
});
modifiers
Args from multiple bindings:
<uizy-box u-tooltip:top u-tooltip:arrow="Help text"></uizy-box>
uizy.directive("tooltip", (el, { value, modifiers }) => {
console.log(modifiers); // ["top", "arrow"]
console.log(value); // "" (from first binding)
});
bindings
Access individual binding values:
<uizy-box u-config:theme="dark" u-config:size="lg"></uizy-box>
uizy.directive("config", (el, { bindings }) => {
// [{ arg: "theme", value: "dark" }, { arg: "size", value: "lg" }]
const config = Object.fromEntries(
bindings.map((b) => [b.arg, b.value || true]),
);
// { theme: "dark", size: "lg" }
});
effect
Register a reactive effect with automatic cleanup:
uizy.directive("autosize", (el, { effect }) => {
effect(() => {
const resize = () => {
el.style.height = "auto";
el.style.height = el.scrollHeight + "px";
};
el.addEventListener("input", resize);
return () => el.removeEventListener("input", resize);
});
});
cleanup
Register cleanup functions directly:
uizy.directive("interval", (el, { value, cleanup }) => {
const id = setInterval(
() => {
el.textContent = new Date().toLocaleTimeString();
},
parseInt(value) || 1000,
);
cleanup(() => clearInterval(id));
});
Examples
Focus on Mount
uizy.directive("focus", (el) => el.focus());
<uizy-box u-focus>Auto-focused</uizy-box>
Click Outside
uizy.directive("click-outside", (el, { value, cleanup }) => {
const handler = (e) => {
if (!el.contains(e.target)) {
new Function(value).call(el);
}
};
document.addEventListener("click", handler);
cleanup(() => document.removeEventListener("click", handler));
});
<uizy-box u-click-outside="uizy.emit('modal.close')">
Click outside to close
</uizy-box>
Lazy Load Images
uizy.directive("lazy", (el, { value, cleanup }) => {
const img = el.querySelector("img") || el;
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
img.src = value;
observer.disconnect();
}
});
observer.observe(el);
cleanup(() => observer.disconnect());
});
<uizy-box u-lazy="/images/large-photo.jpg">
<img src="/images/placeholder.jpg" alt="Photo" />
</uizy-box>
Two-Way Binding
uizy.directive("model", (el, { value, effect }) => {
const input = el.querySelector("input") || el;
effect(() => {
const store = uizy.$(value, { raw: true });
if (!store) return;
const unsubscribe = store.subscribe((v) => {
if (input.value !== v) input.value = v;
});
const onInput = () => store.set(input.value);
input.addEventListener("input", onInput);
return () => {
unsubscribe();
input.removeEventListener("input", onInput);
};
});
});
<uizy-box u-model="form.username">
<input type="text" />
</uizy-box>
Conditional Show/Hide
uizy.directive("show", (el, { value, effect }) => {
effect(() => {
const store = uizy.$(value, { raw: true });
if (!store) {
el.style.display = value ? "" : "none";
return;
}
return store.subscribe((v) => {
el.style.display = v ? "" : "none";
});
});
});
<uizy-box u-show="ui.modalOpen">Modal content</uizy-box>
Inline Styles
uizy.directive("style", (el, { bindings }) => {
for (const { arg, value } of bindings) {
if (arg && value) el.style[arg] = value;
}
});
<uizy-box u-style:color="red" u-style:fontSize="20px"> Styled text </uizy-box>
Best Practices
Always Clean Up
Prevent memory leaks with proper cleanup:
// ✅ Using cleanup()
uizy.directive("interval", (el, { value, cleanup }) => {
const id = setInterval(() => {
/* ... */
}, 1000);
cleanup(() => clearInterval(id));
});
// ✅ Using effect return
uizy.directive("resize", (el, { effect }) => {
effect(() => {
const handler = () => {
/* ... */
};
window.addEventListener("resize", handler);
return () => window.removeEventListener("resize", handler);
});
});
Handle Missing Values
Provide defaults for optional values:
uizy.directive("color", (el, { value }) => {
el.style.color = value || "inherit"; // ✅ Fallback
});
uizy.directive("delay", (el, { value }) => {
const ms = parseInt(value) || 1000; // ✅ Parse + default
});
Keep Directives Focused
Each directive should do one thing:
// ✅ Single-purpose
uizy.directive("focus", (el) => el.focus());
uizy.directive("uppercase", (el) => el.style.textTransform = "uppercase");
// ❌ Too many responsibilities
uizy.directive("setup", (el, { modifiers }) => {
if (modifiers.includes("focus")) el.focus();
if (modifiers.includes("uppercase")) /* ... */
// Split into separate directives instead
});
API Reference
uizy.directive(name, handler)
| Parameter | Type | Description |
|---|---|---|
name | string | Directive name (used as u-name) |
handler | (el, ctx) => void | Handler function |
uizy.directives
| Method | Description |
|---|---|
.add(name, handler) | Register a directive |
.addAll(directives) | Register multiple |
.get(name) | Get handler |
.has(name) | Check if exists |
.names() | List all names |
.clear() | Remove all |
Types
interface DirectiveContext {
value: string;
modifiers: string[];
bindings: { arg: string; value: string }[];
effect: (fn: () => (() => void) | void) => void;
cleanup: (fn: () => void) => void;
}
type DirectiveHandler = (el: HTMLElement, ctx: DirectiveContext) => void;