feat: allow declarations in the template
#18282
Closing issue
Describe the problem
Right now we have {@const ...}, which is a) weird, b) inconsistent with $derived(...), and c) limiting.
Describe the proposed solution
Instead of this...
{#each boxes as box}
{@const area = box.width * box.height}
{box.width} * {box.height} = {area}
{/each}
...we could do this:
{#each boxes as box}
{let area = $derived(box.width * box.height)}
{box.width} * {box.height} = {area}
{/each}
Or if you want it to be read-only, use const instead. You get the idea.
This would also solve a limitation that people periodically encounter — the fact that you can't create local state. This is particularly irksome if a snippet needs to do something stateful, since you now need to convert it to a component (including carefully moving over any CSS that it uses):
{#snippet counter()}
{let count = $state(0)}
<button onclick={() => count += 1}>
clicks: {count}
</button>
{snippet}
{@render counter()}
{@render counter()}
{@render counter()}
Naturally, $state.raw and $derived.by would also be supported, as would normal non-state declarations. We could also allow declarations with multiple declarators:
{let a = 1, b = 2, c = 3}
Migrating existing uses of {@const ...} in runes-mode components would be trivial...
{@const x = y}
{const x = $derived(y)}
...and we could deprecate it at the same time.
This is more powerful, looks nicer, is one less bit of weird non-JavaScript syntax, and is something that would have come in handy a number of times recently.
Scoping would work the same as it does in snippets, i.e. bounded by the parent item (whether that's an element, block or whatever). So this would be possible...
<div>
{let a = 1}
{a}
</div>
<div>
{let a = 1}
{a}
</div>
...but this would be an error because the declaration is duplicated in the same scope:
<div>
{let a = 1}
{a}
{let a = 1}
{a}
</div>
Declarations should precede usage. We probably don't want to allow var since it would be confusing to have different scoping rules, and different scoping rules are the only thing that distinguishes var from let.
We probably do want using, at least eventually (see #16192).
Importance
nice to have
Pull request
Allows {let/const ...} declarations in all places (and more) where we already allow {@const ...} (which will eventually get deprecated in favor of this new feature).
Closes: #16490
Companion PRs:
Info
🦋 Changeset detected
Latest commit: a283cf7
The changes in this PR will be included in the next version bump.
This PR includes changesets to release 1 package
| Name | Type |
|---|---|
| svelte | Minor |
Not sure what this means? Click here to learn what changesets are.
Click here if you're a maintainer who wants to add another changeset to this PR
pnpm add https://pkg.pr.new/svelte@18282I don't remember where I made this comment previously, but I don't think we should allow var. And maybe not function either, at least not as a declaration. What are scoping rules going to be on those?
Mhm yeah it might be better now that I'm thinking about it. Main reason that we can't guarantee the scope of the surrounding block. E.g. the if block scope might be different for if-else-chains (either porous because real if-else or not because functions) depending on non-obvious rules.
I think we should disallow semicolons: #18312. Other than that, and the (I'm pretty sure?) leftover FunctionDeclaration code, this looks excellent. Very pleased to finally have this feature
Allows `{let/var/const/function ...}` declarations in all places where we already allow `{@const ...}` (which will eventually get deprecated in favor of this new feature).Update documentation/docs/03-template-syntax/11-declaration-tags.md
• May 27, 2026, 8:08 PMputting let first makes it slightly easier to disambiguate with legacy const tags
• May 27, 2026, 9:08 PMUpdate packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js
• May 27, 2026, 9:22 PMMerge branch 'declaration-tags' of github.com:sveltejs/svelte into declaration-tags
• May 27, 2026, 9:24 PMI think we should disallow semicolons in declaration tags, because syntactic ambiguity is always unfortunate and `;}` (the little-used 'winking grinch' emoticon) looks pretty ugly. We should _definitely_ remove it from code generated with `print`, even though it unfortunately requires us to duplicate some of the logic in the `ts` printer.
Pro tip: You can prefix GitHub URLs of issues, PRs or discussions with svcl.dev/ to view them on this page! Also try it on a GitHub release ;)