Using Spectrum Components in Adobe Edge Delivery Services Blocks

Adobe's Edge Delivery Services takes a document-first approach to web development - but that doesn't limit you to basic HTML. You can build professional interfaces using Spectrum Web Components - Adobe's own design system built on web standards.

Why Spectrum Components Work Well with EDS

Full code available at: https://github.com/ddttom/spectrum-with-eds

Demo: https://allabout.network/blogs/ddt/integrations/spectrum-component-as-eds-block-in-gdoc

Spectrum Web Components give you professional UI elements that work straight away. You get theming, responsive design, keyboard navigation, and screen reader support without extra work. They slot naturally into EDS's block-based architecture.

The components follow Adobe's design language, so your blocks look and feel like native Adobe experiences. Better yet, they're built on web standards - no framework lock-in or complex build processes.

Creating an Enhanced Card Block with Numbered Slides and Immersive Modal Overlays

Let's build something sophisticated - a card component that combines numbered slide functionality with immersive full-screen modal overlays for content display. This component fetches dynamic content from EDS query-index.json endpoints and provides a modern, visually striking user experience with glassmorphism effects and hero-style typography. It follows the modern EDS pattern for content-driven applications described in our Query-Index PRD.

Here's what the development structure looks like:

/build/spectrum-card/
├── spectrum-card.js
├── spectrum-card.css
├── index.html
├── package.json
├── vite.config.js
└── README.md

This is your development environment. The /blocks/ directory gets created automatically when you build.

Development Workflow

Complexities in the Build Process for Spectrum Components in Adobe Edge Delivery Services Blocks

Integrating Spectrum components within Adobe Edge Delivery Services blocks introduces a multi-layered build process that presents a level of complexity users should be aware of. This process isn't a simple, single-step operation; instead, it involves two distinct stages: component building and a subsequent test build.

Understanding this dual-level structure is crucial for developers working with these technologies. Comprehensive details regarding each of these stages, along with the necessary commands and configurations, can be found in the dedicated documentation file: BUILD_PROCESS.md. This document serves as an essential resource for navigating the intricacies of the build process and ensuring a smooth development workflow.

1. Start Development

cd build/spectrum-card
npm install          # First time only
npm run dev         # Starts http://localhost:5173

2. Edit Component Files

3. Build and Deploy Changes

# Bundles dependencies and copies to /blocks/ for EDS
npm run build:component  

This command:

The Query-Index Pattern

This component fetches dynamic data from EDS query-index.json endpoints. This enables content-driven applications with excellent performance.

Enhanced Features - Numbered Slides and Modal Overlays

Our enhanced Spectrum Card component includes two key features that elevate the user experience:

1. Numbered Slide Badges

Each card displays a circular blue badge with the slide number, positioned in the top-left corner. This provides a clear visual hierarchy and helps users navigate through content sequences.

// Add slide number badge positioned over the card
const slideNumber = document.createElement('div');
slideNumber.textContent = (index + 1).toString(
slideNumber.style.cssText = `
  position: absolute; top: 10px; left: 10px;
  background-color: #0265DC; color: white;
  border-radius: 50%; width: 32px; height: 32px;
  display: flex; align-items: center; justify-content: center;
  font-size: 14px; font-weight: bold; z-index: 10;
  box-shadow: 0 2px 4px rgba(0,0,0,0.2);
`;

2. Immersive Modal Overlay with Full Content

Clicking "Read More" opens a full-screen immersive modal that displays content from the .plain.html endpoint with a visual design. This provides a magazine-style reading experience with background imagery and glassmorphism effects.

// Fetch plain HTML content for modal display

async function fetchPlainHtml(path) {
  try {
    const { baseUrl } = getConfig();
    const response = await fetch(`${baseUrl}${path}.plain.html`, {
      mode: 'cors',
      headers: { 'Accept': 'text/html' }
    });
    
    if (!response.ok) {
      throw new Error(`Failed to fetch: ${response.status}`);
    }

    return await response.text();
  } catch (error) {
    console.error('[spectrum-card] fetch error:', error);
    return null;
  }
}

The immersive modal includes:

Data Source Configuration

// Default endpoint

const QUERY_INDEX_PATH = '/slides/query-index.json';

Custom endpoint via block content:

Expected Data Format

The component expects query-index.json to return data in this format (see live example at https://allabout.network/slides/query-index.json):

{
  "total": 5,
  "offset": 0,
  "limit": 5,
  "data": [
    {
      "path": "/slides/york-minster",
      "title": "York Minster",
      "image": "/slides/media_188fa5bcd003e5a2d56e7ad3ca233300c9e52f1e5.png?width=1200&format=pjpg&optimize=medium",
      "description": "A magnificent Gothic cathedral with centuries of history and breathtaking architecture",
      "lastModified": "1719573871"
    },
    {
      "path": "/slides/the-shambles",
      "title": "The Shambles",
      "image": "/slides/media_14e918fa88c2a9a810fd454fa04f0bd152c01fed2.jpeg?width=1200&format=pjpg&optimize=medium",
      "description": "A picturesque medieval street with overhanging buildings and quaint shops.",
      "lastModified": "1720028161"
    }
  ],
  "columns": ["path", "title", "image", "description", "lastModified"],
  ":type": "sheet"
}

The Component File

Here's the core structure of the component (see full implementation at https://github.com/ddttom/spectrum-with-eds):

// spectrum-card.js
// Import Spectrum Web Components
import '@spectrum-web-components/theme/sp-theme.js';
import '@spectrum-web-components/card/sp-card.js';
import '@spectrum-web-components/button/sp-button.js';
import '@spectrum-web-components/icons-workflow/icons/sp-icon-arrow-right.js';
import '@spectrum-web-components/icons-workflow/icons/sp-icon-close.js';

// Configuration
const SPECTRUM_CARD_CONFIG = {
  CARD_VARIANT: '', 
  BUTTON_TREATMENT: 'accent',
  BUTTON_SIZE: 'm',
  MAX_WIDTH: '400px',
  QUERY_INDEX_PATH: '/slides/query-index.json',
};

// Fetch content from EDS query-index.json
async function fetchCardData(queryPath) {
  try {
    const response = await fetch(queryPath, {
      mode: 'cors',
      headers: { 'Accept': 'application/json' }
    });
    

    if (!response.ok) {
      throw new Error(`Failed to fetch card data: ${response.status}`);
    }

    

    const json = await response.json();
    return json.data || [];
  } catch (error) {
    console.error('[spectrum-card] fetch error:', error);
    return [];
  }
}

// Create a single card element with enhanced features

function createCard(cardData, index) {
  // Create wrapper container for the card and number badge
  const cardWrapper = document.createElement('div');
  cardWrapper.style.position = 'relative';
  cardWrapper.style.maxWidth = SPECTRUM_CARD_CONFIG.MAX_WIDTH;
  // Add slide number badge
  const slideNumber = document.createElement('div');
  slideNumber.textContent = (index + 1).toString();
  slideNumber.style.cssText = `
    position: absolute; top: 10px; left: 10px;
    background-color: #0265DC; color: white;
    border-radius: 50%; width: 32px; height: 32px;
    display: flex; align-items: center; justify-content: center;
    font-size: 14px; font-weight: bold; z-index: 10;
    box-shadow: 0 2px 4px rgba(0,0,0,0.2);
  `;
  cardWrapper.appendChild(slideNumber);
  
  // Create the actual Spectrum Card
  const card = document.createElement('sp-card');
  card.setAttribute('heading', cardData.title || 'Card Title');
  card.style.width = '100%';
  // ... Add image, description, and button elements
  // See full implementation on GitHub
  cardWrapper.appendChild(card);
  return cardWrapper;
}

// The decorate function is called by Franklin/EDS

export default async function decorate(block) {
  try {
    const queryPath = block.dataset.queryPath || SPECTRUM_CARD_CONFIG.QUERY_INDEX_PATH;
    const cardData = await fetchCardData(queryPath);

    
    // Create responsive grid container

    const cardsContainer = document.createElement('div');
    cardsContainer.style.cssText = `
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
      gap: 20px;
    `;
    // Create cards from data

    for (const item of cardData) {
      const card = createCard(item);
      cardsContainer.appendChild(card);
    }

    block.appendChild(cardsContainer);
    
  } catch (err) {
    console.error('[spectrum-card] decorate error', err);
    block.textContent = 'Error loading cards';
  }
}

Testing Your Component Locally

The development environment includes hot reload and live preview. Here's a simplified test file:

<html>
<head>

  <title>Spectrum Card Test</title>
  <script type="module">
    import '@spectrum-web-components/theme/theme-light.js';
    import '@spectrum-web-components/theme/sp-theme.js';
  </script>
</head>
<body>
  <sp-theme color="light" scale="medium" system="spectrum">

    <!-- Default endpoint test -->
    <div class="spectrum-card block"></div>

    <!-- Custom endpoint test -->

    <div class="spectrum-card block">
      <div>/custom/query-index.json</div>
    </div>
  </sp-theme>

 
  <script type="module">
    import decorate from './spectrum-card.js';
    document.querySelectorAll('.spectrum-card.block').forEach(decorate);
  </script>
</body>
</html>

AEM Emulation Layer Implementation

The project includes an AEM emulation layer built with Node.js that provides a production-accurate testing environment:

// server.js - Core emulation layer implementation

import { createServer } from 'http';
import { readFile, access } from 'fs/promises';
import { join, extname } from 'path';
const PORT = process.env.PORT || 3000;
const PROXY_HOST = 'https://allabout.network';

// Intelligent file resolution strategy

async function handleRequest(req, res) {
  const url = req.url === '/' ? '/aem.html' : req.url;
  const filePath = join(__dirname, url.startsWith('/') ? url.slice(1) : url);

  // 1. Try to serve local file first

  if (await fileExists(filePath)) {
    console.log(`Serving local file: ${filePath}`);
    await serveLocalFile(filePath, res);
    return;
  }

  // 2. Proxy to a live environment if local file missing

  console.log(`Proxying request to: ${PROXY_HOST}${url}`);
  await proxyRequest(url, res);
}

Key Architecture Features

Server Configuration

The emulation layer supports MIME type handling:

const mimeTypes = {
  '.html': 'text/html',
  '.js': 'application/javascript',
  '.css': 'text/css',
  '.json': 'application/json',
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
  '.svg': 'image/svg+xml',
  '.woff2': 'font/woff2',
  '.ttf': 'font/ttf'
};

This configuration:

Vite Configuration

The project uses Vite for development with proxy support for EDS endpoints:

import { defineConfig } from 'vite';
export default defineConfig({
  server: {
    port: 5173,
    proxy: {
      '/slides': {
        target: 'https://allabout.network',
        changeOrigin: true,
        secure: true
      }
    }
  },

  build: {
    lib: {
      entry: 'spectrum-card.js',
      name: 'SpectrumCard',
      fileName: () => 'spectrum-card.js',
      formats: ['es']
    }
  }
});

Full configuration available on GitHub with complete build settings.

Building a Component in a Page

Create a block in your EDS document:

Basic Usage

See my implementation here https://allabout.network/blogs/ddt/integrations/spectrum-component-as-eds-block-in-gdoc

Custom Query Path

Setting Up Content

  1. Create Content Folder: Create a folder in your EDS project (e.g., /slides/, /products/)
  2. Add Content Pages: Create individual pages for each slide
  3. Create Query Index: Add a query-index file to the folder with required columns
  4. Configure Metadata: Ensure each page has the required metadata fields
  5. Publish: Publish the query-index to generate the JSON endpoint

How EDS Integration Works

When you deploy your block to /blocks/spectrum-card/, EDS automatically calls your decorate function for each block instance on the page. The block fetches content from the specified query-index.json endpoint and transforms it into fully interactive Spectrum cards.

Authors manage content through familiar document editing tools, while visitors see polished, accessible components that match Adobe's design standards. The beauty lies in this flexibility.

Build Process

The project uses a streamlined build process with dependency bundling:

  1. Development: Work in /build/spectrum-card/ with Vite tooling
  2. Testing: Hot reload at http://localhost:5173
  3. Building: npm run build:component bundles and copies to /blocks/
  4. Deployment: Copy /blocks/spectrum-card/ to your EDS project

AEM Emulation Layer Testing

Start the test environment:

# Starts AEM emulation server from root folder
npm run serve  

The server provides:

Full setup instructions and scripts available on GitHub, in README.md

Performance Considerations

Vite handles the heavy lifting of optimisation. It tree-shakes unused code from Spectrum components, ensuring you only ship what you actually use. The components themselves are built for performance, with efficient rendering and minimal runtime overhead.

The query-index pattern provides excellent performance:

Every interactive element works with keyboards by default. Screen readers announce content properly. Focus indicators appear where expected. These aren't features you add - they come built in.

Common Issues and Solutions

Component not loading data

Cards not rendering

CORS Issues

User Experience Features

The component includes a sophisticated modal system that provides a smooth reading experience:

// Create and show modal overlay with content

function showContentModal(cardData, index) {
  // Create modal overlay

  const overlay = document.createElement('div');
  overlay.style.cssText = `
    position: fixed; top: 0; left: 0; 
    width: 100%; height: 100%;
    background-color: rgba(0, 0, 0, 0.5);
    z-index: 1000; display: flex;
    align-items: center; justify-content: center;
  `;

  // Create modal content container

  const modal = document.createElement('div');
  modal.style.cssText = `
    background-color: white; border-radius: 8px;
    max-width: 800px; max-height: 80vh; width: 100%;
    position: relative; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
    overflow: hidden;
  `;

  // Fetch and display full content from .plain.html

  if (cardData.path) {
    fetchPlainHtml(cardData.path).then(html => {
      if (html) {
        content.innerHTML = html;
      }
    });
  }

  // ... Full implementation on GitHub

}

Key Modal Features

Technical Implementation

The component uses modern JavaScript ES modules and integrates with Adobe's Spectrum Web Components. Here's the key architecture for the numbered badge overlay:

// Create wrapper container for positioning

const cardWrapper = document.createElement('div');
cardWrapper.style.position = 'relative';
// Add numbered slide badge with absolute positioning
const slideNumber = document.createElement('div');
slideNumber.style.position = 'absolute';
slideNumber.style.top = '10px';
slideNumber.style.left = '10px';
slideNumber.style.zIndex = '10';
cardWrapper.appendChild(slideNumber);
// Add the actual Spectrum Card to the wrapper
cardWrapper.appendChild(card);

The full implementation includes:

See the complete source code at: https://github.com/ddttom/spectrum-with-eds

Unusual Pattern - Wrapper Container for Numbered Badges

The component uses a wrapper pattern to achieve the numbered badge overlay effect. This is necessary because Spectrum Card's encapsulated styling prevents direct positioning of external elements over it.

// Create wrapper for positioning context

const cardWrapper = document.createElement('div');
cardWrapper.style.position = 'relative';
// Badge needs absolute positioning
const slideNumber = document.createElement('div');
slideNumber.style.position = 'absolute';
slideNumber.style.zIndex = '10';

// Add both to wrapper
cardWrapper.appendChild(slideNumber);
cardWrapper.appendChild(card);

This pattern maintains compatibility with Spectrum Card updates while enabling custom overlays. See the full implementation notes on GitHub for alternative approaches considered.

Development Features

Moving Beyond Basic Cards

This card example shows the pattern, but you can build far more complex interfaces. The query-index pattern works for any content type:

Each follows the same integration pattern: create content, configure query-index, fetch and render. The real power comes from combining EDS's document-driven approach with Spectrum's component library and the flexibility of query-index.json for dynamic content.

Authors work in familiar tools while developers build sophisticated experiences. Everyone wins - especially your users who get fast, accessible, professional interfaces that work everywhere, with Adobe's design language and tested components.

Current Status

The Spectrum Card component is production-ready with:

Numbered Slide Badges - Visual hierarchy indicators
Modal Overlay System - In-page content display
Full Content Loading - Fetches .plain.html content
Professional Styling - Adobe Spectrum design
Multiple Close Methods - X button, click outside, ESC key
Responsive Design - Works across all devices
Error Handling - Graceful fallbacks
Accessibility - Keyboard and screen reader support

The component successfully demonstrates how to build professional, accessible web components using Adobe's design system while maintaining simplicity and performance.

Get the Code

Full source code and documentation available at: https://github.com/ddttom/spectrum-with-eds

The repository includes:

Resources and Next Steps

For further learning, explore the spectrum-components, Adobe Edge Delivery Services, and the project repository.

Found this helpful? Star the https://github.com/ddttom/spectrum-with-eds on GitHub!

/fragments/ddt/proposition

Related Articles

path=*
path=*
Back to Top