Experience Level: Intermediate | Tools Required: HubSpot CMS, jQuery, MixItUp | Related Topics: HubSpot Custom Modules, JavaScript, CSS, Portfolio Design
Are you looking to showcase your work with a beautiful, filterable portfolio gallery on your HubSpot website? In this tutorial, I'll walk you through creating a custom portfolio gallery module that allows for dynamic category filtering, responsive design, and elegant hover effects.
What We'll Build
Our portfolio gallery will:
- Display client work in a responsive grid
- Allow visitors to filter projects by category
- Feature smooth animations and hover effects
- Be fully customizable through the HubSpot module editor

Step 1: Create a New Custom Module
- In your HubSpot portal, navigate to Marketing > Website > Website Pages
- Click on Settings and select Custom Modules
- Click Create custom module
- Name your module "Portfolio Gallery" and select "Standard page" module type
- Click Create
Step 2: Set Up the Module Fields
Replace the default fields with this JSON schema:
json
[
{
"allow_new_line": false,
"default": "Our Work",
"id": "heading",
"label": "Heading",
"name": "heading",
"required": false,
"type": "text"
},
{
"children": [
{
"allow_new_line": false,
"id": "filter_categories.category_name",
"label": "Category Name",
"name": "category_name",
"required": true,
"type": "text"
},
{
"allow_new_line": false,
"help_text": "Used for filtering. Example: 'design_branding', 'development', 'support' (lowercase, use underscores for spaces)",
"id": "filter_categories.category_id",
"label": "Category ID (lowercase, use underscores for spaces)",
"name": "category_id",
"required": true,
"type": "text",
"validation_regex": "[a-z0-9_]+"
}
],
"id": "filter_categories",
"label": "Filter Categories",
"name": "filter_categories",
"occurrence": {
"max": 10,
"min": 1
},
"required": true,
"tab": "CONTENT",
"type": "group"
},
{
"children": [
{
"allow_new_line": false,
"id": "portfolio_items.client_name",
"label": "Client Name",
"name": "client_name",
"required": true,
"type": "text"
},
{
"default": {
"src": "",
"alt": null
},
"id": "portfolio_items.image",
"label": "Portfolio Image",
"name": "image",
"required": true,
"resizable": true,
"responsive": false,
"type": "image"
},
{
"children": [
{
"id": "portfolio_items.categories.category",
"label": "Category",
"name": "category",
"required": true,
"type": "choice",
"display": "select",
"multiple": false,
"placeholder": "Select a category",
"help_text": "Select a category for this portfolio item",
"dynamic_field_reference": "filter_categories.category_id"
}
],
"id": "portfolio_items.categories",
"label": "Categories",
"name": "categories",
"occurrence": {
"max": 10,
"min": 1
},
"required": true,
"tab": "CONTENT",
"type": "group"
},
{
"default": {
"url": {
"type": "EXTERNAL",
"href": ""
},
"open_in_new_tab": false
},
"id": "portfolio_items.link",
"label": "Portfolio Link",
"name": "link",
"required": false,
"type": "link"
}
],
"id": "portfolio_items",
"label": "Portfolio Items",
"name": "portfolio_items",
"occurrence": {
"max": 100,
"min": 1
},
"required": true,
"tab": "CONTENT",
"type": "group"
}
]
Step 3: Add the HTML Template
Replace the default HTML with:
html
<div class="portfolio-gallery">
<div class="wrap">
<h1></h1>
<div class="gallery-wrap">
<ul id="filters" class="clearfix">
<li><span class="filter active" data-filter="all">All</span></li>
</ul>
<div id="gallery">
</div><!--/gallery-->
</div><!--/gallery-wrap-->
</div>
</div>
Step 4: Add the CSS Styles
Add these styles to make your portfolio gallery look great:
css
/* Portfolio Gallery Module CSS */
.portfolio-gallery {
color: #212121;
line-height: 1.625;
}
.wrap {
margin: 0 auto;
max-width: 1200px;
width: 100%;
padding: 40px 20px;
}
.gallery-wrap,
#gallery {
overflow: hidden;
}
#filters {
margin: 0 0 30px;
padding: 0;
list-style: none;
overflow: hidden;
text-align: center;
}
#filters li {
display: inline-block;
margin: 0 5px 10px;
}
#filters li span {
display: block;
padding: 8px 20px;
text-decoration: none;
color: #212121;
cursor: pointer;
text-transform: uppercase;
font-size: 0.85rem;
font-weight: 600;
border-radius: 4px;
transition: all ease-in-out 0.2s;
}
#filters li:hover span {
color: #000;
background: rgba(222, 255, 42, 0.3);
}
#filters li span.active {
background: #deff2a; /* Brand color */
color: #212121;
}
.gallery-item {
float: left;
width: 33.333%;
padding: 15px;
position: relative;
z-index: 10;
display: none; /* Initially hidden, will be shown by MixItUp */
}
.inside {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.details,
.overlay {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
opacity: 0;
}
.details {
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
transition: all 0.3s ease-in-out;
padding: 20px;
}
.details h2 {
color: #212121;
font-size: 1.5rem;
font-weight: 700;
letter-spacing: 0.5px;
text-align: center;
margin: 0 0 8px;
}
.details p {
color: #212121;
font-size: 0.9rem;
letter-spacing: 1px;
text-align: center;
margin: 0;
text-transform: uppercase;
}
.inside img {
float: left;
width: 100%;
transition: transform 0.5s ease;
}
.placeholder {
background: #f5f5f5;
height: 250px;
display: flex;
align-items: center;
justify-content: center;
color: #999;
}
.overlay {
background: rgba(222, 255, 42, 0.85); /* Brand color with opacity */
z-index: 1;
transition: all 0.3s ease-in-out;
}
.gallery-item:hover .details,
.gallery-item:hover .overlay {
opacity: 1;
}
.gallery-item:hover img {
transform: scale(1.05);
}
@media (max-width: 992px) {
.gallery-item {
width: 50%;
padding: 10px;
}
.details h2 {
font-size: 1.2rem;
}
}
@media (max-width: 576px) {
.gallery-item {
float: none;
width: 100%;
padding: 8px 0;
}
#filters {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
#filters li {
margin: 0 3px 8px;
}
#filters li span {
padding: 6px 14px;
font-size: 0.8rem;
}
}
Step 5: Add JavaScript for Filtering
Finally, add this JavaScript to enable the filtering functionality:
javascript
$(function () {
// Wait for document ready
$(document).ready(function() {
console.log("Portfolio gallery initializing...");
// Initialize MixItUp
$('#gallery').mixItUp({
selectors: {
target: '.gallery-item',
filter: '.filter'
},
load: {
filter: 'all' // Show all items by default
},
animation: {
enable: true,
effects: 'fade translateZ(-100px)',
duration: 400,
easing: 'ease',
perspectiveDistance: '3000px',
perspectiveOrigin: '50% 50%'
},
callbacks: {
onMixStart: function(state) {
console.log("Mix operation started");
},
onMixEnd: function(state) {
console.log("Mix operation complete");
},
onMixFail: function(state) {
console.log("No matching items found");
}
}
});
// Add click handling to filter buttons
$('#filters .filter').click(function() {
// Remove active class from all filters
$('#filters .filter').removeClass('active');
// Add active class to clicked filter
$(this).addClass('active');
});
});
});
Step 6: Add Required JavaScript Libraries
This module requires two external JavaScript libraries:
- jQuery (if not already included in your theme)
- MixItUp for the filtering functionality
In your HubSpot template, go to the template editor and add these scripts in the head section
html
<!-- jQuery (if not already included in your theme) -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- MixItUp Plugin -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mixitup/2.1.11/jquery.mixitup.min.js"></script>
Step 7: Configure and Test Your Module
- Save your module and add it to a page
- Add at least 3-4 filter categories (such as "Design & Branding", "Development", "Support")
- Add several portfolio items, assigning them to different categories
- Preview your page to test the filtering functionality
Troubleshooting Tips
If your portfolio items aren't showing:
- Check Console for Errors: Open browser dev tools to look for JavaScript errors
- Verify jQuery and MixItUp: Make sure both libraries are loading properly
- Check Category IDs: Ensure they follow the lowercase_with_underscores format
- Inspect Gallery Items: Use the browser inspector to verify classes are being applied correctly
Customizing Your Portfolio Gallery
You can easily customize the appearance:
- Change colors in the CSS (look for
#deff2a
and other color values) - Adjust animation speed by modifying the
duration
value in the JavaScript - Change the hover effect by modifying the
.overlay
and.details
CSS
Conclusion
You now have a beautiful, dynamic portfolio gallery that showcases your work professionally and allows visitors to filter by category. The best part is, it's fully customizable - content editors can add or remove categories and portfolio items without touching any code.
Happy HubSpotting!
Tags:
HubspotMay 7, 2025 7:13:26 AM
Comments