Skip to content
djust/docs
Appearance
Mode
djust.org →
Browse documentation

API Reference · 60 entries · dj-*

Directive reference

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

10 entries

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

HTML Template — pass data
<button dj-click="delete" data-item-id="{{ item.id }}">Delete</button>
Python 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]
HTML Inline args (no data-* needed)
<button dj-click="set_period('month')">Monthly</button>
dj-submit Submit a form. Named fields arrive as handler kwargs.
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.

HTML Template
<form dj-submit="save_form">
    {% csrf_token %}
    <input name="title" value="{{ title }}" />
    <button type="submit">Save</button>
</form>
Python 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.
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.

HTML Template
<select dj-change="filter_status">
    <option value="all">All</option>
    <option value="active">Active</option>
</select>
Python 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=.
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.

HTML Template — debounced live search
<input type="text" dj-input="search"
       dj-debounce="300" value="{{ query }}" />
Python 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.
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.

HTML Template
<input name="email" dj-blur="validate_email" value="{{ email }}">
Python 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.
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.

HTML Template
<input name="email" dj-focus="clear_error" value="{{ email }}">
Python Handler
@event_handler()
def clear_error(self, value: str = "", **kwargs):
    self.email_error = ""
dj-click-away Fire when user clicks outside this 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.

HTML Template
<div dj-click-away="close_menu" class="dropdown">
    <!-- menu contents -->
</div>
Python Handler
@event_handler()
def close_menu(self, **kwargs):
    self.menu_open = False
dj-confirm Show a native confirm dialog before the 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.

HTML Template
<button dj-click="delete"
        dj-confirm="Delete this item permanently?">
    Delete
</button>
Python Handler (runs only after confirm)
@event_handler()
def delete(self, **kwargs):
    self.item.delete()
dj-model Two-way binding — auto-syncs self.field_name.
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.

HTML Template — no handler needed
<input type="text" dj-model="search_query">
<input type="checkbox" dj-model="show_archived">
Python 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.
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.

HTML Copy literal text
<button dj-copy="{{ share_url }}">Copy link</button>
HTML Copy another element's text
<button dj-copy="#code-block">Copy code</button>
HTML With feedback + server event
<button dj-copy="{{ api_key }}"
        dj-copy-feedback="Copied!"
        dj-copy-event="key_copied">Copy key</button>
OptionDescription
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

11 entries

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.

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 Basic usage
<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.

HTML Basic usage
<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.

HTML Basic usage
<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.

HTML Basic usage
<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.

HTML Basic usage
<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.

HTML Basic usage
<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.

HTML Basic usage
<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.

HTML Basic usage
<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().

HTML Basic usage
<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.

HTML Basic usage
<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.

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

Navigation

4 entries

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.

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 Basic usage
<a dj-navigate="/dashboard/">Dashboard</a>
dj-patch Replace dj-root content via AJAX (no full reload).

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 Basic usage
<a dj-patch="?page=2">Next</a>
dj-prefetch Prefetch link target on hover / touchstart.

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 Basic usage
<a dj-prefetch href="/dashboard/">Dashboard</a>
dj-sticky-view Keep a LiveView alive across navigation (app-shell widgets).
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. (Intent: keep a widget alive when navigating between pages; the widget does not restart, reset, or lose state on page change; persists across navigation; app-shell pattern.)

Python View class
class AudioPlayerView(LiveView):
    sticky = True
    sticky_id = "audio-player"
    template_name = "audio_player.html"
HTML Template — embed + slot
{# 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

1 entries

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

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 Basic usage
<div dj-poll="refresh"></div>

Loading states

3 entries

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.

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 Basic usage
<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.

HTML Basic usage
<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.

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

Submit protection

2 entries

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.

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 Basic usage
<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.

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

UI feedback

6 entries

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

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 Basic usage
<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.

HTML Basic usage
<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.

HTML Basic usage
<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.

HTML Basic usage
<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.

HTML Basic usage
<button dj-copy dj-copy-event="copied">
dj-dialog Drive a native <dialog> open/close declaratively.
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 Template
<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

4 entries

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.

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 Basic usage
<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.

HTML Basic usage
<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.

HTML Basic usage
<input dj-change="on" dj-no-recover>
dj-track-static Warn or reload when a tracked asset changes on deploy.
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.

HTML Template — with the convenience tag
{% load live_tags %}

<script {% djust_track_static %}
        src="{% static 'js/app.abc123.js' %}"></script>
HTML Auto-reload variant
<script dj-track-static="reload" src="..."></script>

VDOM identity

9 entries

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.

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 Basic usage
<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.

HTML Basic usage
<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.

HTML Basic usage
<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.

HTML Basic usage
<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.

HTML Basic usage
<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.

HTML Basic usage
<div dj-hook="chart" id="chart">...</div>
dj-mutation Fire a server event when the DOM mutates (MutationObserver).
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.

HTML Template
<div dj-mutation="handle_change"
     dj-mutation-attr="class,style"></div>
Python Handler
@event_handler()
def handle_change(self, mutation: str = "", attrs: list = None,
                  added: int = 0, removed: int = 0, **kwargs):
    # mutation is 'attributes' or 'childList'
    ...
OptionDescription
dj-mutation-attr="a,b" Comma-separated list of attributes to observe
dj-mutation-debounce="ms" Coalesce mutation bursts into one event (default 150ms)
dj-hook-value-* Typed, JSON-coerced data passing to hooks via this.values.

Exposes dj-hook-value-<kebab> attributes as this.values.<camelCase> inside the hook (djust 1.1+). Coercion is JSON-first with raw-string fallback: arrays, objects, numbers, and booleans just work; a literal string that looks like JSON is written JSON-quoted (dj-hook-value-code='"007"'). Reads are LIVE — after a server re-render updates the attribute, updated() sees the fresh value with no re-mount. Read-only (assignment throws) and named-key access only. Replaces hand-rolled JSON.parse(this.el.dataset.*) parsing. Distinct from dj-value-* (event params sent to the server).

HTML Template
<canvas dj-hook="Chart"
        dj-hook-value-points="[1,2,3]"
        dj-hook-value-animated="true"
        dj-hook-value-title="Sales"></canvas>
JavaScript Hook — typed reads, live after every re-render
mounted() {
    this.values.points     // [1, 2, 3]  — Array
    this.values.animated   // true       — Boolean
    this.values.title      // "Sales"    — string fallback
},
updated() {
    // the morph already updated the attribute; reads are live
    this.chart.data.datasets[0].data = this.values.points;
    this.chart.update();
}
dj-hook-target Name descendant elements for this.target() / this.targets() lookup.

Marks descendants of a dj-hook element so the hook can find them without unique IDs or querySelector gymnastics (djust 1.1+): this.target('name') returns the first match (Element or null), this.targets('name') all matches (Element[]). Lookups are live subtree queries scoped to the hook's own element. Descendants of a nested hook are not excluded — prefer unique target names when nesting. Distinct from dj-target (scoped-update targeting for events).

HTML Template
<div dj-hook="Player">
    <video dj-hook-target="video"></video>
    <button dj-hook-target="mute">🔇</button>
</div>
JavaScript Hook
this.target('video')   // first match (Element or null)
this.targets('mute')   // all matches (Element[])

Data passing

3 entries

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.

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 Basic usage
<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.

HTML Basic usage
<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.

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

Animation & transitions

4 entries

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

HTML Template — fade in over 300ms
<div dj-transition="opacity-0 transition-opacity-300 opacity-100">
  Hello
</div>
HTML 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.
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.

HTML Template — fade out before removal
<li id="toast-42"
    dj-remove="opacity-100 transition-opacity-300 opacity-0">
  Saved!
</li>
HTML 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.
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.

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

HTML Template
<ul dj-flip>
  {% for item in items %}
    <li id="item-{{ item.pk }}">{{ item.name }}</li>
  {% endfor %}
</ul>
HTML 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

2 entries

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

HTML Template — token-by-token target
<div dj-stream="response" dj-stream-mode="append"
     class="message assistant"></div>
Python 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")
OptionDescription
dj-stream-mode="append" Default insertion mode for stream_text (append / replace / prepend)
dj-virtual Render only the visible window of a huge list.
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.

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

1 entries

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

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