Home Features Docs Blog Philosophy Examples FAQ Live Demo Hosting
API Reference

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 bindings

Bind 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.
# dj-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

HTML
<button dj-click="delete" data-item-id="{{ item.id }}">Delete</button>

Handler

Python
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)

HTML
<button dj-click="set_period('month')">Monthly</button>
dj-submit Submit a form. Named fields arrive as handler kwargs.
# dj-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

HTML
<form dj-submit="save_form">
    {% csrf_token %}
    <input name="title" value="{{ title }}" />
    <button type="submit">Save</button>
</form>

Handler

Python
@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.
# dj-change

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

HTML
<select dj-change="filter_status">
    <option value="all">All</option>
    <option value="active">Active</option>
</select>

Handler

Python
@event_handler()
def filter_status(self, value: str = "all", **kwargs):
    self.status = value
    self._refresh()
dj-input Fire on every keystroke. Handler gets value=.
# dj-input

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

HTML
<input type="text" dj-input="search"
       dj-debounce="300" value="{{ query }}" />

Handler

Python
@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.
# dj-blur

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

HTML
<input name="email" dj-blur="validate_email" value="{{ email }}">

Handler

Python
@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.
# dj-focus

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

HTML
<input name="email" dj-focus="clear_error" value="{{ email }}">

Handler

Python
@event_handler()
def clear_error(self, value: str = "", **kwargs):
    self.email_error = ""
dj-click-away Fire when user clicks outside this element.
# dj-click-away

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

HTML
<div dj-click-away="close_menu" class="dropdown">
    <!-- menu contents -->
</div>

Handler

Python
@event_handler()
def close_menu(self, **kwargs):
    self.menu_open = False
dj-confirm Show a native confirm dialog before the event is sent.
# dj-confirm

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

HTML
<button dj-click="delete"
        dj-confirm="Delete this item permanently?">
    Delete
</button>

Handler (runs only after confirm)

Python
@event_handler()
def delete(self, **kwargs):
    self.item.delete()
dj-model Two-way binding — auto-syncs self.field_name.
# dj-model

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

HTML
<input type="text" dj-model="search_query">
<input type="checkbox" dj-model="show_archived">

View — just declare the attributes

Python
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.
# dj-copy

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

HTML
<button dj-copy="{{ share_url }}">Copy link</button>

Copy another element's text

HTML
<button dj-copy="#code-block">Copy code</button>

With feedback + server event

HTML
<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 chords

React 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.
# dj-keydown

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.

HTML
<input dj-keydown.enter="submit">
dj-keyup Keyup event. Same modifier support as dj-keydown.
# dj-keyup

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.

HTML
<div dj-keyup.escape="cancel">...</div>
dj-shortcut Bind keyboard chords (ctrl+k, escape, shift+enter).
# dj-shortcut

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.

HTML
<div dj-shortcut="ctrl+k:open_search:prevent">...</div>
dj-window-keydown window keydown. Modifier filtering supported.
# dj-window-keydown

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.

HTML
<div dj-window-keydown.escape="close">...</div>
dj-window-keyup window keyup.
# dj-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.

HTML
<div dj-window-keyup="log">...</div>
dj-window-scroll window scroll (150ms throttle).
# dj-window-scroll

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.

HTML
<div dj-window-scroll="on_scroll">...</div>
dj-window-click window click (catches background clicks).
# dj-window-click

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.

HTML
<div dj-window-click="on_bg_click">...</div>
dj-window-resize window resize (150ms throttle).
# dj-window-resize

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.

HTML
<div dj-window-resize="on_resize">...</div>
dj-document-keydown document keydown (capture-phase).
# dj-document-keydown

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().

HTML
<div dj-document-keydown.escape="close">...</div>
dj-document-keyup document keyup.
# dj-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.

HTML
<div dj-document-keyup="log">...</div>
dj-document-click document click (capture-phase).
# dj-document-click

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.

HTML
<div dj-document-click="on_doc_click">...</div>

Navigation

Page + URL transitions

Move between pages and update the URL without reloading. dj-navigate and dj-patch drive SPA-style transitions over the socket; dj-prefetch warms the cache on hover. Pair these with the View API's live_patch() / live_redirect() and handle_params().

dj-navigate Client-side navigation with history — no full reload.
# dj-navigate

Client-side SPA navigation to a different LiveView over the existing WebSocket connection without a full page reload. The target path is resolved against the auto-derived route map (from your Django URLconf), with browser back/forward and bookmark support working out of the box. Automatically sets aria-current="page" on matching links for styling active nav state. Falls back to a full page reload only if the target path isn't in the route map, which triggers a system check warning.

HTML
<a dj-navigate="/dashboard/">Dashboard</a>
dj-patch Replace dj-root content via AJAX (no full reload).
# dj-patch

Updates the browser URL and re-renders the reactive region (dj-root) without remounting the LiveView or reloading the page. Fires the server-side handle_params() callback with the new query parameters, making it ideal for pagination, filtering, sorting, and tab switching within a view. Use dj-patch instead of dj-click for URL-driven state changes to maintain browser history and bookmarkability.

HTML
<a dj-patch="?page=2">Next</a>
dj-prefetch Prefetch link target on hover / touchstart.
# dj-prefetch

Prefetch the link target proactively on hover (with 65ms debounce) or touchstart to warm the HTTP cache before the user clicks. Works client-only without a server round-trip, uses link rel="prefetch" injection (or fetch fallback), respects Data Saver mode, and deduplicates URLs across the page. Ideal for dashboard nav and result lists where the next click is predictable; not needed for tiny pre-rendered sites or single-page apps with only live_patch navigation.

HTML
<a dj-prefetch href="/dashboard/">Dashboard</a>
dj-sticky-view Keep a LiveView alive across navigation (app-shell widgets).
# dj-sticky-view

Fires on

Render; paired with sticky=True server-side

Handler receives

Client-only — fires sticky lifecycle events

Part of the sticky LiveView pattern for app-shell widgets (audio players, sidebars) that must not flicker on navigation. Set sticky = True on the LiveView and embed with {% live_render ... sticky=True %}; destination layouts declare a re-attachment point with dj-sticky-slot. On live_redirect the state, form values, scroll, focus, and background tasks all survive — it's the same Python instance.

View class

Python
class AudioPlayerView(LiveView):
    sticky = True
    sticky_id = "audio-player"
    template_name = "audio_player.html"

Template — embed + slot

HTML
{# Dashboard — mount the sticky view #}
{% live_render "myapp.views.AudioPlayerView" sticky=True %}

{# Other page — re-attachment point #}
<div dj-sticky-slot="audio-player"></div>

Polling

Recurring server refresh

Refresh 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).
# dj-poll

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.

HTML
<div dj-poll="refresh"></div>

Loading states

Visual feedback during round-trips

Give 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.
# dj-debounce

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".

HTML
<input dj-input="search" dj-debounce="300">
dj-throttle Throttle handler calls to N ms. Per element.
# dj-throttle

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.

HTML
<button dj-click="poll" dj-throttle="500">Refresh</button>
dj-loading Auto class / show / hide / disable during a round-trip.
# dj-loading

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.

HTML
<button dj-click="save" dj-loading.disable>

Submit protection

Disable + lock during submits

Prevent 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.
# dj-disable-with

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.

HTML
<button dj-submit dj-disable-with="Saving...">
dj-lock Block event until server responds (prevents double-fire).
# dj-lock

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.

HTML
<button dj-click="save" dj-lock>

UI feedback

Cloak, scroll, copy, loading bar

Small 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).
# dj-cloak

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.

HTML
<div dj-cloak>...</div>
dj-scroll-into-view Auto-scroll element into view after render (one-shot).
# dj-scroll-into-view

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.

HTML
<div dj-scroll-into-view="center">...</div>
dj-copy-feedback Button text shown for 2s after dj-copy success.
# dj-copy-feedback

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.

HTML
<button dj-copy dj-copy-feedback="Done!">
dj-copy-class CSS class added for 2s after dj-copy success.
# dj-copy-class

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.

HTML
<button dj-copy dj-copy-class="btn-success">
dj-copy-event Server event fired after successful dj-copy.
# dj-copy-event

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.

HTML
<button dj-copy dj-copy-event="copied">
dj-dialog Drive a native <dialog> open/close declaratively.
# dj-dialog

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.

HTML
<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 recovery

Hook 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.
# dj-mounted

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.

HTML
<div dj-mounted="on_ready" dj-value-id="42">...</div>
dj-auto-recover Fire on WebSocket reconnect with form values + data-*.
# dj-auto-recover

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.

HTML
<div dj-auto-recover="restore_state">...</div>
dj-no-recover Opt a field out of automatic form recovery on reconnect.
# dj-no-recover

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.

HTML
<input dj-change="on" dj-no-recover>
dj-track-static Warn or reload when a tracked asset changes on deploy.
# dj-track-static

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

HTML
{% load live_tags %}

<script {% djust_track_static %}
        src="{% static 'js/app.abc123.js' %}"></script>

Auto-reload variant

HTML
<script dj-track-static="reload" src="..."></script>

VDOM identity

Reactive region + keying + hooks

Control 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.
# dj-view

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.

HTML
<body dj-view="{{ dj_view_id }}">
dj-root Marks the reactive subtree. Only HTML inside is diffed.
# dj-root

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.

HTML
<div dj-root>...</div>
dj-key Stable list-item identity for optimal VDOM diffing.
# dj-key

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.

HTML
<li dj-key="{{ item.id }}">...</li>
data-key Same as dj-key, no prefix. Use whichever you prefer.
# data-key

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.

HTML
<div data-key="{{ item.id }}">...</div>
dj-update Opt out of patching. Use for charts / rich text / external JS.
# dj-update

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.

HTML
<div dj-update="ignore" id="chart">...</div>
dj-hook Bind a JS lifecycle hook (mounted/updated/destroyed).
# dj-hook

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.

HTML
<div dj-hook="chart" id="chart">...</div>
dj-mutation Fire a server event when the DOM mutates (MutationObserver).
# dj-mutation

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

HTML
<div dj-mutation="handle_change"
     dj-mutation-attr="class,style"></div>

Handler

Python
@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 handlers

Get 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.
# data-*

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.

HTML
<button dj-click="pick" data-id="{{ item.id }}">
dj-value-* Extra value kwargs without the data- prefix.
# dj-value-*

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.

HTML
<button dj-click="h" dj-value-mode="edit">
dj-target Scoped DOM updates via CSS selector.
# dj-target

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.

HTML
<button dj-click="x" dj-target="#row-{{ id }}">

Animation & transitions

Enter / leave / reorder motion

Declarative 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.
# dj-transition

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

HTML
<div dj-transition="opacity-0 transition-opacity-300 opacity-100">
  Hello
</div>

Re-trigger from JS

HTML
el.setAttribute('dj-transition',
  'scale-0 transition-transform-200 scale-100');
dj-remove Play a CSS exit transition before the element is removed.
# dj-remove

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

HTML
<li id="toast-42"
    dj-remove="opacity-100 transition-opacity-300 opacity-0">
  Saved!
</li>

With duration override

HTML
<li dj-remove="slide-out" dj-remove-duration="500">...</li>
dj-transition-group Auto-wire enter / leave animations for a list's children.
# dj-transition-group

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)

HTML
<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

HTML
<ul dj-transition-group="fade-in | fade-out">
  <li>A</li>
  <li>B</li>
</ul>
dj-flip Smoothly animate keyed children when they reorder.
# dj-flip

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

HTML
<ul dj-flip>
  {% for item in items %}
    <li id="item-{{ item.pk }}">{{ item.name }}</li>
  {% endfor %}
</ul>

Combined with a transition group

HTML
<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 rendering

Handle 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.
# dj-stream

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

HTML
<div dj-stream="response" dj-stream-mode="append"
     class="message assistant"></div>

View

Python
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.
# dj-virtual

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

HTML
<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

HTML
<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, progress

Chunked 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.
# dj-upload

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

HTML
<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

Python
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