Using Web Components in Adobe Edge Delivery Services Blocks

What Makes Web Components Special
Web Components bring together four technologies that work in harmony. Custom Elements let you define new HTML elements with your own behaviour. Shadow DOM keeps your styles and markup neatly contained. HTML Templates provide reusable patterns you can stamp out whenever needed. ES Modules handle packaging and distribution.
The beauty lies in their simplicity - they work natively in modern browsers without external frameworks. No build steps, no complex toolchains, just standard web APIs doing what they do best.
Why EDS and Web Components Work So Well Together
EDS blocks and web components share a philosophy of encapsulation and reusability. Each EDS block handles a specific piece of functionality, and web components provide the perfect way to implement that functionality with clean boundaries.
Since web components are framework-agnostic, you avoid getting locked into any particular technology stack. They integrate naturally with EDS's block-based architecture, keeping concerns separated while remaining highly reusable across projects.
The encapsulation that Shadow DOM provides means your blocks stay predictable. Styles don't leak out to affect other parts of the page, and external styles don't accidentally break your component's appearance.
Starting Simple - A Counter Component
Let's begin with something straightforward - a counter that increments and decrements a number. This example shows the fundamental patterns without getting lost in complexity.
The component starts as a simple table in your document:
Live view, click + and -
Your block's JavaScript transforms this into an interactive web component. The key insight is that you're creating a custom HTML element that encapsulates all its behaviour:
class CounterElement extends HTMLElement {
constructor() {
super();
this.count = parseInt(this.getAttribute('initial-value') || '0', 10);
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
this.setupEventListeners();
}
// Render method creates the shadow DOM content
render() {
const { shadowRoot } = this;
shadowRoot.innerHTML = `
<style>
/* Scoped styles that won't affect the rest of the page */
</style>
<div class="counter-wrapper">
<button class="counter-button">-</button>
<div class="counter-display">${this.count}</div>
<button class="counter-button">+</button>
</div>
`;
}
}
// Register the component
customElements.define('counter-element', CounterElement);
The EDS block's decorate function creates and configures the component:
export default function decorate(block) {
const cells = Array.from(block.children);
const initialValue = cells[0]?.textContent.trim();
const counter = document.createElement('counter-element');
if (initialValue) {
counter.setAttribute('initial-value', initialValue);
}
block.textContent = '';
block.appendChild(counter);
}
This pattern demonstrates several important concepts. Shadow DOM keeps styles contained - your component's CSS won't interfere with the page, and the page's CSS won't break your component. Custom properties allow theming through CSS variables. Event-based communication means your component can emit events when state changes, creating loose coupling with other blocks.
Moving Beyond Basic Components
While building custom components gives you complete control, sometimes you need to move faster. This is where component libraries become valuable. Libraries like Shoelace provide professionally designed components that work seamlessly with EDS blocks.
However, integrating external libraries in EDS requires a different approach than traditional web development. You can't simply add script tags to your HTML. Instead, EDS provides specific functions for loading external resources, ensuring proper loading order and maintaining performance benefits.
Loading a library in your block looks like this:
import { loadCSS, loadScript } from '../../scripts/aem.js';
export default async function decorate(block) {
await loadCSS('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.20.1/cdn/themes/light.css');
await loadScript('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.20.1/cdn/shoelace-autoloader.js', {
type: 'module'
});
// Now you can use Shoelace components
const button = document.createElement('sl-button');
button.textContent = 'Click me';
block.appendChild(button);
}
Development Workflow and Testing
Working with web components in EDS requires understanding how blocks are processed and tested. EDS operates with a dual HTML system where content transforms from a served state (minimal structure) to a rendered state (full block structure with proper attributes).
The project includes a local development server that makes testing much more effective. Instead of juggling multiple systems, you can test blocks in isolation while maintaining EDS compatibility. This approach proves particularly valuable when working with complex components that require external dependencies.
Creating test files follows specific patterns. Your test HTML must replicate the exact EDS block structure:
<div class="my-block block" data-block-name="my-block" data-block-status="initialized">
<div>
<div>
<p>Your test content here</p>
</div>
</div>
</div>
The nested div structure and data attributes aren't arbitrary - they're essential for proper EDS processing. Understanding this structure helps debug issues when components don't behave as expected.
For comprehensive debugging guidance, including AI-assisted development workflows, server configuration, and troubleshooting patterns, see the complete debugging documentation.
Building Something Beautiful - The Shoelace Card Component
The real power of combining web components with EDS becomes clear when you tackle more ambitious projects. The Shoelace Card component represents a significant step up in sophistication, featuring advanced glassmorphism effects, immersive modal overlays, and modern web development practices.
I built this component using the json described in https://allabout.network/blogs/ddt/integrations/building-headless-applications-with-edge-delivery-services
This component demonstrates what becomes possible when you combine Shoelace's design system with careful attention to user experience. Cards appear with smooth animations, images preload to eliminate jarring content shifts, and modals provide full-screen content viewing with background imagery integration.
The Challenge of Flash of Unstyled Content
One critical user experience issue the Shoelace Card addresses is the Flash of Unstyled Content (FOUC). Users see content building progressively - text appearing first, followed by slowly loading images. This creates an unprofessional experience.
The solution involves comprehensive image preloading:
async function preloadImage(src, timeout = 5000) {
return new Promise((resolve, reject) => {
const img = new Image();
const timer = setTimeout(() => {
reject(new Error(`Image load timeout: ${src}`));
}, timeout);
img.onload = () => {
clearTimeout(timer);
resolve(img);
};
img.src = src;
});
}
async function preloadAllImages(cardData) {
const imageUrls = cardData
.map(card => card.image)
.filter(Boolean);
const preloadPromises = imageUrls.map(url =>
preloadImage(url).catch(error => {
console.warn(`Failed to preload image: ${url}`, error);
return null;
})
);
return Promise.all(preloadPromises);
}
This approach transforms the user experience from jarring progressive build-up to smooth, professional loading. Users see a clean loading state while images preload in parallel, followed by the simultaneous appearance of fully-formed cards.
Advanced Modal System with Integrated Headers
The modal implementation features sophisticated glassmorphism effects with an innovative integrated title header design. Instead of floating UI elements that can obstruct content, the title and ESC button are logically grouped in a unified header area.
function createAdvancedModal(cardData) {
const overlay = document.createElement('div');
overlay.className = 'shoelace-card-modal-overlay';
overlay.setAttribute('role', 'dialog');
overlay.setAttribute('aria-modal', 'true');
const modal = document.createElement('div');
modal.className = 'shoelace-card-modal';
modal.style.backgroundImage = `url(${cardData.image})`;
// Integrated title header with ESC button
const titleHeader = document.createElement('div');
titleHeader.className = 'shoelace-card-modal-header';
const title = document.createElement('h1');
title.textContent = cardData.title;
const closeButton = document.createElement('button');
closeButton.innerHTML = 'ESC';
closeButton.className = 'shoelace-card-modal-close';
titleHeader.appendChild(title);
titleHeader.appendChild(closeButton);
return overlay;
}
This design follows standard modal conventions while providing clear visual hierarchy and intuitive placement that matches user expectations from other applications.
AI-Assisted Development Workflow
The Shoelace Card implementation demonstrates a better approach to EDS development - one that enables meaningful AI assistance. Traditional EDS workflows fragment development across Google Docs, code repositories, and deployment systems, creating impossible situations for AI assistants.
The solution implements a local-first, proxy-fallback approach. The development server checks if a requested file exists locally first. If it does, it serves that file. If not, it fetches the file from your production server. This simple but powerful pattern means you can develop individual blocks without juggling multiple systems while still accessing real EDS resources.
This unified environment allows AI to see code, content, and data in one place, enabling intelligent assistance throughout the development process.
The Complete Implementation
The full Shoelace Card component includes far more than what we've covered here - comprehensive error handling, accessibility features, performance optimizations, and sophisticated animation systems. The component successfully bridges the gap between design system consistency and creative visual expression.
Key features include multi-layer backdrop blur effects, responsive design that adapts to mobile devices, content integration with EDS's content management systems, and comprehensive testing with automated quality assurance.
For the complete implementation, including build scripts, deployment tools, and comprehensive documentation, visit the GitHub repository at
https://github.com/ddttom/webcomponents-with-eds.
Understanding EDS Constraints
Working effectively with EDS requires understanding some important constraints. EDS core scripts - scripts/scripts.js
, scripts/aem.js
, and scripts/delayed.js
- belong to Adobe and cannot be modified. These files control the fundamental processing that transforms your content into working blocks.
EDS controls page visibility through a specific system. Initially, CSS sets body { display: none; }
, then EDS scripts add the appear
class when processing completes. If you clear block content during decoration (as many web components do), you must preserve EDS attributes like data-block-status
and data-block-name
to maintain proper functionality.
This becomes particularly important with Shadow DOM components, where style isolation can create timing conflicts with EDS processing. The solution involves preserving EDS attributes after clearing innerHTML:
export default function decorate(block) {
// Preserve EDS attributes before clearing
const blockStatus = block.getAttribute('data-block-status');
const blockName = block.getAttribute('data-block-name');
// Clear and rebuild
block.innerHTML = '';
block.classList.add('my-component-block');
// Restore EDS attributes
if (blockStatus) block.setAttribute('data-block-status', blockStatus);
if (blockName) block.setAttribute('data-block-name', blockName);
// Continue with component logic
}
See the Finished page in EDS here: https://allabout.network/blogs/ddt/integrations/shoelace-version-of-slide-viewer-in-gdocs
Even with fetching, decorating we still achieve 100/100/100/100 on Google Pagespeed
Best Practices and Real-World Benefits
Building effective web components for EDS requires thoughtful implementation. Start with configuration objects and use constants for repeated values. Build proper error boundaries with clear messages. Handle edge cases gracefully rather than letting them break the user experience.
Testing deserves special attention. Create test files that replicate EDS block structure exactly - the nested div pattern and data attributes are essential for proper block processing. Use the local development server to test components in isolation while maintaining EDS compatibility. Always test both single and multiple instances of your components.
Performance matters - keep DOM operations minimal and handle events efficiently. Shadow DOM provides style isolation, but every interaction should still feel instant and responsive. When using external libraries, load them through EDS's loadCSS
and loadScript
functions to ensure proper sequencing.
Accessibility deserves special attention from the start. Include ARIA attributes throughout your components, support keyboard navigation, and maintain proper contrast ratios. Write thorough documentation with usage examples and clear customisation options.
Understanding the EDS processing lifecycle helps avoid common pitfalls. Blocks are discovered through specific CSS selectors, decorated with proper attributes, then your component's decorate function executes. Respect this flow and work with it rather than against it.
The benefits are significant. Isolated components are easier to maintain because they have clear boundaries. Reusability becomes natural when components can be shared across blocks. Since these are native browser features, there's no framework overhead - just efficient rendering and solid performance.
Getting Started
Starting with web components in your EDS blocks is straightforward. Create a new block directory and build your custom element using standard web APIs. Register it in your block's JavaScript, then write documentation and examples that others can follow.
Whether you build custom components or use libraries like Shoelace for sophisticated UI elements, you have powerful options at your disposal. The key is starting small, following standards, and building gradually. Each component you create becomes a building block for more complex interactions, all while maintaining the simplicity that makes EDS powerful.
For further learning, explore the https://shoelace.style/, Adobe Edge Delivery Services, and the project repository.
⭐ Found this helpful? Star the https://github.com/ddttom/webcomponents-with-eds on GitHub!
Related Articles