Spec-Zone.ru › htmx

hx-download

Download files with hx-swap='download'

The download extension saves a response as a file download instead of swapping it into the DOM. It streams the response body and fires progress events.

It activates in three ways:

  • hx-swap="download" on the element
  • The server responds with Content-Disposition: attachment (auto-detected, no attribute needed)
  • The server responds with HX-Download: <url> (extension fetches that URL as the download, while the original response is still swapped normally)

Installing

<script src="https://cdn.jsdelivr.net/npm/htmx.org@4.0.0/dist/htmx.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@4.0.0/dist/ext/hx-download.js"></script>

Usage

Explicit swap style

Use hx-swap="download" to always treat the response as a download:

<button hx-get="/files/report.pdf" hx-swap="download">
    Download Report
</button>

Auto-detect via Content-Disposition

If the server returns Content-Disposition: attachment, the extension triggers a download automatically — no hx-swap needed:

<button hx-get="/files/report.pdf" hx-target="#result">
    Download Report
</button>
Content-Disposition: attachment; filename="report.pdf"

HX-Download header

When the backend cannot stream the file directly as the htmx response (e.g. it needs to redirect to a separate download endpoint), return an HX-Download header pointing to the file URL. The extension will fetch that URL as the download while htmx processes the original response body as a normal swap:

<button hx-get="/prepare-download" hx-target="#status">
    Download Report
</button>
HX-Download: /files/report.pdf
Content-Type: text/html

<span>Your download has started...</span>

The <span> is swapped into #status and the file at /files/report.pdf is downloaded simultaneously.

This approach also avoids the indicator/disabled-element cleanup problem that occurs with HX-Redirect pointing to a download URL — indicators are cleared correctly after the original request completes.

Progress Bar

The extension fires events on the source element as the response streams in:

<button hx-get="/files/report.pdf" hx-swap="download">
    Download Report
</button>
<progress id="prog" value="0" max="100"></progress>

<script>
document.body.addEventListener("htmx:download:progress", e => {
    document.getElementById("prog").value = e.detail.percent
})
</script>

The server must send a Content-Length header for percentage-based progress. Without it, percent will be null and only loaded (bytes received) is available.

Events

Event Detail Description
htmx:download:start {total} Response headers received, streaming begins. total is null if no Content-Length
htmx:download:progress {loaded, total, percent} Fired for each chunk received. percent is null if no Content-Length
htmx:download:complete {filename, size} File download triggered in the browser

Notes

  • Binary files (PDFs, images, archives) are handled correctly.
  • The filename is extracted from the Content-Disposition header (filename or filename*=UTF-8''...), falling back to the URL’s last path segment.
  • Cancellation works automatically via htmx’s built-in request abort handling.
  • For HX-Download, indicators and disabled elements are cleared correctly after the original request completes — the secondary fetch runs independently.

Licensed under the Zero-Clause BSD License.
https://four.htmx.org/extensions/hx-download/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API