sveltejs/svelte

Repository tracker

Pull requests

feat: add parent hierarchy to __svelte_meta objects at dev time

#16255
today • Jul 1, 2025
This adds a parent property to the __svelte_meta properties that are added to elements at dev time. This property represents the closest non-element parent the element is related to. For example for {#if ...}<div>foo</div>{/if} the parent of the div would be the line/column of the if block. The parent is recursive and goes upwards (through component boundaries) until the root component is reached, which has no parent.
image
part of #11389 - I believe the hierarchy structure along with the line/column/block type info will make things much easier for tooling wanting to represent hierarchies. This deliberately doesn't include any firing of events yet, that can happen later once we've figured out at which times we want/need it (my guess is we want it to fire it exactly when those parent relationships are established). cc @jacob-8 Before submitting the PR, please make sure you do the following It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs Prefix your PR title with feat:, fix:, chore:, or docs:. This message body should clearly illustrate what problems it solves. Ideally, include a test that fails without this PR but passes with it. If this PR changes code within packages/svelte/src, add a changeset (npx changeset). Tests and linting Run the tests with pnpm test and lint the project with pnpm lint

feat: add print(...) function

#16188
yesterday • Jun 30, 2025
Over on https://github.com/sveltejs/esrap/pull/68 we're working on making esrap pluggable, so that it can be used to print any AST composed of { type: string, ... } nodes rather than just estree and its TypeScript extensions. That includes Svelte ASTs. The main motivation for exposing this is so that we can make it easier to write preprocessors. Historically, Svelte exposed a preprocess API, but it's all strings and duct tape, and it's difficult to integrate preprocessors cleanly with bundlers as we've seen with enhanced-img. When the preprocessor API was introduced, things looked very different. Preprocessing was necessary to support things like TypeScript and Sass. Today, TypeScript is supported natively, and CSS is sufficiently capable that Sass is little more than a historical curiosity. In the long term, we'd therefore like to move away from the preprocessor API in favour of providing more robust lower-level utilities. For example enhanced-img, which can only be used in a Vite context, really should just be a Vite plugin: import { parse, print } from 'svelte/compiler'; import { walk } from 'zimmerframe'; function transform(code) { const ast = parse(code); const transformed = walk(ast, null, { RegularElement(node, context) { if (node.name !== 'enhanced:img') return; // ... } }); return print(ast); } There are other potential uses, such as migrations or sv add or having a 'format' button in the playground. As a side-effect, quality of compiler output will be slightly better in certain cases, such as when encountering comments inside nodes. (Today, we attach leadingComments and trailingComments to each node, but this is a brittle and not-very-widely-used convention. The new esrap API expects an array of comments to be passed instead.) This functionality already exists in svelte-ast-print (thank you @xeho91!), but having it in core means we can re-use the esrap version that's already installed alongside svelte, and will help ensure it stays current with new features. Before submitting the PR, please make sure you do the following It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs Prefix your PR title with feat:, fix:, chore:, or docs:. This message body should clearly illustrate what problems it solves. Ideally, include a test that fails without this PR but passes with it. If this PR changes code within packages/svelte/src, add a changeset (npx changeset). Tests and linting Run the tests with pnpm test and lint the project with pnpm lint

feat: support using, link top-level using declarations in components to lifecycle

#16192
7 days ago • Jun 25, 2025
Alternative to #16190, with the distinction that using declarations at the top level of a component are only disposed when the component unmounts. I think I prefer this — it's more useful and more intuitive. Demo here. On the server it doesn't transform anything, meaning resources are correctly disposed of once rendering is complete. Obviously all this is of limited use until support is sufficiently widespread, but I don't see any reason to hold it back until then. (The demo above actually works in all browsers, because top-level using gets compiled away for client code, but that doesn't apply to server code or non-top-level using.) Before submitting the PR, please make sure you do the following It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs Prefix your PR title with feat:, fix:, chore:, or docs:. This message body should clearly illustrate what problems it solves. Ideally, include a test that fails without this PR but passes with it. If this PR changes code within packages/svelte/src, add a changeset (npx changeset). Tests and linting Run the tests with pnpm test and lint the project with pnpm lint

fix: update indirectly-affected bindings on mutation

#16200
13 days ago • Jun 18, 2025
WIP alternative to #16165. The real fix, I think, is to not use effects for synchronization at all, but rather to invalidate indirect bindings on mutation. In other words in a case like this... <script> export let selected; export let tasks; </script> <select bind:value={selected}> {#each tasks as task} <option value='{task}'>{task.description}</option> {/each} </select> <label> <input type='checkbox' bind:checked={selected.done}> {selected.description} </label> <h2>Pending tasks</h2> {#each tasks.filter(t => !t.done) as task} <p>{task.description}</p> {/each} ...updating selected should also update tasks, because the bindings are linked. I think we might be able to use this mechanism for each blocks as well and end up with simpler compiler code, though I don't have time to finish it right now and my brain needs a rest anyway because this stuff is confusing as hell. Can't wait to be able to delete all this legacy gubbins. Before submitting the PR, please make sure you do the following It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs Prefix your PR title with feat:, fix:, chore:, or docs:. This message body should clearly illustrate what problems it solves. Ideally, include a test that fails without this PR but passes with it. If this PR changes code within packages/svelte/src, add a changeset (npx changeset). Tests and linting Run the tests with pnpm test and lint the project with pnpm lint

feat: add onAnimationFrame lifecycle function

#14594
May 20, 2025
Just an idle thought I had while washing the dishes: should we have an onFrame function? It would mean for example that this demo could be written like this: $effect(() => { const context = canvas.getContext('2d'); onFrame(() => { paint(context, Date.now()); }); }); Alternatively we might want to have some sort of reactive time primitive (other than SvelteDate) so that it could be used in deriveds too. Either way not wedded to it, just an idle thought that I thought was worth a PR Before submitting the PR, please make sure you do the following It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs Prefix your PR title with feat:, fix:, chore:, or docs:. This message body should clearly illustrate what problems it solves. Ideally, include a test that fails without this PR but passes with it. If this PR changes code within packages/svelte/src, add a changeset (npx changeset). Tests and linting Run the tests with pnpm test and lint the project with pnpm lint

docs: update store documentation

#14083
Nov 6, 2024
It doesn't really make sense to have a whole load of documentation on svelte/store on /docs/svelte/stores page and then again on /docs/svelte/svelte-store. The first should describe the store contract at a high level, while the second should detail the things you can import from the module. This is draft because we really need to fix the way overloads are handled first — it's too confusing at the moment

fix: more hydration mismatch stuff

#12791
Aug 10, 2024
follow-up to #12755. Just a failing test for now Before submitting the PR, please make sure you do the following It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs Prefix your PR title with feat:, fix:, chore:, or docs:. This message body should clearly illustrate what problems it solves. Ideally, include a test that fails without this PR but passes with it. Tests and linting Run the tests with pnpm test and lint the project with pnpm lint

Discussions

Asynchronous Svelte

#15845
7 days ago • Jun 24, 2025
This is a long document; get yourself a cup of tea. tl;dr You can now use the await keyword in Svelte — in your <script>, inside $derived expressions, and in your markup — by installing the async branch... npm i https://pkg.pr.new/svelte@async ...and adding the experimental.async option to your svelte.config.js (or wherever you configure Svelte): // svelte.config.js export default { compilerOptions: { experimental: { async: true } }, kit: { // ... } }; You can also try things out in the async playground. You will find bugs! This is in no way production-ready. The PR that accompanies this discussion is at https://github.com/sveltejs/svelte/pull/15844. Background In olden times, we did asynchronous work like fetching data inside onMount or an {#await ...} block. This works but it's quite verbose, and offers no coordination — if two components are both fetching stuff, then they will likely have independently managed error/loading states, often resulting in a janky UI with multiple spinners. Frameworks like SvelteKit (or Remix or pre-App-Router Next.js, etc) offer an alternative: instead of fetching inside the component, we do our asynchronous work outside the component, for example in load functions. This typically results in better user experience (we can server-render the data, preload it, and coordinate everything) but it too has problems — prop-drilling, type shenanigans, coarse-grained invalidation, and logic that's hard to delete because it's often not obvious whether something in your load function is even being used. In recent years component frameworks have explored a third way: putting asynchronous work back inside components, but in a way that is coordinated: React has a variety of primitives — <Suspense>, startTransition, useTransition, use and React Server Components, that together allow you to manage async updates Solid has a createResource API that can be used with <Suspense> and startTransition/useTransition for the same purpose Vue allows you to use await inside a <script setup> in a component inside a <Suspense> boundary, though this experimental API only works for component creation, not subsequent updates We believe Svelte's compiler-centric nature offers us a way to have component-level async work with significantly better ergonomics and fewer drawbacks than existing approaches. Requirements minimal ceremony — we want to just write code, ideally using the await keyword. Avoid framework-specific APIs and idioms to the extent possible flexibility — it should be possible to use asynchronous values anywhere (in the <script>, inside $derived expressions, in the template, in attributes and component props, in control flow like {#if ...} blocks, or wherever else) parallel by default — since expressions in the template can be safely assumed to be pure, sequential await expressions need not result in sequential async work. Similarly, asynchronous work in two sibling components (for example) should happen simultaneously automatic coördination — if foo changes, and await bar(foo) is (or will be) visible on the page, any occurrences of foo in the UI should not update until bar(foo) resolves overlapping updates — a state change that happens while asynchronous work is ongoing should be visible immediately without waiting for the work to complete (unless it results in more async work) We also want this to have minimal impact on performance and memory for existing codebases, and to cause little or no breakage for existing apps. Design In a nutshell: you can now use await in three places that will today result in a syntax error: at the top level of a component <script> in a $derived expression in a template expression (i.e. in a component's markup) We also introduce a pending snippet to <svelte:boundary>, which allows you to provide UI when await expressions inside the boundary are first resolving. For now, await expressions must be inside a boundary with a pending snippet (however deeply nested — for example you might have a single boundary at the very root of your app), though this constraint will likely be relaxed in future. If state is read inside a $derived or template expression with an await, changes to that state will not be reflected in the UI until the expression resolves. For example in a situation like this... <h1>Weather forecast for {city}</h1> <p>{await getWeatherForecast(city)}</p> ...the <h1> will not update until the <p> does. (If getWeatherForecast were to fail, it would activate the nearest error boundary.) To know if an update is currently pending, you can use $effect.pending(): {#if $effect.pending()} <p>loading new data...</p> {/if} If unrelated state changes while an update is pending, it will be visible immediately (assuming it doesn't separately cause asynchronous updates). Use cases The most obvious use case is loading data (which could be as simple as fetch, but in many cases will likely involve yet-to-be-designed utilities for client-server communication), but others include: demo preloading images to avoid jank (<img alt="..." src={await preload('...')}>) demo lazily importing modules and components as needed demo moving expensive computation to a web worker Avoiding waterfalls As much as framework authors like to witter on about rendering performance, the thing that really slows apps down is latency. In some cases you can reduce latency by speeding up your back end, or prefetching content you expect to need, or serving content from somewhere close to the user, but those things aren't always possible. What you can do is mitigate the effect of latency by minimising the number of round trips between the browser and the server. Svelte helps by doing as much work as possible in parallel. For example here... <li>apples {await getPrice('apple')} each</li> <li>bananas {await getPrice('banana')} each</li> <li>canteloupes {await getPrice('canteloupe')} each</li> ...the three calls to getPrice will happen simultaneously, even though they appear in sequence. Similarly, the three wise monkeys will do all their async work together: <WiseMonkey verb="see" /> <WiseMonkey verb="hear" /> <WiseMonkey verb="speak" /> Not all work can be parallelized. Any await expressions inside the <script> run before expressions in the template, and in the sequence you would expect... // `a` will be calculated and _then_ `b` will be calculated let a = $derived(await foo(x)); let b = $derived(await bar(x)); ...though note that if x subsequently updates, a and b will be recomputed in parallel. You can of course avoid the initial waterfall like so: let aPromise = $derived(foo(x)); let bPromise = $derived(bar(x)); let a = $derived(await aPromise); let b = $derived(await bPromise);
A brief digression on 'necessary' waterfalls and server components
While unnecessary waterfalls can generally be prevented by pushing async work as far into the 'leaves' of your application as possible, there are also necessary waterfalls to contend with. For example, you can't use Promise.all here, because you need to know artist.id before you can call getTrackListing: let artist = $derived(await search(query)); let tracks = $derived(await getTrackListing(artist.id)); It's better if you can do both fetches on the server, near your database. There are two ways to solve this: either anticipate the need for the track listing when you do the search... let [artist, tracks] = $derived(await searchAndGetTrackListing(query)); ...or use a mechanism like React Server Components, where the rendering happens on the server. Option 1 has less-than-ideal ergonomics, though it's basically equivalent to SvelteKit's load function. Option 2 is optimal from a waterfall prevention perspective, but we feel RSCs have significant trade-offs. We anticipate that opinionated data-fetching patterns will emerge over time, along with new approaches inspired by RSCs, to solve this problem.
Future work The work presented so far is the first step of a multi-stage process. While it's useful in its current form, more value will be unlocked with later stages: Async SSR Today, server-side rendering is fully synchronous. Because of this, if a <svelte:boundary> with a pending snippet is encountered during SSR, the pending snippet will be rendered. It would be better to render the content instead, but this requires that SSR become an asynchronous operation. In an ideal world we would also be able to stream the result where appropriate. This is complicated by the fact that you ideally need to know what's in the <head> before you get to the <body>. We have some ideas for how to design this sensibly, but it's not a current priority. Once we get to this part, it's likely that the requirement for await expressions to be contained in a <svelte:boundary> will go away. Forking In SvelteKit we preload the code and data necessary for a navigation before it even occurs: when you hover over a link, or tap it, we import any modules needed by the route and begin running whichever load functions need to run. (This behaviour is configurable, of course.) To make this work in a world where asynchronous work happens inside components, we need to be able to pretend that a state change occurred, and do the resulting updates 'off-screen'. The result can either be applied or discarded. Colloquially, we've been describing this as a 'fork'. You could imagine several unresolved forks coexisting simultaneously until one reality is chosen (by the user clicking on one of several links they've been close to interacting with, for example). Most likely, we'll add an API to Svelte that allows frameworks like SvelteKit (but also particularly ambitious application authors) to enter the multiverse. This all sounds very complicated, but in reality it's not all that different to how asynchronous updates already work. Nevertheless, it will take some finagling to get right, and in the interests of shipping we haven't included this work in the current PR. Client-server utilities Unless we want to create server endpoints for everything we want to fetch inside a component, we'll need tools for interacting with the server. Those tools will need to consider security, HTTP caching, mutations and invalidation, optimistic UI, batching, streaming, custom serialization, type safety and lots of other things. We're at the early stages of figuring out what this all looks like, but we're very confident that the foundation we're laying here will allow us to design something really good. Improving SvelteKit Anticipating everyone's reaction: no, we're not going to suddenly make you rewrite all your SvelteKit apps. But taking all the other stuff together, a picture starts to emerge: a version of SvelteKit that's a thinner layer on top of Svelte. For example, as proud as we are of our zero-effort type safety, it's something you no longer need if you're fetching data directly inside your component. Meanwhile the framework internals could potentially get simpler, because we'd be able to rely on common primitives inside Svelte itself. (Today, we can't reuse that much code between the server and client portions of SvelteKit, and we rely a lot on virtual modules and generated code. Some of that is for historical reasons, but some is necessary because anything asynchronous has to happen outside Svelte.) It seems likely that a lot of interesting new possibilities will open up as a result of this work, and it seems at least plausible that we'll collectively find ourselves drifting away from primitives like load. Exactly what this would look like is to be determined, and it's something that we'll figure out together as a community. Nuances No startTransition/useTransition If you've used React or Solid's suspense implementation, you will have encountered startTransition and useTransition. These functions allows you to update state in such a way that the nearest suspense boundary doesn't show its fallback while waiting for any async work that is downstream of the state change. In Svelte, we don't do this. Instead, state changes that result in asynchronous work always have their effects deferred until the asynchronous work is complete. While that work is ongoing, $effect.pending() is true. Any pending snippets are only shown when a boundary is first being created. This does create the possibility that something distantly connected to a given piece of state has the power to delay (or prevent!) changes to that state from being reflected in the UI. We think this is preferable to the alternative (in which updates outside a useTransition or similar cause unwanted fallback UI to appear), though it may be necessary to develop techniques for identifying these chains. Overlapping updates that touch the same state must be applied in linear order In a case like this... <p>{a} + {b} = {await fetch(`/add/${a}/${b}`).then((r) => r.json())}</p> ...it's possible to imagine a scenario in which a change to a is followed by a change to b while the first fetch is ongoing. If the second fetch somehow finishes first, we don't want the first fetch to be applied afterwards, since the resulting UI would show stale values. As such, a given async expression must only resolve after its previous versions have also resolved. Async deriveds are eager Normally in Svelte, $derived expressions use 'push-pull' reactivity — they are invalidated when their dependencies change (the 'push') but are not re-evaluated until something reads them (the 'pull'). This doesn't work for async deriveds, because if we waited until the value was read the resulting promise would never resolve in time. Instead, an 'async derived' is really just an effect and a source signal in a trenchcoat. We evalute the expression in the effect, and when the promise resolves, set the source value (unless the effect fired again in the interim). For the most part, you don't need to think about this. There is one important implication though — while it's possible to create an 'unowned derived' (in other words, a derived that is not part of the 'effect tree', such as one created in an event handler) it is not possible to create an unowned async derived, because effects can only be created inside other effects. (Don't worry if you didn't follow this — it's inside baseball stuff, and describes an edge case you're unlikely to encounter. Svelte will tell you if you get this wrong.) Context is preserved in expressions When a reaction (i.e. an effect or derived) runs, we track its dependencies by seeing which values are read during its execution. In pseudo-code: let dependencies = null; function get(source) { dependencies?.add(source); return source.value; } function update_reaction(reaction) { dependencies = new Set(); reaction.fn(); reaction.dependencies = dependencies; dependencies = null; } (This is simplified and wrong, but you get the idea.) For asynchronous functions, this presents a challenge. To understand why, consider the order in which things happen here: function a() { console.log('a 1'); b(); console.log('a 2'); } async function b() { console.log('b 1'); await 0; console.log('b 2'); } a(); This will log a 1, b 1, a 2, b 2 — in other words despite the fact that b isn't even awaiting an asynchronous value, b 2 isn't logged until after it has returned to a. In Svelte terms, this would mean that $derived(await a + b) or an equivalent {await a + b} template expression would register a dependency on a but not on b. But since we're a compiler we have a trick up our sleeves: we wrap the await expression in a function that restores the effect context, so that b is treated as a dependency. On one level this is spooky compiler magic. But on another, it's just making the system work how a reasonable person would expect it to work. It does mean that if you extract the logic out into a function — {await a_plus_b()} — it will be treated differently, but during development Svelte will catch those cases and help you fix them. No async $derived.by While you can do this... let x = $derived.by(async () => await y); ...x will then be a Promise, not the value of await y. There's no sensible place to put the await that would result in x not being a Promise. (This is why it's pretty great that we're a compiler and can make the expression form the default, even though it makes other framework authors mad.) If you have sufficiently complex logic that you need to use a function block, just declare the function and call it: async function add(p1: Promise<number>, p2: Promise<number>) { return await p1 + await p2; } let sum = $derived(await add(p1, p2)); Note the aforementioned caveat around context preservation — make sure you pass state into the function, rather than having the function close over the state it references, so that dependencies are correctly attached. Focused inputs are not overridden Normally, state does not update in the UI until everything that depends on it has finished resolving. This does not apply to a focused <input> element, which in many cases will be the source of the state change in question — instead, the rest of the UI 'catches up' to the input. Breaking changes Because of how async reactivity works, there is one small and unavoidable breaking change: beforeUpdate and $effect.pre callbacks no longer run before control flow blocks are updated. If you're using those callbacks to set state that control flow logic depends on — which you absolutely shouldn't be — then you may experience breakage. As a result, we may have to wait until 6.0 for a non-experimental release of this stuff. Ok so That was a lot; congratulations to those of you still here. I am sure you have questions, and that's a good thing. This is the place to ask them, or to offer feedback on the design. Let's go!

Svelte Toolbar

#15910
May 30, 2025
[!NOTE] this post is a collection of ideas and goals and things will be refined based on the discussion that follows, nothing is set in stone yet. svelte toolbar As Rich mentioned briefly during his svelte summit talk we are working on a toolbar to improve DX and allow better insights into svelte applications. Goals available everywhere regardless of how you use svelte, adding the toolbar should give you access to the tools in our ecosystem The toolbar itself is going to be part of the svelte package under a new subpath export svelte/toolbar. First party tools added automatically via svelte/toolbar or other packages within the org (eg bundler plugins) customizable While we are going to provide first party tools, we also want to provide an api to add third party tools and allow customization add tools libraries or packages for svelte can ship with dedicated tools community can add useful generic tools you can add custom made ones for your app configure The toolbar itself and each tool has a configuration that can be modified by invoking a function provided on svelte/toolbar The configuration is going to be stored locally so you can tailor it to your needs and it stays that way. Possible storage locations would be localstorage, indexeddb or .env.local files. mode aware Most tools are going to be available during dev, but some can also be used in a production build. powerful access svelte build-time information from the svelte compiler access svelte runtime information integrate with dev server functionality Planned tools first release svelte inspector to be ported from vite-plugin-svelte , its functionality is going to remain the same, but instead of hardcoding how file editors are opened by a call to a vite middleware, it is going to leverage the devserver integration toolbar config editor visual editor for the toolbar config, with options to persist future releases reactivity graph viewer $inspect is nice and surgical, but it would be even nicer if there was a way to visualize the reactivity graph and more easily follow dependencies through your app. Solid already has a tool for this and so should we sveltekit route viewer params, matchers, layout groups, the file system tree can become a bit messy. A dedicated ui with links to open the relevant files can help understand and work with your application structure. component hierarchy viewer The original svelte devtools browser extension had this view, and we can bring it back. your idea here please share ideas for tools you'd like to have/build and any apis they might need. now is the time to shape the foundation the owl The toolbar The toolbar itself is going to provide basic common features to the tools and renders them with provided name&icon. Of course it is going to be a svelte component and to avoid colliding with your application it will be mounted on document.documentElement outside of body by default, which should avoid collisions with your app in most cases. A tool A tool needs to be registered with the toolbar and receives api & configuration. Optionally it can provide its own svelte component for ui. If the toolbar is going to add this component or the tools is responsible for mounting it is still up for discussion. How to add it Ideally, bundler plugins are going to inject the toolbar to avoid the need for putting code into your app yourself (like svelte-inspector is added today) and the tools just show up as configured. But manually putting them in your root layout can work as well. The hardest one is adding them for production, this might require a web extension and building with a special flag so it can aquire a reference to the toolbar api. Feedback & Ideas wanted! If you want to build a tool, what api do you prefer. Should a tool always be a svelte component or should it be more similar to a vite plugin with an initializer function and hooks? Do you think tools should be aware of each other and able to collaborate? What are the most important tools you want to use? Would you prefer a web extension with ui separately in the devtools tab or is rendering in the apps window ok?

Tenets

#10085
Nov 12, 2024
This is an attempt to articulate the Svelte philosophy — our bedrock principles, that guide our design decisions. Edit: for visitors from Hacker News, and anyone else who is curious, here is the video that this list came out of The web matters We work on Svelte because we believe that the web is a critically important technology, and that its continued survival is not guaranteed. Optimise for vibes People use Svelte because they like Svelte. They like it because it aligns with their aesthetic sensibilities. Instead of striving to be the fastest or smallest or whateverest, we explicitly aim to be the framework with the best vibes. Don't optimise for adoption We're not trying to be the most popular framework, we're trying to be the best framework. Sometimes that means making choices that we believe in but that go against the grain of web development trends. HTML, The Mother Language HTML is a really good language for describing UI. Svelte augments HTML in a way that makes it a really good language for describing interactive UI. Most frameworks are JS-centric, because JS is the most powerful language. But then they find themselves jumping through hoops to make it feel like you're writing HTML. We think both options are valid, but the HTML-first approach ends up feeling more natural. Embrace progress There is a tendency in the web developer community towards a harmful form of pessimistic nostalgia — the idea that things were better in the prelapsarian age before bundlers, TypeScript, client-side routing and other trappings of modernity. This is nonsense. As a community our default position is one of optimism about technology — the platform is getting better, our tools are getting better, our devices are getting better, and if we embrace that fact we can make better stuff. And when other frameworks introduce new ideas like signals or server components, we look at them with interest and jealousy, and try to work out how we can incorporate good ideas, instead of resting on our laurels. There is always room for improvement. Numbers lie Lighthouse has broken the brains of a generation of web developers. We have replaced good judgment with subservience to metrics that were only ever intended to be used as a diagnostic tool. Goodhart's Law states that When a measure becomes a target, it ceases to be a good measure and this is very true in web development. Numerical rigour is good, and we pay attention to the various numbers, but when designing Svelte we think qualitatively, not quantitatively. Magical, not magic There's a subtle line between something feeling magical, and something feeling like magic. We want Svelte to feel magical — we want you to feel like a wizard when you're writing Svelte code. Historically I think Svelte went too far into magic territory, where it's not 100% clear why things work a certain way, and that's something that we're rectifying with Svelte 5. Dream big 'Choose the right tool for the job' is sensible but boring advice. It makes us small in our ambitions. I want us to dream bigger. I don't want to feel like my tools can't handle evolving requirements, or that if I want to dabble in a new field I need to learn an entirely new way of working first. Even if it turns out to be unachievable, I find it valuable to ask the question 'what would it take for SvelteKit to be the best framework for any app?', whether it's purely static content, or a realtime multiplayer app, or an offline-first productivity app, or even something built for an augmented reality headset. No-one cares Most people do not care about frameworks. They just want to build something cool, and Svelte is for those people too. So when we design things we need to think about the people who haven't read the docs in a while, if at all, and don't care about things like fine-grained rendering or configuring their build tool. This means that things need to be intuitive, that we shouldn't need to worry about manual optimisations like memoisation, that we should have as few APIs as possible, and that things need to be discoverable — for example you should be able to hover over a rune and get a link to comprehensive documentation. This also informs our approach to documentation and tutorials — it should be possible to build what you want by just learning the concepts that you need, and worrying about the other stuff for another day. Design by consensus Svelte is a community-driven and consensus-led project. It's important that the community — that's you — has a stake in the project's future. Many of Svelte's best ideas originated outside the core team. When we introduce new plans, we want to communicate them openly and provide everyone with good opportunities to offer their feedback. Those of us who work on the project every day have a vision for what kind of thing we want it to be, but we don't want to foist that vision on people against their will. And so while we can't get unanimous agreement on every change, we can at least say that dissenting voices have been heard and considered.

Issues

Remove auto-parenthesis logic for MediaQuery

#15938
May 29, 2025
Describe the bug We added the auto-parenthesis logic before releasing it but it had multiple issues that we kinda had to patch with special cases and regexes. We can't remove it without a breaking change so opening this issue for 6.0 Reproduction Logs System Info - Severity annoyance

Improved error when attempting to export $derived state as a let?

#15842
Apr 28, 2025
Describe the problem If you use export const foo = $derived(...) to attempt to expose derived state on a component instance, you get a helpful error message about instead needing to export a function that returns the derived state. However, if you write export let foo = $derived(...) instead, the compiler thinks you are trying to use the export syntax to define a prop, and directs you to use $props() instead. Adjacently, something like export const foo = 42 (in runes mode) is allowed by the compiler, which seems to just expose that value on the component instance like export function foo() { ... } would, and I don't know whether that was intentionally left in as a feature. Describe the proposed solution I'm not sure, entirely. There certainly are situations where the best thing would be to continue to throw the error about how this isn't the right way to define props anymore. But I think people using lets for $derived()s isn't that uncommon, and so using let vs const to decide what's happening here isn't ideal. Importance nice to have