📘 HubSpot CMS Development Textbook — 2026 Edition
Chapter 5

Theme design and construction

A complete explanation of theme development, the modern standard for HubSpot CMS. We will systematically organize design token settings using theme.json, structural design of the drag & drop editor, and how to create a structure that allows clients to edit safely.

🎯 Target level:Intermediate to advanced
⏱ Estimated reading completion:60-90 minutes
🔗 Previous chapter:Chapter 4 Custom module design and development

Contents of this chapter

  1. What is a theme? Selection criteria for thematic vs. non-thematic composition
  2. Theme directory structure
  3. theme.json complete guide
  4. Structure and design philosophy of drag and drop editor
  5. Implementation of dnd_area, Section, Row, Column, and Module
  6. Editable area control design
  7. Designing style sections (Style Fields)
  8. Flow of creating a new theme and how to proceed in practice
  9. Chapter 5 Summary
Section 5-1

What is a theme? Selection criteria for thematic vs. non-thematic composition

HubSpotThemeThe templates, modules, CSS, JS, and images that make up the site This is a system that manages them as a single package. It has been strongly recommended since around 2021, and currentlyTheme structure is the de facto standard for developmentIt has become.

Thematic configuration vs. non-thematic configuration

🎨 Theme structure (recommended)

  • Files are managed as one package
  • Centrally manage design tokens with theme.json
  • You can change the color and font in "Theme Settings" from the admin screen.
  • Highly compatible with drag & drop editor
  • Can be published and distributed to HubSpot Marketplace
  • Easy bulk fetch/upload with CLI
  • Easy team development and version control

📂 Non-theme configuration (legacy)

  • Template modules are scattered separately
  • No design token mechanism
  • To change colors, modify each CSS file individually
  • Limited cooperation with DnD editor
  • Not compatible with marketplace distribution
  • Remains as legacy configuration of past projects
  • Not recommended for new projects
⚠️ Points to note when renewing existing projects

When renewing an existing project with a non-theme configuration, please consider migrating to a theme configuration. However, the transition from non-theme to theme is not simple, as the template path reference and module placement location change. Complete transition at the timing of full renewalmosquito, Partial improvement while maintaining the existing configurationDetermine whether this is the case based on the number of man-hours required.

When to choose a theme


Section 5-2

Theme directory structure

A theme organizes all files under one root directory. CLI hs create theme テーマ名 You can automatically generate the basic configuration using commands.

my-theme/ │ ├── theme.json ← ★Theme settings file. Design token definition │ ├── templates/ ← Template file group │ ├── layouts/ │ │ └── base.html ← Base template (inheritance source) │ ├── home.html ← Top page template │ ├── page.html ← General purpose page template │ ├── blog-listing.html ← Blog list template │ ├── blog-post.html ← Blog details template │ ├── blog-tag.html ← Tag archive template │ ├── blog-author.html ← Author Archive Template │ ├── landing-page.html ← LP template (no header/footer) │ └── system/ │ ├── 404.html ← 404 error page │ └── password-prompt.html ← Password protection page │ ├── modules/ ← Custom module group │ ├── hero-banner.module/ │ ├── card-grid.module/ │ └── recent-posts.module/ │ ├── partials/ ← Reuse partial │ ├── header.html ← Global header │ ├── footer.html ← Global footer │ ├── og-tags.html ← OGP / meta tag │ └── macros.html ← Common macro definition │ ├── css/ ← Global CSS │ ├── variables.css ← ★CSS variable definition (corresponds to theme.json) │ ├── main.css ← Base style for the entire site │ ├── reset.css ← Reset CSS │ ├── layout.css ← Grid container layout │ ├── components.css ← Common components such as buttons and forms │ └── blog.css ← Blog style │ ├── js/ ← Global JavaScript │ ├── main.js ← JS for the entire site (navigation, scrolling, etc.) │ └── utils.js ← Utility functions │ └── images/ ← Images included with the theme (logo, default image, etc.)
ℹ️ About the default configuration generated by hs create theme

hs create theme my-theme will generate the default template theme provided by HubSpot. Rather than using this as is,Delete unnecessary files and organize to the minimum configuration that meets your project requirements before you start using it.We recommend that you do so. The default template contains demo content provided by HubSpot, which will be noise in actual projects.


Section 5-3

theme.json complete guide

theme.json is the core file of the theme. defined hereDesign tokens (color, font, spacing, rounded corners, etc.)But, It will be displayed as "Theme Settings" on the HubSpot admin screen, Marketers can change the design of the entire site without any code.

Overall structure of theme.json

theme.json — complete sample
{
  "label"  : "My Company Theme",
  "preview_path": "./templates/home.html",
  // Template used for theme settings preview

  "settings": {

    // ===== Color palette =====
    "color": {
      "label" : "Color Palette",
      "fields": [
        {
          "name"   : "primary_color",
          "label"  : "Primary color",
          "type"   : "color",
          "default": { "color": "#0052CC", "opacity": 100 }
        },
        {
          "name"   : "secondary_color",
          "label"  : "Secondary Color",
          "type"   : "color",
          "default": { "color": "#FF5630", "opacity": 100 }
        },
        {
          "name"   : "accent_color",
          "label"  : "accent color",
          "type"   : "color",
          "default": { "color": "#36B37E", "opacity": 100 }
        },
        {
          "name"   : "bg_color",
          "label"  : "Background color (entire site)",
          "type"   : "color",
          "default": { "color": "#FFFFFF", "opacity": 100 }
        },
        {
          "name"   : "text_color",
          "label"  : "Text color (body)",
          "type"   : "color",
          "default": { "color": "#2D3748", "opacity": 100 }
        }
      ]
    },

    // ===== Typography =====
    "typography": {
      "label" : "Font settings",
      "fields": [
        {
          "name"   : "heading_font",
          "label"  : "Heading font",
          "type"   : "font",
          "default": {
            "font"        : "Noto Sans JP",
            "font_set"    : "GOOGLE",
            "size"        : "40px",
            "size_unit"   : "px",
            "bold"        : true,
            "italic"      : false,
            "underline"   : false,
            "color"       : "#1A202C",
            "line_height" : "1.3",
            "letter_spacing": "0"
          }
        },
        {
          "name"   : "body_font",
          "label"  : "Body font",
          "type"   : "font",
          "default": {
            "font"        : "Noto Sans JP",
            "font_set"    : "GOOGLE",
            "size"        : "16px",
            "size_unit"   : "px",
            "bold"        : false,
            "color"       : "#4A5568",
            "line_height" : "1.85"
          }
        }
      ]
    },

    // ===== Spacing =====
    "spacing": {
      "label" : "spacing",
      "fields": [
        {
          "name"   : "section_vertical_spacing",
          "label"  : "Section top and bottom margins",
          "type"   : "spacing",
          "default": {
            "top"   : "80px",
            "bottom": "80px"
          }
        },
        {
          "name"   : "container_max_width",
          "label"  : "Container maximum width",
          "type"   : "number",
          "default": 1200,
          "suffix" : "px"
        }
      ]
    },

    // ===== Border Shadow =====
    "border": {
      "label" : "Border/Rounded Corners",
      "fields": [
        {
          "name"   : "border_radius",
          "label"  : "Border radius (rounded corners)",
          "type"   : "number",
          "default": 6,
          "suffix" : "px"
        },
        {
          "name"   : "button_border_radius",
          "label"  : "Rounded corners of buttons",
          "type"   : "number",
          "default": 4,
          "suffix" : "px"
        }
      ]
    }
  }
}

Reference theme.json setting values ​​in template

Values ​​defined in theme.json can be used from templates and modules. theme.settings.カテゴリ名.フィールド名 You can refer to it in this format.

HubL — Reference theme.json setting values
{# Use theme colors with inline styles #}
<section style="background-color: {{ theme.settings.color.bg_color.color }};">{# Output font settings as CSS variables in <head> (recommended pattern) #}
<style>
  :root {
    --color-primary   : {{ theme.settings.color.primary_color.color }};
    --color-secondary : {{ theme.settings.color.secondary_color.color }};
    --color-accent    : {{ theme.settings.color.accent_color.color }};
    --color-text      : {{ theme.settings.color.text_color.color }};
    --color-bg        : {{ theme.settings.color.bg_color.color }};

    --font-heading    : '{{ theme.settings.typography.heading_font.font }}', sans-serif;
    --font-body       : '{{ theme.settings.typography.body_font.font }}', sans-serif;
    --font-size-body  : {{ theme.settings.typography.body_font.size }};
    --line-height-body: {{ theme.settings.typography.body_font.line_height }};

    --spacing-section : {{ theme.settings.spacing.section_vertical_spacing.top }};
    --container-width : {{ theme.settings.spacing.container_max_width }}px;
    --border-radius   : {{ theme.settings.border.border_radius }}px;
    --border-radius-btn: {{ theme.settings.border.button_border_radius }}px;
  }
</style>
✅ Best practice is to work with CSS variables

By outputting the setting values of theme.json as CSS variables and referencing the CSS variables in the CSS file, Marketers simply change the theme settings on the admin screen to update the overall design of the site. Rather than writing setting values ​​directly in inline stylesMaintainability is greatly improvedvariables.css The standard configuration is to prepare a CSS variable and read the CSS variables with global CSS.

Google Fonts loading settings

layouts/base.html — dynamically load Google Fonts from theme.json
{# Automatically load fonts set to font_set: "GOOGLE" in theme.json #}
{# HubSpot may autoload standard_header_includes #}
{# If you want to control manually, write as follows #}

{% set heading_font = theme.settings.typography.heading_font %}
{% set body_font    = theme.settings.typography.body_font    %}

{% if heading_font.font_set == "GOOGLE" %}
  <link
    rel="stylesheet"
    href="https://fonts.googleapis.com/css2?family=
      {{- heading_font.font|replace(" ", "+") -}}
      :wght@400;700;900&display=swap">{% endif %}

Section 5-4

Structure and design philosophy of drag and drop editor

HubSpot's drag and drop editor (DnD editor) It allows marketers to change the structure of a page without any code. The developer is"Which range should be editable?"designed, Marketers arrange and rearrange modules within that range.

4-layer structure of DnD editor

Hierarchical structure of DnD area

🔵 dnd_section (section) - full width block of lines
dnd_column (1/2 width)
dnd_row (row)
hero banner.module
dnd_column (1/2 width)
dnd_row (row)
form module
🔵 dnd_section (separate section)
dnd_column (1/3 width)
card module
dnd_column (1/3 width)
card module
dnd_column (1/3 width)
card module

Role of each layer

layerHubL tagroleEditor operations
dnd_area {% dnd_area %} Editable top-level container. Multiple locations can be placed in a template. No operation (defined by developer in template)
dnd_section {% dnd_section %} Full width section. Background color and padding can be set. Add/delete/sort/background color settings
dnd_column {% dnd_column %} Column within section. Specify the width ratio as a percentage. Adjust width/add/delete columns
dnd_row {% dnd_row %} Row within column. A unit for arranging modules vertically. Add/delete rows
dnd_module {% dnd_module %} Actual content part (custom or standard module). Add/delete/sort/edit fields

Section 5-5

Implementation of dnd_area, Section, Row, Column, and Module

Basic dnd_area implementation

templates/page.html — Basic implementation of DnD area
{% block main_content %}

{# ====== Simple DnD Area ====== #}
{% dnd_area "main_area"
  label="Main content area"
%}
  {# Define default section/module configuration here #}
  {% dnd_section
    vertical_alignment="TOP"
    padding='{"top": "80px", "bottom": "80px"}'
  %}
    {% dnd_column width=12 %}  {# 12/12 = 100% #}
      {% dnd_row %}
        {% dnd_module "hero"
          path="../modules/hero-banner.module"
          label="Hero Banner"
        %}
        {% end_dnd_module %}
      {% end_dnd_row %}
    {% end_dnd_column %}
  {% end_dnd_section %}
{% end_dnd_area %}

{% endblock %}

Default configuration for two-column layout

templates/page.html — 2 columns (body + sidebar)
{% dnd_area "content_area" label="Content area" %}
  {% dnd_section %}

    {# Main content: 8/12 = 66.6% width #}
    {% dnd_column width=8 %}
      {% dnd_row %}
        {% dnd_module "main_content"
          path="@hubspot/rich_text"
          label="Main text"
        %}
        {% end_dnd_module %}
      {% end_dnd_row %}
    {% end_dnd_column %}

    {# Sidebar: 4/12 = 33.3% width #}
    {% dnd_column width=4 %}
      {% dnd_row %}
        {% dnd_module "sidebar_recent"
          path="../modules/recent-posts.module"
          label="Latest articles"
          count=5
        %}
        {% end_dnd_module %}
      {% end_dnd_row %}
      {% dnd_row %}
        {% dnd_module "sidebar_cta"
          path="../modules/cta-banner.module"
          label="Sidebar CTA"
        %}
        {% end_dnd_module %}
      {% end_dnd_row %}
    {% end_dnd_column %}

  {% end_dnd_section %}
{% end_dnd_area %}

Styling options for dnd_section

HubL — Advanced style settings for dnd_section
{% dnd_section
  {# Vertical alignment #}
  vertical_alignment="MIDDLE"      {# TOP / MIDDLE / BOTTOM #}

  {# Padding (specified by JSON string) #}
  padding='{"top":"80px","bottom":"80px","left":"0px","right":"0px"}'

  {# Background color (can refer to theme.json color) #}
  background_color='{"color":"#f7fafc","opacity":100}'

  {# Background image #}
  background_image='{"src":"https://example.com/bg.jpg","size_type":"cover","position":"center center"}'

  {# Maximum width (with or without container) #}
  max_width="1200px"

  {# Gutter width between columns #}
  margin='{"top":"0px","bottom":"0px"}'
%}
💡 Meaning of width=12 (12 column grid)

HubSpot's DnD editor uses a 12-column grid. width=12 100%,width=6 50%,width=4 33.3%,width=3 It becomes 25%. Setting this value in the default configuration code will display it as the initial layout when a marketer creates a page.


Section 5-6

Editable area control design

One of the most important design decisions in HubSpot development is“Where should I let the client touch?”is. If the degree of freedom is too high, the design will collapse; if it is too low, it will be difficult to use. Appropriate control design determines long-term maintenance quality.

3 levels of editing control

🔒

Fully fixed

Write directly in the template. No DnD area. Can only be changed by the developer. Header/footer template skeleton.

⚙️

Field editing only

Arranged as a module and its position is fixed. Text, images, and link contents can be changed. The configuration cannot be changed.

✏️

free editing

Placed within the DnD area. You can add, delete, rearrange, and change the style of modules. maximum freedom.

Implementation examples by pattern

templates/home.html — Example of how to use different control levels
{% extends "./layouts/base.html" %}
{% block main_content %}

  {# ① Completely fixed: The position and design of the hero banner are fixed #}
  {# (Position is fixed by template, content can only be edited by field) #}
  {% module "hero"
    path="../modules/hero-banner.module"
    label="Top Hero"
  %}

  {# ② Fixed placement + field editing: The latest articles are always here #}
  {% module "recent_posts"
    path="../modules/recent-posts.module"
    label="Latest articles (number of articles and tags can be changed)"
  %}

  {# ③ DnD area: Marketers can freely rearrange within this area #}
  {% dnd_area "flexible_area"
    label="Free editing area (modules can be added, deleted, and rearranged)"
  %}
    {% dnd_section %}
      {% dnd_column width=12 %}
        {% dnd_row %}
          {% dnd_module "default_cta"
            path="../modules/cta-banner.module"
          %}
          {% end_dnd_module %}
        {% end_dnd_row %}
      {% end_dnd_column %}
    {% end_dnd_section %}
  {% end_dnd_area %}

  {# ④ Fully fixed: Form section is always fixed at the bottom #}
  {% module "contact_form"
    path="../modules/form-section.module"
    label="Inquiry form"
  %}

{% endblock %}

Limit the modules that can be used in the DnD area

By default, you can place all modules in the DnD area, including HubSpot standard modules. allowed_modules You can restrict it to specific modules by setting . This is an important setting that prevents the client from placing unintended modules and disrupting the design.

HubL — dnd_area to limit available modules
{% dnd_area "restricted_area"
  label="Content area (limited available modules)"
%}

  {# *Depending on the HubSpot CLI version, it may be set in theme.json #}
  {# Usable modules are limited by the modules_allowed setting in theme.json #}

{% end_dnd_area %}
theme.json — Limit settings for modules that can be used in the DnD area
{
  "label": "My Theme",
  "settings": { /* ... */ },

  // Limit the modules allowed in the DnD editor
  "modules_allowed": [
    "../modules/hero-banner.module",
    "../modules/card-grid.module",
    "../modules/cta-banner.module",
    "../modules/recent-posts.module",
    "../modules/faq.module",
    "../modules/testimonials.module",
    "@hubspot/rich_text",    // HubSpot standard: rich text
    "@hubspot/linked_image",  // HubSpot standard: linked image
    "@hubspot/video"           // HubSpot Standard: Video
    // ← Only the modules listed here will be available in the DnD area
  ]
}
⚠️ Too much restriction makes it difficult to use

Module restrictions are an effective way to prevent client operations from occurring incorrectly, but If you limit it too much, marketers won't be able to create the content they need. before deliveryAfter coordinating with the client what they would like to edit,It is important to determine an appropriate limit range.


Section 5-7

Designing style sections (Style Fields)

In addition to the usual "content field", module fields include Style Section (Style Fields)can be defined. This allows you to create a "design settings panel" that allows you to change the appearance of the module using the GUI.

How to define style fields

fields.json — Add style fields
[
  // === Content field (usually) ===
  {
    "name"   : "title",
    "label"  : "Heading",
    "type"   : "text",
    "default": "Section title"
  },

  // === Style field (specify "tab": "STYLE") ===
  {
    "name"     : "bg_color",
    "label"    : "Background color",
    "type"     : "color",
    "tab"      : "STYLE",              // ← This is the key displayed in the style tab
    "default"  : { "color": "#ffffff", "opacity": 100 }
  },
  {
    "name"     : "text_color",
    "label"    : "Text Color",
    "type"     : "color",
    "tab"      : "STYLE",
    "default"  : { "color": "#2d3748", "opacity": 100 }
  },
  {
    "name"     : "padding",
    "label"    : "Top and top padding",
    "type"     : "spacing",
    "tab"      : "STYLE",
    "default"  : { "top": "64px", "bottom": "64px" }
  },
  {
    "name"     : "layout_style",
    "label"    : "Layout",
    "type"     : "choice",
    "tab"      : "STYLE",
    "choices"  : [["default","standard"],["wide","wide"],["narrow","Narrow"]],
    "default"  : "default"
  }
]
module.html — Style field references
<section
  class="section-block section-block--{{ module.layout_style }}"
  style="
    background-color: {{ module.bg_color.color }};
    color: {{ module.text_color.color }};
    padding-top: {{ module.padding.top }};
    padding-bottom: {{ module.padding.bottom }};
  ">
  <div class="container">
    <h2>{{ module.title }}</h2>
  </div>
</section>
💜 Style field design guidelines

Content field:Things related to “what to display” such as text, images, links, etc.
Style field:Things related to "how to display" such as background color, padding, layout, etc.

This separation organizes the editing panel on the admin screen into two tabs: "Content" and "Style". This allows editors to operate intuitively. However, if you add too many style fields, management becomes complicated, so Only settings that change frequentlyWe recommend making it a style field.


Section 5-8

Flow of creating a new theme and how to proceed in practice

Standard flow for creating a new theme

stepWork detailspoint
① Template generation hs create theme テーマ名 Generate a template and organize unnecessary files with Remove demo content and start with minimal configuration
② theme.json design Define design system colors, fonts, and spacing in theme.json With the intention of transcribing the design comp token as is.
③ CSS variable linkage Implement code to output theme.json → CSS variables in base.html All subsequent CSS is written by referring to CSS variables.
④ base.html implementation Implement head, header, footer, and common blocks in the base template Don't forget standard_header/footer_includes
⑤ Global partial creation Implement global partials for header.html and footer.html Clearly design what parts of your module should be editable
⑥ Template implementation Implement each template by inheriting base.html (page / blog-* / landing-page) Finalize the control design for the DnD area here
⑦ Custom module development Implement each section of your design comp as a module Use the pattern collection in Chapter 4 to improve efficiency
⑧ Client confirmation Check the usability by actually operating the DnD editor on a test account Verifying the ease of editing from a marketer's perspective
⑨ Actual deployment hs upload src/ @my-theme --portal prod to reflect in the actual production. in advance hs lint Make sure there are no errors with

Judgment points for theme design that are common in practice

✅ Final checklist (before theme delivery)

theme.json:The settings for color, font, and spacing have been checked and are displayed correctly in the theme settings panel of the administration screen.
CSS variables:Is the change in theme.json reflected on the entire site? (Check by changing the color)
All templates:Is standard_header/footer_includes missing?
DnD area:Does the marketer actually operate it and it works as intended?
Module limitations:Does the allowed_modules setting match your requirements?
Global partial:Have you checked that changes in one place are reflected on all pages?


Section 5-9

Chapter 5 Summary

📌 Key points to keep in mind in this chapter

Theme structure is modern standard

All new projects are developed using a theme structure. Non-theme configurations are legacy. Depending on the theme, you can use theme.json, DnD editor, and marketplace distribution.

Linking theme.json and CSS variables

The standard design is to output the setting values ​​of theme.json as CSS variables in base.html and refer to the entire CSS using CSS variables. Marketers can simply change the theme settings and it will be reflected throughout.

4-layer structure of DnD editor

Understand and design the hierarchy of dnd_area → dnd_section → dnd_column (12 grids) → dnd_row → dnd_module. Section background and padding are specified using JSON strings.

3 levels of editing control

Three levels can be used depending on the client's requirements: completely fixed (template direct writing), field editing only (module placement), and free editing (dnd_area).

Prevent erroneous operations with allowed_modules

Limit usable modules with modules_allowed in theme.json. Don't be too restrictive; set an appropriate range in consultation with the client.

Separate settings with style fields

If you add "tab": "STYLE" to a field, it will be displayed in the "Style" tab of the edit panel. Separating content and style keeps the editing UI organized.

Next chapter: Chapter 6 Data Utilization/Dynamic Content

We will explain everything from a deep dive into blog functions, tag design, pagination implementation, HubDB utilization, and smart content.

Go to Chapter 6 →