Custom Elements: The Superpower of Svelte 5
Svelte 5 and the Future of Truly Reusable Web Components
In today's web development ecosystem, framework fragmentation can be a headache. Can you imagine creating components that work equally well in React, Vue, Angular, or even plain HTML? With Svelte 5 and Web Components technology, this isn't just possible—it's surprisingly simple.
The Challenge
Modern frontend development is based on reusable components. However, these components often remain "trapped" within the ecosystem for which they were created. A button made in React will hardly work in a Vue application without significant modifications.
In this article, we'll explore how to create truly framework-agnostic components using Svelte 5 and Storybook 8+. Our example will be a fully customizable Alert component that will work in any framework or environment—in some cases directly, in others with minimal modifications.
What's the Problem?
Imagine this situation: your company has several teams working on different projects. Team A uses React, team B uses Vue, and new project C is being developed with Svelte.
If each team creates their own components from scratch:
Furthermore, if tomorrow they decided to migrate to another framework, they would have to rewrite all the components. This represents an enormous cost in time and resources.
How to Solve It: Web Components with Svelte 5
The magic of our solution lies in the combination of Svelte 5 with Web Components (or Custom Elements) and their documentation in Storybook 8+.
Let's look at the code for our Alert.svelte component:
Breaking down the code:
The magic is in this line: <svelte:options customElement="garnet-alert" /> This directive converts our Svelte component into a Web Component that can be used in any framework or plain HTML. When you compile this component, Svelte generates a JavaScript file that, when included in any project, registers the custom element <garnet-alert> which you can use like any other HTML tag.
The rest of the code consists of the $props() directive, which automates reactivity. This reactive directive allows you to declare and destructure all the properties that the component can receive from the outside, assigning them default values, very similar to a React prop. Then there are a couple of functions: getIconSVG() to get the corresponding icon from the svgIcons object, and finally the closeAlert() function to close the notification dialog box.
Let's look at the HTML code of the component:
The structure of a Svelte component consists of the JavaScript/TypeScript script that provides functionality, the HTML which is the rendering template, and the associated style either in CSS within the same file or using tailwindcss among others. The Shadow DOM will always ensure that the CSS belongs exclusively to the defined component. In this case, it's omitted. Basically, we have a dialog tag whose style varies depending on the type of icon (info, warn, error, success, dark, default), which provides the appropriate style. Inside, there are the title and description props that will be displayed in the notification, as seen in the following image:
Documenting with Storybook
The configuration of Storybook version 8+ in a Svelte v5 project created via Vite is actually automatic, but you can refer to the official documentation for installation at StoryBook Docs, Installation. What we have then is a file with the extension Alert.stories.js, which tells the project and StoryBook where the component comes from and how to render it and add component variations. It's also possible to include a table of properties that can be modified live to observe the component's behavior, as shown in the example code.
The heart of our file begins with export default {...}. This is like the main menu of a restaurant: it tells Storybook all the basic information about our component. The title is super important—it's like your home address but for Storybook. In this case, we're saying: "Hey, place this component in the 'Garnet UI Library' folder, inside 'Notification Components', and call it 'Alert'". When you open Storybook, you'll see exactly that folder structure in the left sidebar menu. Inside that export default, we find argTypes. This section is like the instruction manual for our component.
Each property here defines:
The table part is like adding a detailed price tag—it shows technical information in Storybook's properties table. Then we have parameters, which are like the extra menu configuration; Here we're saying: "The general description of the component is this" and "when showing examples, give them a height of 150px". It's like giving additional instructions to the chef on how to present the dish. In this case, the chef is Storybook.
Now come the most interesting parts: the stories. Each export const is like a visual example of our component in a specific situation:
Each story (Default, Warn, Error, etc.) shows a different variant of the same component, like photos of the same furniture in different colors so the client can choose.
In Summary
The Storybook file is like an interactive catalog for our Alert component:
This way, any developer using our component can:
Recommended by LinkedIn
And all this without having to read a single line of the component's internal code. It's like test-driving a car without needing to understand how the engine works!
What Makes These Components Framework-Agnostic?
The key to understanding why our component is agnostic lies in Web Components (or Custom Elements) technology. Web Components are a set of standard web technologies that allow creating custom, encapsulated, and reusable HTML elements. When Svelte compiles a component with the customElement directive, it generates a Web Component that:
In frameworks that have their own engine, their lifecycle is different, especially those using a reconciliation algorithm for their Virtual DOM; more code needs to be created to adapt the requests or states specific to the framework regarding the functionality established in the Web Component.
How to Use Our Component in Different Environments?
Once compiled, our component can be used like this:
In plain HTML:
In React:
In Vue.js:
In SolidJS:
It's that simple! Web Components are used like any standard HTML tag in both frameworks. However, as I mentioned before, if a component has more complex functional logic, for example, returning a state that needs to be obtained to perform other logic, in these types of scenarios is where the use in React and Vue.js becomes complicated since you need to add functionalities compatible with their engines, as the philosophy of state management is specific to each engine.
Advantages of Web Components with Svelte
Disadvantages
How to Overcome Web Components Disadvantages
Browser support Solution: Use polyfills for compatibility with older browsers.
Custom Elements limitations Solution: Design a clear event API and use standard communication patterns.
Less mature ecosystem Solution: Leverage existing libraries and contribute to the ecosystem.
Ease of Implementation by Framework
From easiest to most difficult:
Why Architects Should Consider This Solution
As a software architect or technical leader, there are powerful reasons to consider Web Components in Svelte:
Conclusion
The combination of Svelte 5 and Web Components represents a revolutionary approach to user interface development. It allows creating truly agnostic components that work in any environment, reducing duplication of efforts and ensuring a consistent user experience. Additionally, incorporating Storybook as a documentation tool significantly elevates the value of these components. By documenting each variant, property, and possible behavior, Storybook not only facilitates the use of components for other developers but also serves as a visual and functional source of truth. This living documentation allows teams to test, visualize, and understand how components work without needing to read their internal code, accelerating adoption and reducing the learning curve. Ultimately, a well-documented agnostic component with Storybook becomes a high-value asset that transcends frameworks and teams, materializing the true potential of reuse in modern frontend development.
Although it's not the perfect solution for all use cases, it's a powerful option that architects and developers should seriously consider, especially in organizations with multiple teams or projects using different technologies.
What did you think of this article on Svelte 5 and Web Components? We'd love to hear about your experience implementing these solutions in your projects. Have you found other advantages or challenges not mentioned? This agnostic component approach is revolutionizing the way we build interfaces, and we've barely scratched the surface. In upcoming articles, we'll delve into topics such as advanced documentation integration in Storybook, Web Component performance in large-scale applications, and how to build complete design systems with this architecture. We'll also explore npm deployment and how to perform tests in Storybook. Stay connected to discover how to take your components to the next level, and don't hesitate to share this article with other developers interested in building more efficient and coherent interfaces!