Description: Create new x- attributes using Alpine.directive() to encapsulate reusable DOM manipulation logic or integrate with third-party libraries, extending Alpine's vocabulary. This allows you to build powerful, declarative custom behaviors directly into your HTML markup.
Alpine.directive('name', callback): This is the core function for registering a new custom directive.
name: A string representing the name of your directive (e.g., 'tooltip', 'resize-observer'). Alpine will automatically prefix this with x-, so 'tooltip' becomes x-tooltip.callback(el, { expression, modifiers, originalExpression }, { Alpine, effect, cleanup, evaluate, evaluateLater }): A function that Alpine calls when it encounters your directive on an element. It receives:
el: The DOM element the directive is attached to.expression: The JavaScript expression string provided as the value of your directive attribute (e.g., "myFunction()" in x-my-directive="myFunction()").modifiers: An array of strings representing any modifiers used (e.g., ['debounce', 'lazy'] for x-my-directive.debounce.lazy="expression").originalExpression: The raw, unevaluated string as it appeared in the HTML.Alpine: The global Alpine object, useful for accessing stores or other Alpine internals.effect(callbackFn): Creates a reactive effect. The callbackFn will re-run whenever any reactive Alpine data it accesses changes. This is how directives can react to state changes.cleanup(callbackFn): Registers a function to execute when the element is removed from the DOM or the directive is re-initialized. Essential for tearing down event listeners, observers, or other resources to prevent memory leaks.evaluate(expressionStr): Evaluates the expressionStr within the current Alpine component's scope and returns the result immediately.evaluateLater(expressionStr): Returns a function. When this returned function is called, it will then evaluate expressionStr in the current Alpine component's scope. This is very useful for setting up event handlers or callbacks that should execute later.Example structure:
document.addEventListener('alpine:init', () => {
Alpine.directive('my-custom-behavior', (el, { expression, modifiers }, { effect, cleanup, evaluateLater }) => {
// Directive logic here
// Example: Log the expression when a button is clicked
const tellMe = evaluateLater(expression);
el.addEventListener('click', () => {
tellMe(value => console.log('Expression evaluated to:', value));
});
});
});
alpine:init event:
document object.x-data) on the page.Alpine.directive(). It ensures Alpine knows about your custom x-attributes when it first scans the DOM.document.addEventListener('alpine:init', () => {
// Register directives here
Alpine.directive('highlight', el => {
el.style.backgroundColor = 'yellow';
});
// Register stores, data, etc.
Alpine.data('myComponent', () => ({ message: 'Hello' }));
});
Alpine needs to know about your custom directives during its initial DOM scan. If you define Alpine.directive() *after* Alpine has already processed an element that tries to use it, the directive won't be applied. Always register directives inside a document.addEventListener('alpine:init', () => { ... }) callback.
Analogy for Python devs: This is like trying to use a function or class in Python before it has been defined or imported. The interpreter needs to know the definition first.
The arguments passed to your directive's callback function (el, expression, modifiers, and the utility functions like evaluate, evaluateLater, effect, cleanup) are powerful tools. Knowing how and when to use them is essential for writing robust and efficient directives.
eval() the expression string; use evaluate(expression) or evaluateLater(expression) which correctly handle scope and reactivity.cleanup() to remove event listeners, IntersectionObservers, ResizeObservers, or any other resources your directive sets up. Forgetting this can lead to memory leaks, especially in Single Page Applications (SPAs) or when elements are frequently added/removed.effect() when your directive needs to react to changes in Alpine component data.Analogy for Python devs: Think of the directive API as a specific SDK or library interface. You need to use its provided functions and understand its lifecycle (like cleanup being similar to a context manager's __exit__ or a finally block for resource management) to work effectively.
This example demonstrates a custom directive x-observe-resize. It uses a ResizeObserver to monitor an element's dimensions and calls a component method when the size changes. Try resizing the text area below.
Width: 0 px
Height: 0 px
textarea has x-observe-resize="updateDimensions".x-observe-resize directive is registered within alpine:init.ResizeObserver attached to the textarea.textarea is resized, the observer triggers.updateDimensions method on our Alpine component, passing the resize event data.updateDimensions method updates currentWidth and currentHeight, which are reactively displayed.