Documentation
Luxury Testimonial Pro
Module Documentation

Luxury Testimonial Pro

A premium HubSpot CMS custom module for displaying client testimonials with refined aesthetics, three display modes, and comprehensive editorial control - all without touching code.

Module ID: 342578057950
9 Field Groups · 50+ Fields
Landing, Site & Blog pages
Documentation based on fields.json and meta.json. Sections requiring module.html confirmation are marked Verify. All field-level documentation is complete and authoritative.
01

Overview

Luxury Testimonial Pro allows marketers and content editors to showcase social proof with rich styling options. It supports three display modes - Slider/Carousel, Grid, and List - each responsive and accessible out of the box.

Three Display Modes

Slider carousel, multi-column grid, and stacked list - switchable with a single toggle.

21 Color Controls

Individual color pickers for every UI element, with HubSpot theme inheritance for brand consistency.

Scroll Reveal Animations

Fade-up, fade-in, and slide-in entrance effects with configurable speed and reduced-motion support.

Fully Responsive

Configurable column counts per breakpoint (desktop, tablet, mobile) with hardcoded sensible fallbacks.

Accessibility-Aware

ARIA roles, keyboard navigation, focus styles, and screen reader support baked in.

Google Fonts

Curated luxury font pairings (Cormorant Garamond, Playfair Display, Jost, Inter) loaded dynamically.

Supported content types: Landing Page, Site Page, Blog Post. Can be placed in any template layout including drag-and-drop and coded templates ("host_template_types": ["ANY"]).

02

Module Metadata

Sourced from meta.json. This file registers the module with HubSpot and controls where it can be used.

Module ID
342578057950
Label
Luxury Testimonial Pro
Icon
quote-right
Global Module
No - page-level only
Available for New Content
Yes
Host Template Types
ANY
Categories
DESIGN, FUNCTIONALITY
Content Tags
SLIDER (user-defined)
Note on global: false - Because this is not a global module, each page instance is independent. Changes to one module instance do not propagate across the site.
03

File Structure

luxury-testimonial-pro/
module.html - HubL template · renders HTML, CSS & JavaScript
fields.json - Field schema · all editor-configurable settings
meta.json - Module metadata · registration & content type settings
FileRoleAffects Rendering?
module.htmlRendering template - HubL logic, CSS, JavaScript, HTML outputYES
fields.jsonEditor field schema - provides data that module.html readsINDIRECT
meta.jsonModule registration - content types & editor appearance onlyNO
04

How The Module Works

When a HubSpot page containing this module is rendered, the following sequence occurs:

STEP 01

meta.json Validation

HubSpot reads meta.json to confirm the module is valid for the current content type and template.

STEP 02

Field Values Resolved

Editor-saved values (or defaults from fields.json) are applied to every configurable property.

STEP 03

HubL Engine Executes

module.html is processed - namespace aliases, derived values, unique module ID, and font URLs are computed.

STEP 04

CSS Variables Emitted

All color, typography, layout, and animation values are written into a scoped <style> block as CSS custom properties.

STEP 05

HTML Output

Section header, background decorations, testimonial cards (via HubL macro), and display-mode wrappers are conditionally rendered.

STEP 06

JavaScript Initialised

Inline <script> reads data-* attributes and initialises slider, scroll-reveal animations, keyboard navigation, and touch/swipe.

05

Dynamic vs Static Functionality

The table below summarises the key feature areas and whether they are controlled by editor fields (dynamic) or hardcoded (static).

Feature AreaSourceTypeNotes
Section header visibilitysection_settings.show_headerDynamicSingle HubL conditional hides entire header block
Display modedisplay_mode.enable_sliderDynamicMaster switch: slider vs static rendering
Testimonials repeatertestimonials[ ]Dynamic3–20 items; each renders via HubL macro
All color valuescolors.*DynamicEmitted as CSS custom properties
All typography valuestypography.*DynamicFont URLs constructed in HubL
Slider configslider_settings.*DynamicPassed as data-* attributes; read by JavaScript
Animationsanimation.*DynamicIntersectionObserver + CSS class toggling
SVG icon artworkmodule.htmlStaticQuote mark, arrows, map-pin - hardcoded SVGs
Star rating glyphmodule.htmlStaticUnicode star glyph; only quantity is dynamic
Responsive breakpointsCSSStaticFixed at 1024px, 768px, 480px
Card internal paddingCSSStaticNot configurable via editor
Orb sizes & positionsCSSStaticOnly orb color is dynamic
Google Fonts base URLHubLStaticFont names are dynamic; URL structure is hardcoded
06

Field Reference

All fields are organised by group as defined in fields.json. Fields in the CONTENT tab appear in the Content panel; fields in the STYLE tab appear in the Style panel.

6.1 section_settings CONTENT

Controls the section header displayed above all testimonial cards. Expanded by default in the editor.

FieldLabelTypeDefaultRequired
show_headerShow Section HeaderBoolean toggletrueNo
pre_titlePre-Title LabelText"Client Testimonials"No
headingSection HeadingText"What Our Clients Say"No
descriptionSection SubtextRich Text"Real experiences from the people we are proud to serve."No
Setting show_header: false suppresses all four child fields simultaneously via a single HubL {% if %} block. Both pre_title and heading have allow_new_line: false, preventing multi-line input.

6.2 display_mode CONTENT

Controls whether testimonials render as a slider or as a static layout, and configures the static layout's column counts.

FieldLabelTypeDefault
enable_sliderEnable Slider/CarouselBoolean toggletrue
static_layoutStatic LayoutSelect"grid"
grid_columns_desktopGrid Columns (Desktop)Select (1–4)"3"
grid_columns_tabletGrid Columns (Tablet)Select (1–3)"2"
grid_columns_mobileGrid Columns (Mobile)Select (1–2)"1"
Known Issue: Grid column fields have no visibility condition - they always appear in the editor even when slider is active. See Known Issue #4 for the recommended fix.

6.3 testimonials CONTENT - REPEATER

Core content repeater. Each item represents one testimonial card. Min: 3 items · Max: 20 items · Default: 3 items. The editor uses customer_name as the item label.

FieldLabelTypeDefaultRequired
category_badgeCategory / Tag BadgeText"Facial Treatment"No
star_ratingStar Rating (1–5)Number (1–5)5No
contentTestimonial ContentRich Text(long default quote)Yes
customer_imageCustomer PhotoImagesrc: "" · 72×72pxNo
customer_nameCustomer NameText"Isabelle Laurent"Yes
designationDesignation / Job TitleText"Creative Director"No
companyCompany NameText""No
company_logoCompany LogoImagesrc: "" · 120×40pxNo
locationLocationText"Dubai, UAE"No
show_ctaShow CTA ButtonBoolean togglefalseNo
cta_textCTA Button TextText"Read Full Story"No
cta_linkCTA Button LinkLinkhref: "#" · new tab: falseNo
Image behaviour: customer_image.src is empty by default - this triggers an initials placeholder in the template. company_logo is only rendered when an image is actually selected. The cta_link supports EXTERNAL, CONTENT (HubSpot page), FILE, and EMAIL_ADDRESS link types.

6.4 visibility CONTENT

Global show/hide toggles. These affect all cards simultaneously. The only per-card toggle is testimonials[n].show_cta.

FieldLabelDefaultApplies To
show_starsShow Star RatingstrueAll cards
show_avatarShow Client AvatartrueAll cards
show_designationShow DesignationtrueAll cards
show_companyShow CompanyfalseAll cards
show_locationShow LocationtrueAll cards
show_badgeShow Category BadgetrueAll cards
show_quote_iconShow Decorative Quote IcontrueAll cards
show_bg_decorShow Background DecorationstrueSection
show_arrowsShow Navigation ArrowstrueSlider only
show_dotsShow Pagination DotstrueSlider only

6.5 slider_settings CONTENT - CONDITIONAL

Only visible in the editor when display_mode.enable_slider = true. This group is correctly enforced via a visibility block in fields.json - the best-implemented conditional group in the module.

FieldLabelTypeDefault
autoplayEnable AutoplayBoolean toggletrue
autoplay_speedAutoplay Speed (ms)Number (1000–20000)6000
transition_speedTransition Speed (ms)Number (200–2000)700
infinite_loopInfinite LoopBoolean toggletrue
pause_on_hoverPause on HoverBoolean toggletrue
cards_desktopCards Per View (Desktop)Select (1–3)"1"
cards_tabletCards Per View (Tablet)Select (1–2)"1"

6.6 animation CONTENT

FieldLabelTypeDefault
enable_revealEnable Scroll Reveal AnimationsBoolean toggletrue
enable_hover_liftEnable Card Hover LiftBoolean toggletrue
reveal_styleReveal Animation StyleSelect"fade-up"
anim_speedAnimation SpeedSelect"normal" (0.6s)

Reveal style options: fade-up - Elegant · fade-in - Subtle · slide-in - Dynamic

Speed mapping: slow = 0.9s · normal = 0.6s · fast = 0.4s

6.7 layout CONTENT

FieldLabelTypeDefaultOptions
header_alignHeader AlignmentSelect"center"left · center · right
card_alignCard Content AlignmentSelect"left"left · center
avatar_positionAvatar PositionSelect"bottom"top · bottom
max_widthMax Content WidthSelect"1240px"960px · 1100px · 1240px · 1400px

6.8 colors STYLE

21 individually configurable color properties using HubSpot's color picker (hex + opacity). Two fields inherit from the HubSpot site theme.

Section Background#F7F3F0
Card Background#FFFFFF
Card Border#EDE8E3
Accent / Brand#C9A882 · theme
Heading Color#2C2118 · theme
Testimonial Text#3D2E22
Meta Color#9C8878
Star Color#C9A882
Dot Inactive#D9CFC8
Decor Orb#C9A882 · 8% opacity
Opacity Alert: colors.decor_color defaults to 8% opacity - all other fields are 100%. If module.html only reads the hex value and ignores opacity, orbs will render at full intensity, breaking the subtle decorative intent. See Known Issue #1.

6.9 typography STYLE

FieldLabelDefaultOptions / Mapping
heading_fontHeading FontCormorant GaramondCormorant Garamond · Playfair Display · DM Serif Display · Libre Baskerville
body_fontBody FontJostJost · Plus Jakarta Sans · Inter · Lato
testimonial_sizeTestimonial Text Size"large"small=1rem · medium=1.125rem · large=1.3rem · xlarge=1.5rem
card_radiusCard Border Radius"20px"0px · 8px · 16px · 20px · 28px
card_shadowCard Shadow"soft"none · soft · medium · elevated
section_paddingSection Padding"large"small=48px · medium=72px · large=100px · xlarge=140px
07

HubL Logic Breakdown

The following is inferred from the field schema. Direct code references require module.html confirmation.

Namespace Aliases

HubL / Jinja2
{% set ss = module.section_settings %}
{% set dm = module.display_mode %}
{% set vis = module.visibility %}
{% set sl = module.slider_settings %}
{% set anim = module.animation %}
{% set lay = module.layout %}
{% set col = module.colors %}
{% set typ = module.typography %}

Value Resolver Maps

HubL / Jinja2
{# Typography size map #}
{% set testimonial_size_map = {
  "small": "1rem",
  "medium": "1.125rem",
  "large": "1.3rem",
  "xlarge": "1.5rem"
} %}

{# Section padding map #}
{% set padding_map = {
  "small": "48px", "medium": "72px",
  "large": "100px", "xlarge": "140px"
} %}

{# Card shadow map #}
{% set shadow_map = {
  "none": "none",
  "soft": "0 4px 24px rgba(44,33,24,0.07)",
  "medium": "0 8px 32px rgba(44,33,24,0.13)",
  "elevated": "0 16px 48px rgba(44,33,24,0.18)"
} %}

{# Animation speed map #}
{% set speed_map = {
  "slow": "0.9s", "normal": "0.6s", "fast": "0.4s"
} %}

CSS Custom Properties Block

HubL output → CSS
<style>
  #{{ module_id }} {
    --section-bg: {{ col.section_bg.color }};
    --card-bg: {{ col.card_bg.color }};
    --accent: {{ col.accent.color }};
    --heading-color: {{ col.heading_color.color }};
    --text-color: {{ col.text_color.color }};
    --star-color: {{ col.star_color.color }};
    --testimonial-size: {{ testimonial_font_size }};
    --card-radius: {{ typ.card_radius }};
    --card-shadow: {{ card_shadow_val }};
    --section-padding: {{ section_padding_val }};
    --anim-speed: {{ anim_speed_val }};
    --max-width: {{ lay.max_width }};
    /* ... all 21 color vars + layout vars */
  }
</style>
08

Display Modes

Slider / Carousel

Default mode. Activates all JavaScript slider logic with configurable autoplay, looping, and per-viewport card counts.

Grid

Renders cards in a CSS grid. Column count is independently configurable for desktop (1–4), tablet (1–3), and mobile (1–2).

List

Cards stacked vertically at full container width using flexbox. No column configuration applies.

ModeTrigger ConditionWrapper Class
Sliderenable_slider = true.ltp-slider-wrapper
Gridenable_slider = false AND static_layout = "grid".ltp-grid
Listenable_slider = false AND static_layout = "list".ltp-list
09

Slider & JavaScript Architecture

All slider behaviour is JavaScript-driven. Configuration is passed via data-* attributes on the section wrapper and read at initialisation. No external slider library is used.

Data Attributes → JavaScript Config

Data AttributeSource FieldExample
data-sliderdisplay_mode.enable_slider"true"
data-autoplayslider_settings.autoplay"true"
data-autoplay-speedslider_settings.autoplay_speed"6000"
data-transitionslider_settings.transition_speed"700"
data-infiniteslider_settings.infinite_loop"true"
data-pause-hoverslider_settings.pause_on_hover"true"
data-cards-desktopslider_settings.cards_desktop"1"
data-cards-tabletslider_settings.cards_tablet"1"
data-revealanimation.enable_reveal"true"
data-reveal-styleanimation.reveal_style"fade-up"
data-hover-liftanimation.enable_hover_lift"true"

Initialisation Sequence

01

DOM Ready

DOM-ready helper fires; reduced-motion preference is checked via matchMedia.

02

Slider Check

If data-slider = false, skip slider init and proceed to scroll-reveal only.

03

Viewport & Card Width

cardsPerView is computed from viewport width vs desktop/tablet thresholds; each slide width is set to 100% / cardsPerView.

04

Infinite Loop Cloning

First N and last N slides are cloned and prepended/appended for seamless looping when data-infinite = true.

05

Event Listeners

Arrow clicks, dot clicks, keyboard (←/→), touch/swipe, mouse drag, hover pause, and Page Visibility API are all wired up.

06

Autoplay Start

If data-autoplay = true and no reduced-motion preference, an interval fires every data-autoplay-speed ms.

07

Resize Handler

Debounced resize listener recalculates card widths and re-clones for infinite mode on viewport change.

Keyboard Navigation

KeyAction
TabMoves focus between arrows, dots, and CTA buttons
Arrow Left ←Previous slide (when slider is focused)
Arrow Right →Next slide (when slider is focused)
Enter / SpaceActivates focused arrow or dot button
10

CSS Architecture

All CSS is scoped using the unique module ID (#ltp-[unique-id]) as a prefix, ensuring multiple instances on one page cannot conflict.

Component Class Reference

ClassComponentKey CSS Variable(s)
.ltp-sectionSection wrapper--section-bg, --section-padding
.ltp-containerInner container--max-width
.ltp-headerSection header block--header-align
.ltp-cardTestimonial card--card-bg, --card-border, --card-radius, --card-shadow
.ltp-badgeCategory badge pill--badge-bg, --badge-text, --badge-border
.ltp-starsStar rating row--star-color
.ltp-contentQuote body text--text-color, --testimonial-size
.ltp-avatar imgCustomer photoborder-radius: 50% (fixed)
.ltp-ctaCTA button--cta-bg, --cta-text
.ltp-arrowPrev/next slider arrows--arrow-bg, --arrow-icon
.ltp-dot--activeActive pagination dot--dot-active
.ltp-gridStatic grid wrapper--cols-desktop, --cols-tablet, --cols-mobile

Reduced Motion Support

CSS
@media (prefers-reduced-motion: reduce) {
  #ltp-[id] .ltp-card {
    transition: none;
    transform: none;
  }
  #ltp-[id] .ltp-slider-track {
    transition: none;
  }
}
11

Accessibility

FeatureExpected Implementation
role="group"Each testimonial card carries this role
aria-roledescription="testimonial"Applied to each card wrapper for semantic context
aria-label on stars"N out of 5 stars" for screen readers
aria-hidden on SVGsDecorative icons (quote, map-pin, arrows) hidden from AT
role="tablist" on dotsApplied to the .ltp-dots container
aria-selected on dots"true" for active dot, "false" for others; updated by JS
aria-label on arrows"Previous testimonial" / "Next testimonial"
aria-live="polite"Off-screen region announces "Slide X of Y" after navigation
loading="lazy"Applied to all customer images
prefers-reduced-motionCSS media query + JS matchMedia check both present
12

Responsive Behavior

> 1024px
Desktop
Slider shows cards_desktop (1–3) cards. Grid uses grid_columns_desktop (default 3). Arrows visible if enabled. Full configured padding and font sizes.
≤ 1024px
Tablet
Slider shows cards_tablet (1–2) cards. Grid uses grid_columns_tablet (default 2). Padding and font sizes slightly reduced.
≤ 768px
Mobile
Slider hardcoded to 1 card. Grid uses grid_columns_mobile (default 1). Arrows hidden. Reduced padding and font sizes.
≤ 480px
Small Mobile
Grid forced to 1 column. Minimum padding. Smallest font sizes applied.
13

Editor Usage Guide

Adding & Managing Testimonials

  • 1Open the module in the HubSpot page editor and navigate to the Testimonials panel.
  • 2Click Add to create a new testimonial (up to 20 total; minimum 3 required).
  • 3Fill in the Customer Name (required) and Testimonial Content (required) for each item.
  • 4Optionally add a Customer Photo (leave empty for initials placeholder), designation, location, badge, and company info.
  • 5Toggle Show CTA Button per card and fill in the button text and link destination.

Choosing a Display Mode

  • 1Go to the Display Mode panel.
  • 2Toggle Enable Slider/Carousel: ON for slider, OFF for static layout.
  • 3If OFF, choose Grid View or List View, then set column counts per breakpoint.

Configuring Slider Behaviour

  • 1Open Slider Settings (only visible when slider is enabled).
  • 2Set Autoplay Speed - e.g. 6000 = 6 seconds between slides.
  • 3Set Transition Speed - e.g. 700 = 0.7 second slide animation.
  • 4Enable Infinite Loop for seamless wrap-around from last to first.
  • 5Set Cards Per View for desktop (1–3) and tablet (1–2) independently.

Customising Colors

  • 1Click the Style tab in the module editor.
  • 2Open the Colors panel - 21 independent color pickers are available.
  • 3Accent and Heading colors inherit from the site theme by default - override only if needed.
  • 4The Decor Orb color defaults to 8% opacity - keep this low for a subtle effect.
14

Default Configuration

Setting GroupKey Defaults
Section HeaderShow: Yes · Pre-Title: "Client Testimonials" · Heading: "What Our Clients Say"
Display ModeSlider: Enabled · Static: Grid · Desktop cols: 3 · Tablet cols: 2 · Mobile cols: 1
SliderAutoplay: On · Speed: 6000ms · Transition: 700ms · Infinite: On · Pause on hover: On · Cards desktop: 1
AnimationScroll reveal: On · Hover lift: On · Style: Fade Up · Speed: Normal (0.6s)
LayoutHeader align: Center · Card align: Left · Avatar: Bottom · Max width: 1240px
TypographyHeading: Cormorant Garamond · Body: Jost · Text size: Large (1.3rem) · Radius: 20px · Shadow: Soft · Padding: 100px
Default Testimonials3 items: Isabelle Laurent (Dubai) · Sophia Carter (London) · Emma Wilson (New York) - all 5 stars
VisibilityShow company: OFF · Everything else: ON
15

Known Issues & Recommendations

Items marked Potential Bug are possible defects; UX Improvement items are usability suggestions; Verify items require module.html confirmation.

ISSUE 01 Color Opacity May Be Ignored on Decor Orbs Potential Bug Verify

Observation: colors.decor_color has a default opacity of 8%, while all other color fields are 100%. If module.html only reads col.decor_color.color (hex) and ignores the opacity value, orbs render at full intensity, breaking the subtle decorative intent.

Recommended fix: Confirm the template converts opacity to rgba(). Note that HubL has no built-in hex_to_rgb filter - a custom macro is typically required for this conversion.

ISSUE 02 CTA rel Attribute May Be Incomplete When Both Flags Are Set Security Risk Verify

Observation: When both open_in_new_tab = true and no_follow = true, the security-complete rel value should be "nofollow noreferrer noopener". If the template only sets rel="nofollow" without checking the tab flag, the missing noreferrer noopener values create a reverse tabnapping vulnerability.

Recommended fix: Verify module.html builds the complete rel string based on both boolean values.

ISSUE 03 Static Layout Fields Always Visible Even When Slider Is Active UX Improvement

Observation: static_layout, grid_columns_desktop, grid_columns_tablet, and grid_columns_mobile have no visibility condition in fields.json - they always appear in the editor, even when the slider is enabled and these fields have no effect.

Recommended fix: Add a visibility condition to each field: controlling_field: "display_mode.enable_slider", operator: "EQUAL", controlling_value_regex: "false".

ISSUE 04 Arrow/Dot Toggles Visible When Slider Is Disabled UX Improvement

Observation: visibility.show_arrows and visibility.show_dots have help text noting they only apply to slider mode, but no visibility condition hides them when the slider is disabled. This may confuse editors.

Recommended fix: Add visibility conditions linked to display_mode.enable_slider = true.

ISSUE 05 Dots Count Inaccurate When cards_desktop > 1 Potential Bug Verify

Observation: With 2 cards per view and 6 testimonials, there are only 3 unique slide positions - but if dots are HubL-generated via {% for t in module.testimonials %}, 6 dots would be rendered, causing desynchronised dot states.

Recommended fix: Dots should be counted as ceil(total / cards_per_view), or generated by JavaScript after slider initialisation. Verify which approach module.html uses.

ISSUE 06 No cards_mobile Field - Mobile Always Shows 1 Card Enhancement

Observation: The slider has cards_desktop and cards_tablet fields but no cards_mobile equivalent. Mobile viewport is hardcoded to 1 card in JavaScript, with no editor override available.

Recommended fix: Add a slider_settings.cards_mobile choice field (1 card only option for simplicity, or 1–2) with visibility condition matching the other slider fields.

16

Summary

Luxury Testimonial Pro is a sophisticated, feature-complete HubSpot CMS testimonial module built for premium brands requiring highly polished design and rich editorial control.

Strengths

  • 9 field groups, 50+ fields - deep configurability without code changes
  • Three display modes: Slider, Grid, and List
  • 21 independent color controls with HubSpot theme inheritance
  • Scoped CSS - multiple instances on one page never conflict
  • Accessibility-aware: ARIA, keyboard nav, reduced-motion support
  • Google Fonts integration with curated luxury font pairings
  • slider_settings correctly hidden when slider is disabled
  • IntersectionObserver scroll-reveal with three animation styles

Limitations

  • module.html not provided - several details are inferred
  • Static layout fields lack visibility conditions for slider-on state
  • Arrow/dot toggles visible even when slider is off
  • Dots count may not account for cards_desktop > 1
  • Decor orb opacity may not be applied to rgba() conversion
  • CTA rel attribute combination needs security verification
  • No cards_mobile field - slider mobile always shows 1 card

Recommended Next Steps

  • 1Provide module.html for full code-level verification of all items marked Verify.
  • 2Add visibility conditions to static layout fields and arrow/dot toggles.
  • 3Verify opacity handling for colors.decor_color - confirm rgba() conversion.
  • 4Audit CTA rel attribute logic for security completeness when both flags are true.
  • 5Audit dot count generation for multi-card-per-view slider configurations.
  • 6Consider adding a cards_mobile field to slider_settings.