Shubbi's avatar

Resolved AlpineJs + Ajax plugin $slot-issue: not replacing x-target

I've encountered an issue that took me hours to resolve - and for me, it was a weird one. I just wanna share it and hopefully someone (1) can explain it, or/and (2) maybe this post will help someone to find a solution to the same issue.

My layout component file is a simple one, without any clutter it's structured like this:

<html>
<head>
// scripts and styles
</head>
<body>
@include('_drawer')
{{ $slot }}
</body>
</html>

The included drawer file is also a simple html file used to load async content into via JavaScript, but that file is not used in this example - it's just idle/included. BUT, it has something that should not be there {{ $slot }}. That variable was something of a legacy from previous version of the application and that should have been removed:

<div id="drawer" class="hidden">{{ $slot }}</div>

The layout file is used like this:

<x-layout>
  <form x-target="risks-list" method="post" action="/search">
    @csrf
    <input type="text" name="keyword" />
    <button type="submit">Search</button>
  </form>
  <div id="risks-list"></div>
</x-layout>

When the form is submitted, the Ajax response is pure HTML (without any layout file):

<div id="risks-list">New Content</div>

Expected to happen

The old #risks-list should be replaced with the new one returned from the Ajax request.

What actually happened

The #risks-list is not replaced. Instead, the HTML response is appended to #drawer where {{ $slot }} is.

How to resolve it

Well, I removed {{ $slot }} and all is now find and dandy. I just don't understand it. Any guesses on what this might be? I'm just curious.

Tech stack

0 likes
2 replies
LaryAI's avatar
Level 58

You're absolutely right that the stray {{ $slot }} in your _drawer file caused the confusion. Let me explain what happened:

Why Did This Happen?

In Laravel Blade components, whenever you use {{ $slot }} in a component file, the content you pass between the component tags gets inserted there.

When you used:

<x-layout>
  <form x-target="risks-list" ...>...</form>
  <div id="list"></div>
</x-layout>

The part starting from <form ...> to </div> (everything between the <x-layout> tags) is assigned to $slot. If an included (nested or partial) file inside your layout also contains {{ $slot }}, then your content appears multiple times in the rendered HTML—once in the main layout and once in _drawer.

Why did Alpine Ajax plugin Target the Wrong Place?

  • Alpine's Ajax plugin probably scans for a matching DOM element (#risks-list in your example).
  • Since your layout ends up rendering the same content multiple times (inside both <body> and #drawer)—you have multiple #risks-list elements!
  • When Alpine tries to replace #risks-list, depending on plugin logic, it may replace the first occurrence it finds—which could be the one inside your drawer (which is not visible!), not the expected one on the page.
  • This is why you saw new content ending up in your hidden drawer.

Solution Recap

Remove any unintended or duplicate {{ $slot }} from partials or includes that aren't meant to host your main content.

So your _drawer partial should be:

<div id="drawer" class="hidden"></div>

And your layout should include the slot only where the page content should appear:

<html>
<head>
  <!-- scripts and styles -->
</head>
<body>
  @include('_drawer')
  {{ $slot }}
</body>
</html>

Summary

  • Never include {{ $slot }} in partials or includes unless that partial is directly designed to be a slot host!
  • Always check rendered HTML for duplicate IDs or slot content—this can trip up any client-side code, especially when working with DOM manipulation/replacement.
  • The plugin acted on the first #risks-list it found, which was in the hidden drawer due to the duplicate slot.

This is a classic case of "unexpected Blade slot propagation"—an easy mistake to make, but you've got it resolved!

If you have any more AlpineJS or Blade questions, feel free to ask.

Shubbi's avatar

Oh my goodness. I think I need to get myself some sleep - or another job. Of course...the content is being rendered two times.

Thanks.

Please or to participate in this conversation.