
A module for showcasing B2B case studies, client portfolios, or success stories in a responsive, interactive slider format. Editors can easily add multiple case study cards, each with an image, tags, title, company name, and a link.


This module is designed to display multiple case studies or client achievements in a visually appealing and space-efficient slider format.
Purpose of Use
What it can achieve
Implementation Points & Notes
repeater field for the "Case Studies" section. The group for the repeater should contain an image field, text field (title), text field (company name), another repeater field for the tags, and a link field.color picker (for primary_color) and number fields (for font sizes) to allow for easy visual customization without needing to edit code.object-fit: cover, which handles minor size variations, but consistency is best for a clean design.[
{
"name": "main_title",
"label": "メインタイトル",
"type": "text",
"default": "導入インタビュー"
},
{
"name": "primary_color",
"label": "プライマリーカラー",
"type": "color",
"default": {
"color": "#007bff"
}
},
{
"name": "title_font_size",
"label": "タイトルフォントサイズ(px)",
"type": "number",
"default": 32
},
{
"name": "company_font_size",
"label": "会社名フォントサイズ(px)",
"type": "number",
"default": 14
},
{
"name": "description_font_size",
"label": "説明文フォントサイズ(px)",
"type": "number",
"default": 16
},
{
"name": "tag_font_size",
"label": "タグフォントサイズ(px)",
"type": "number",
"default": 12
},
{
"name": "case_studies",
"label": "事例",
"type": "group",
"expanded": true,
"occurrence": {
"min": 1,
"max": 20,
"default": 3
},
"children": [
{
"name": "image",
"label": "画像",
"type": "image",
"required": false,
"resizable": true
},
{
"name": "title",
"label": "タイトル",
"type": "text",
"default": "事例のタイトルを入力してください",
"required": true
},
{
"name": "company_name",
"label": "会社名",
"type": "text",
"default": "会社名を入力してください",
"required": true
},
{
"name": "link_url",
"label": "リンク先URL",
"type": "url",
"required": false
},
{
"name": "tags",
"label": "タグ",
"type": "group",
"occurrence": {
"min": 0,
"max": 10,
"default": 2
},
"children": [
{
"name": "tag_text",
"label": "タグテキスト",
"type": "text",
"required": true,
"help_text": "表示したいタグのテキストを入力してください"
}
]
}
]
}
]
{% set module_id = name|lower|replace(' ', '_') %}
<div class="case-study-slider" id="{{ module_id }}"
style="--primary-color: {{ module.primary_color.color }};
--title-font-size: {{ module.title_font_size }}px;
--company-font-size: {{ module.company_font_size }}px;
--description-font-size: {{ module.description_font_size }}px;
--tag-font-size: {{ module.tag_font_size }}px;">
{% if module.main_title %}
<h2 class="slider-title">{{ module.main_title }}</h2>
{% endif %}
<div class="slider-container">
<div class="slider-track" id="{{ module_id }}_track">
{% for case in module.case_studies %}
<div class="case-card">
{% if case.image and case.image.src %}
<div class="case-image">
<img src="{{ case.image.src }}" alt="{{ case.image.alt or case.title }}" loading="lazy">
</div>
{% else %}
{# デバッグ用:画像がない場合のプレースホルダー #}
<div class="case-image placeholder">
<div class="placeholder-content">No Image</div>
</div>
{% endif %}
<div class="case-content">
{# タグの表示 #}
{% if case.tags and case.tags|length > 0 %}
<div class="tags-container">
{% for tag in case.tags %}
{% if tag.tag_text %}
<span class="tag">{{ tag.tag_text }}</span>
{% endif %}
{% endfor %}
</div>
{% endif %}
{% if case.title %}
<h3 class="case-title">{{ case.title }}</h3>
{% endif %}
{% if case.company_name %}
<p class="company-name">{{ case.company_name }}</p>
{% endif %}
{% if case.link_url and case.link_url.href %}
<a href="{{ case.link_url.href }}" class="case-link"
{% if case.link_url.open_in_new_tab %}target="_blank"{% endif %}>
<svg class="arrow-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</a>
{% endif %}
</div>
</div>
{% endfor %}
</div>
<div class="slider-controls">
<button class="prev-btn" data-target="{{ module_id }}_track" aria-label="前のスライド">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</svg>
</button>
<div class="pagination">
{% for case in module.case_studies %}
<button class="pagination-dot{% if loop.first %} active{% endif %}"
data-slide="{{ loop.index0 }}" data-target="{{ module_id }}_track"></button>
{% endfor %}
</div>
<button class="next-btn" data-target="{{ module_id }}_track" aria-label="次のスライド">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</button>
</div>
</div>
</div>
.case-study-slider {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.slider-title {
font-size: var(--title-font-size, 32px);
font-weight: bold;
margin-bottom: 40px;
text-align: left;
color: var(--primary-color, #333);
}
.slider-container {
position: relative;
overflow: hidden;
border-radius: 12px;
}
.slider-track {
display: flex;
transition: transform 0.3s ease;
gap: 20px;
}
.case-card {
flex: 0 0 calc(33.333% - 14px);
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.case-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.case-image.placeholder {
background-color: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
}
.placeholder-content {
color: #999;
font-size: 14px;
}
.case-image {
position: relative;
height: 200px;
overflow: hidden;
}
.case-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.case-card:hover .case-image img {
transform: scale(1.05);
}
.case-content {
padding: 24px;
position: relative;
}
.tags-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
}
.tag {
background-color: var(--primary-color, #007bff);
color: white;
padding: 6px 12px;
border-radius: 20px;
font-size: var(--tag-font-size, 12px);
font-weight: 500;
white-space: nowrap;
}
.case-title {
font-size: var(--description-font-size, 16px);
font-weight: 600;
line-height: 1.5;
margin-bottom: 16px;
color: #333;
min-height: 48px;
}
.company-name {
font-size: var(--company-font-size, 14px);
color: #666;
margin-bottom: 20px;
font-weight: 500;
}
.case-link {
position: absolute;
bottom: 20px;
right: 20px;
width: 40px;
height: 40px;
background-color: var(--primary-color, #007bff);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
transition: all 0.3s ease;
}
.case-link:hover {
transform: scale(1.1);
background-color: color-mix(in srgb, var(--primary-color, #007bff) 80%, black);
}
.arrow-icon {
width: 20px;
height: 20px;
color: white;
}
.slider-controls {
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
margin-top: 30px;
}
.prev-btn, .next-btn {
width: 48px;
height: 48px;
border: 2px solid var(--primary-color, #007bff);
background: transparent;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
color: var(--primary-color, #007bff);
}
.prev-btn:hover, .next-btn:hover {
background-color: var(--primary-color, #007bff);
color: white;
transform: scale(1.05);
}
.prev-btn:disabled, .next-btn:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.prev-btn:disabled:hover, .next-btn:disabled:hover {
background: transparent;
color: var(--primary-color, #007bff);
transform: none;
}
.prev-btn svg, .next-btn svg {
width: 20px;
height: 20px;
}
.pagination {
display: flex;
gap: 8px;
}
.pagination-dot {
width: 12px;
height: 12px;
border-radius: 50%;
border: none;
background-color: #ddd;
cursor: pointer;
transition: all 0.3s ease;
}
.pagination-dot.active {
background-color: var(--primary-color, #007bff);
}
.pagination-dot:hover {
background-color: var(--primary-color, #007bff);
opacity: 0.7;
}
/* レスポンシブデザイン */
@media (max-width: 768px) {
.case-card {
flex: 0 0 calc(50% - 10px);
}
.slider-title {
font-size: calc(var(--title-font-size, 32px) * 0.8);
}
}
@media (max-width: 480px) {
.case-card {
flex: 0 0 calc(100% - 0px);
}
.slider-track {
gap: 10px;
}
.case-content {
padding: 20px;
}
.slider-title {
font-size: calc(var(--title-font-size, 32px) * 0.7);
margin-bottom: 30px;
}
}
document.addEventListener('DOMContentLoaded', function() {
// すべてのスライダーを初期化
const sliders = document.querySelectorAll('.case-study-slider');
sliders.forEach(function(slider) {
initializeSlider(slider);
});
function initializeSlider(sliderElement) {
const track = sliderElement.querySelector('.slider-track');
const prevBtn = sliderElement.querySelector('.prev-btn');
const nextBtn = sliderElement.querySelector('.next-btn');
const paginationDots = sliderElement.querySelectorAll('.pagination-dot');
const cards = sliderElement.querySelectorAll('.case-card');
if (!track || cards.length === 0) return;
let currentIndex = 0;
let cardsPerView = getCardsPerView();
const totalCards = cards.length;
const maxIndex = Math.max(0, totalCards - cardsPerView);
// ウィンドウサイズに応じて表示カード数を決定
function getCardsPerView() {
const width = window.innerWidth;
if (width <= 480) return 1;
if (width <= 768) return 2;
return 3;
}
// スライダーの位置を更新
function updateSliderPosition() {
const cardWidth = cards[0].offsetWidth;
const gap = 20;
const translateX = -(currentIndex * (cardWidth + gap));
track.style.transform = `translateX(${translateX}px)`;
// ボタンの状態を更新
if (prevBtn) prevBtn.disabled = currentIndex === 0;
if (nextBtn) nextBtn.disabled = currentIndex >= maxIndex;
// ページネーションドットの状態を更新
paginationDots.forEach(function(dot, index) {
dot.classList.toggle('active', index === currentIndex);
});
}
// 前のスライドに移動
function goToPrevSlide() {
if (currentIndex > 0) {
currentIndex--;
updateSliderPosition();
}
}
// 次のスライドに移動
function goToNextSlide() {
if (currentIndex < maxIndex) {
currentIndex++;
updateSliderPosition();
}
}
// 特定のスライドに移動
function goToSlide(index) {
currentIndex = Math.min(Math.max(0, index), maxIndex);
updateSliderPosition();
}
// イベントリスナーを設定
if (prevBtn) {
prevBtn.addEventListener('click', goToPrevSlide);
}
if (nextBtn) {
nextBtn.addEventListener('click', goToNextSlide);
}
paginationDots.forEach(function(dot, index) {
dot.addEventListener('click', function() {
goToSlide(index);
});
});
// キーボードナビゲーション
sliderElement.addEventListener('keydown', function(e) {
if (e.key === 'ArrowLeft') {
goToPrevSlide();
e.preventDefault();
} else if (e.key === 'ArrowRight') {
goToNextSlide();
e.preventDefault();
}
});
// タッチ/スワイプサポート
let startX = 0;
let isDragging = false;
track.addEventListener('touchstart', function(e) {
startX = e.touches[0].clientX;
isDragging = true;
});
track.addEventListener('touchmove', function(e) {
if (!isDragging) return;
e.preventDefault();
});
track.addEventListener('touchend', function(e) {
if (!isDragging) return;
const endX = e.changedTouches[0].clientX;
const diff = startX - endX;
if (Math.abs(diff) > 50) {
if (diff > 0) {
goToNextSlide();
} else {
goToPrevSlide();
}
}
isDragging = false;
});
// マウスドラッグサポート
track.addEventListener('mousedown', function(e) {
startX = e.clientX;
isDragging = true;
track.style.cursor = 'grabbing';
e.preventDefault();
});
document.addEventListener('mousemove', function(e) {
if (!isDragging) return;
e.preventDefault();
});
document.addEventListener('mouseup', function(e) {
if (!isDragging) return;
const endX = e.clientX;
const diff = startX - endX;
if (Math.abs(diff) > 50) {
if (diff > 0) {
goToNextSlide();
} else {
goToPrevSlide();
}
}
isDragging = false;
track.style.cursor = 'grab';
});
// リサイズ時の再計算
window.addEventListener('resize', function() {
const newCardsPerView = getCardsPerView();
if (newCardsPerView !== cardsPerView) {
cardsPerView = newCardsPerView;
currentIndex = Math.min(currentIndex, Math.max(0, totalCards - cardsPerView));
updateSliderPosition();
}
});
// 初期化
updateSliderPosition();
track.style.cursor = 'grab';
}
});No, you do not. This module is designed so that website editors can easily add, delete, and reorder case studies directly from the HubSpot editor interface, without touching any code. All settings for each case study's image, title, company name, tags, and link, as well as the module's overall design (colors and font sizes), can be changed from the provided fields.
Yes, there are limits. According to the article's "Field Configuration" section (JSON definition), you can set a minimum of 1 and a maximum of 20 case study cards (case_studies). Additionally, the number of tags (tags) you can add within each card is from 0 to a maximum of 10.
The most likely cause is that the JavaScript file required for the slider functionality is not loading correctly. As noted in the "Implementation Points" section, all of the module's slider logic (responsive behavior, drag controls, button functions, etc.) is managed by JavaScript. Please check that the module's JS file is loading correctly and is not conflicting with any other scripts.
We can customize this sample to match your specific business requirements.
Book Free ConsultationPut it on Trello!Need a fix for HubSpot, CMS, or GAS? Post it on Trello.
Development Requests Here