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:

  1. Direct Inheritance: Subclassing Grid and appending elements via getElement().appendChild() places components outside the Shadow DOM, making them invisible.
  2. External Components: Associating a separate toolbar component with a Grid creates API fragmentation and maintenance headaches.
  3. Wrapper Components: Encapsulating Grid within another layout forces developers to proxy all Grid methods, resulting in clunky syntax like toolbarGrid.getRealGrid().doAction().
  4. 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