Skip to content

Fix: outerHTML swap with body fragment corrupts non-body targets - #3983

Merged
1cg merged 2 commits into
bigskysoftware:four-devfrom
MichaelWest22:body-partial-fix
Aug 28, 2026
Merged

Fix: outerHTML swap with body fragment corrupts non-body targets#3983
1cg merged 2 commits into
bigskysoftware:four-devfrom
MichaelWest22:body-partial-fix

Conversation

@MichaelWest22

@MichaelWest22 MichaelWest22 commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Description

When a server response contains <body> as the first element (e.g., full HTML documents from frameworks like Drupal), htmx incorrectly upgrades outerHTML to outerSync regardless of the target element. This causes:

  • Target element loses its id and other attributes
  • <body>'s attributes get copied onto the target
  • DOM corruption for non-body targets

Example

<!-- Page -->
<div id="widget" class="my-class" hx-get="/api" hx-swap="outerHTML">Old</div>

<!-- Response -->
<body class="page-class"><p>New content</p></body>

<!-- Before fix (broken): widget's id removed, body attrs copied -->
<div class="page-class"><p>New content</p></div>

<!-- After fix: body stripped, inner content replaces target -->
<p>New content</p>

Fix

// Before (buggy)
if (fragment.firstElementChild?.tagName === 'BODY') {
    if (swapStyle === 'outerHTML') swapStyle = 'outerSync';
    else if (!swapStyle.startsWith('outer')) swapSpec.strip = true;
}

// After (fixed)
if (fragment.firstElementChild?.tagName === 'BODY') {
    const keepBody = target === document.body && swapStyle.startsWith('outer')
    if (keepBody && swapStyle === 'outerHTML') swapStyle = 'outerSync'
    swapSpec.strip ??= !keepBody
}

Logic

  1. Only upgrade outerHTMLouterSync when target === document.body
  2. Strip body wrapper for everything except outer* styles targeting document.body
  3. Respect user's explicit strip modifier setting via ??=

Tests

Added 3 tests to test/tests/attributes/hx-swap.js:

  • outerHTML with body fragment on non-body target strips body wrapper
  • outerHTML with full HTML document on non-body target strips body wrapper
  • innerHTML with body fragment strips body wrapper

Checklist

  • I have read the contribution guidelines
  • I have targeted this PR against the correct branch (master for website changes, dev for
    source changes)
  • This is either a bugfix, a documentation update, or a new feature that has been explicitly
    approved via an issue
  • I ran the test suite locally (npm run test) and verified that it succeeded

@MichaelWest22 MichaelWest22 added bug Something isn't working htmx 4 Issues specific to htmx version 4 labels Aug 28, 2026
@1cg
1cg merged commit 506bbc9 into bigskysoftware:four-dev Aug 28, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working htmx 4 Issues specific to htmx version 4

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants