svelte
svelte copied to clipboard
[feature] Add `Portal`s to Svelte
Describe the problem
I'm aware that this discussion has been split across a few issues. However, none of those issues have been explicit feature requests for portals. Some issues have been closed early, or been distracted from the original topic, or been too narrow (e.g., <:Body> tags). Thus, I'm creating this guy to make a central, focused issue around portals (with new thoughts).
The problem that this feature would address is the need for clean, well-supported portals in svelte. I believe @arggh caught most of the common use cases in their comment on another issue, such as modals and popovers. These use cases are pretty common and therefore can't really be ignored.
I'm aware of other solutions that have spun off from other discussions, such as @ThomasJuster's solution, and the solution that gained inspiration from it: svelte-portal. While these solutions are clever, they are still flawed for multiple reasons:
- They pollute the DOM unnecessarily. Regarding the first solution, it permanently pollutes the DOM unnecessarily. (The wrapping
<div class="portal-clone">empties its children, but the node still remains). Regardingsvelte-portal, it temporarily pollutes the DOM unnecessarily. (More accurately, it temporarily renders to an "incorrect" location in the DOM, then moves the node to the correct location.) - As many have pointed out, the existing solutions are a bit hackish. They basically render hidden content, move the content, and then unhide the content.
- Because the existing approaches are workarounds/hacks and not native features, they have their own sets of bugs. Last month a bug was reported at romkor/svelte-portal#101. It seems that issue may not be addressable.
- Because the existing approaches basically do "DOM dancing", they can run the risk of temporarily creating invalid HTML. For instance, I'm working on a
formwhere people can add items which get rendered to atable. Each row of the table is clickable, opening up a modal for editing (this isn't the place to debate table edit approaches). I can circumvent the need for creating state variables by using another form in the modal that "submits" (saves) the data. But nestingforms is forbidden. I know the nestedformwould appear only temporarily with the existing approaches, but that still shouldn't be necessary.
Arguments can be made for workarounds, workarounds, and more workarounds to defer responsibility to devs. ("Why are you using modal edits for tables?" "Is using a form to avoid state variables really that advantageous?" "Maybe you can try some tricks with position: fixed for modals?" etc.) But ultimately these workarounds ignore the real question, and they fail because:
- They put
svelteusers in an unnecessarily inconvenient situation. (For example, I shouldn't have to create external, self-closingformelements and tediously pollute my DOM elements withformattributes.) - They can't get away from valid edge cases.
Other well-established frameworks like React and Vue have supported portal features because this is a common, basic use case. Svelte would benefit from this as well.
Describe the proposed solution
A clear new feature like <svelte:portal to={LOCATION}>CONTENT</svelte:portal> would be sufficient. This solution should render CONTENT directly to the correct LOCATION, instead of doing an awkward "DOM dance". If it's an unnecessary amount of effort to support selectors like Vue, it should be sufficient to require explicit DOM Nodes like React. After all, it's easy enough to say document.getElementById("ID"), and this would give the user more granular control over what node is used. (I'd probably prefer selectors.)
This solution would automatically resolve the request for <:Body /> tags (#1133). And it has the added bonus of providing flexibility for where the portal sends content.
I'm aware that @Conduitry has brought up SSR concerns across a few of these floating issues. I agree with @arggh that ignoring portals would be fine (and even desirable) in SSR.
Other well-respected frameworks like Next.js seem to avoid it altogether. For instance, timneutkens himself pointed to the official example for portals, which still only runs client-side. (If the component name wasn't obvious, useEffect never runs server-side.) Nuxt.js 3 is still in beta, so I don't know what they'll do about teleport. But I'm assuming it's possible they'll ignore teleport too. As has been said previously, most users are expecting to use portals client-side anyway.
Alternatives considered
The alternatives I considered were already mentioned in the opening problem statement. However, none of these alternatives seem sufficient on their own.
Importance
would make my life easier
If ignoring the portal server-side isn't desirable, some potential SSR approaches were mentioned. I'm not aware of Svelte's internals, but perhaps another solution is the following:
While the HTML to render is being generated server-side, <svelte:portal />s could be excluded from the originally rendered HTML. Instead, they'd be added to a separate array/map of sorts that tracks the portals that need to be rendered later. This list would use the LOCATION (denoted by to) for keys and the physical CONTENT for values. Once the HTML is generated, the process would check for any potential portals that need to be resolved. If none are present, the HTML gets sent to the client immediately. If portals are present, the process could loop through each portal and use pattern matching to place portals in the proper location. Then the HTML would get sent to the client.
For instance <svelte:portal to="#modals"> would denote that the HTML string should be searched for the first occurrence of id="modals". The portal's content string could be inserted either right after the opening tag or right before the closing tag.
If we're going with the approach of ignoring portals server-side, then we'd just exclude portals from the rendered HTML.
Related (and potentially related) issues: #3088, #1133, #4237 (becomes obsolete if this feature is addressed), #4036
Outside sveltejs/svelte: romkor/svelte-portal#101
I solved this issue, this is what I created https://github.com/YeungKC/svelte-portal
No dom move Support SSR Support update
But I still think svelte should provide <portal >
No dom move Support SSR Support update
No ability to pass <slot> as well (as it uses svelte:component).
I really hope this feature gets implemented.
Here is my hack:
<script>
function portal(node,{to}){
const target = document.querySelector(to);
target&&target.appendChild(node);
return {}
}
</script>
<div use:portal={{to:'body'}}></div>
It's works. But I don't know if it has any side effects.
No ability to pass
<slot>as well (as it uses svelte:component).
If #8067 was implemented, it would be easy to support slots as well.
It's a bit unfortunate to say this given the additional interest shown by the other votes on the OP... But this feature request (and its brothers, such as #4036) may not be valid anymore. As I mentioned previously, I agree with someone else's statement that the large majority of the use cases for portals are probably Modals. However, now we have the standardized dialog element. So that defeats the need for portals in many situations.
There's perhaps a smaller set of scenarios where portals are needed for other things like popovers. But now we have browsers rushing to support the Popover API. If this API gets adopted, Svelte may not need to bother with portals at all considering that: A) Popovers will be a part of the native spec and B) We already have an [albeit-undesirable] workaround in Svelte.
Pinging @Conduitry because you commented on #4036 and @pngwn because you've managed the tags on that issue. I just want to make sure that this is a visible consideration. Not sure where Svelte stands on portals at the moment. I'm only leaving this open in case Svelte is interested in supporting portals; otherwise, I might close.
If people are aware of other use cases that browsers aren't ultimately intending to support, that might change things... maybe. But for now, it seems like browsers are seeking to give us what we need.
A Portal component is now really easy to achieve thanks to slots just being props in Svelte 5.
A Portal component is now really easy to achieve thanks to slots just being props in Svelte 5.
Small note for people who stumble on this answer when searching for Portals in Svelte 5, createRoot was removed during development, so the code should be changed to use mount instead. Other than that, nothing changes.