Hacking the Shadow DOM: Extending Vaadin's Grid Component Against All Odds
Share this article
Vaadin's evolution from version 8 to 24 represents a significant architectural shift, particularly in how components handle encapsulation. As Robert Zenz discovered during a migration project, the framework's move toward Shadow DOM in modern versions created an unexpected roadblock: the once-flexible Grid component became virtually impossible to extend conventionally. This limitation becomes critical when implementing expected features like contextual toolbars—functionality that existed in Vaadin 8 but vanished in later iterations.
The Extension Conundrum
Traditional extension approaches fail spectacularly with Vaadin 24's Grid:
- Direct Inheritance: Subclassing
Gridand appending elements viagetElement().appendChild()places components outside the Shadow DOM, making them invisible. - External Components: Associating a separate toolbar component with a Grid creates API fragmentation and maintenance headaches.
- Wrapper Components: Encapsulating Grid within another layout forces developers to proxy all Grid methods, resulting in clunky syntax like
toolbarGrid.getRealGrid().doAction(). - Header/Footer Rows: Attempts to repurpose these built-in features trigger rendering glitches, frozen column issues, and unexpected exceptions when modifying rows.
Zenz explains the core problem: "Grid isn't a container. Adding elements to its main HTML hierarchy ignores Shadow DOM boundaries, and accessing the ShadowRoot server-side returns an empty Optional."
The Shadow DOM Breakthrough
The solution lies in client-side manipulation. While server-side Java can't penetrate the Shadow DOM, JavaScript executed from the server can:
@Override
protected void onAttach(AttachEvent attachEvent) {
super.onAttach(attachEvent);
getElement().executeJs("this.shadowRoot.appendChild(...)");
}
Zenz's technique involves:
1. Adding "dummy" elements to the component's root with a marker class (e.g., .shadow-injection-child)
2. Using executeJs to relocate these elements into the Shadow DOM during attachment
3. Enabling dynamic updates by manipulating the injected elements server-side
Styling and Customization Unleashed
This approach unlocks unprecedented control:
- Dynamic Styles: Inject
<style>tags directly into the Shadow DOM to override component CSS - Arbitrary Components: Toolbars, buttons, or custom widgets can be embedded within Grid's private DOM
- Live Updates: Server-driven content changes propagate seamlessly to injected elements
Implementation Toolkit
Zenz's demo project introduces a ShadowInjector utility that abstracts the complexity:
// Prepare element for injection
Element toolbar = new Div().addClassName("toolbar");
ShadowInjector.addForInjection(this, toolbar);
// Execute injection during attach
@Override
protected void onAttach(AttachEvent event) {
ShadowInjector.injectChilds(this);
}
The Maintenance Paradox
While this technique works, it comes with caveats:
- Framework Volatility: Shadow DOM internals may change across Vaadin versions
- Accessibility Risks: Bypassing official APIs risks breaking screen readers or keyboard navigation
- Testing Complexity: Injected elements evade standard Vaadin TestBench selectors
As Zenz notes: "ERP applications require identical functionality across hundreds of screens. Custom components aren't luxuries—they're maintenance necessities." This hack exemplifies the tension between framework constraints and real-world development needs, forcing teams to choose between API purity and deliverable functionality.
Source: Extending not extendable Vaadin components by Robert Zenz