In a previous article, I wrote about the state of SSR for Web Components. Since then, I have received many questions about when we will see Declarative Custom Elements (DCEs). Teams are looking for ways to eliminate JavaScript requirements for presentational components, address FOUC, and simplify SSR solutions.
Current Challenges with Web Components
As the adoption of web components continues to grow, teams encounter challenges with their client-side-only nature. Components require JavaScript for initial rendering, which can lead to Flash of Unstyled Content (FOUC). Server-side rendering implementations require framework-specific integrations and are often complex. Even purely presentational components need JavaScript to bootstrap their markup and styles.
Requires JavaScript
Even for purely presentational components that consist only of markup and CSS, JavaScript is required to bootstrap the component definition.
Flash of Unstyled Content
A frequently cited issue with web components is FOUC, which occurs during page load.
This issue is particularly problematic for teams using content management systems that lazy-load components for performance optimization. The delay between initial page load and component rendering creates a suboptimal user experience.
For information on how to reduce FOUC on your pages, check out my article on the topic.
Server-Side Rendering (SSR)
SSR support for web components requires framework-specific implementations, often with specific configuration requirements and restrictions on APIs that can be used in component authoring.
Declarative Shadow DOM
Recognizing these pain points, the web platform introduced Declarative Shadow DOM as a solution. While DSD made progress, it brought its own set of trade-offs.
Scalability
DSD templates cannot be reused - they must be repeated for each component instance on a page. Got 50 buttons? That's 50 complete shadow DOM templates - styles and all. Here's a very simple example of what that looks like:
<my-button>
<template shadowrootmode="open">
<style>
button {
padding: 0.25rem;
border: solid 1px black;
}
</style>
<button>
<slot></slot>
</button>
</template>
My Button 1
</my-button>
<my-button>
<template shadowrootmode="open">
<style>
button {
padding: 0.25rem;
border: solid 1px black;
}
</style>
<button>
<slot></slot>
</button>
</template>
My Button 2
</my-button>
<!-- Repeat for EVERY custom button... -->
You can see the limitation here. Each component instance requires the complete template, resulting in significant duplication of styles and markup across the page. This is the HTML equivalent of repeating a function definition instead of calling a reusable function.
Library Dependency
In order to hydrate web components with a Declarative Shadow DOM, teams depend on library-specific integrations to parse and inject the component's content into the DOM. This often comes with particular configuration requirements and constraints on component authoring patterns.
SSR Complexity
Server-side rendering of client-side code has no standard. Each framework implements its own approach. That means for your web components to be server-rendered in frameworks, you need a special implementation for each of those frameworks.
Enter Declarative Custom Elements
The core concept behind DCEs is simple: define a custom element template once and reuse it throughout the document. This addresses the duplication issues inherent in DSD while maintaining the benefits of declarative markup.
Here's what a simple DCE definition could look like:
<definition name="my-element">
<template shadowrootmode="closed">
...
</template>
</definition>
NOTE: All DCE examples and APIs in the article are hypothetical and have not been defined yet.
The first phase of this could basically be a reusable Declarative Shadow DOM. The definition is specified once, then the element can be used multiple times throughout the document. No duplication, no JavaScript required for initial rendering, and no FOUC.
A Real Example
Let's look at a more realistic example. Here's a simple badge component with multiple variants and styling.
<definition name="my-badge">
<template shadowrootmode="open">
<style>
:host {
--badge-fg-color: white;
--badge-bg-color: blue;
--badge-border-color: blue;
padding: 8px;
border-radius: 4px;
}
:host([hollow]) {
--badge-fg-color: blue;
--badge-bg-color: transparent;
}
:host([variant="danger"]) {
--badge-bg-color: red;
--badge-border-color: red;
}
:host([variant="warning"]) {
--badge-fg-color: black;
--badge-bg-color: yellow;
--badge-border-color: yellow;
}
.base {
color: var(--badge-fg-color);
background-color: var(--badge-bg-color);
border: 1px solid var(--badge-border-color);
}
</style>
<span class="base" part="base">
<slot></slot>
</span>
</template>
</definition>
Now you can use it anywhere in your markup:
<my-badge>Default</my-badge>
<my-badge hollow>Hollow</my-badge>
<my-badge variant="danger">Danger</my-badge>
<my-badge variant="warning">Warning</my-badge>
Each badge renders immediately with the correct styles. The browser can render <my-badge> elements because the definition is available declaratively in the document.
Benefits of DCEs
No JavaScript Required
Removing the JavaScript requirement for CSS-only components provides significant benefits. Many design system components are purely presentational, like badges and layout components, which don't require interactivity. With DCEs, these components function correctly even when JavaScript is disabled, slow to load, or blocked.
Improved FOUC Experience
DCEs address the Flash of Unstyled Content issue directly. Components render immediately with correct styles because the browser has access to the template definition from the start. This eliminates the delay while JavaScript loads, executes, and defines custom elements.
Simplified Server-Side Rendering
DCEs could greatly simplify and standardize the SSR implementation for a more framework-agnostic approach to pre-rendering components. Instead of relying on framework-specific integrations with their own quirks and restrictions, you could pre-render DCEs in a standardized way that works anywhere.
Progressive Enhancement
DCEs could support progressive enhancement naturally. If interactivity is needed, custom elements can be upgraded by defining them with JavaScript. The DCE provides the initial render, and JavaScript adds behavior when available - combining the benefits of both approaches. This hybrid approach means your design system components work immediately for all users, while JavaScript-enhanced features activate seamlessly for those with JS enabled.
How to Use Them
How can we translate our existing components into DCEs and make sure they end up on the page when we need them?
I think there are many ways to solve this problem. Much of this will depend on your development environment, tooling, and framework preferences.
I made a simple proof-of-concept that parses the Custom Elements Manifest and generates them in various formats, including framework wrappers, so they can be provided by libraries and easily imported into an application.
NOTE: Again, all DCE examples and APIs in this article are hypothetical and have not been defined yet.
Real-World Performance Analysis
I conducted performance experiments on a site using Shoelace, a popular web component library, and the DCE Generator mentioned above, to evaluate the practical impact of DCEs:
Test setup: A page with 1,362 custom elements - 943 of them Shoelace components, using 22 different components from the library.
DSD approach: Generating Declarative Shadow DOM for each component instance added about 1.9MB of markup and CSS
DCE approach: Loading all 58 Shoelace DCE definitions added about 150KB of markup and CSS
This represents a significant difference: 150KB versus 1.9MB - approximately 12x smaller. This also does not include the contents of the other 419 components on the page. Additionally, the DCE payload size remains constant regardless of component instance count, while DSD increases with each component instance.
This page has since been refactored to be more lightweight (thankfully), but pages like this are not outside the realm of possibility for large, complex applications.
The good news is that because of the repetition of the templates, the DSD example was efficiently gzipped and was not much larger than the DCE version for file transfer.
These findings suggest that DCEs offer predictable performance characteristics and scalable markup strategies, especially for large applications with many repeated components.
Implementation Challenges and Open Questions
While the proposal offers clear benefits, there are some exciting design questions to explore as the specification develops.
Scope Management
Discussions about DCEs tend to expand into related APIs and features. Maintaining focus on a minimal, achievable initial proposal while acknowledging future extensibility is an ongoing challenge.
Future phases of this API should include things like attribute mapping and integration with DOM parts, but the initial proposal of reusable shadow DOMs would provide huge benefits in performance and user experience.
Browser Implementation
Achieving consensus and implementation across all major browsers presents coordination challenges. However, DCEs build on existing web platform primitives like templates and custom elements, which may facilitate implementation.
FAQs
Can I use DCEs today?
No. DCEs are a proposal, and all APIs shown in this article are hypothetical. No browser has implemented them yet, and the specification is still being defined.
However, the proof-of-concept DCE Generator package on npm lets you experiment with the concept by parsing your Custom Elements Manifest and generating definitions in various formats, including framework wrappers.
How are DCEs different from Declarative Shadow DOM?
DSD lets you define shadow DOM declaratively, but requires a full template copy for every single component instance on the page. DCEs build on that foundation by allowing you to define the template once and reuse it across all instances, solving DSD's core scalability and framework integration problems.
What about attributes?
Attributes are a key aspect of custom elements. I have an API proposal for this, but attribute binding would likely be a fast-follow feature since they would depend on a feature like DOM Parts, whose spec is still in development.
Because it depends on a feature that is still in development and not required to address some of these initial issues, it would be a shame not to hold these features hostage waiting for that.
What about conditional rendering?
Some components render different content depending on defined attributes or slotted content (ie - button components that can render a button or an anchor tag). This would add a great deal of flexibility to components, but, like attributes, would likely depend on specs that aren't available yet, like DOM Parts and additional elements or attributes to make it work. This would need to be a feature that comes in a future iteration of the spec.
What about lifecycle hooks?
In their initial form, they probably won't have lifecycle hooks. The core proposal is focused on declarative, presentational rendering - essentially a reusable shadow DOM template that requires no JavaScript. Lifecycle hooks like connectedCallback, disconnectedCallback, and attributeChangedCallback are fundamentally JavaScript APIs and wouldn't fit the no-JS nature of a pure DCE.
If access to those is needed, they can be included in the autonomous custom element defined in JavaScript.
Should these only work with Shadow DOM?
Autonomous custom elements can be rendered without a shadow root, so should DCEs allow rendering into the light DOM, not just the shadow DOM? This could broaden their applicability and simplify integration with existing markup, but it also introduces questions about style encapsulation and component boundaries.
Conclusion
Declarative Custom Elements represent a natural evolution of the web platform's component story. By building on the foundation of Declarative Shadow DOM and addressing its scalability limitations, DCEs promise to make web components more accessible, performant, and developer-friendly.
Declarative Shadow DOM has been a great step in improving the web component ecosystem. Declarative Custom Elements feel like the next evolution of that vision, with exciting capabilities for dynamic templating and data mapping with technologies like DOM Parts to follow.
Top comments (1)
Custom Elements - three flavours
Every browser vendor (Google since 2016, Apple since 2017 and Mozilla since 2018)
processes EVERY
<tag-name>(with a dash) as VALID HTMLElement WITHOUT using JavaScript!So, for nearly a decade now, Custom Elements come in 2 flavours:
UNdefined Custom Elements :
<tag-name>hardly blogged about, even AI wrongly calls them unknown elements, they are not!
They pass every HTML Validator because they extend from
HTMLElementnotHTMLUnknownElementlike<tagname>(no dash!)Defined Custom Elements :
<tag-name>upgraded/defined with JavaScript, what the whole world thinks Custom Elements are, since every Web Components explanation dives straight into the JavaScript code.
| No one ever wrote acronyms for these 2 flavours. UCE and DCE?
UNdefined Custom Elements are great for layout and styling. Just remember to set the CSS display property. That means every DIV or SPAN can be replaced with a meaningful
<tag-name>.Yes! you could have done so for nearly a decade now...
"Declarative"
DSD - Declarative ShadowDOM (baseline: feb'24) IS a UNdefined Custom Element.
Easily proven with a
*:not(:defined){ display:none }in your HTML page; it will hide all DSDsSome gurus use this CSS selector to fight FOUCs.... but then all UCE in the page are forever hidden.
Technically speaking UNdefined Custom Elements are Declarative, so maybe the acronym DUNCE (Declarative UNdefined Custom Elements) might be a better one to distinguish them from the DCE
The DCE you describe has been used as acronym in conversation for many moons now, so the acronym will stick.
Kinda like we call Web Components Custom Elements and vice-versa.
3 types of Custom Elements
That would give us three Custom Element flavours:
That will also allow the phrase to be used:
"dunce! you don't know DUNCE!"