Why Validate Components?

Without validation, configuration errors go undetected until runtime - or worse, silently produce incorrect output. Validation catches problems early in the build process.

Common errors validation prevents:

  • Misspelled property names (isDark vs isDArk)
  • Wrong value types (true as string instead of boolean)
  • Invalid enum values (buttonStyle: "red" when only "primary"/"secondary" allowed)
  • Missing required fields (mapProvider required but not provided)
  • Incorrect data structures (object instead of array)

Benefits:

  • Build-time detection: Errors caught before deployment
  • Clear error messages: Know exactly what's wrong and where
  • Self-documenting: Validation schema shows what's expected
  • Editor support: IDEs can use schemas for autocomplete
  • Confidence: Know your components are configured correctly

Validation in Manifest Files

Each component's manifest.json can include a validation object using JSON Schema syntax:

Basic structure:

{
  "name": "hero",
  "type": "section",
  "styles": ["hero.css"],
  "scripts": [],
  "requires": ["text", "ctas", "image"],
  "validation": {
    "required": ["sectionType"],
    "properties": {
      "sectionType": {
        "type": "string",
        "const": "hero"
      }
    }
  }
}

The validation object defines:

  • required: Array of required property names
  • properties: Schema for each property's validation rules

Type Validation

The most basic validation ensures values are the correct type:

{
  "validation": {
    "properties": {
      "isFullScreen": {
        "type": "boolean"
      },
      "title": {
        "type": "string"
      },
      "slideCount": {
        "type": "number"
      },
      "items": {
        "type": "array"
      },
      "settings": {
        "type": "object"
      }
    }
  }
}

Error examples:

# WRONG - string instead of boolean
isFullScreen: "true"
# Error: Property isFullScreen must be boolean, got string

# WRONG - number instead of string
title: 42
# Error: Property title must be string, got number

# CORRECT
isFullScreen: true
title: "Welcome"

Enum Validation (Controlled Values)

Enum validation ensures values come from a predefined list:

{
  "validation": {
    "properties": {
      "mapProvider": {
        "type": "string",
        "enum": ["leaflet", "openlayers"],
        "default": "leaflet"
      },
      "buttonStyle": {
        "type": "string",
        "enum": ["primary", "secondary", "tertiary", "inverted"]
      },
      "imageScreen": {
        "type": "string",
        "enum": ["light", "dark", "none"]
      }
    }
  }
}

Error examples:

# WRONG - invalid provider
mapProvider: leaflet.js
# Error: mapProvider must be one of: leaflet, openlayers

# WRONG - typo
buttonStyle: primery
# Error: buttonStyle must be one of: primary, secondary, tertiary, inverted

# CORRECT
mapProvider: leaflet
buttonStyle: primary
imageScreen: dark

Enums are perfect for:

  • Provider choices (leaflet vs openlayers)
  • Style variants (primary, secondary, tertiary)
  • Size options (small, medium, large)
  • Display modes (grid, list, masonry)
  • Theme options (light, dark, auto)

Const Validation (Exact Values)

The const keyword validates that a property has an exact value:

{
  "validation": {
    "required": ["sectionType"],
    "properties": {
      "sectionType": {
        "type": "string",
        "const": "hero"
      }
    }
  }
}

This ensures the component is used with the correct sectionType:

# WRONG - incorrect section type
- sectionType: hiro
# Error: sectionType must be exactly "hero"

# WRONG - wrong component
- sectionType: banner
# Error: sectionType must be exactly "hero"

# CORRECT
- sectionType: hero

Every component validates its sectionType to prevent copy-paste errors.

Nested Property Validation

Use dot notation to validate nested properties:

{
  "validation": {
    "properties": {
      "containerFields.inContainer": {
        "type": "boolean"
      },
      "containerFields.noMargin.top": {
        "type": "boolean"
      },
      "containerFields.noMargin.bottom": {
        "type": "boolean"
      },
      "containerFields.background.isDark": {
        "type": "boolean"
      },
      "containerFields.background.imageScreen": {
        "type": "string",
        "enum": ["light", "dark", "none"]
      },
      "text.titleTag": {
        "type": "string",
        "enum": ["h1", "h2", "h3", "h4", "h5", "h6"],
        "default": "h1"
      }
    }
  }
}

This validates complex frontmatter structures:

sections:
  - sectionType: hero
    containerFields:
      inContainer: true
      noMargin:
        top: true
        bottom: false
      background:
        isDark: true
        imageScreen: dark
    text:
      title: 'Welcome'
      titleTag: h1

Array Validation

Validate array items with the items schema:

{
  "validation": {
    "properties": {
      "ctas": {
        "type": "array",
        "items": {
          "properties": {
            "isButton": {
              "type": "boolean"
            },
            "buttonStyle": {
              "type": "string",
              "enum": ["primary", "secondary", "tertiary", "inverted"]
            },
            "isSmall": {
              "type": "boolean"
            }
          }
        }
      }
    }
  }
}

This validates each CTA in the array:

ctas:
  - url: '/contact'
    label: 'Contact Us'
    isButton: true
    buttonStyle: primary
    isSmall: false
  - url: '/learn-more'
    label: 'Learn More'
    isButton: false

Error example:

ctas:
  - isButton: "yes"        # WRONG - should be boolean
    buttonStyle: red       # WRONG - not in enum
# Errors:
# - ctas[0].isButton must be boolean, got string
# - ctas[0].buttonStyle must be one of: primary, secondary, tertiary, inverted

Required Properties

Specify which properties must be present:

{
  "validation": {
    "required": ["sectionType", "mapProvider", "mapData"],
    "properties": {
      "sectionType": {
        "type": "string",
        "const": "maps"
      },
      "mapProvider": {
        "type": "string",
        "enum": ["leaflet", "openlayers"]
      },
      "mapData": {
        "type": "string"
      },
      "height": {
        "type": "string"
      }
    }
  }
}

Error example:

# WRONG - missing required mapData
- sectionType: maps
  mapProvider: leaflet
  height: 600px
# Error: Required property mapData is missing

# CORRECT
- sectionType: maps
  mapProvider: leaflet
  mapData: london-landmarks
  height: 600px

Properties not in required are optional. In this example, height is optional with a sensible default.

Default Values

Specify default values for optional properties:

{
  "validation": {
    "properties": {
      "mapProvider": {
        "type": "string",
        "enum": ["leaflet", "openlayers"],
        "default": "leaflet"
      },
      "text.titleTag": {
        "type": "string",
        "enum": ["h1", "h2", "h3", "h4", "h5", "h6"],
        "default": "h2"
      },
      "settings.maxResults": {
        "type": "number",
        "default": 20
      }
    }
  }
}

Usage:

# Explicit value
- sectionType: maps
  mapProvider: openlayers  # Uses openlayers

# Omitted - uses default
- sectionType: maps
  # mapProvider defaults to "leaflet"

Defaults document expected values and reduce configuration boilerplate.

Complete Real-World Example

Here's a complete validation schema from the maps component:

{
  "name": "maps",
  "type": "section",
  "styles": ["maps.css"],
  "scripts": ["maps.js"],
  "requires": ["ctas", "text", "commons"],
  "modules": {
    "providers": ["leaflet.js", "openlayers.js"],
    "helpers": ["load-script.js", "maps-utils.js"]
  },
  "validation": {
    "required": ["sectionType", "mapProvider", "mapData"],
    "properties": {
      "sectionType": {
        "type": "string",
        "const": "maps"
      },
      "mapProvider": {
        "type": "string",
        "enum": ["leaflet", "openlayers"],
        "default": "leaflet"
      },
      "mapData": {
        "type": "string"
      },
      "height": {
        "type": "string"
      },
      "isDisabled": {
        "type": "boolean"
      },
      "containerFields.inContainer": {
        "type": "boolean"
      },
      "containerFields.background.isDark": {
        "type": "boolean"
      },
      "containerFields.background.imageScreen": {
        "type": "string",
        "enum": ["light", "dark", "none"]
      },
      "text.titleTag": {
        "type": "string",
        "enum": ["h1", "h2", "h3", "h4", "h5", "h6"],
        "default": "h2"
      }
    }
  }
}

This validates everything from required fields to nested container options.

Understanding Error Messages

When validation fails, you get clear error messages during the build:

Type error:

Validation error in hero component:
Property "isFullScreen" must be boolean, got string ("true")
File: src/index.md, section 1

Enum error:

Validation error in maps component:
Property "mapProvider" must be one of: leaflet, openlayers
Got: "google-maps"
File: src/maps-demo.md, section 2

Missing required error:

Validation error in maps component:
Required property "mapData" is missing
File: src/maps-demo.md, section 2

Nested property error:

Validation error in hero component:
Property "text.titleTag" must be one of: h1, h2, h3, h4, h5, h6
Got: "heading"
File: src/about.md, section 1

Error messages include:

  • Component name
  • Property path
  • Expected vs actual value
  • File location
  • Section number

Best Practices for Validation Schemas

Follow these guidelines when creating validation schemas:

1. Validate the component identifier:

{
  "required": ["sectionType"],
  "properties": {
    "sectionType": {
      "type": "string",
      "const": "your-component"
    }
  }
}

2. Use enums for controlled values:

  • Better than type validation alone
  • Documents available options
  • Catches typos and invalid values

3. Mark truly required fields:

  • Only mark as required if component can't function without it
  • Use defaults for optional configuration

4. Validate nested structures:

  • Use dot notation for deep properties
  • Prevents errors in complex configurations

5. Include defaults for common options:

  • Reduces configuration boilerplate
  • Documents expected values

6. Validate arrays when items have structure:

  • Ensures all items follow the same schema
  • Catches errors in each array item

Common Validation Patterns

Boolean flags:

{
  "isDisabled": {"type": "boolean"},
  "isReverse": {"type": "boolean"},
  "isFullScreen": {"type": "boolean"}
}

Heading tags:

{
  "text.titleTag": {
    "type": "string",
    "enum": ["h1", "h2", "h3", "h4", "h5", "h6"],
    "default": "h2"
  }
}

Button styles:

{
  "buttonStyle": {
    "type": "string",
    "enum": ["primary", "secondary", "tertiary", "inverted"],
    "default": "primary"
  }
}

Screen overlays:

{
  "imageScreen": {
    "type": "string",
    "enum": ["light", "dark", "none"],
    "default": "none"
  }
}

Container fields (common across components):

{
  "containerFields.inContainer": {"type": "boolean"},
  "containerFields.isAnimated": {"type": "boolean"},
  "containerFields.noMargin.top": {"type": "boolean"},
  "containerFields.noMargin.bottom": {"type": "boolean"},
  "containerFields.noPadding.top": {"type": "boolean"},
  "containerFields.noPadding.bottom": {"type": "boolean"},
  "containerFields.background.isDark": {"type": "boolean"}
}

When to Skip Validation

You don't need to validate everything:

Skip validation for:

  • Free-form text: Titles, prose, descriptions
  • Optional URLs: Links that may or may not be present
  • Flexible objects: Data structures that vary by use case
  • Pass-through properties: Values used directly in templates

Example - minimal validation:

{
  "validation": {
    "required": ["sectionType"],
    "properties": {
      "sectionType": {"type": "string", "const": "text-only"}
      // Don't validate text.title, text.prose, etc.
      // They're free-form content
    }
  }
}

Focus validation on:

  • Component identifiers (sectionType)
  • Controlled values (enums for providers, styles, modes)
  • Required technical properties (data source names, IDs)
  • Boolean flags that affect rendering
  • Nested configuration objects

Creating Your Own Validation Schema

When building a new component, add validation step-by-step:

1. Start with component identifier:

{
  "validation": {
    "required": ["sectionType"],
    "properties": {
      "sectionType": {
        "type": "string",
        "const": "my-component"
      }
    }
  }
}

2. Add required properties:

{
  "validation": {
    "required": ["sectionType", "dataSource"],
    "properties": {
      "sectionType": {"type": "string", "const": "my-component"},
      "dataSource": {"type": "string"}
    }
  }
}

3. Add controlled values with enums:

{
  "properties": {
    "sectionType": {"type": "string", "const": "my-component"},
    "dataSource": {"type": "string"},
    "displayMode": {
      "type": "string",
      "enum": ["grid", "list", "masonry"],
      "default": "grid"
    }
  }
}

4. Add boolean flags:

{
  "properties": {
    "sectionType": {"type": "string", "const": "my-component"},
    "dataSource": {"type": "string"},
    "displayMode": {
      "type": "string",
      "enum": ["grid", "list", "masonry"],
      "default": "grid"
    },
    "showFilters": {"type": "boolean"},
    "enableSorting": {"type": "boolean"}
  }
}

5. Test with intentional errors:

  • Try wrong types
  • Try invalid enum values
  • Omit required fields
  • Use nested properties incorrectly

The build will show you if validation is working correctly.

Summary

Component validation with manifest schemas provides essential quality assurance:

  • Build-time errors: Catch problems before deployment
  • Clear messages: Know exactly what's wrong and where
  • Type safety: Ensure properties are correct types
  • Enum validation: Control allowed values
  • Required fields: Guarantee essential properties exist
  • Nested validation: Validate complex configurations
  • Defaults: Document and provide sensible defaults
  • Self-documenting: Schemas show what components expect

Every component in this library includes validation in its manifest. Use these examples as templates for your own components, and let validation catch errors early in your development workflow.