Skip to content

How to Use Web Components in HubSpot CMS Templates and Modules

A design system is a collection of reusable components that go from very small things like icons to larger ones that may be made up of many of the smaller components. Design systems are popular among growing businesses and often simplify development and make code bases and designs easier to scale. In addition, they also help create a brand identity and a consistent experience for the user. 

There are some key ways that modularization can make life easier for developers. The more you repeat patterns and the less you similar CSS, the easier it is to keep things accessible and the simpler it is to maintain. CMS Hub's components were made to enable building design systems that work for both developers and marketers, composed of different sized building blocks: Themes, templates, partials, dnd areas, sections, and modules - being the smallest.

But what if you need a reusable building block that's smaller than a module, one that can be used in multiple modules and templates? There are a few ways to accomplish this, including HubL macros, or using a JavaScript framework like Vue.js, but today we're going to talk about something that's not platform/framework specific. We're going to use the standard web feature called web components.

What are web components?

Web components are the web's built-in answer of how to create reusable components for a design system. The easiest way to think of them is as custom HTML elements that you create. Because they're just HTML elements, you can include them inside your different modules and templates as needed. You can pass information to the HTML elements the exact same way you would with normal HTML. If you've built with Vue.js, React.js or really any other JavaScript framework, web components can easily be integrated into those projects because web components are based on existing web standards.

Similar to HubSpot modules, your web component is made up of HTML, CSS and JavaScript. This means it's just building on skills you already have, making it a lot less effort to get started.

For the sake of keeping a component self-contained, often much of your component's CSS and HTML are written in a single JavaScript file that you load into the page:

class businessCard extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); } connectedCallback() { // You can get the attributes set on the custom element just like you would any other HTML element. let firstName = this.getAttribute("data-first"); let lastName = this.getAttribute("data-last"); let jobTitle = this.getAttribute("data-title"); let imageSrc = this.getAttribute("data-src"); // Web components are made of the same HTML, and CSS, you're used to. It's just within a self contained space, called the Shadow DOM. We set the inner HTML of the custom element almost the same as you would with any normal HTML element. this.shadowRoot.innerHTML = ` <style> /* There are a few different ways to style web components, one way is using a style block inside the shadow DOM. :host is a pseudoclass targetting the custom element itself. */ :host{ border-radius:6px; width:500px; max-width:100%; padding:15px; contain: layout; display:inline-flex; flex-flow: row wrap; align-items:center; justify-content:flex-start; gap:20px; box-shadow:2px 3px 7px 0px rgb(0 0 0 / 50%); transition: .2s ease-in; } :host(:hover){ box-shadow:4px 3px 7px 2px rgb(0 0 0 / 50%); transform:translateZ(10px); } .avatar{ max-width:100%; flex:1; object-fit:cover; aspect-ratio:1/1; } .text{ flex:1 1 175px; } .name{ margin-bottom:0; } .job-title{ margin-bottom:15px; font-weight:bold; color:blue; } </style> <img src="${imageSrc}" class="avatar" width="150px" loading="lazy" alt="Photo of ${firstName}"> <div class="text"> <h3 class="name">${firstName} ${lastName}</h3> <div class="job-title">${jobTitle}</div> <div class="bio"><slot></slot></div> <!-- The slot element, tells the browser, any time someone puts HTML/text inside the the element <business-card>Like this</business-card> place it in this spot in the ShadowDOM --> </div> `; } } window.customElements.define("business-card", businessCard);

Then to display the component itself you write HTML:

<business-card data-first="Jon" data-last="McLaren" data-title="Sr. CMS Developer Advocate" data-src="https://assets.codepen.io/166666/internal/avatars/users/default.png"> Web Developer creating developer education content to help developers grow better. </business-card> <business-card data-first="Everest" data-last="McLaren" data-title="Intern" data-src="https://assets.codepen.io/166666/everest+on+office+chair-min.jpg"> German Shorthair pointer, blue heeler dog, with a knack for Inbound Marketing. </business-card>

Visually what gets rendered is the web component with its shadow DOM and CSS. In this case the JavaScript is simple and is getting attributes from the web component which then gets shown as the component is rendered.

Screenshot of webcomponent which resembles a business card with image on left text on right stating the persons name.

Using web components in HubSpot custom modules

You may already be seeing how this can fit in with custom modules that you build for CMS Hub. The HTML, CSS, and JS that's all tied to the display and interactions with that web component can be taken advantage of inside your modules to simplify their code and make your code more DRY (Don't Repeat Yourself). This lets you keep your module code focused on what it does that's different from your other assets that use that component, and reduces the duplication of code across a website project.

To use a web component in your module, you add the require_js HubL function to your module.html file and then add the custom HTML tags you created.

{{ require_js("../business-card.js") }} <business-card data-first="{{ module.first }}" data-last="{{ module.last }}" data-title="{{ module.title }}" data-src="{{ module.avatar.src }}"> {{ module.bio }} </business-card>

If you have module field values you want to pass to the module, you do it the same as you would with standard HTML elements.

Using web components in HubSpot templates

Very similar to how you would approach modules, with HubSpot templates you would include that same require_js HubL function and then add your HTML tags.

<!-- templateType: page isAvailableForNewContent: true label: About screenshotPath: ../images/template-previews/about.png --> {% extends "./layouts/base.html" %} {% block body %} {{require_js("../business-card.js")}} <business-card data-first="Jon" data-last="McLaren" data-title="Sr.CMS Developer Advocate" data-src="/my-photo.png"> Some biography text. </business-card> {% endblock body %}

I personally like the idea of keeping the require statement close to the code that actually requires it (pun intended). It helps you find where the web component is coming from if you or a coworker needs to find it later. The require_js statement by default loads in the footer and the same file will only be called once per page.

A useful tool in your web development toolbox

Since web components are largely self-contained, using require_js in HubL makes it easy to call them when and where you need them and the same component can be used in multiple modules and templates at the same time. Using all of the CMS components and web components together can make your website builds faster and maintenance easier.

Learn more about how the CMS components all fit together.