Let's make our modules responsive without doubling our code base... or punching ourselves in the face. Because we like and respect our face.

Overview

So you've built a sick module that has some pretty slick features. It's flexible and clear to the user. You've provided a great deal of customization through various fields. You're well on your way to being the greatest HubSpot developer that the world has ever seen.

You ship your module over for review. The client says they love it...BUT...they want to be able to modify the heading color or that background image on mobile vs desktop. No problem -- There are a lot of different ways to skin this cat. Lets dive in!

The Setup

module.html
<div class="card">
   <h1>Heading Goes Here</h1>
</div>
Fields
   - heading_color_desktop
   - heading_color_mobile

The "Dirty" Solution (Wouldn't recommend but it gets the job done)

Im pretty sure it's frowned upon in just about every practice but at the end of the day "if it works, it works". If you're looking to get dirty and you've got to make your module fields responsive you can always copy your markup, make a mobile version of module, and responsively show and hide the components as needed.

This might look something like this:

module.html
<div class="card">
   <h1 class="desktop" style="color: {{module.heading_color_desktop.color}};">Heading Goes Here</h1>
   <h1 class="mobile" style="color: {{module.heading_color_mobile.color}};">Heading Goes Here</h1>
</div>
module.css
.card .desktop {
   display: block;
}
.card .mobile {
   display: none;
}
@media(max-width: 768px) {
   .card .desktop {
      display: none;
   }
   .card .mobile  {
      display: block;
   }
}

I think its pretty clear why this is dirty. The codebase is more than doubled. We are repeating ourselves all over the place. We have a few different places to maintain code if any edits need to be made in the future. Gross. Ick. Blah.

While I wouldn't recommend this. Im also not shooting it down because... we have all been here in various stages our of programming/development journeys. And as some of my mentors in the past have said "If it works, it works" and this... works. But let's take a look at some better options!

Require Css Blocks

I think a perfectly acceptable solution to this problem would be making use of HubSpot's {% require_css %} block.

In the example above we are inlining our color declarations directly into the h1 tag itself. But using this method we would be moving all of our CSS into the {% require_css %} block inside of module.html.

module.html
<div class="card">
   <h1>Heading Goes Here</h1>
</div>

{% require_css %}
<style>
{% scope_css %}
   .card h1 {
      color: {{module.heading_color_desktop.color}};
   }
   @media(max-width: 768px) {
   .card h1 {
       color: {{module.heading_color_mobile.color}};
   }
}
{% end_scope_css %}
</style>
{% end_require_css}

So this is a pretty remarkable improvement. Our markup for our card is back to normal. We simply have one h1 element. And we are handling all of our style declarations inside of a style block. We are making use of the scope_css block which is scoping all of our css to this individual module. This means we can have this module on our page multiple times and the css will be namespaced or scoped to that individual module.

The best part is that this method will still work with repeater groups. We would just need to add some unique identifiers to the markup and the for loop in our css to get the job done.

Fields
   - cards (repeater)
      - heading_color_desktop
      - heading_color_mobile
module.html
{% for card in module.cards %}
<div id="{{name}}-card-{{loop.index}}" class="card">
   <h1>Heading Goes Here</h1>
</div>
{% endfor %}

{% require_css %}
<style>
{% for card in module.cards %}
   #{{name}}-card-{{loop.index}} h1 {
      color: {{card.heading_color_desktop.color}};
   }
   @media(max-width: 768px) {
   #{{name}}-card-{{loop.index}} h1 {
      color: {{card.heading_color_mobile.color}};
   }
}
{% endfor %}
</style>
{% end_require_css}

This is getting a bit more complicated. But it too gets the job done. We are creating a new unique id based on the module's name (HubSpot generated), the element (card), and the index of the loop. This way we can identify them specifically in the CSS declarations. In this case we don't need to use scope css because we are scoping the styles ourselves.

CSS Custom Properties

This is likely the cleanest way to handle a lot of issues in HubSpot. If you are unfamiliar with what css custom properties are I would take some time to give it a quick google. There are plenty of resources out there that explain them, like this one here.

module.html
<div class="card" style="--color-desktop: {{module.heading_color_desktop.color}}; --color-mobile: {{module.heading_color_mobile.color}};">
   <h1>Heading Goes Here</h1>
</div>
module.css
.card h1 {
   color: var(--color-desktop);
}
@media(max-width: 768px) {
   .card h1 {
      color: var(--color-mobile);
   }
}

This is super clean for a few reasons. First off, it separates our languages. We are keeping html and hubl in module.html and we are keeping all of our css inside of module.css. Secondly, we are not repeating anything any more than we would if we were hardcoding a responsive style change. Lastly, if we have a repeater group like in the example above. We only need to modify module.html with a for loop that iterates through the field. We don't have to edit our css at all! Pretty neat!