In the previous post, we introduced the Structured Content Starter and its component-based philosophy. Today, we're going to take a look a real components to understand how they work, why they're designed this way, and their patterns.
If you've ever wondered how to build truly modular web pages without the complexity of modern frameworks, this is where theory meets practice. We'll examine actual components from the starter, understand the data flow, and see how the Metalsmith Bundled Components plugin orchestrates everything.
The Component System Architecture
Before we look into specific components, let's understand the system that makes them work. Each section component in our architecture consists of four interconnected parts:
- Configuration Schema (YAML in frontmatter)
- Template (Nunjucks)
- Manifest (dependency declaration)
- Assets (CSS, JavaScript when needed)
These represent a separation of concerns that's been refined through building dozens of real websites. Content lives in configuration. Presentation lives in templates. Dependencies are listed in manifests. Behavior and styles live in assets.
The Hero Component
Let's start with the hero section – one of the most important components on any webpage. Here's a real hero section from the starter home page:
- sectionType: hero
containerTag: section
classes: 'first-section merge-with-next'
id: ''
description: "Landing page hero section"
isDisabled: false
isFullScreen: false
isReverse: true
containerFields:
inContainer: false
isAnimated: true
noMargin:
top: true
bottom: true
noPadding:
top: false
bottom: false
background:
isDark: true
color: ''
image: '/assets/images/sample4.jpg'
imageScreen: 'dark'
text:
leadIn: ''
title: "Welcome to Metalsmith 2025 Structured Content Starter"
titleTag: 'h1'
subTitle: 'Component-Driven Web Development Without the Framework Overhead'
prose: 'Welcome to the Metalsmith2025 Structured Content Starter – a demonstration of how modern websites...'
ctas:
- url: ''
label: ''
isButton: true
buttonStyle: 'primary'
image:
src: ''
alt: ''
caption: ''
This might look like a lot, but every property has a purpose. Let's break it down.
The Section Component Property Model
Through building production sites, we've identified three distinct layers of component properties:
Section Identity
sectionType: hero # Which section template to use
containerTag: section # Semantic HTML element
classes: 'first-section' # Custom styling hooks
id: 'main-hero' # Anchor targeting
description: "..." # Documentation
isDisabled: false # Toggle without deletion
These properties define what the section is. The sectionType
is the only required property – it tells Metalsmith which template to use.
Container Behavior
containerFields:
inContainer: false # Full-width background
isAnimated: false # Scroll-triggered animations
noMargin:
top: true # No margin to top of page
bottom: false # Standard spacing below
background:
isDark: true # Light text on dark
image: '...' # Background image
imageScreen: 'dark' # Overlay for readability
These control how the section behaves in the page flow. Notice how inContainer: false
lets the background stretch edge-to-edge while keeping content centered. This is a common pattern for heroes and feature sections.
Functional Content
text:
title: "..."
titleTag: 'h1'
subTitle: "..."
prose: "..."
ctas:
- url: "..."
label: "..."
image:
src: "..."
alt: "..."
This is the actual content – what users read and interact with. These properties map directly to template partials.
The Template Implementation
Now let's see how the template interprets this configuration:
{# hero.njk #}
{% from "components/_partials/ctas/ctas.njk" import ctas %}
{% from "components/_partials/text/text.njk" import text %}
{% from "components/_partials/image/image.njk" import image %}
<div class="hero-content">
{% if section.text %}
<div class="hero-text flow">
{% if section.text %}
{{ text(section.text) }}
{% endif %}
{% if section.ctas | hasCtas > 0 %}
{{ ctas(section.ctas) }}
{% endif %}
</div>
{% endif %}
{% if section.image | hasImage %}
<div class="hero-image">
{{ image(section.image) }}
</div>
{% endif %}
</div>
Notice the pattern: import partials, check for data, render conditionally. A hero works with just a title, or a title and image, or a full configuration. The template adapts.
The Manifest Declaration
Every component package includes a manifest that declares its dependencies:
{
"name": "hero",
"type": "section",
"styles": ["hero.css"],
"scripts": [],
"dependencies": ["ctas", "text", "image", "commons"],
"validation": {
"required": ["sectionType"],
"properties": {
"isFullScreen": { "type": "boolean" },
"isReverse": { "type": "boolean" },
"buttonStyle": {
"enum": ["primary", "secondary", "outline", "link"]
}
}
}
}
The Metalsmith Bundled Components plugin reads this manifest and:
- Includes
hero.css
in the global bundle - Resolves dependencies recursively (hero needs ctas, ctas needs buttons, etc.)
- Validates configuration at build time (For example, "false" is a string and always true, use
false
) - Generates optimized bundles containing only used components
Component Composition Patterns
The real power emerges when components compose. Let's examine a media-image section to see advanced composition.
- sectionType: media-image
containerTag: aside
isReverse: false # Image on left, text on right
text:
leadIn: "and what is this?
title: "Media Section Example"
prose: "Example of media section with text and image..."
ctas:
- url: "https://apple.com"
label: "go to apple"
image:
src: "/assets/feature.jpg"
alt: "Feature demonstration"
The media-image component reuses the same text, ctas, and image partials as the hero. But it arranges them differently:
{# media-image.njk #}
<div class="media-image {% if section.isReverse %}is-reverse{% endif %}">
{% if section.image %}
<div class="image">{{ image(section.image) }}</div>
{% endif %}
{% if section.text or section.ctas %}
<div class="text flow">
{{ text(section.text) }}
{{ ctas(section.ctas) }}
</div>
{% endif %}
</div>
Same partials, different arrangement. The isReverse
property flips the layout with a single CSS class. No template duplication.
The Data Flow
Understanding how data flows through the system is crucial:
- Content Author writes YAML configuration in frontmatter
- Metalsmith reads the file and parses the frontmatter
- Page Template (
sections.njk
) iterates through the sections array - Section Lookup matches
sectionType
to the appropriate template - Template Rendering processes the configuration through the template
- Partial Functions handle specific content types (text, images, CTAs)
- HTML Output is generated and written to disk
At build time, the Bundled Components plugin intercepts this flow to:
- Validate configurations against schemas
- Track component usage
- Resolve dependency graphs
- Generate optimized CSS/JS bundles
Advanced Patterns
Conditional Rendering
Components adapt to available data:
{% if section.text %}
<div class="prose flow">
{% if section.text.leadIn %}
<p class="lead-in">{{ section.text.leadIn }}</p>
{% endif %}
{% if section.text.title %}
<{{ section.text.titleTag or 'h2' }}>
{{ section.text.title }}
</{{ section.text.titleTag or 'h2' }}>
{% endif %}
{% if section.text.prose %}
{{ section.text.prose | markdown | safe }}
{% endif %}
</div>
{% endif %}
Every element is optional. The component gracefully handles any combination.
Container Control
The container system provides sophisticated layout control:
{# In the page template #}
{# section wrapper may be article, aside, section or div depending on section.containerTag setting #}
<{{ section.containerTag }} id="{{ section.id }}" {{ buildContainerAttributes(section) }}>
{# if section has background add a background image #}
{% if section.containerFields.background.image %}
<div class="background-image" style="background-image: url({{ section.containerFields.background.image }})"></div>
{% endif %}
{# section content #}
{{ renderSection(section, navigation.breadcrumbs, data, collections, previous, next, urlPath) }}
{% set containerTag = section.containerTag %}
</{{ containerTag }}>
This pattern lets any section have a full-width background while maintaining readable content width.
Spacing Orchestration
The margin/padding system creates visual rhythm:
.section {
margin-top: var(--space-l);
margin-bottom: var(--space-l);
}
.section.no-margin-top {
margin-top: 0;
}
.section.no-margin-bottom {
margin-bottom: 0;
}
.merge-with-next {
margin-bottom: 0;
}
.merge-with-next + .section {
margin-top: 0;
}
Sections can connect seamlessly or maintain breathing room. The configuration controls it all.
Performance Implications
This architecture isn't just about developer experience – it delivers exceptional performance:
Build-Time Optimization: The Bundled Components plugin analyzes the entire site and creates optimal bundles. If you only use 5 of 20 available components, you only ship CSS for those 5.
Single Global Bundle: Instead of per-page bundles (which defeat caching), we generate one CSS file and one JS file for the entire site. First page load gets everything. Subsequent pages are instant.
Progressive Enhancement: Components work without JavaScript. Interactive features enhance the experience but aren't required. The platform (HTML/CSS) does the heavy lifting.
Zero Runtime Overhead: No component framework. No virtual DOM. No hydration. Just HTML with styles and occasional JavaScript sprinkles.
Component Development Workflow
Building new components follows a predictable pattern:
- Design the Data Model: What configuration options does this component need?
- Create the Template: How should this data render to HTML?
- Write the Styles: How should it look?
- Declare Dependencies: What partials does it use?
- Add Validation: What constraints should be enforced?
- Document Usage: How do content authors use it?
Let's say we need a testimonial component:
# Configuration model
- sectionType: testimonial
containerTag: aside
classes: ''
id: ''
isDisabled: false
isReverse: false
containerFields:
inContainer: false
isAnimated: true
noMargin:
top: true
bottom: false
noPadding:
top: false
bottom: false
background:
color: 'lightgray'
image: ''
imageScreen: 'none' # light, dark, none
quote:
text: "You've got to be very careful if you don't know where you are going, because you might not get there."
cite: 'https://en.wikipedia.org/wiki/Yogi_Berra'
quotee:
portrait:
src: '/assets/images/yogi-berra-baseball-great.jpg'
alt: "Lawrence Peter 'Yogi' Berra"
name: 'Yogi Berra'
title: 'Baseball Great'
company: 'New York Yankees'
logo: '/assets/images/new-york-yankees-logo.svg'
{# testimonial.njk #}
{% from "components/_partials/ctas/ctas.njk" import ctas %} {% from "components/_partials/text/text.njk" import text %}
{% from "components/_partials/image/image.njk" import image %}
<div class="testimonial content container {% if section.isReverse %}is-reverse{% endif %}">
<div class="quotee">
{% if section.quotee.portrait.src %}
<div class="portrait">{{ image(section.quotee.portrait)}}</div>
{% endif %}
<div class="name">{{ section.quotee.name }}</div>
{% if section.quotee.title %}
<div class="title">{{ section.quotee.title }}</div>
{% endif %} {% if section.quotee.company %}
<div class="company">{{ section.quotee.company }}</div>
{% endif %} {% if section.quotee.logo %}
<div class="logo">
<img src="{{ section.quotee.logo }}" alt="{{ section.quotee.company }}" />
</div>
{% endif %}
</div>
<blockquote cite="{{ section.quote.cite }}">
<p>{{ section.quote.text }}</p>
</blockquote>
</div>
// manifest.json
"name": "testimonial",
"type": "section",
"styles": ["testimonial.css"],
"scripts": [],
"requires": ["ctas", "text", "image", "commons"],
"validation": {
"required": ["sectionType"],
"properties": {
"sectionType": {
"type": "string",
"const": "testimonial"
},
"isReverse": {
"type": "boolean"
}
Component complete! It automatically integrates with the build system, gets validated, and contributes to the global bundle.
Key Principles
This component architecture embodies several key principles:
Separation of Concerns: Content, presentation, and behavior are cleanly separated. Content authors never touch code. Developers rarely touch content.
Composition Over Inheritance: Components compose from smaller partials rather than extending base classes. This creates more flexible, maintainable systems.
Platform-First: We use the web platform's native capabilities rather than reinventing them. HTML for structure, CSS for presentation, JavaScript for enhancement.
What's Next
In the next post, we'll explore the Metalsmith Bundled Components plugin in detail – how it traces dependencies, validates configurations, and generates optimized bundles.
The component-based approach isn't just a different way to build pages – it's a fundamental shift in how we think about web content. By treating pages as compositions of reusable sections, we create systems that are both more powerful and simpler to maintain.
Want to explore the components yourself? Check out the Component Library or experiment with the Structured Content Starter.
Questions or insights to share? Find me on Bluesky.