Component Structure

Components are the building blocks of pages in the Serverless CMS. This guide covers the structure, files, and packaging of component bundles.

What is a Component?

A component is a reusable UI element that can be added to pages. Examples include:

  • Title headings
  • Rich text content
  • Images with captions
  • Buttons and links
  • Navigation menus
  • Content teasers

Each component consists of:

  • HTML template: The structure and markup
  • CSS styles: The visual appearance
  • Dialog schema: The configurable properties
  • Metadata: Name, description, and purpose

Component Bundle Structure

A component bundle is a folder containing required and optional files:

my-component/

├── details.json (required) — Component metadata

├── dialog.json (required) — Configurable properties

├── component.html (required) — HTML template

├── component.css (required) — CSS styles

├── component.js (optional) — Data fetching function

├── editor.js (optional) — Editor-only editorial controls

└── variables.json (optional) — Design variable definitions

Required Files

details.json

Contains component metadata:

{

  "name": "Hero Banner",

  "description": "A large hero banner with image and call-to-action",

  "purpose": "Display prominent content at the top of pages",

  "optionsDescription": "Configure heading, subheading, image, and button"

}

Fields:

  • name: Display name (shown in component palette)
  • description: Brief description of the component
  • purpose: When and why to use this component
  • optionsDescription: Summary of configurable options

dialog.json

Defines configurable properties (see Dialog Schema):

{

  "schemaVersion": "1.0",

  "fields": [

    {

      "type": "text",

      "name": "heading",

      "label": "Heading",

      "required": true

    },

    {

      "type": "textarea",

      "name": "subheading",

      "label": "Subheading",

      "required": false

    }

  ]

}

component.html

The HTML template with variable markers (see Templating Syntax):

<div class="hero">

  <h1>{{heading}}</h1>

  {{#if subheading}}

    <p class="subheading">{{subheading}}</p>

  {{/if}}

</div>

component.css

The CSS styles for the component:

.hero {

  padding: var(--spacing-2xl);

  background: var(--color-primary);

  color: white;

  text-align: center;

}

 

.hero h1 {

  font-size: var(--font-size-xl);

  margin-bottom: var(--spacing-md);

}

 

.subheading {

  font-size: var(--font-size-lg);

  opacity: 0.9;

}

Optional Files

component.js

A data fetching function executed at publish time:

/**

 * Fetch data for the component

 * @param {Object} dialogValues - Values from the dialog

 * @param {Object} context - Context object with helper functions

 * @returns {Object} - Additional data to merge with dialogValues

 */

export async function fetchData(dialogValues, context) {

  // Query related pages

  const relatedPages = await context.queryPages({

    tags: dialogValues.tag,

    limit: 3

  });

 

  return {

    relatedPages: relatedPages.map(p => ({

      title: p.title,

      slug: p.slug,

      description: p.description

    }))

  };

}

Context API:

  • context.queryPages(options): Query pages in the site
  • context.getSite(): Get site metadata
  • context.getPage(pageId): Get a specific page

Constraints:

  • Must export a fetchData function
  • Must return within 5 seconds (timeout)
  • Cannot make external HTTP requests
  • Runs in a sandboxed environment

editor.js

An editor-only script that controls the component's editorial experience — for example, which rich-text formatting controls an author sees. It is loaded only inside the CMS editor and is never bundled into or served from a published site (published pages receive only component.html and component.css).

export default function registerComponentEditor(cms) {

  return {

    fields: {

      // Keyed by the field `name` from dialog.json

      content: {

        editor: 'richtext',

        toolbar: {

          paragraphStyles: [

            { label: 'Paragraph', value: 'p' },

            { label: 'Heading 1', value: 'h1' },

            { label: 'Heading 2', value: 'h2' },

          ],

          inline: ['bold', 'italic', 'underline', 'strikethrough'],

          blocks: ['unorderedList', 'orderedList', 'blockquote', 'horizontalRule'],

          insert: ['link', 'unlink'],

          utilities: ['removeFormat'],

        },

      },

    },

  };

}

Constraints:

  • Must export default a factory function (or a plain config object)
  • Field keys must match name values in dialog.json
  • Because it is editor-only, it is not HTML-sanitized
  • Runs only in the editor; not available to published output

See the Editor Scripts guide for the full contract, toolbar options, and a complete rich-text example.

variables.json

Design variable definitions (see Design Variables):

{

  "variables": [

    {

      "name": "--hero-min-height",

      "category": "layout",

      "description": "Minimum height of hero banner",

      "defaultValue": "400px"

    }

  ]

}

File Naming Conventions

Required naming:

  • details.json (exact name)
  • dialog.json (exact name)
  • component.html (exact name)
  • component.css (exact name)
  • component.js (exact name, if present)
  • editor.js (exact name, if present)
  • variables.json (exact name, if present)

Case sensitivity: All filenames are case-sensitive.

File Size Limits

File
Maximum Size
details.json
512 KB
dialog.json
512 KB
component.html
512 KB
component.css
512 KB
component.js
512 KB
editor.js
512 KB
variables.json
512 KB
Total bundle
10 MB (when zipped)

Packaging Components

Single Component

To package a single component:

  1. Create a folder with the component name
  2. Add all required files
  3. Zip the folder:

zip -r my-component.zip my-component/

Multiple Components

To package multiple components in one zip:

  1. Create a parent folder
  2. Add component subfolders
  3. Zip the parent folder:

components/

├── hero/

│ ├── details.json

│ ├── dialog.json

│ ├── component.html

│ └── component.css

└── card/

    ├── details.json

    ├── dialog.json

    ├── component.html

    └── component.css

zip -r components.zip components/

Note: Each top-level subfolder is treated as a separate component.

Uploading Components

To upload components to the CMS:

  1. Navigate to Components from the main navigation
  2. Click Upload Component
  3. Select your zip file
  4. Wait for validation
  5. Review validation results
  6. Click Confirm Upload if validation passes

Validation Process

The CMS validates each component:

  1. File presence: All required files exist
  2. JSON validity: details.json and dialog.json are valid JSON
  3. Schema conformance: Files conform to expected schemas
  4. Marker cross-reference: All {{markers}} in HTML reference dialog fields
  5. HTML sanitization: HTML is safe (no <script>, javascript:, on* handlers)
  6. File size: No file exceeds 512 KB
  7. Total size: Zip file doesn't exceed 10 MB

Validation Errors

If validation fails, you'll see detailed error messages:

Error Code
Cause
Solution
ZIP_INVALID
Invalid or oversized zip
Check zip file integrity and size
COMPONENT_MISSING_FILES
Missing required files
Add all required files
DETAILS_INVALID
Invalid details.json
Fix JSON syntax and required fields
DIALOG_INVALID_JSON
Invalid dialog.json
Fix JSON syntax
DIALOG_SCHEMA_VIOLATION
Invalid dialog schema
Fix field definitions
HTML_MARKER_MISMATCH
Marker references undefined field
Add field to dialog or remove marker
FILE_TOO_LARGE
File exceeds 512 KB
Reduce file size

All-or-Nothing Upload

Component uploads are atomic:

  • All components valid: All components are uploaded
  • Any component invalid: Nothing is uploaded

This prevents partial uploads that could break the component library.

Updating Components

To update an existing component:

  1. Navigate to Components
  2. Click the ⋮ menu next to the component
  3. Select Update
  4. Upload the new zip file
  5. Confirm the update
Important: Updating a component affects all pages using it. Republish pages to apply the update.

Deleting Components

To delete a component:

  1. Navigate to Components
  2. Click the ⋮ menu next to the component
  3. Select Delete
  4. Confirm the deletion
Important: You cannot delete a component if any pages are using it. Remove the component from all pages first.

Component Categories

Components are categorized as:

  • built-in: Platform-provided components (cannot be deleted)
  • custom: User-uploaded components (can be updated and deleted)

Best Practices

Structure

  • Single responsibility: Each component should do one thing well
  • Reusability: Design components to work in multiple contexts
  • Flexibility: Provide enough options without overwhelming users
  • Defaults: Set sensible default values for optional fields

HTML

  • Semantic markup: Use appropriate HTML elements
  • Accessibility: Include ARIA attributes where needed
  • Responsive: Design for mobile-first
  • Clean code: Use consistent indentation and formatting

CSS

  • Use design variables: Reference design variables for consistency
  • Scoped styles: Use specific class names to avoid conflicts
  • Mobile-first: Start with mobile styles, add desktop overrides
  • Performance: Avoid expensive CSS (complex selectors, animations)

Dialog

  • Clear labels: Use descriptive field labels
  • Helpful descriptions: Add descriptions for complex fields
  • Logical order: Order fields in a logical sequence
  • Required vs optional: Mark required fields appropriately

Testing

  • Test in context: Add component to a page and test
  • Test all options: Try different field combinations
  • Test responsive: Check on mobile, tablet, and desktop
  • Test accessibility: Verify keyboard navigation and screen readers

Example: Complete Component

See Component Example for a complete, working component with all files.

Related Topics