Directive reference
All 58dj-* template directives — click any row to expand examples and details.
Directives are dj-* HTML attributes you add to your templates. They bind browser events to server-side handlers, drive declarative UI, and mark the reactive regions djust patches over the WebSocket — no custom JavaScript required. Most pair with an @event_handler method on your LiveView (see the Decorators tab).
Events
User-input event bindingsBind browser events to server-side handlers. dj-click, dj-submit, dj-input, and dj-change cover the common cases — the handler runs on the server and djust patches the result back over the WebSocket. Pass data with data-* attributes or inline arguments, and guard destructive actions with dj-confirm.
dj-click
Fire a handler on click. data-* attrs become kwargs. Click
Fires on
Click
Handler receives
data-* attrs as kwargs
The workhorse directive. On click, djust calls the named handler on the server. Any plain data-* attribute (or the data-dj-* form) is passed as a keyword argument, and type hints coerce the string automatically — data-item-id="5" arrives as item_id=5. For simple literals you can skip data-* entirely and call the handler inline: set_period('month').
Template — pass data
<button dj-click="delete" data-item-id="{{ item.id }}">Delete</button>
Handler
from djust.decorators import event_handler
@event_handler()
def delete(self, item_id: int = 0, **kwargs):
# data-item-id="5" is coerced to item_id=5
self.items = [i for i in self.items if i["id"] != item_id]
Inline args (no data-* needed)
<button dj-click="set_period('month')">Monthly</button>
dj-submit
Submit a form. Named fields arrive as handler kwargs. Form submit
Fires on
Form submit
Handler receives
All named form fields as kwargs
Binds to a form's submit event and prevents the default full-page POST. Every named field in the form arrives as a keyword argument, so collect them with **form_data. The _target kwarg holds the submitter button's name when one is present.
Template
<form dj-submit="save_form">
{% csrf_token %}
<input name="title" value="{{ title }}" />
<button type="submit">Save</button>
</form>
Handler
@event_handler()
def save_form(self, **form_data):
self.title = form_data.get("title", "")
Post.objects.create(title=self.title)
dj-change
Fire on blur / select change with the current value. Input / select loses focus
Fires on
Input / select loses focus
Handler receives
value=, _target=
Fires when an <input> or <select> loses focus or a selection changes — not on every keystroke. The current value arrives as the magic value kwarg (always name it value). When several fields share one handler, _target identifies which one fired.
Template
<select dj-change="filter_status">
<option value="all">All</option>
<option value="active">Active</option>
</select>
Handler
@event_handler()
def filter_status(self, value: str = "all", **kwargs):
self.status = value
self._refresh()
dj-input
Fire on every keystroke. Handler gets value=. Every keystroke (keyup)
Fires on
Every keystroke (keyup)
Handler receives
value=, _target=
Fires on every keystroke, ideal for live search and inline validation. The current value arrives as value. Because keystrokes are frequent, pair it with dj-debounce to wait for a pause (in ms), or dj-debounce="blur" to defer until the field loses focus.
Template — debounced live search
<input type="text" dj-input="search"
dj-debounce="300" value="{{ query }}" />
Handler
@event_handler()
def search(self, value: str = "", **kwargs):
self.query = value
self.results = Product.objects.filter(
name__icontains=value
)
dj-blur
Fire when focus leaves the element. Focus leaves element
Fires on
Focus leaves element
Handler receives
value=
Fires when the element loses focus (focusout). Commonly used to validate a single field the moment the user tabs away from it.
Template
<input name="email" dj-blur="validate_email" value="{{ email }}">
Handler
@event_handler()
def validate_email(self, value: str = "", **kwargs):
self.email = value
self.email_error = "" if "@" in value else "Invalid email"
dj-focus
Fire when focus enters the element. Focus enters element
Fires on
Focus enters element
Handler receives
value=
Fires when the element gains focus (focusin) — useful for clearing a previous error message or revealing a help hint as the user begins editing a field.
Template
<input name="email" dj-focus="clear_error" value="{{ email }}">
Handler
@event_handler()
def clear_error(self, value: str = "", **kwargs):
self.email_error = ""
dj-click-away
Fire when user clicks outside this element. Click outside element
Fires on
Click outside element
Handler receives
dj-value-* attrs as kwargs
Fires when the user clicks anywhere outside this element — the classic "close the dropdown" behaviour. It uses a capture-phase document listener, so stopPropagation() inside the element does not suppress it. Pass parameters with dj-value-* attributes.
Template
<div dj-click-away="close_menu" class="dropdown">
<!-- menu contents -->
</div>
Handler
@event_handler()
def close_menu(self, **kwargs):
self.menu_open = False
dj-confirm
Show a native confirm dialog before the event is sent. Before the wrapped event is sent
Fires on
Before the wrapped event is sent
Handler receives
Same as the wrapped event
A guard, not a handler of its own. Add it alongside dj-click (or another event) and the browser shows a native confirm() dialog first. The server handler only runs if the user clicks OK — no round-trip happens on cancel.
Template
<button dj-click="delete"
dj-confirm="Delete this item permanently?">
Delete
</button>
Handler (runs only after confirm)
@event_handler()
def delete(self, **kwargs):
self.item.delete()
dj-model
Two-way binding — auto-syncs self.field_name. Every keystroke (input event)
Fires on
Every keystroke (input event)
Handler receives
Auto-syncs self.<field_name>
Two-way binding with no handler to write — like Vue's v-model. djust keeps the named view attribute in sync on every input and re-renders. The built-in ModelBindingMixin coerces to the attribute's existing type and only allows existing, non-private fields. Use dj-model.lazy to sync on blur, or dj-model.debounce-300 to throttle.
Template — no handler needed
<input type="text" dj-model="search_query">
<input type="checkbox" dj-model="show_archived">
View — just declare the attributes
class SearchView(LiveView):
def mount(self, request, **kwargs):
self.search_query = ""
self.show_archived = False
# dj-model updates these automatically on input
dj-copy
Client-only clipboard copy. No server round-trip. Click
Fires on
Click
Handler receives
Client-only — no server round-trip
Copies text to the clipboard entirely on the client — no event reaches the server unless you opt in with dj-copy-event. Pass literal text (often a template variable) or a #selector to copy another element's textContent. Built-in feedback swaps the button label for two seconds.
Copy literal text
<button dj-copy="{{ share_url }}">Copy link</button>
Copy another element's text
<button dj-copy="#code-block">Copy code</button>
With feedback + server event
<button dj-copy="{{ api_key }}"
dj-copy-feedback="Copied!"
dj-copy-event="key_copied">Copy key</button>
Companion attributes
| dj-copy-feedback="text" | Button text shown for 2s after copy (default: "Copied!") |
| dj-copy-class="class" | CSS class added for 2s after copy (default: dj-copied) |
| dj-copy-event="handler" | Server event fired after a successful copy |
Keyboard
Key events + shortcut chordsReact to key presses and global shortcuts. Modifier suffixes like .enter and .escape filter dj-keydown and dj-keyup by key; dj-shortcut binds chords (ctrl+k, shift+enter). Use dj-window-* and dj-document-* variants for document-level listeners.
dj-keydown
Keydown event. Supports .enter / .escape / .space modifiers.
Fires when a key is pressed on the element. Supports key modifiers .enter, .escape, and .space to filter by specific keys (handler only fires if the pressed key matches). Pair with data-* or dj-value-* to pass additional context; the handler receives any custom dj-value-* attributes as kwargs. Use on form inputs and contenteditable elements to bind actions to specific key presses. Note: the matched key itself is not passed as a parameter — use dj-window-keydown or dj-document-keydown if you need key event details like the key code.
<input dj-keydown.enter="submit">
dj-keyup
Keyup event. Same modifier support as dj-keydown.
Fires when a key is released on the element. Supports the same modifier syntax as dj-keydown (.enter, .escape, .space). Similar to dj-keydown, it passes `key` and `code` information to the handler along with the element's `value` and `field` name. Supports `dj-value-*` attributes for passing additional context. Useful for detecting when user finishes typing or releases a held key.
<div dj-keyup.escape="cancel">...</div>
dj-shortcut
Bind keyboard chords (ctrl+k, escape, shift+enter).
Binds keyboard chords and complex key combinations (ctrl+k, shift+enter, escape). Syntax is key:handler[:prevent] or modifier+key:handler for multiple bindings separated by commas. Use the :prevent suffix to call preventDefault() and suppress browser defaults like ctrl+s. Shortcuts automatically skip form inputs unless dj-shortcut-in-input is added; the handler receives key, code, and the matched shortcut string.
<div dj-shortcut="ctrl+k:open_search:prevent">...</div>
dj-window-keydown
window keydown. Modifier filtering supported.
Attaches a keydown listener to the window object instead of the element, enabling global keyboard shortcuts that fire regardless of focus. Supports key modifiers like .escape to filter specific keys. The element provides context (component ID, dj-value-* attributes) but the listener is window-scoped. Ideal for modal close buttons or global app shortcuts.
<div dj-window-keydown.escape="close">...</div>
dj-window-keyup
window keyup.
Attaches a keyup listener to the window object, firing when any key is released anywhere on the page. Supports key modifiers like dj-window-keydown. The declaring element provides context only; the actual listener is global. Use cases include tracking when a held modifier key is released or implementing global hotkey sequences.
<div dj-window-keyup="log">...</div>
dj-window-scroll
window scroll (150ms throttle).
Fires when the user scrolls the window. The handler receives scroll position data (scrollY and scrollX) and any dj-value-* attributes from the declaring element. Pair with a data-attribute to track which section is in view for analytics or UI state.
<div dj-window-scroll="on_scroll">...</div>
dj-window-click
window click (catches background clicks).
Fires when the user clicks anywhere in the window. The declaring element provides context (dj-value-* params) but the listener is attached to the global window object. Both dj-window-click and dj-document-click use bubble-phase listeners; the difference is the event target (window vs document). Unlike these, dj-click-away uses capture phase to fire when the user clicks outside an element—common for dropdowns or modals. For window-level click detection (e.g., to detect any click for analytics or UI state), use dj-window-click.
<div dj-window-click="on_bg_click">...</div>
dj-window-resize
window resize (150ms throttle).
Fires when the browser window is resized, with an automatic 150ms throttle to prevent flooding. Override throttle with data-throttle or data-debounce if needed. The handler receives innerWidth and innerHeight from the resize event, plus any dj-value-* attributes from the declaring element. Use for responsive layout adjustments or recomputing dimensions of dynamic content.
<div dj-window-resize="on_resize">...</div>
dj-document-keydown
document keydown (capture-phase).
Attaches a keydown listener on the document, firing during the bubble phase (after inner element handlers can prevent propagation). Supports key modifiers like .escape. The declaring element provides context (dj-value-*) but the listener captures globally. Unlike dj-click-away which uses capture phase, dj-document-keydown fires in the normal bubble phase. Useful when you need to handle keyboard events globally, e.g., keyboard navigation or modal shortcuts that don't require bypassing inner element stopPropagation().
<div dj-document-keydown.escape="close">...</div>
dj-document-keyup
document keyup.
Attaches a keyup listener in bubble phase on the document, firing after element-level keyup handlers complete. Supports key modifiers like dj-document-keydown. The declaring element provides context only; the listener is at document scope in bubble phase. Use for global key release detection that should fire after element-level handlers have had a chance to run.
<div dj-document-keyup="log">...</div>
dj-document-click
document click (capture-phase).
Attaches a click listener in bubbling phase on the document. The declaring element provides context via dj-value-* attributes and receives clientX and clientY from the event. Similar to dj-click-away but operates in bubbling phase rather than capture phase, meaning stopPropagation() inside elements can prevent detection. Use for detecting document-level clicks when you don't need to bypass inner element event handling.
<div dj-document-click="on_doc_click">...</div>
Polling
Recurring server refreshRefresh from the server on a timer. dj-poll re-invokes a handler at a fixed interval (default 5s) — useful for dashboards, counters, and status widgets. Set dj-poll-interval to override.
dj-poll
Re-run a handler on an interval (default 5s).
Automatically calls a server handler on a fixed interval (default 5 seconds, configurable via dj-poll-interval attribute in milliseconds). The element must remain in the DOM for polling to continue; removing it stops the timer. Use for refreshing data like chat messages, notifications, or live dashboards that don't require real-time push updates.
<div dj-poll="refresh"></div>
Loading states
Visual feedback during round-tripsGive immediate visual feedback during server round-trips. dj-loading toggles a CSS class, shows/hides, or disables an element while a request is in flight — buttons and regions can show spinners without any JavaScript.
dj-debounce
Delay sending until the user pauses. Per element.
Delays sending an event to the server until the user pauses for a specified duration. Commonly paired with dj-input to avoid firing on every keystroke; for example, dj-input with dj-debounce="300" waits 300ms after the last keystroke before firing. Works with any event type and applies per-element — each element gets its own independent timer. Can also defer until blur with dj-debounce="blur", or disable the default debounce with dj-debounce="0".
<input dj-input="search" dj-debounce="300">
dj-throttle
Throttle handler calls to N ms. Per element.
Throttles handler calls to fire at most once per N milliseconds. For example, dj-throttle="500" on a button ensures the handler fires at most every 500ms even if clicked repeatedly. Pairs well with dj-click for rapid actions (polls, form submissions) to prevent request flooding. Like debounce, throttle is per-element and works with any event directive (dj-click, dj-change, dj-input, etc.). HTML attributes and Python decorators can be combined: the HTML attribute controls client-side timing (when events are sent to the server), while the Python decorator controls server-side timing (how often handlers execute). HTML attributes take precedence over data-debounce/data-throttle legacy attributes.
<button dj-click="poll" dj-throttle="500">Refresh</button>
dj-loading
Auto class / show / hide / disable during a round-trip.
Auto-manages CSS classes or DOM properties (show/hide/disable) on an element while a server round-trip is in progress. By default, applies the djust-loading class; modifiers like dj-loading.disable, dj-loading.show, and dj-loading.hide control visibility and interactivity. Use dj-loading.class="foo" to add a custom class (e.g., dj-loading.class="opacity-50"). The dj-loading.for modifier ties loading state to a specific event name regardless of where the element sits in the DOM, enabling spinners and disabled buttons far from the trigger. Loading state persists through background async work if using start_async() or the @background decorator.
<button dj-click="save" dj-loading.disable>
Submit protection
Disable + lock during submitsPrevent double-submits and racing events. dj-disable-with swaps a button's label and disables it during submission; dj-lock blocks further events until the server responds — essential for payment buttons and one-shot actions.
dj-disable-with
Disable button + replace text during submission.
Automatically disable a button and replace its text during form submission, restoring both after the server responds. Works with both dj-submit forms and dj-click buttons for visual feedback that a request is in flight. Pair it with dj-lock for full protection: dj-disable-with provides cosmetic feedback while dj-lock actually blocks duplicate events.
<button dj-submit dj-disable-with="Saving...">
dj-lock
Block event until server responds (prevents double-fire).
Block an element from firing its event again until the server responds, preventing accidental double-submits. Unlike dj-disable-with which is purely visual, dj-lock enforces a hard lock — form elements are disabled, non-form elements get a djust-locked CSS class. All locked elements unlock when any server response arrives. Combine with dj-disable-with for the complete protection pattern.
<button dj-click="save" dj-lock>
UI feedback
Cloak, scroll, copy, loading barSmall declarative polish: dj-cloak hides content until the socket mounts (no FOUC), dj-scroll-into-view auto-scrolls freshly rendered elements, dj-copy puts text on the clipboard client-side, and dj-dialog drives a native modal.
dj-cloak
Hide elements until the connection is live (no FOUC).
Hide elements until the WebSocket or SSE connection is established and mount response arrives, preventing flash of unconnected content (FOUC). The CSS rule [dj-cloak] { display: none !important; } is injected automatically by client.js; the attribute is then removed when the mount response succeeds. Only cloak elements that depend on WebSocket — if the connection fails to establish, cloaked content remains hidden.
<div dj-cloak>...</div>
dj-scroll-into-view
Auto-scroll element into view after render (one-shot).
Automatically scroll an element into view after it appears in the DOM via mount or VDOM patch, firing only once per DOM node. Supports values: empty string or default (smooth scroll, block nearest), instant (no animation), center (block center), start (block start), and end (block end). Ideal for auto-focusing chat messages, alerts, or newly created items; VDOM-replaced elements scroll again on the fresh node.
<div dj-scroll-into-view="center">...</div>
dj-copy-feedback
Button text shown for 2s after dj-copy success.
Replace button text with custom feedback text for 1500ms (1.5 seconds) after a successful dj-copy operation (default feedback is Copied!). Restores the original button text when the timeout expires. Pair with dj-copy on a button element to provide immediate visual confirmation that the text was copied to the clipboard.
<button dj-copy dj-copy-feedback="Done!">
dj-copy-class
CSS class added for 2s after dj-copy success.
Add a CSS class to an element for 2 seconds after a successful dj-copy operation (default class is dj-copied if not specified). Enables styling-based feedback like color changes or animations without modifying the DOM structure. Use when you prefer CSS-driven visual feedback over text changes.
<button dj-copy dj-copy-class="btn-success">
dj-copy-event
Server event fired after successful dj-copy.
Fire a server-side event after a successful dj-copy operation, enabling analytics, logging, or state updates. The handler receives a `text` parameter with the copied text. Currently, dj-value-* attributes are NOT automatically passed as parameters (this is a known limitation - use data-* attributes or separate handler parameters for now). No server round-trip is required for the copy itself; the event fires after the local clipboard operation succeeds. Combine with dj-copy-feedback and dj-copy-class for full client + server feedback.
<button dj-copy dj-copy-event="copied">
dj-dialog
Drive a native <dialog> open/close declaratively. Attribute value changes
Fires on
Attribute value changes
Handler receives
Client-only — no handler
Flips a native <dialog> between modal-open and closed based on the attribute value ("open" / "close") — no JS hook needed. A document-level MutationObserver watches for changes. When the user dismisses with Escape the browser flips the open attribute, so pair with dj-ignore-attrs="open" to stop the next patch from re-opening it.
<button dj-click="open_modal">Edit profile</button>
<dialog dj-dialog="{{ modal_open|yesno:'open,close' }}"
dj-ignore-attrs="open">
<form method="dialog">
<button value="save">Save</button>
</form>
</dialog>
Lifecycle
Mount, focus, reconnect recoveryHook into mount, reconnection, and deployment events. dj-mounted fires when an element enters the DOM; recovery attributes (like dj-auto-recover, dj-no-recover) control state restoration after connection drops; dj-track-static warns when deployed assets change.
dj-mounted
Fire after element enters DOM via a VDOM patch.
Fire a server event when an element enters the DOM after a VDOM patch. Does not fire on initial page load, only after subsequent patches insert the element. Includes dj-value-* attributes as event parameters and is useful for triggering data loading, initializing third-party widgets, or scrolling new elements into view. Each element fires only once per DOM node using a WeakSet internally to prevent duplicates.
<div dj-mounted="on_ready" dj-value-id="42">...</div>
dj-auto-recover
Fire on WebSocket reconnect with form values + data-*.
Fire a server event automatically when the WebSocket reconnects to restore complex state. Serializes all form field values and data-* attributes from the container element and passes them to the handler, enabling recovery of state that default form-value replay cannot restore (canvas state, editor cursors, drag positions). Does not fire on initial page load, only after reconnection, and takes precedence over automatic form recovery for fields within its container.
<div dj-auto-recover="restore_state">...</div>
dj-no-recover
Opt a field out of automatic form recovery on reconnect.
Opt a specific form field out of automatic form recovery on WebSocket reconnect. Prevents the field value from being replayed to the server when the connection is restored. Useful for ephemeral search fields, temporary scratch inputs, or fields where server state is the authoritative source. Fields with dj-no-recover are skipped even if they carry dj-change or dj-input bindings.
<input dj-change="on" dj-no-recover>
dj-track-static
Warn or reload when a tracked asset changes on deploy. WebSocket reconnect
Fires on
WebSocket reconnect
Handler receives
Client-only — fires dj:stale-assets
Snapshots the src/href of every tagged element at page load; on each WebSocket reconnect it re-checks them and, if any changed, dispatches a dj:stale-assets CustomEvent (detail.changed lists the URLs). Use dj-track-static="reload" to auto-reload instead. Essential for zero-downtime deploys where the client could be running stale JS/CSS.
Template — with the convenience tag
{% load live_tags %}
<script {% djust_track_static %}
src="{% static 'js/app.abc123.js' %}"></script>
Auto-reload variant
<script dj-track-static="reload" src="..."></script>
VDOM identity
Reactive region + keying + hooksControl how djust's Rust VDOM identifies and patches markup. dj-root marks the reactive region, dj-view ties it to a LiveView (both required); dj-key / data-key give list items stable identity for correct diffing; dj-update="ignore" opts a subtree out; dj-hook attaches JS lifecycle callbacks.
dj-view
On <body> — identifies the WebSocket session. Required.
Place this attribute on the root element (typically `<div>` or `<body>`) to identify which LiveView class handles this page's reactive updates. The value must be the full Python import path to the LiveView class (e.g., `"myapp.views.CounterView"` as a string, or `{{ dj_view_id }}` as a template variable in legacy templates). Without dj-view, djust cannot establish the WebSocket connection and the page behaves as static HTML. The element with dj-view automatically becomes the reactive region unless you explicitly add dj-root elsewhere.
<body dj-view="{{ dj_view_id }}">
dj-root
Marks the reactive subtree. Only HTML inside is diffed.
Marks the boundary of the reactive subtree — only HTML inside dj-root is diffed and patched by the VDOM. Elements outside this boundary (headers, sidebars, footers) are never modified. Optional alongside dj-view; djust auto-infers dj-root from dj-view if not explicitly provided. The Rust diff engine establishes a baseline of dj-root's HTML at mount time and emits minimal patches on every event.
<div dj-root>...</div>
dj-key
Stable list-item identity for optimal VDOM diffing.
Assign a stable, unique identity to list items for optimal VDOM diffing. When items can reorder, insert, or delete, dj-key enables keyed diffing that emits MoveChild patches instead of cascading replacements, preserving DOM state like focus and scroll position. dj-key is equivalent to the legacy data-key attribute, which is retained for compatibility. Keys must be unique within the parent element.
<li dj-key="{{ item.id }}">...</li>
data-key
Same as dj-key, no prefix. Use whichever you prefer.
Alternative to dj-key for stable list-item identity. Assigns a unique key that enables keyed VDOM diffing instead of position-based diffing. Use on any repeating elements (for-loop children) where the list can reorder or have items added/removed. Works identically to dj-key; choose whichever syntax matches your style.
<div data-key="{{ item.id }}">...</div>
dj-update
Opt out of patching. Use for charts / rich text / external JS.
Set to ignore to opt out of VDOM patching for a subtree. Essential when external JavaScript libraries (charts, maps, rich text editors) own the DOM inside — djust will skip all patches on that element and its children, leaving full control to the library. Pass dj-update="ignore" to prevent VDOM-library conflicts.
<div dj-update="ignore" id="chart">...</div>
dj-hook
Bind a JS lifecycle hook (mounted/updated/destroyed).
Bind a JavaScript lifecycle hook (mounted, updated, destroyed) to an element for integrating third-party libraries. The hook name is matched against window.djust.hooks registry; callbacks fire when the element enters the DOM, re-renders, or leaves. Essential for Chart.js, maps, editors — use instead of inline scripts to survive SPA navigation.
<div dj-hook="chart" id="chart">...</div>
dj-mutation
Fire a server event when the DOM mutates (MutationObserver). Attribute or child change on the element
Fires on
Attribute or child change on the element
Handler receives
mutation, attrs, added, removed
Bridges third-party JS (charts, maps, editors) that mutate the DOM outside djust's control. dj-mutation-attr lists which attributes to watch and dj-mutation-debounce (ms, default 150) coalesces bursts. A cancelable dj-mutation-fire CustomEvent bubbles first — calling preventDefault() skips the server call.
Template
<div dj-mutation="handle_change"
dj-mutation-attr="class,style"></div>
Handler
@event_handler()
def handle_change(self, mutation: str = "", attrs: list = None,
added: int = 0, removed: int = 0, **kwargs):
# mutation is 'attributes' or 'childList'
...
Companion attributes
| dj-mutation-attr="a,b" | Comma-separated list of attributes to observe |
| dj-mutation-debounce="ms" | Coalesce mutation bursts into one event (default 150ms) |
Data passing
Sending values to handlersGet values from the template into your Python handler. Plain data-* attributes arrive as typed kwargs (coerced from your type hints); dj-value-* attaches extra values to non-form events; dj-target scopes a DOM update to a specific selector.
data-*
Coerced to native type (bool/int/float/str) and passed as kwargs.
HTML data-* attributes are automatically extracted from the triggering element, converted to kwargs, and passed to the handler. Dashes in attribute names convert to underscores, so data-item-id becomes item_id. Type coercion rules: strings "true", "1", "yes", "on" become bool True; strings "false", "0", "no", "off", and "" become bool False; numeric strings become int or float, all others stay as str. Use type hints in the handler for automatic coercion, or add type suffixes like data-count:int to coerce client-side before sending.
<button dj-click="pick" data-id="{{ item.id }}">
dj-value-*
Extra value kwargs without the data- prefix.
Similar to data-* attributes but with dj-value- prefix instead of data-. Dashes convert to underscores just like data-*. When both data-* and dj-value-* attributes are present on the same element, dj-value-* takes precedence. Use this form to avoid collisions with third-party libraries that also use data-* attributes, or to keep event parameters visually separate from semantic HTML data attributes.
<button dj-click="h" dj-value-mode="edit">
dj-target
Scoped DOM updates via CSS selector.
Restricts the server re-render to update only a specific DOM element (identified by CSS selector) instead of the entire dj-root region. Scopes the VDOM diff to the target selector. Useful for reducing re-render scope on specific regions.
<button dj-click="x" dj-target="#row-{{ id }}">
Animation & transitions
Enter / leave / reorder motionDeclarative CSS motion driven by VDOM changes — no JS animation code. dj-transition plays enter animations, dj-remove delays removal until an exit animation finishes, dj-transition-group wires both onto a list's children, and dj-flip smoothly animates keyed items on reorder.
dj-transition
Declarative CSS enter transition via phased classes. Element enters DOM / attr changes
Fires on
Element enters DOM / attr changes
Handler receives
Client-only — no handler
Applies three space-separated class tokens in phases: the start class synchronously, then the active and end classes on the next animation frame to trigger the CSS transition. On transitionend the active class is removed and the end class persists, with a 600ms fallback cleanup. Changing the attribute value re-runs the whole sequence.
Template — fade in over 300ms
<div dj-transition="opacity-0 transition-opacity-300 opacity-100">
Hello
</div>
Re-trigger from JS
el.setAttribute('dj-transition',
'scale-0 transition-transform-200 scale-100');
dj-remove
Play a CSS exit transition before the element is removed. A patch would remove the element
Fires on
A patch would remove the element
Handler receives
Client-only — no handler
When a VDOM patch would physically remove an element carrying dj-remove, djust delays removal until the described CSS transition finishes (Phoenix phx-remove parity). Override the wait with dj-remove-duration (ms); a 600ms fallback applies otherwise. If a later patch removes the attribute, the pending removal cancels and the element stays mounted.
Template — fade out before removal
<li id="toast-42"
dj-remove="opacity-100 transition-opacity-300 opacity-0">
Saved!
</li>
With duration override
<li dj-remove="slide-out" dj-remove-duration="500">...</li>
dj-transition-group
Auto-wire enter / leave animations for a list's children. Children are added or removed
Fires on
Children are added or removed
Handler receives
Client-only — no handler
Set on a list container, it automatically applies dj-transition and dj-remove to children from an enter/leave spec — either long form (dj-group-enter / dj-group-leave) or short form ("enter | leave"). By default only the leave spec applies to initial children; add dj-group-appear to animate them on first paint. Children that already carry their own dj-transition / dj-remove are left untouched.
Template — long form (preferred)
<ul dj-transition-group
dj-group-enter="opacity-0 transition-opacity-300 opacity-100"
dj-group-leave="opacity-100 transition-opacity-300 opacity-0">
{% for toast in toasts %}
<li id="toast-{{ toast.id }}">{{ toast.text }}</li>
{% endfor %}
</ul>
Short form
<ul dj-transition-group="fade-in | fade-out">
<li>A</li>
<li>B</li>
</ul>
dj-flip
Smoothly animate keyed children when they reorder. VDOM emits move patches for keyed children
Fires on
VDOM emits move patches for keyed children
Handler receives
Client-only — no handler
Uses the FLIP technique (First, Last, Invert, Play) to animate position changes when keyed children reorder. Children need stable id attributes so the VDOM diff emits move patches; without keys, reorders fall back to delete+insert and FLIP no-ops. Tunable via dj-flip-duration (default 300ms) and dj-flip-easing, and it respects prefers-reduced-motion.
Template
<ul dj-flip>
{% for item in items %}
<li id="item-{{ item.pk }}">{{ item.name }}</li>
{% endfor %}
</ul>
Combined with a transition group
<ul dj-transition-group="fade-in | fade-out" dj-flip>
{% for task in tasks %}
<li id="task-{{ task.pk }}">{{ task.title }}</li>
{% endfor %}
</ul>
Streaming & big lists
Real-time append + virtualized renderingHandle real-time appends and very large lists efficiently. dj-stream marks a target the server pushes tokens into (ideal for LLM output); dj-virtual renders only the visible window of a huge list, keeping the DOM small. Both pair with matching View API streaming methods.
dj-stream
Mark an element as a target for real-time streamed updates. Server calls a stream_* method
Fires on
Server calls a stream_* method
Handler receives
Client-only — server pushes via StreamingMixin
Names an element (dj-stream="response") so the server can target it with streaming operations from StreamingMixin — stream_text(), stream_insert(), stream_start(), stream_done(), and more. dj-stream-mode ("append", "replace", or "prepend") sets the default insertion mode. Rapid updates auto-batch to 60fps, and overflow containers auto-scroll when the user is near the bottom — ideal for LLM token streaming.
Template — token-by-token target
<div dj-stream="response" dj-stream-mode="append"
class="message assistant"></div>
View
from djust.streaming import StreamingMixin
from djust import LiveView
class ChatView(StreamingMixin, LiveView):
@event_handler()
async def send(self, content="", **kwargs):
await self.stream_start("response")
async for token in llm_stream(content):
await self.stream_text("response", token)
await self.stream_done("response")
Companion attributes
| dj-stream-mode="append" | Default insertion mode for stream_text (append / replace / prepend) |
dj-virtual
Render only the visible window of a huge list. Mount + scroll (RAF-batched)
Fires on
Mount + scroll (RAF-batched)
Handler receives
Client-only — no handler
Renders thousands of rows while keeping only the visible window (plus overscan) in the DOM, offsetting with translateY. Requires dj-virtual-item-height and a container with a fixed height and overflow:auto. Set dj-virtual-variable-height (with dj-virtual-estimated-height) for rows of differing heights. Element identity is preserved across scroll, so dj-hook mounts and dj-model bindings survive.
Template — fixed-height rows
<div dj-virtual="rows"
dj-virtual-item-height="48"
dj-virtual-overscan="5"
style="height: 600px; overflow: auto;">
{% for row in rows %}
<div id="row-{{ row.id }}">{{ row.label }}</div>
{% endfor %}
</div>
Template — variable-height rows
<div dj-virtual
dj-virtual-variable-height
dj-virtual-estimated-height="60"
style="height: 600px; overflow: auto;">
{% for item in items %}
<div>{{ item.content }}</div>
{% endfor %}
</div>
Companion attributes
| dj-virtual-item-height="px" | Fixed row height in pixels (required for fixed-height mode) |
| dj-virtual-overscan="N" | Extra rows rendered above/below the viewport (default 3) |
| dj-virtual-variable-height | Measure rows with ResizeObserver instead of a fixed height |
| dj-virtual-estimated-height="px" | Placeholder height for unmeasured variable-height rows (default 50) |
File uploads
Chunked uploads, drag-drop, progressChunked file uploads over the WebSocket. dj-upload binds a file input to a server-configured slot; companion attributes add drag-and-drop zones, live image previews, and per-file progress bars. Files are validated server-side and read via consume_uploaded_entries().
dj-upload
Bind a file input to a server-configured upload slot. User selects or drops files
Fires on
User selects or drops files
Handler receives
Files via self.consume_uploaded_entries(slot)
Binds a file input to a named upload slot declared on the server with allow_upload(). The accept and multiple attributes are set automatically from that config. Files are split into 64KB chunks and sent as binary WebSocket frames; the handler reads them via consume_uploaded_entries(), which yields UploadEntry objects (client_name, client_size, file, ...). The companion attributes below build the drag-drop, preview, and progress UI for the same slot.
Template — input, preview, progress
<form dj-submit="save_avatar">
<input type="file" dj-upload="avatar">
<div dj-upload-preview="avatar"></div>
<div dj-upload-progress="avatar"></div>
<button type="submit">Save</button>
</form>
View + handler
from djust.uploads import UploadMixin
from django.core.files.storage import default_storage
class ProfileView(UploadMixin, LiveView):
def mount(self, request, **kwargs):
self.allow_upload('avatar',
accept='.jpg,.png', max_file_size=5_000_000)
@event_handler()
def save_avatar(self, **kwargs):
for entry in self.consume_uploaded_entries('avatar'):
default_storage.save(entry.client_name, entry.file)
Companion attributes
| dj-upload-drop="slot" | Turn a container into a drag-and-drop zone (adds .upload-dragover during drag) |
| dj-upload-preview="slot" | Auto-populated container of image thumbnails for selected files |
| dj-upload-progress="slot" | Auto-populated container of per-file progress bars |