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.
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.
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.
A theme organizes all files under one root directory.
CLI hs create theme テーマ名 You can automatically generate the basic configuration using commands.
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.
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.
{
"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"
}
]
}
}
}
Values defined in theme.json can be used from templates and modules.
theme.settings.カテゴリ名.フィールド名 You can refer to it in this format.
{# 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>
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 improved。
variables.css The standard configuration is to prepare a CSS variable and read the CSS variables with global CSS.
{# 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 %}
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.
| layer | HubL tag | role | Editor 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 |
{% 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 %}
{% 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 %}
{% 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"}' %}
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.
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.
Write directly in the template. No DnD area. Can only be changed by the developer. Header/footer template skeleton.
Arranged as a module and its position is fixed. Text, images, and link contents can be changed. The configuration cannot be changed.
Placed within the DnD area. You can add, delete, rearrange, and change the style of modules. maximum freedom.
{% 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 %}
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.
{% 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 %}
{
"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
]
}
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.
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.
[ // === 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" } ]
<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>
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.
| step | Work details | point |
|---|---|---|
| ① 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 |
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?
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.
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.
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.
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).
Limit usable modules with modules_allowed in theme.json. Don't be too restrictive; set an appropriate range in consultation with the client.
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.