
Building Blocks
- 29 installs
- 45 repo stars
- Updated August 4, 2026
- adobe/helix-website
building-blocks is a Claude Code skill for creating new AEM Edge Delivery blocks or modifying existing ones through JavaScript decoration and CSS styling.
About
This skill guides creating a new AEM Edge Delivery block or making significant changes to an existing one, following Content Driven Development principles. It walks through verifying prerequisites, finding similar blocks, creating block files, implementing the decorate(block) JavaScript, adding scoped CSS, testing, and documenting. A front-end developer uses it during the implementation phase of a CDD workflow to build reusable EDS blocks.
- Creates or modifies AEM Edge Delivery blocks with JS decoration and CSS
- Follows Content Driven Development (CDD) with prerequisites before implementation
- Enforces block-scoped CSS and prototype-based decorate(block) patterns
Building Blocks by the numbers
- 29 all-time installs (skills.sh)
- Ranked #1,464 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
building blocks capabilities & compatibility
Free; runs locally against an EDS project
- Capabilities
- frontend component build · dom decoration · css styling
- Use cases
- frontend · ui design · documentation
- Pricing
- Free
What building blocks says it does
Guide for creating new AEM Edge Delivery blocks or modifying existing blocks. Use this skill whenever you are creating a new block from scratch or making significant changes to existing blocks
JS file exports a default `decorate(block)` function (can be async if needed)
All CSS selectors must be scoped to the block (start with `.{block-name}`)
npx skills add https://github.com/adobe/helix-website --skill building-blocksAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 29 |
|---|---|
| repo stars | ★ 45 |
| Last updated | August 4, 2026 |
| Repository | adobe/helix-website ↗ |
What it does
Create or modify an AEM Edge Delivery block with JS decoration and scoped CSS following Content Driven Development.
Who is it for?
Building or significantly changing an AEM Edge Delivery block within a Content Driven Development workflow after content and models are ready.
Skip if: Use before CDD is complete, or when only content modeling / page import is needed rather than block code.
When should I use this skill?
Creating a new block from scratch or making significant changes involving JavaScript decoration, CSS styling, or content model changes.
What you get
A working, tested, documented EDS block with a decorate(block) function and block-scoped CSS following EDS conventions.
- A blocks/{block-name}/ directory with {block-name}.js and {block-name}.css
- decorate(block) JavaScript
- block documentation
By the numbers
- 7-step process from verifying prerequisites to documenting the block
- JS file exports a default decorate(block) function
Files
Building Blocks
This skill guides you through creating new AEM Edge Delivery blocks or modifying existing ones, following Content Driven Development (CDD) principles. Blocks are the reusable building blocks of AEM sites - each transforms authored content into rich, interactive experiences through JavaScript decoration and CSS styling. This skill covers the complete development process: understanding content models, implementing decoration logic, applying styles, and maintaining code quality standards.
Related Skills
- content-driven-development: MUST be invoked before using this skill to ensure content and content models are ready
- block-collection-and-party: Use to find similar blocks for patterns
- testing-blocks: Automatically invoked after implementation for comprehensive testing
When to Use This Skill
This skill should ONLY be invoked from the content-driven-development skill during Phase 2 (Implementation).
If you are not already following the CDD process:
- STOP - Do not proceed with this skill
- Invoke the content-driven-development skill first
- The CDD skill will ensure test content and content models are ready before implementation
This skill handles:
- Creating new block files and structure
- Implementing JavaScript decoration
- Adding CSS styling
- Code quality and testing
Prerequisites
REQUIRED before using this skill:
- ✅ Test content must exist (in CMS or local drafts)
- ✅ Content model must be defined
- ✅ Test content URL must be available
Information needed: 1. Block name: What should the block be called? 2. Content model: The defined structure authors will use 3. Test content URL: Path to test content for development
Process Overview
1. Verify Prerequisites (CDD completed) 2. Find Similar Blocks (for patterns and reuse) 3. Create or Modify Block Structure (files and directories) 4. Implement JavaScript Decoration (DOM transformation) 5. Add CSS Styling (scoped, responsive styles) 6. Test the Implementation (local testing, linting) 7. Document Block (developer and author-facing docs)
Detailed Process
1. Verify Prerequisites
Before proceeding, confirm with the user:
"Do you have:
- ✅ Test content created (URL or path)?
- ✅ Content model defined?
If not, we need to use the content-driven-development skill first."
If prerequisites are not met, STOP and invoke the content-driven-development skill.
If prerequisites are met, get the test content URL from the user and proceed to step 2.
2. Find Similar Blocks
For new blocks or major modifications:
1. Search the codebase for similar blocks that might provide useful patterns or code we can re-use 2. Use the block-collection-and-party skill to find relevant reference blocks
Review the implementation patterns in similar blocks to inform your approach.
For minor modifications to existing blocks: Skip to step 3.
3. Create or Modify Block Structure
For new blocks:
1. Create directory: blocks/{block-name}/ 2. Create files: {block-name}.js and {block-name}.css 3. Use the boilerplate structure (or reference templates in resources/ if helpful):
- JS file exports a default
decorate(block)function (can be async if needed) - CSS file targets the
.{block-name}class
For existing blocks:
1. Locate the existing block directory in blocks/{block-name}/ 2. Review the current implementation before making changes 3. Understand the existing decoration logic and styles
4. Implement JavaScript Decoration
Follow patterns and conventions in resources/js-guidelines.md:
- Use DOM APIs to transform the initial block HTML structure
- Keep decoration logic focused and single-purpose
- Handle variants appropriately (check block.classList for variant classes)
- Follow established patterns from similar blocks
Read `resources/js-guidelines.md` for detailed examples, code standards, and best practices.
5. Add CSS Styling
Follow patterns and conventions in resources/css-guidelines.md:
- All CSS selectors must be scoped to the block (start with
.{block-name}) - Use BEM-like naming within the block scope
- Leverage CSS custom properties for theming
- Write mobile-first responsive styles
- Keep specificity low
- Follow established patterns from similar blocks
Read `resources/css-guidelines.md` for detailed examples, code standards, and best practices.
6. Test the Implementation
After implementation is complete, invoke the testing-blocks skill:
The testing-blocks skill will guide you through:
- Writing unit tests for any logic-heavy utilities
- Browser testing to validate block behavior
- Taking screenshots for validation and PR documentation
- Running linting and fixing issues
- Verifying GitHub checks pass
Provide the testing-blocks skill with:
- Block name being tested
- Test content URL (from CDD process)
- Any variants that need testing
Return to this skill after testing is complete to proceed to step 7.
7. Document Block
Blocks require two types of documentation:
Developer Documentation
- Most blocks are simple and self-contained and only need code comments for documentation
- If a block is especially complex (has many variants, or especially complex code) consider adding a brief README.md in the block folder
- Keep any README documentation very brief so it can be consumed at a glance
Author-Facing Documentation
Author-facing documentation helps content authors understand how to use the block in the CMS. This documentation typically exists as draft/library content in the CMS itself, not in the codebase.
When author documentation is needed:
Almost all blocks should have author-facing documentation. The only exceptions are:
- Deprecated blocks that should no longer be used but can't be removed yet
- Special-purpose blocks used very infrequently on a need-to-know basis
- Auto-blocked blocks that shouldn't be used directly by authors
Maintaining author documentation:
Author documentation must be kept in sync with the block implementation:
- Update when variants are added, removed, or modified
- Update when the content structure changes
- Update when block behavior or functionality changes
Where author documentation lives:
Different projects use different approaches for author documentation:
1. Sidekick Library (Google Drive/SharePoint authoring):
- Uses https://github.com/adobe/franklin-sidekick-library
- Check for
/tools/sidekick/library.htmlin the codebase - If present, guide user to add/update block documentation in the library
2. Document Authoring (DA) Library:
- Uses https://docs.da.live/administrators/guides/setup-library
- Different implementation than Sidekick Library
- If in use, guide user to update block documentation in DA library
3. Universal Editor (UE) projects:
- Often skip dedicated author documentation libraries
- May use inline help or other mechanisms
4. Simple documentation pages:
- Some projects maintain documentation under
/draftsor/docs - Pages contain authoring guides and block examples
What to include in author documentation:
The specific content of author documentation varies by project. As an agent: 1. Identify that author documentation needs to be created or updated 2. Determine which documentation approach the project uses (check for /tools/sidekick/library.html as a signal) 3. Guide the user on what aspects of the block should be documented based on the changes made 4. Provide specific guidance based on the project's documentation approach
Reference Materials
resources/js-guidelines.mdresources/css-guidelines.md
CSS Guidelines for AEM Blocks
Block Scoping
All CSS selectors must be scoped to the block. This is critical to prevent style leakage between blocks.
✅ Good - scoped to block:
main .my-block {
padding: 1rem;
}
main .my-block h2 {
font-size: var(--heading-font-size-m);
}❌ Bad - not scoped:
/* This will affect ALL h2 elements on the page */
h2 {
font-size: var(--heading-font-size-m);
}
/* This will affect elements outside the block */
.item {
padding: 1rem;
}Scoping pattern:
- Always start selectors with
main .{block-name} - This ensures styles only apply within your block
- Use additional classes for sub-elements within the block
⚠️ Special note on `-wrapper` and `-container` classes:
The platform automatically adds .{block-name}-wrapper and .{block-name}-container divs outside your block. If you need to style elements with these class names inside your block, you must scope them carefully:
/* ❌ Bad - will affect the wrapper OUTSIDE your block */
main .my-block-wrapper {
padding: 2rem;
}
/* ✅ Good - only affects wrappers INSIDE your block */
main .my-block .my-block-wrapper {
padding: 2rem;
}
/* Better - avoid using these class names inside your block */
main .my-block .inner-wrapper {
padding: 2rem;
}Best practice: Avoid using -wrapper or -container suffix for classes inside your block to prevent confusion.
Naming Conventions
Use BEM-like naming for elements within your block:
/* Block */
main .my-block {
/* block styles */
}
/* Element - using descriptive class names */
main .my-block .item {
/* item styles */
}
main .my-block .item-title {
/* item title styles */
}
/* Modifier/variant */
main .my-block.dark {
/* dark variant styles */
}
main .my-block.wide .item {
/* item styles in wide variant */
}Key points:
- Use lowercase with hyphens for class names (kebab-case)
- Choose descriptive, semantic names
- Avoid generic names like
.container,.wrapper- be specific to your block
CSS Custom Properties (Variables)
Leverage CSS custom properties defined in styles/styles.css for consistency:
Colors:
main .my-block {
background-color: var(--background-color);
color: var(--text-color);
}
main .my-block a:any-link {
color: var(--link-color);
}
main .my-block a:hover {
color: var(--link-hover-color);
}Typography:
main .my-block h2 {
font-family: var(--heading-font-family);
font-size: var(--heading-font-size-m);
}
main .my-block p {
font-family: var(--body-font-family);
font-size: var(--body-font-size-m);
}Layout:
main .my-block {
max-width: var(--max-content-width);
padding-inline: var(--inline-section-padding);
}Available custom properties:
- Colors:
--clr-*,--link-color,--background-color,--text-color, etc. - Fonts:
--body-font-family,--heading-font-family,--fixed-font-family - Font sizes:
--heading-font-size-*,--body-font-size-* - Layout:
--max-content-width,--inline-section-padding
See styles/styles.css for the complete list.
Mobile-First Responsive Design
Write styles mobile-first, then add media queries for larger screens:
/* Mobile styles (default) */
main .my-block {
padding: 1rem;
flex-direction: column;
}
/* Tablet and up */
@media (width >= 600px) {
main .my-block {
padding: 2rem;
}
}
/* Desktop and up */
@media (width >= 900px) {
main .my-block {
flex-direction: row;
padding: 4rem;
}
}Standard breakpoints:
- Mobile: default (< 600px)
- Tablet:
@media (width >= 600px) - Desktop:
@media (width >= 900px)
Modern syntax:
- Use range syntax:
(width >= 600px)instead of(min-width: 600px) - Use logical properties where appropriate
Modern CSS Features
Use modern CSS features for better maintainability and performance:
Logical properties:
/* Use logical properties for internationalization */
main .my-block {
padding-inline: 1rem; /* left/right in LTR, right/left in RTL */
padding-block: 2rem; /* top/bottom */
margin-inline-start: 1rem; /* left in LTR */
border-inline-start: 2px solid black;
}Modern layout:
/* Flexbox */
main .my-block {
display: flex;
gap: 1rem; /* Better than margin hacks */
flex-wrap: wrap;
}
/* Grid */
main .my-block {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
}Modern color syntax:
main .my-block {
background-color: rgb(0 0 0 / 20%); /* Modern RGB with alpha */
color: hsl(200 50% 50%); /* HSL syntax */
}Keep Specificity Low
Avoid overly specific selectors:
✅ Good - low specificity:
main .my-block .item {
padding: 1rem;
}
main .my-block .item-title {
font-size: 1.5rem;
}❌ Bad - high specificity:
main .my-block div div div.item {
padding: 1rem;
}
main div.my-block > div > h2.item-title {
font-size: 1.5rem;
}Best practices:
- Use classes, not tag names when possible
- Avoid ID selectors
- Keep selector chains short (2-3 levels max)
- Don't nest deeper than necessary
Handling Variants
Use the variant class alongside the block class:
/* Base block */
main .my-block {
background-color: var(--background-color);
color: var(--text-color);
}
/* Dark variant */
main .my-block.dark {
background-color: var(--dark-color);
color: var(--clr-white);
}
/* Wide variant */
main .my-block.wide {
max-width: 100%;
}
/* Combining variants */
main .my-block.dark.wide {
/* Styles for both dark and wide */
}Performance Considerations
Minimize reflows and repaints:
/* Prefer transforms over position changes */
main .my-block .item {
transform: translateX(10px); /* Better performance */
}
/* Avoid this: */
main .my-block .item {
left: 10px; /* Triggers reflow */
}Use will-change sparingly:
/* Only for elements that will definitely animate */
main .my-block .animated-item {
will-change: transform;
}Avoid expensive properties on large elements:
/* Be careful with these on large areas: */
/* box-shadow, border-radius, opacity, filters */Code Style
Formatting:
- Use 2-space indentation
- One selector per line for multiple selectors
- Opening brace on same line as selector
- One property per line
- Space after colon in property declarations
- No space before colon
- End all declarations with semicolon
Example:
main .my-block,
main .my-block .item {
display: flex;
padding: 1rem;
gap: 1rem;
}Order of properties (recommended): 1. Layout (display, position, top, left, etc.) 2. Box model (width, height, margin, padding, border) 3. Visual (background, color, font, etc.) 4. Animation/transform
Common Patterns
Reset list styles
main .my-block ul {
list-style: none;
margin: 0;
padding: 0;
}Center content
main .my-block {
max-width: var(--max-content-width);
margin-inline: auto;
}Aspect ratio containers
main .my-block .video-container {
aspect-ratio: 16 / 9;
}Truncate text
main .my-block .truncated {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}Anti-Patterns to Avoid
❌ Don't use !important:
/* Avoid this */
main .my-block {
color: red !important;
}
/* Fix specificity issues properly instead */❌ Don't style elements outside your block:
/* Bad - modifies header globally */
main .my-block {
/* ... */
}
header {
background: red;
}❌ Don't hardcode values when variables exist:
/* Bad */
main .my-block {
font-family: 'Lato', sans-serif;
color: #666;
}
/* Good */
main .my-block {
font-family: var(--body-font-family);
color: var(--text-color);
}❌ Don't use absolute positioning for layout:
/* Prefer flexbox or grid for layout */
/* Use absolute positioning only for visual effects */JavaScript Guidelines for AEM Blocks
Basic Block Structure
Every block must export a default decorate function that receives the block element as a parameter. The function can be async if needed.
Basic structure:
/**
* decorate the block
* @param {Element} block the block
*/
export default async function decorate(block) {
// Your decoration logic here
}Key points:
- Always export the decorate function as the default export
- The function receives the block DOM element as the first parameter
- Use
asyncif you need to await operations (fetching data, loading modules, etc.) - Include JSDoc comments describing the functions
DOM Manipulation Patterns
Good Patterns
✅ Re-use existing DOM elements when possible:
// Good - re-use the existing element
const paragraph = block.querySelector('p');
paragraph.classList.add('decorated');
// Also good - extract and re-use
const picture = block.querySelector('picture');
const figure = document.createElement('figure');
figure.append(picture); // Re-uses the picture element
block.replaceChildren(figure);
// Avoid - creating new elements unnecessarily
const text = block.querySelector('p').textContent;
const newP = document.createElement('p');
newP.textContent = text;
// This throws away the original <p> and creates a new one✅ Use semantic HTML:
const blockquote = document.createElement('blockquote');
const figure = document.createElement('figure');✅ Use spread operator for multiple elements:
const pars = block.querySelectorAll('p');
const container = document.createElement('div');
container.append(...pars);✅ Replace content efficiently:
block.replaceChildren(newElement);✅ Query within the block scope:
// Good - scoped to block
const links = block.querySelectorAll('a');
// Avoid - queries entire document
const links = document.querySelectorAll('a');Bad Patterns
❌ Don't use innerHTML for complex structures:
// Bad - hard to maintain, XSS risk
block.innerHTML = '<div class="wrapper"><p>Text</p></div>';
// Good - use DOM APIs
const wrapper = document.createElement('div');
wrapper.className = 'wrapper';
const p = document.createElement('p');
p.textContent = 'Text';
wrapper.append(p);
block.append(wrapper);❌ Don't mutate elements from other blocks:
// Bad - affects global state
const header = document.querySelector('header');
header.classList.add('modified-by-my-block');
// Good - only modify your block
block.classList.add('has-special-behavior');❌ Don't leave temporary DOM elements:
// Bad - leaves empty paragraphs
const text = block.querySelector('p').textContent;
const newDiv = document.createElement('div');
newDiv.textContent = text;
block.append(newDiv);
// Good - remove the original
const p = block.querySelector('p');
const newDiv = document.createElement('div');
newDiv.textContent = p.textContent;
p.replaceWith(newDiv);Handling Variants
Blocks can have variant classes applied (e.g., <div class="my-block dark">). Check for variants using classList:
export default async function decorate(block) {
const isDark = block.classList.contains('dark');
const isWide = block.classList.contains('wide');
// Apply variant-specific logic
if (isDark) {
// Handle dark variant
}
}Key points:
- Variant classes are added alongside the block class
- Use
classList.contains()to check for variants - CSS should handle most variant styling; use JS only for behavior changes
Common Patterns
Working with Images
Images in authored content come with srcset and alt attributes from the platform. You can also create optimized images programmatically using createOptimizedPicture() from aem.js:
import { createOptimizedPicture } from '../../scripts/aem.js';
export default async function decorate(block) {
// Working with existing images
const images = block.querySelectorAll('img');
images.forEach((img) => {
// Images come with srcset and alt from the platform
// Wrap in figure if needed
const figure = document.createElement('figure');
img.replaceWith(figure);
figure.append(img);
});
// Creating optimized pictures programmatically
const picture = createOptimizedPicture('/path/to/image.jpg', 'Alt text', false, [{ width: '750' }]);
block.append(picture);
}Fetching Data
Use async/await for data fetching:
export default async function decorate(block) {
try {
const response = await fetch('/path/to/data.json');
const data = await response.json();
// Use the data
renderData(block, data);
} catch (error) {
// eslint-disable-next-line no-console
console.error('Failed to load data:', error);
}
}Loading Additional Modules
Use regular imports for modules that are always needed:
import { someFunction } from '../../scripts/utils.js';
export default async function decorate(block) {
someFunction(block);
}Use dynamic imports for modules that are conditionally needed or can be loaded later:
export default async function decorate(block) {
// Conditionally load a module
if (block.classList.contains('advanced')) {
const { advancedFunction } = await import('../../scripts/advanced.js');
advancedFunction(block);
}
// Or defer loading until later
setTimeout(async () => {
const { heavyModule } = await import('../../scripts/heavy.js');
heavyModule.init(block);
}, 0);
}Handling Multiple Content Patterns
Sometimes a block might have different content structures:
export default async function decorate(block) {
const rows = block.querySelectorAll(':scope > div');
// Pattern 1: Single row with image and text
if (rows.length === 1) {
// Handle simple pattern
}
// Pattern 2: Multiple rows
if (rows.length > 1) {
// Handle complex pattern
}
}Note: Try to avoid this. Multiple content patterns increase complexity. Work with content authors to agree on a single, clear content model when possible.
Code Style
This project uses Airbnb ESLint configuration with some modifications:
Key rules:
- Use ES6+ features (const, let, arrow functions, template literals)
- Always include
.jsextension in imports:import { foo } from './bar.js'; - Use single quotes for strings
- No semicolons are enforced, but be consistent
- Use 2-space indentation
- Unix line endings (LF)
- Parameter reassignment is allowed for properties:
block.foo = 'bar'
Naming conventions:
- Use camelCase for variables and functions
- Use PascalCase for classes
- Use UPPER_CASE for constants
File naming:
- Block files must match block name:
my-block.js,my-block.css - Use kebab-case for file names
Performance Considerations
Minimize work in the decoration function:
- Only do what's necessary to achieve the desired structure
- Defer expensive operations (heavy calculations, large data fetching) when possible
- Remember: blocks in the first section load eagerly and affect LCP
Example - defer heavy work:
export default async function decorate(block) {
// Do minimal initial decoration
block.classList.add('initialized');
// Defer expensive work
setTimeout(async () => {
const data = await fetchHeavyData();
renderComplexUI(block, data);
}, 0);
}Example - use Intersection Observer to load only when viewed:
export default async function decorate(block) {
// Useful for embeds like YouTube videos, heavy widgets, etc.
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
observer.disconnect();
// Load heavy content only when block is visible
loadYouTubeEmbed(block);
}
});
observer.observe(block);
}Helper Functions
You can import helpful utilities from scripts/aem.js:
import {
buildBlock,
decorateBlock,
loadBlock,
loadCSS,
loadScript,
toClassName,
getMetadata,
createOptimizedPicture,
} from '../../scripts/aem.js';Common helpers:
toClassName(text)- Converts text to a valid CSS class namegetMetadata(name)- Gets page metadata valueloadCSS(href)- Loads a CSS file and returns a promise that resolves when loadedloadScript(url, attrs)- Loads a JavaScript file with optional attributes (async, type, etc.)createOptimizedPicture(src, alt, eager, breakpoints)- Creates a responsive picture element with optimized imagesbuildBlock(name, cells)- Programmatically creates a block DOM structure. Note: For the block to fully display, it must be decorated and loaded after being built and added to the DOM. In most cases, you'll need to calldecorateBlock()andloadBlock()after building.
Example - building and loading a block:
// Create the block
const myBlock = buildBlock('my-block', [[document.createElement('p')]]);
// Blocks must be wrapped in a div and placed inside a section
const wrapper = document.createElement('div');
wrapper.append(myBlock);
// Add to a section (either find existing or create new)
const section = document.querySelector('main .section') || document.querySelector('main > div');
section.append(wrapper);
// Decorate and load (required in most contexts)
decorateBlock(myBlock);
await loadBlock(myBlock);See scripts/aem.js for the complete list (but remember: NEVER MODIFY aem.js).
Related skills
FAQ
What must be true before using this skill?
Test content must exist, the content model must be defined, and a test content URL must be available; otherwise use the content-driven-development skill first.
How are CSS selectors scoped?
All CSS selectors must be scoped to the block, starting with .{block-name}, using BEM-like naming and low specificity.