Description: `Alpine.directive()` allows you to create new custom `x-` attributes (directives) to encapsulate reusable DOM manipulation logic or integrate with third-party libraries. This extends Alpine's built-in vocabulary, making your components more expressive and maintainable.
Alpine.directive('name', (el, { expression, modifiers }, {
evaluate, evaluateLater, effect, cleanup }) => { ...
})
This is the core function for defining a custom directive. Let's break down its arguments:
'name': A string representing the name of your
directive. If you name it 'foo', you'll use it in
HTML as x-foo.
el: The actual DOM element the directive is
attached to. You can manipulate this element directly.
{ expression, modifiers }: An object containing:
expression: (String) The JavaScript
expression string passed to the directive. For
x-foo="bar()", expression would
be "bar()". For x-foo="message",
expression would be "message".
modifiers: (Array of strings) An array of any
modifiers applied to the directive. For
x-foo.debounce.500ms,
modifiers would be
['debounce', '500ms'].
{ evaluate, evaluateLater, effect, cleanup }: An
object containing utility functions:
evaluate(expression): Executes a JavaScript
expression string within the current Alpine component's
scope and returns its value. Use this for immediate,
one-time evaluation.
evaluateLater(expression): Returns a
function. When this returned function is called, it
executes the JavaScript expression string within the
current Alpine component's scope and passes the result to
its callback. This is crucial for reactive expressions
used within an effect.
effect(callback): Registers a callback
function that Alpine.js will run reactively. If any
reactive Alpine data (e.g., from x-data)
accessed inside this callback changes, the callback will
re-run automatically. This is the heart of making
directives dynamic.
cleanup(callback): Registers a callback
function that will be executed when the element is removed
from the DOM or when Alpine cleans up the component. This
is essential for releasing resources, clearing intervals,
or removing event listeners to prevent memory leaks.
Here's a basic structure:
document.addEventListener('alpine:init', () => {
Alpine.directive('my-directive', (el, { expression, modifiers }, { effect, evaluateLater }) => {
// Get a function that evaluates the expression reactively
const getValue = evaluateLater(expression);
effect(() => {
// When the expression's value changes, this will re-run
getValue(value => {
el.textContent = `Directive value: ${value}, Modifiers: ${modifiers.join(', ')}`;
});
});
});
});
alpine:init Event
The alpine:init event is dispatched on the
document object when Alpine.js has finished its
initial setup but *before* it starts initializing components on
the page. This is the correct and only reliable place to
register custom directives using
Alpine.directive(), as well as defining global
stores with Alpine.store() and global component
data with Alpine.data().
document.addEventListener('alpine:init', () => {
// Register directives here
Alpine.directive('highlight', el => {
el.style.backgroundColor = 'yellow';
});
// Register stores and global data here
Alpine.store('darkMode', { on: false });
});
Alpine.directive() calls inside a
document.addEventListener('alpine:init', () => { ...
});
callback. If you try to register a directive after Alpine has
already scanned the DOM and initialized components, it won't
recognize your new directive on existing elements.
el gives you direct DOM access. Use it sparingly
for simple manipulations or when integrating libraries that
need a DOM node.
expression is a string. You often need
evaluate(expression) for a one-time read or
evaluateLater(expression) combined with
effect() for reactivity. Simply using
expression directly as a value will not work if
it refers to a component property.
modifiers allow for configurable behavior in your
directive (e.g., x-markdown.sanitize).
evaluate vs evaluateLater:
evaluate is for immediate, non-reactive
evaluation. evaluateLater is for setting up
reactive evaluations, typically inside effect.
Forgetting this distinction can lead to directives that don't
update when data changes.
effect is essential for reactivity. If your
directive needs to update when some Alpine data changes, wrap
the logic that depends on that data within an
effect, and access the reactive data using the
function returned by evaluateLater.
cleanup is crucial for preventing memory leaks or
unintended side effects, especially if your directive sets up
event listeners, intervals, or integrates with third-party
libraries that need explicit teardown.
x-data) or a
JavaScript function called from Alpine might be a simpler
solution. Directives are best for reusable, element-specific DOM
enhancements.
This example demonstrates a custom x-markdown directive
that takes a string of Markdown text and renders it as HTML. For
simplicity, this example only supports basic Markdown features
(headers, bold, italics, and line breaks). In a real-world scenario,
you'd integrate a robust Markdown library like
Marked.js
or
Showdown.
The x-markdown directive can also accept a
.uppercase modifier to transform the output to
uppercase.