Skip to content

[6.x] Include Tag - #15182

Open
JohnathonKoster wants to merge 2 commits into
statamic:6.xfrom
JohnathonKoster:feat/antlers-include-tag
Open

[6.x] Include Tag#15182
JohnathonKoster wants to merge 2 commits into
statamic:6.xfrom
JohnathonKoster:feat/antlers-include-tag

Conversation

@JohnathonKoster

@JohnathonKoster JohnathonKoster commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #8175
Fixes #10703
Fixes #11486
Fixes #12709

Overview

This PR adds a new include tag: a strictly-scoped alternative to partial for rendering another view, available in both Antlers and Blade.

The partial tag automatically shares every variable from the template using it with the partial being rendered. That convenience is the root cause of a long line of historical scoping issues and little paper-cuts. Variables set inside a partial leaking back out (but only sometimes), parameters and front matter showing up in other partials rendered later on the page, and behavior changing depending on which syntax was used.

Fixing these issues with the existing partial tag would absolutely break a ton of sites, so include is here!

<s-include:cards/author name="Jimothy" :bio="author_bio" />
<s:include:cards/author name="Jimothy" :bio="$authorBio" />

How it differs from partial

partial include
Variables from the surrounding template All of them Only what you pass in
Variables set inside the view Can leak back into the page Stay inside the include
The Cascade (page, globals, etc.) Automatically available Requires cascade="true"
Front matter Visible to other views rendered later Stays with the include
Slots Rendered up front, passed as strings Rendered on demand, and the view can pass data to them

Passing data

You must pass data to the include tag explicitly. Parameters become variables inside the view, and you can spread an entire array using :params. Inside the view, use params to check what was passed in:

<s-include:cards/author :params="author" role="Editor" />

{{# Inside the view: {{ name }}, {{ avatar }}, {{ role }}, {{ params:role }} ... #}}

Use handle_prefix to make prefixed keys like hero_title available as both hero_title and title:

<s-include:hero :params="entry" handle_prefix="hero_" />

Slots

Content between the tag pair becomes the default slot, and you can define named slots with slot:name pairs.

<s-include:modal title="Delete this entry?">
    <s-slot:footer><button>Cancel</button></s-slot:footer>
    <p>This action cannot be undone.</p>
</s-include:modal>
{{# views/modal.antlers.html #}}
<h2>{{ title }}</h2>
<main>{{ slot }}</main>
<footer>{{ slot:footer }}</footer>

Slots only render when the view actually uses them, and the view can pass data back to your slot content. A view can render a slot once per item in a loop, for example:

{{# views/table.antlers.html #}}
<table>{{ rows }}<tr>{{ slot:row :cell="value" }}</tr>{{ /rows }}</table>

{{# Your template: #}}
<s-include:table :rows="rows">
    <s-slot:row><td>{{ cell }}</td></s-slot:row>
</s-include:table>

You can also forward a slot you received on to another include:

{{# views/panel.antlers.html #}}
<section class="panel">{{ slot }}</section>

{{# views/card.antlers.html forwards its slot along: #}}
<s-include:panel :slot="slot" />

{{# Your template: #}}
<s-include:card>Card content</s-include:card>

The Cascade

Included views don't see the Cascade by default. Pass cascade="true" when you want it:

<s-include:site_header cascade="true" />

Conditionals and existence

Use when and unless to control whether anything renders. exists and if_exists work the same way they do on partial:

<s-include:promo :when="show_promo" />
<s-include:if_exists src="cards/{type}" />

The issues

Notes for reviewers

  • When a partial uses handle_prefix, the prefix rewrites variable lookups in everything rendered inside it. The include tag suspends this while it renders, so an enclosing partial's prefix never reaches the included view. Components rendered inside such a partial still inherit the prefix. That is probably unintentional, but changing it could break existing sites, so it's left alone for now.

@JohnathonKoster
JohnathonKoster marked this pull request as draft August 13, 2026 07:31
@JohnathonKoster
JohnathonKoster marked this pull request as ready for review August 13, 2026 17:29
@jackmcdade

Copy link
Copy Markdown
Member

Looking forward to using this! After a review and playing around, got a couple things I want to make sure are intentional:

Blade → Antlers cascade — If I do <s:include:some_antlers_view /> from Blade, it still sees Cascade values even without cascade="true". Antlers → Antlers correctly gets nothing. Feels like isolation only kicks in on the Antlers tag path — is that working as intended?

scope punching out — Using scope inside an include writes straight to Cascade, and you can even smuggle a deferred slot out and render it later. Is that a blessed escape hatch we should call out, or something we want to plug?

if_exists body in Blade — exists uses the tag body as conditional output, but if_exists turns it into a slot. That feel right to you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants