Event Modifiers
Event modifiers give you fine-grained control over how events are handled. Chain multiple modifiers together for precise event behavior.
Overview
Modifiers are applied after the event name using dot notation. Multiple modifiers can be chained together:
<button on-click.prevent.stop.self="handleClick()">Click Me</button>
<input on-keydown.ctrl.s.exact.debounce.500="save()">
Action Modifiers
Control the default behavior and propagation of events.
.prevent
Calls e.preventDefault() to prevent default browser behavior.
<!-- Prevents form submission -->
<form on-submit.prevent="handleSubmit()">
<input type="text">
<button type="submit">Submit</button>
</form>
<!-- Prevents link navigation -->
<a href="/page" on-click.prevent="navigate()">Click Me</a>
.stop
Calls e.stopPropagation() to prevent event bubbling.
<div on-click="console.log('Parent')">
<button on-click.stop="console.log('Child')">
Click Me
</button>
<!-- Only "Child" logs, parent doesn't fire -->
</div>
.once
Removes the event listener after the first trigger.
<button on-click.once="initialize()">
Initialize (only works once)
</button>
.passive
Adds a passive event listener for better scroll performance.
<div on-scroll.passive="handleScroll()">
<!-- Optimized for smooth scrolling -->
</div>
.capture
Uses capture phase instead of bubbling phase.
<div on-click.capture="handleBeforeChild()">
<button on-click="handleChild()">
<!-- Parent fires before child -->
</button>
</div>
Target Modifiers
Control which element should receive the event.
.self
Only triggers if the event originated from the element itself, not a child.
<div on-click.self="handleDivClick()">
<button>Click me</button>
<!-- Clicking button doesn't trigger handleDivClick -->
</div>
.not-self
Only triggers if the event originated from a child element.
<div on-click.not-self="handleChildClick()">
<button>Click me</button>
<!-- Clicking button triggers handleChildClick -->
</div>
.window
Attaches the event listener to the window object.
<!-- Keyboard shortcuts anywhere on the page -->
<div on-keydown.window.ctrl.s.exact="saveDocument()">
<!-- Ctrl+S works even when focused elsewhere -->
</div>
<!-- Resize handler -->
<div on-resize.window="handleResize()">
<!-- Responds to window resize -->
</div>
Mouse Button Modifiers
Restrict event triggers to specific mouse buttons.
.left
Only triggers on left mouse button click.
<button on-click.left="handleLeftClick()">
Left Click Only
</button>
.middle
Only triggers on middle mouse button click.
<button on-click.middle="handleMiddleClick()">
Middle Click Only
</button>
.right
Only triggers on right mouse button click.
<button on-click.right="handleRightClick()">
Right Click Only
</button>
<!-- Different actions for different buttons -->
<button on-click.left="select()" on-click.right.prevent="showContextMenu()" on-click.middle="openInNewTab()">
Click me with any button!
</button>
Keyboard Modifiers
Create keyboard shortcuts with system key combinations.
.ctrl
Requires the Ctrl key to be pressed.
<input on-keydown.ctrl.s="save()">
<!-- Ctrl+S saves -->
<button on-click.ctrl="save()">
Ctrl + Click to Save
</button>
.shift
Requires the Shift key to be pressed.
<input on-keydown.shift.enter="sendMessage()">
<!-- Shift+Enter sends message -->
.alt
Requires the Alt key to be pressed.
<input on-keydown.alt.x="extraAction()">
<!-- Alt+X triggers extra action -->
.meta
Requires the Meta key (Cmd on Mac, Windows key on PC).
<input on-keydown.meta.s="save()">
<!-- Cmd+S on Mac, Win+S on PC -->
.exact
Ensures only the specified modifiers are pressed. No extra modifiers allowed.
<!-- Triggers only on Ctrl+S, not Ctrl+Shift+S -->
<input on-keydown.ctrl.s.exact="save()">
<!-- Triggers only on Ctrl, no other keys -->
<div on-click.ctrl.exact="handleCtrlClick()">
Click with Ctrl (no Shift, Alt, or Meta)
</div>
Key Aliases
Use friendly names for common keys instead of remembering key codes.
.enter
Enter/Return key
.tab
Tab key
.delete
Delete or Backspace
.esc
Escape key
.space
Spacebar
.up
Arrow Up
.down
Arrow Down
.left
Arrow Left
.right
Arrow Right
.home
Home key
.end
End key
.pageup
Page Up
.pagedown
Page Down
.a through .z
Letter keys
<!-- Using key aliases -->
<input on-keydown.enter="submitForm()">
<input on-keydown.esc="closeModal()">
<input on-keydown.up="previousItem()">
<input on-keydown.down="nextItem()">
<!-- Key aliases with system modifiers -->
<input on-keydown.ctrl.enter="submitAndStay()">
<input on-keydown.shift.delete="permanentDelete()">
Wheel Direction Modifiers
Handle scroll wheel events based on direction.
.wheel-up
Triggers when scrolling up.
<div on-wheel.wheel-up="previousPage()">
Scroll up to go back
</div>
.wheel-down
Triggers when scrolling down.
<div on-wheel.wheel-down="nextPage()">
Scroll down to go forward
</div>
.wheel-left
Triggers when scrolling left (horizontal scroll).
<div on-wheel.wheel-left="previousImage()">
Scroll left for previous
</div>
.wheel-right
Triggers when scrolling right (horizontal scroll).
<div on-wheel.wheel-right="nextImage()">
Scroll right for next
</div>
Form State Modifiers
Trigger events based on form input states.
.dirty
Triggers only if the input has been modified.
<input on-change.dirty="markAsDirty()">
.pristine
Triggers only if the input hasn't been modified.
<input on-change.pristine="useDefaultValue()">
.valid
Triggers only if the input passes validation.
<input type="email" required on-change.valid="saveEmail()">
.invalid
Triggers only if the input fails validation.
<input type="email" required on-change.invalid="showError()">
<!-- Complete form validation example -->
<form on-submit.prevent="handleSubmit()">
<div>
<label>Email</label>
<input type="email" required @value="@{email.value}" on-change.valid="emailValid.value = true" on-change.invalid="emailValid.value = false">
<div if="!emailValid.value && email.value">
<d-text class="text-red-500 text-sm">Please enter a valid email</d-text>
</div>
</div>
<div>
<label>Username</label>
<input type="text" minlength="3" @value="@{username.value}" on-input.dirty="checkUsername()">
</div>
<button type="submit" if="emailValid.value && username.value.length >= 3">
Submit
</button>
</form>
Timing Modifiers
Control when and how often events execute with debouncing and throttling.
.debounce
Delays execution until after the last event. Great for search inputs.
<!-- Debounce search with custom delay (500ms default) -->
<input on-input.debounce.300="search()">
<!-- Debounce with different delay -->
<input on-input.debounce.1000="expensiveSearch()">
.300) sets the delay in milliseconds. Default is 300ms.
.throttle
Limits execution to at most once per interval. Perfect for scroll events.
<!-- Throttle scroll with custom interval (500ms default) -->
<div on-scroll.throttle.200="handleScroll()">
<!-- Throttle with different interval -->
<div on-scroll.throttle.1000="updatePosition()">
</div></div>
.200) sets the throttle interval in milliseconds. Default is 300ms.
<!-- Practical examples -->
<div class="space-y-4">
<!-- Real-time search with debounce -->
<div>
<label>Search</label>
<input type="text" @value="@{query.value}" on-input.debounce.300="performSearch()" placeholder="Type to search...">
<div if="searching.value">Searching...</div>
<ul for-each="result in results.value" for-key="{{results.id}}">
<li>@{result}</li>
</ul>
</div>
<!-- Scroll position tracking with throttle -->
<div on-scroll.throttle.100="updateScrollPosition()" style="max-height: 300px; overflow-y: auto;">
<!-- Long content here -->
<p>Scroll position: @{scrollY.value}px</p>
</div>
<!-- Window resize with throttle -->
<div on-resize.window.throttle.500="updateLayout()">
Current width: @{windowWidth.value}px
</div>
</div>
Advanced Combinations
Combine multiple modifiers for complex event handling scenarios.
<!-- Rich text editor shortcuts -->
<div on-keydown.ctrl.b.exact="bold()" on-keydown.ctrl.i.exact="italic()" on-keydown.ctrl.u.exact="underline()" on-keydown.ctrl.s.exact="save()" contenteditable="true">
<!-- Rich text editor content -->
</div>
<!-- Drag and drop with modifiers -->
<div on-mousedown.left="startDrag()" on-mousemove.window.throttle.16="updateDrag()" on-mouseup.window="endDrag()">
Draggable element
</div>
<!-- Modal with multiple interaction methods -->
<div class="modal">
<!-- Click outside to close -->
<div out-click.self="closeModal()">
<!-- ESC key to close -->
<div on-keydown.esc="closeModal()">
<!-- Click close button -->
<button on-click.prevent="closeModal()">×</button>
<!-- Prevent clicks inside from closing -->
<div on-click.self="console.log('Modal content clicked')">
Modal content
</div>
</div>
</div>
</div>
<!-- Form with all validation modifiers -->
<form on-submit.prevent="handleSubmit()">
<input type="email" required @value="@{email.value}" on-change.dirty.valid="emailValid = true" on-change.dirty.invalid="emailValid = false" on-change.pristine="emailValid = null">
<input type="password" minlength="8" @value="@{password.value}" on-input.debounce.300.valid="checkPasswordStrength()">
<button type="submit" if="emailValid && password.value.length >= 8">
Register
</button>
</form>
Quick Reference
| Category | Modifier | Description |
|---|---|---|
| Action | .prevent | Prevents default browser behavior |
| .stop | Stops event propagation | |
| .once | Triggers only once | |
| .passive | Adds passive event listener | |
| Target | .self | Only triggers on the element itself |
| .not-self | Triggers only on children | |
| .window | Attaches to the window object | |
| Mouse | .left | Left mouse button only |
| .middle | Middle mouse button only | |
| .right | Right mouse button only | |
| Keyboard | .ctrl | Requires Ctrl key |
| .shift | Requires Shift key | |
| .alt | Requires Alt key | |
| .meta | Requires Meta key (Cmd/Win) | |
| .exact | Only specified modifiers allowed | |
| Wheel | .wheel-up | Scrolling up |
| .wheel-down | Scrolling down | |
| .wheel-left | Scrolling left | |
| .wheel-right | Scrolling right | |
| Form | .dirty | Input has been modified |
| .pristine | Input hasn't been modified | |
| .valid | Input passes validation | |
| .invalid | Input fails validation | |
| Timing | .debounce | Delays execution after last event |
| .throttle | Limits execution frequency |
Best Practices
Use .exact for Keyboard Shortcuts
When defining keyboard shortcuts, always use .exact to prevent accidental triggers when extra modifiers are pressed.
Combine .debounce with .dirty
For search inputs, combine .debounce with .dirty to only search when the user has actually typed something.
Use .throttle for Performance
Always throttle scroll and resize events to prevent performance issues. A 100-200ms throttle is usually sufficient.
Combine .self with out-*
For click-outside behavior, combine .self with out-click to ensure it only triggers when clicking outside the element.
Use .passive for Smooth Scrolling
Use .passive with scroll events to improve scrolling performance, especially on mobile devices.
Page Not Found
We couldn't find the page you're looking for. It might have been moved or deleted.
Go back home