CtrlK
BlogDocsLog inGet started
Tessl Logo
PluginPublic

adobe/aem-cloud-service

All AEM as a Cloud Service skills: component development, Workflow model design, development, triggering, launchers, debugging, and triaging, Dispatcher config authoring, advisory, incident response, performance tuning, and security hardening, Rapid Development Environment (RDE) deployment, log inspection, snapshots, and troubleshooting via `aio aem rde`, Jackrabbit Oak query/index tuning (property, Lucene index definitions), and extracting/inventorying JCR queries (XPath, SQL2, QueryBuilder) across a codebase.

Quality

85%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

core-components.mdskills/create-component/references/

AEM Reusable Component Library + Design System Guide

A comprehensive guide to building maintainable, scalable component architectures in Adobe Experience Manager.


Table of Contents

  1. The Core Problem
  2. Component Library Architecture
  3. Anti-Patterns to Avoid
  4. Governance & Process
  5. ROI Calculation

The Core Problem

Most AEM projects fail at component reusability because:

  1. No upfront design system alignment - Developers build what's in the wireframe, not what's in a system
  2. Component explosion - 150+ components when 30 well-designed ones would suffice
  3. Copy-paste inheritance - Teams duplicate components instead of extending
  4. Dialog inconsistency - Same field configured 47 different ways across components

Component Library Architecture

Layer 1: Foundation (Design Tokens)

/apps/mysite/clientlibs/clientlib-base/
├── tokens/
│   ├── _colors.scss       # $color-primary, $color-secondary
│   ├── _typography.scss   # $font-heading, $font-body, scale
│   ├── _spacing.scss      # $space-xs, $space-sm, $space-md
│   └── _breakpoints.scss  # $bp-mobile, $bp-tablet, $bp-desktop

Why it matters: One token change updates entire site. Without this, you're hunting through 200 files to change a brand color.


Layer 2: Core Components Extension Strategy

Don't reinvent — extend Adobe Core Components:

Core ComponentYour ExtensionWhat You Add
core/wcm/components/imagemysite/components/imageBrand-specific renditions, lazy-load config
core/wcm/components/teasermysite/components/cardCard variants via style system
core/wcm/components/containermysite/components/sectionBackground options, spacing presets

Sling Resource Merger approach:

<!-- /apps/mysite/components/image/.content.xml -->
<jcr:root
    jcr:primaryType="cq:Component"
    sling:resourceSuperType="core/wcm/components/image/v3/image"
    componentGroup="MySite - Content"/>

Layer 3: Component Taxonomy

Organize by atomic design principles:

/apps/mysite/components/
├── atoms/
│   ├── button/          # Single CTA, no children
│   ├── icon/            # SVG sprite reference
│   └── badge/           # Labels, tags
├── molecules/
│   ├── card/            # Image + text + CTA
│   ├── form-field/      # Label + input + validation
│   └── breadcrumb/      # Navigation molecule
├── organisms/
│   ├── header/          # Logo + nav + search
│   ├── hero/            # Full-width showcase
│   └── product-grid/    # Collection display
├── templates/
│   └── layouts/         # Page structure containers
└── _shared/
    ├── models/          # Shared Sling Models
    └── utils/           # HTL templates, helpers

Layer 4: Sling Model Patterns

Anti-Pattern: Fat models with business logic everywhere

// Anti-pattern: 500-line model doing everything
@Model(adaptables = Resource.class)
public class MegaCardModel {
    // 47 @ValueMapValue fields
    // Complex conditional logic
    // Formatting, validation, transformation all mixed
}

Best Practice: Composition via interfaces and delegation

// Pattern: Interface-based composition
@Model(adaptables = Resource.class,
       adapters = Card.class,
       defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class CardImpl implements Card, Linkable, Imageable {

    @Self @Via(type = ResourceSuperType.class)
    private Teaser coreTeaser;  // Delegate to core

    @ValueMapValue
    private String cardVariant;

    @Override
    public Link getLink() {
        return coreTeaser.getLink();
    }

    @Override
    public String getVariantClass() {
        return "card--" + StringUtils.defaultIfBlank(cardVariant, "default");
    }
}

Layer 5: Style System Architecture

The multiplier effect — 1 component × Style System = many variants without code:

// /apps/mysite/components/card/cq:design_dialog/.content.xml
{
  "tabs": {
    "styletab": {
      "styles": {
        "groups": [
          {
            "label": "Card Style",
            "items": [
              { "label": "Default", "value": "card--default" },
              { "label": "Featured", "value": "card--featured" },
              { "label": "Minimal", "value": "card--minimal" },
              { "label": "Horizontal", "value": "card--horizontal" }
            ]
          },
          {
            "label": "Card Size",
            "items": [
              { "label": "Small", "value": "card--sm" },
              { "label": "Medium", "value": "card--md" },
              { "label": "Large", "value": "card--lg" }
            ]
          }
        ]
      }
    }
  }
}

Result: 4 styles × 3 sizes = 12 visual variants, 1 component to maintain.


Layer 6: Dialog Standardization

Create reusable dialog snippets:

<!-- /apps/mysite/components/_shared/dialogs/link-fields.xml -->
<linkFields jcr:primaryType="nt:unstructured">
    <linkUrl
        jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
        fieldLabel="Link URL"
        name="./linkUrl"
        rootPath="/content"/>
    <linkTarget
        jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/coral/foundation/form/checkbox"
        text="Open in new tab"
        name="./linkTarget"
        value="_blank"
        uncheckedValue="_self"/>
    <linkAriaLabel
        jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
        fieldLabel="Accessibility Label"
        name="./linkAriaLabel"/>
</linkFields>

Include in any component dialog:

<items jcr:primaryType="nt:unstructured">
    <linkGroup
        jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/coral/foundation/include"
        path="/apps/mysite/components/_shared/dialogs/link-fields.xml"/>
</items>

Anti-Patterns to Avoid

Anti-PatternWhy It HurtsBetter Approach
One-off componentsEach page request spawns new componentExtend existing, use Style System
Hardcoded breakpointsCan't adapt to design changesUse token variables
Logic in HTLUntestable, unmaintainableMove to Sling Models
Inline stylesAuthors create inconsistencyPredefined style options only
Component per page typeExplosion of similar componentsComposable containers
No component governanceWild west, duplicates emergeComponent review in PR process

Governance & Process

Component Request Workflow

1. Author/Designer requests new component
         ↓
2. Check: Can existing component + Style System solve this?
         ↓ No
3. Check: Can we extend a Core Component?
         ↓ No
4. Design review: Atomic design placement, token usage
         ↓
5. Build with full documentation
         ↓
6. Add to component library showcase

Living Documentation

Build a component showcase page (often called "Kitchen Sink" or "Pattern Library"):

  • Every component rendered with all variants
  • Copy-paste dialog configurations
  • Accessibility annotations
  • Performance benchmarks

ROI Calculation Example

MetricWithout LibraryWith Library
Components built15035
Avg time per component16 hours20 hours (more robust)
Total component dev time2,400 hours700 hours
New page build time40 hours8 hours
Bug fix propagationManual per instanceSingle fix, everywhere

Net savings: 50-70% development time over project lifecycle


Quick Reference Checklist

Before Building a New Component

  • Can an existing component + Style System handle this?
  • Can I extend a Core Component?
  • Does it fit atomic design taxonomy?
  • Are design tokens used (no hardcoded values)?
  • Is the Sling Model using composition/interfaces?
  • Are dialog fields reusing shared snippets?
  • Is it documented in the component showcase?

Component Health Metrics

  • Component count: Target < 50 custom components
  • Style System coverage: > 80% of components should use it
  • Core Component extension rate: > 60% should extend core
  • Dialog snippet reuse: > 70% of common fields shared

Further Reading

skills

README.md

tile.json