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:
- Create a folder with the component name
- Add all required files
- Zip the folder:
zip -r my-component.zip my-component/
Multiple Components
To package multiple components in one zip:
- Create a parent folder
- Add component subfolders
- 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:
- Navigate to Components from the main navigation
- Click Upload Component
- Select your zip file
- Wait for validation
- Review validation results
- Click Confirm Upload if validation passes
Validation Process
The CMS validates each component:
- File presence: All required files exist
- JSON validity: details.json and dialog.json are valid JSON
- Schema conformance: Files conform to expected schemas
- Marker cross-reference: All {{markers}} in HTML reference dialog fields
- HTML sanitization: HTML is safe (no <script>, javascript:, on* handlers)
- File size: No file exceeds 512 KB
- 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:
- Navigate to Components
- Click the ⋮ menu next to the component
- Select Update
- Upload the new zip file
- Confirm the update
Important: Updating a component affects all pages using it. Republish pages to apply the update.
Deleting Components
To delete a component:
- Navigate to Components
- Click the ⋮ menu next to the component
- Select Delete
- 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
- Dialog Schema — Define configurable properties
- Templating Syntax — Use variable markers in HTML
- Design Variables — Use design variables in CSS
- Validation Rules — Understand validation requirements
- Editor Scripts — Add editor-only editorial controls (editor.js)