Coded module files

Last updated:

Module files are reusable building blocks that compose pages, blogs, and email templates. Modules are the smallest pieces that help form the HubSpot CMS development environment. Modules can be used to add content, styling, and functionality to content. 

 Modules have these three front-end related files:

  • Module.html
  • Module.css
  • Module.js

Email modules don't support module.css and module.js. This is because email clients don't support JavaScript and support for linked CSS files is very low.

These files will always be rendered/loaded to the page when an instance of the module is on the page (Module instances are the individual rendered modules on the page). 

Module files are represented in the design manager in a multi-pane module editor. When viewing a module locally, the files are contained within module-name.module folders. Your team’s preferences will determine the  method you use to edit modules. See creating an efficient developer workflow for recommendations.

HTML + HubL (Module.html)

Multiple module instances can exist on the same page. Wherever those instances are placed in the page editor or template files determines where the contents of the module.html file will be rendered. 

The module.html file acts like a HubL include in the page wherever the module is placed. The module.html file can access the module's field values through HubL.

CSS (Module.css)

The Cascading Style Sheet (CSS) associated with your module is only loaded to a page once, regardless of how many times the module is used on the page. Loading the CSS only once makes building modularly more efficient in terms of loading and rendering. 

Your module field values can't change the CSS in this file.

It is by design that module field values can't change the CSS in the file. If module field values could affect the module.css file, every instance of a module's CSS would need to be loaded separately, because every instance could be different. The result would likely be duplicate CSS and potentially conflicting styles when multiple instances of a module occur on a page.

The CSS for all of the modules used within a page is automatically combined and minified into one CSS file. Dozens of modules on a page does not mean dozens of HTTP requests.

Module.css supports a very limited subset of HubL. Specifically, you can use module_asset_url("my-image.png") for images added as module linked assets. This enables linking assets such as images, packaged with the module itself.

CSS
.testimonial-module__wrapper{
  background: url("{{ module_asset_url("bg-pattern.png") }}");
  background-repeat:repeat;
  min-height:200px;
  width:100%;
  display:block;
}

Styling based on module field values

There are a few ways you can influence the styling of your module based on the module’s fields. Choose the way that works best for your specific use case.

  • CSS Classes
  • Require_css block
  • Inline styles

CSS Classes

When you have predefined appearances for your module, CSS classes added to your module.css file are the best option.

For example, you may have an image and text module. You want content creators to be able to place the image to the right or left of the text based on a choice field.

HubL
<section class="img-text__wrapper img-text--{{ module.positioning }}" aria-label="{{ module.heading }}">
  {# module.position is a choice field with two values "img-left" or "img-right". This dictates the order they appear on desktop. Controlled by CSS #}
  <div class="img-text__img">
      <img src="{{ module.image.src }}" alt="{{ module.image.alt }}">
  </div>
  <div class="img-text__text">
      <h2>
        {% inline_text field="heading" value="{{ module.heading }}" %}
      </h2>
      {% inline_rich_text field="text" value="{{ module.text }}" %}
  </div>
</section>
CSS
/*CSS that makes the image show adjacent to the text, and positioned based on the positioning field.*/
/* The media query ensures that on mobile the image will always appear above the text, purely for visual consistency. */
@media(min-width:768px){
  .img-text__wrapper{
    display:flex;
    align-items:row;
  }
  .img-text__img,.img-text__text{
    flex:1; 
    padding:10px;
  }
  .img-text--img-right{
    flex-direction:row-reverse;
  }
}

Because your CSS is class based, the styles will be the same for every instance of the module. You will not have duplicate CSS.

Require_css block

When you need to give content creators direct granular control over multiple instance specific properties and when classes are not ideal, style tags with require_css blocks are the best option.

In your Module.html the HubL Coded module files prints the module instance id to the page. This can be useful in more complicated modules to provide a unique targetable identifier for that instance of the module. You can then also use Coded module files inside of a require_css block.

For example, you have a simple image module. You want content creators to be able to set a custom border on the image.

HubL
{# module.html #}
<div class="img__wrapper" id="{{ name }}">
  {% if module.image.src %}
    {% set sizeAttrs = 'width="{{ module.image.width }}" height="{{ module.image.height }}"' %}
    {% if module.image.size_type == 'auto' %}
      {% set sizeAttrs = 'style="max-width: 100%; height: auto;"' %}
    {% elif module.image.size_type == 'auto_custom_max' %}
      {% set sizeAttrs = 'width="100%" height="auto" style="max-width: {{ module.image.max_width }}px; max-height: {{ module.image.max_height }}px"' %}
    {% endif %}
    <img src="{{ module.image.src }}" alt="{{ module.image.alt }}" {{ sizeAttrs }}>
  {% endif %}
</div>

{% require_css %}
<style>
  #{{name}}{
    border-width:{{ module.border_width }}px;
    border-color:rgba({{ module.border_color.color|convert_rgb }},{{ module.border_color.opacity/100 }});
  }
</style>
{% end_require_css %}

require_css in block form moves the <style> tag into the <head> of your page within the standard_header_includes statement.

CSS
/* module.css - you would still place css that applied to all instances of the module here */
.img__wrapper{
  max-width: 100%; 
  height: auto;
  border-style:solid;
}

Inline Styles

When you need to give content creators granular control over only a few properties and when classes are not ideal, you can directly add the values to a style attribute in the HTML.

HubL
{# Module.html #}
<div style="background: rgba({{ module.bg_color.color|convert_rgb }},{{ module.bg_color.opacity/100 }});">
  {% inline_rich_text field="richtext" value="{{ module.richtext }}" %}
</div>

If you have many properties and the code becomes hard to read, consider switching to the require_css block method.

Require_css

Require_css is a HubL function that tells HubSpot that a particular module or template requires a particular CSS file to display. A link tag pointing to the css file is added to the page's <head> inside of the standard_header_includes

The require_css function will only load that CSS file once, regardless of how many times that same file is required by modules and templates on a particular page. This makes it great for situations where styles may be shared across multiple modules, but where adding the CSS directly to the main stylesheets used on every page for your site may not make sense.

Require_css and linked CSS files fill the same purpose, but require_css can be used conditionally based on field values. This prevents loading unnecessary code.

HubL
<!-- module.html -->
{{ require_css(get_asset_url("/modules/shared_layout_styles.css")) }}

Require_css must be added in the module.html of a module. 

JavaScript (Module.js)

Module.js is a JavaScript file loaded only when the module is used in a page. Module.js is only loaded once per page, even if more than one instance of the module appears in a page. 

Like the Module.css file, the Module.js file does not support HubL.

Scripting based on field values

There are a few ways you can build modules, where the JavaScript acts differently based on field values. Understanding which method to use and when can mean performance benefits on every page the module is used. 

For example, you have a custom image module, you want to give content creators the ability to make it so the image can open in a lightbox. Content creators only want that for specific images, and not all instances of the module.

Data attributes

Data attributes are HTML 5 standard custom attributes that developers add to elements. Just as all elements support class="yourClassName", all elements support data-your-attribute="yourValue".

HubL
<!-- module.html-->
<div class="img-module img-module__wrapper" data-lightbox="{{ module.is_lightbox_enabled }}" data-caption="above">
  <!-- module.is_lightbox_enabled is a boolean field, module.caption_position is a choice field. -->
  {% if module.image.src %}
    {% set sizeAttrs = 'width="{{ module.image.width }}" height="{{ module.image.height }}"' %}
    {% if module.image.size_type == 'auto' %}
      {% set sizeAttrs = 'style="max-width: 100%; height: auto;"' %}
    {% elif module.image.size_type == 'auto_custom_max' %}
      {% set sizeAttrs = 'width="100%" height="auto" style="max-width: {{ module.image.max_width }}px; max-height: {{ module.image.max_height }}px"' %}
    {% endif %}
    <img src="{{ module.image.src }}" alt="{{ module.image.alt }}" {{ sizeAttrs }}>
  {% endif %}
</div>

You can use data attributes to pass the field values of your module instances to be handled by your module.js file.

To use the values in your module.js file, you will need to loop through all of the instances of your module. Adding a module-specific class name to the outermost wrapper element of your module will give you a target to use, so that you can loop through each of your module instances.

JavaScript
// module.js

let imgModules = document.getElementsByClassName('img-module');
Array.from(imgModules).forEach(function(element) { // loop through each of the instances of the module
  // set data attributes to variables to make it easy to work with
  let isLightboxEnabled = element.dataset.lightbox;
  let captionStyle = element.dataset.caption;
  if(isLightboxEnabled){
    element.addEventListener('click', function(){
      showLightbox(captionStyle); // Execute your code for the action you want to take, you can pass your data attributes into functions from libraries.
    });
  }
});

The data attributes will allow you to retrieve the field values for each module instance in your module.js. 

Require_js block

In advanced situations, perhaps when using a JavaScript templating library or a reactive framework like Vue.js or React.js, you may prefer outputting just the data, while the framework handles rendering.

In this case, use a script tag surrounded by a require_js block to provide variables you can access from your templating script.

HubL
{% require_js %}
<script>
  let myArray = [
    {%- for item in module.repeating_text_field -%}"{{ item }}",{%- endfor -%}
  ];
</script>
{% end_require_js %}

This technique can be useful for supplying advanced applications with an initial set of data from which to render. This eliminates an initial JavaScript call to retrieve data.

Require_js

Require_js is a HubL function that tells HubSpot that a particular module or template requires a particular JavaScript file to load properly. The function takes two parameters: the path to the file and the location the file is to be added to ("head" or "footer"). 

In a module require_js can only be added to the module.html. The JavaScript file referred to in the require_js statement will only be loaded once per page, regardless of how many times it is required by modules and templates within the page. This reduces the number of HTTP requests and prevents duplicate code. 

Some situations where this becomes handy:

  • If you have multiple modules or templates that require the same JavaScript, you can use require_js to share that script across modules.
  • If you're working with a JavaScript bundler like webpack, it can be easier to output your js files to one specific location. Using require_js, you can associate the JavaScript with your module.

Require_js and linked javascript files serve the same purpose, but require_js can be done conditionally based on field values. This prevents unnecessary code from being loaded. You also have the additional option of loading JavaScript in the head, should you need that.

Since JavaScript is render-blocking , the default location require_js places JavaScript is the "footer". Learn more about optimizing for performance.