Product Comparison Table — Architecture & Usage Guide
Overview
Section titled “Overview”Www::ProductCompareTableComponent renders a responsive, scrollable product comparison table
used on all product controls / thermostat pages. Each product appears as a column; feature
rows run horizontally with a sticky label column on the left.
The component is driven entirely by a static SKU_FEATURES hash — no additional database
queries are needed beyond the standard product presenter load. This keeps it fast and easy
to maintain.
Used on:
floor-heating/thermostatsradiant-heat-panels/controlssnow-melting/controlstowel-warmer/controlspipe-freeze-protection/controlsroof-and-gutter-deicing/controls
| File | Purpose |
|---|---|
app/components/www/product_compare_table_component.rb | Ruby component: data loading, feature lookup, helpers |
app/components/www/product_compare_table_component.html.erb | ERB template: 5-sub-row header + feature rows |
client/stylesheets/www/03-components/product_compare_table_component.scss | Scoped SCSS: sticky column, cell alignment, responsive breakpoints |
Constructor Parameters
Section titled “Constructor Parameters”Www::ProductCompareTableComponent.new( skus: Array, # required — ordered list of product SKUs features: Array, # optional — feature keys to show; auto-detected if nil section_title: String, # optional — H2 above the table section_description: String, # optional — paragraph below the title sort_by_price: Boolean # default: true — set false to preserve SKU array order)sort_by_price
Section titled “sort_by_price”By default products are sorted ascending by effective_price. Pass sort_by_price: false
when the SKU array encodes a meaningful order (e.g. thermostat tier: WiFi → Programmable →
Non-programmable → Power module). All other callers retain the default true.
Template Structure
Section titled “Template Structure”The header is split into 4 dedicated sub-rows so that varying title / tagline lengths never push sibling product columns out of vertical alignment:
┌────────────────────────────────────────────────────────┐│ IMAGE ROW │ [img] │ [img] │ [img] │ [img] │ align-items: end│ TITLE ROW │ Title │ Title │ Title │ Title │ align-items: stretch│ TAGLINE ROW* │ tag │ tag │ tag │ tag │ align-items: stretch│ SKU+PRICE ROW │ [sku] │ [sku] │ [sku] │ [sku] │ align-items: stretch│ "Features" ↙ │ $249 │ $259 │ $159 │ $149 │├────────────────┼───────┼───────┼───────┼───────────────┤│ WiFi-Enabled │ ✓ │ ✗ │ ✗ │ ✗ │ feature rows│ Programmable │ ✓ │ ✓ │ ✗ │ — ││ … │ │ │ │ │├────────────────┼───────┼───────┼───────┼───────────────┤│ │[View] │[View] │[View] │ [View] │ CTA row└────────────────┴───────┴───────┴───────┴───────────────┘* Tagline row is omitted when no product has a short_description.
Feature Value Types
Section titled “Feature Value Types”Each entry in SKU_FEATURES[sku][feature_key] can be:
| Value | Renders as |
|---|---|
true | Green check-circle icon (with tooltip popover if tip defined) |
false | Muted × icon |
String | Small text badge (e.g. "Up to 10 schedules", "LED touch") |
:aerial / :slab | Yellow “Pair with…” badge linking to the sensors section |
nil / absent | ”N/A” in muted text |
Sensor features (moisture_sensor, air_temp_sensor, slab_temp_sensor) additionally
render true as a green “Included” pill badge instead of a plain checkmark.
Adding a New SKU
Section titled “Adding a New SKU”- Add the SKU to
SKU_FEATURESinproduct_compare_table_component.rb:
'MY-SKU' => { 'wifi' => true, 'remote_support' => false, 'voice_control' => true, 'programmable' => true, 'schedules' => 'Up to 6 per day', 'floor_sensor_included' => true, 'display' => '3.5" touchscreen', 'max_load' => '15A @ 120/240V'},- Include the SKU in the page’s SKU array:
tstat_skus = %w[UWG5-4999-WY MY-SKU UTN5-4999]- Pass the array to the component. If the SKU has no entry in
SKU_FEATURES, all its feature cells fall back tofalse(× icon).
Adding a New Feature Row
Section titled “Adding a New Feature Row”- Add a tooltip in
FEATURE_TOOLTIPS:
'my_feature' => 'Plain-English explanation shown on hover/focus.'- Add a display name in
feature_display_name:
when 'my_feature' then 'My Feature Label'-
Add the value for every relevant SKU in
SKU_FEATURES. -
Include
'my_feature'in thefeatures:array on the calling page.
Alignment Architecture
Section titled “Alignment Architecture”The SCSS (.product-compare-table) defines two complementary rules:
.feature-cell { display: flex; align-items: center; justify-content: center; // product value cells: horizontally centred text-align: center;}
.sticky-column { justify-content: flex-start; // overrides feature-cell — label column always left text-align: left;}Without the .sticky-column override, short single-line labels (e.g. “WiFi-Enabled”)
would appear visually centred while long wrapping labels (e.g. “Remote Troubleshooting /
Updates”) appeared left — an inconsistency caused by flex centering a short inline text node.
Mobile / Responsive Behaviour
Section titled “Mobile / Responsive Behaviour”- The
table-scroll-containerusesoverflow-x: auto— the table scrolls horizontally on narrow viewports rather than collapsing columns. - A dynamic
min-widthinline style (200 + products.size × 160 px) is applied to the inner wrapper to prevent columns from becoming unreadably narrow before the scroll kicks in. - The sticky label column (
position: sticky; left: 0) remains visible as the user scrolls right, keeping feature labels always in view. min-widthof the sticky column scales down at tablet / compact-laptop breakpoints:240px→200px→180px→160px.
Floor-Heating Thermostats Page
Section titled “Floor-Heating Thermostats Page”/floor-heating/thermostats
Section Order
Section titled “Section Order”| # | Section ID | Component / Content |
|---|---|---|
| 1 | hero | Www::FullWidthLandingPageHeaderComponent |
| 2 | benefits | Www::BenefitsListComponent — 4 universal thermostat guarantees |
| 3 | comparison | Www::ProductCompareTableComponent — thermostats + nJoin module |
| 4 | accessories | Www::ProductCardsComponent — sensor + 3 rough-in kits |
| 5 | guide | ”Which thermostat is right for you?” — 3 editorial cards |
| 6 | shop-by-system | Internal cross-reference links (floor type / room / system) |
| 7 | showcases | Www::ShowcaseGridComponent — conditional on CMS content |
| 8 | videos | Www::VideoSectionComponent — conditional on CMS content |
| 9 | documents | Www::CardGridComponent — conditional on CMS content |
| 10 | posts | Www::CardGridComponent — conditional on CMS content |
| 11 | faq | Www::FaqListComponent — conditional on CMS content |
| 12 | contact | lazy_lead_form_modal |
SKU Configuration
Section titled “SKU Configuration”# US markettstat_skus = %w[UWG5-4999-WY UDG4-4999 UTN5-4999 USG5-4000]accessory_skus = %w[FH-BACKUP-SENSOR FHE-ROUGH-IN-KIT-S1 FHE-ROUGH-IN-KIT-S2 FHE-ROUGH-IN-KIT-S3]
# Canadatstat_skus = %w[UWG5-4999-WY UDG4-4999-WY UTN5-4999 USG-4000]accessory_skus = %w[FH-BACKUP-SENSOR FHE-ROUGH-IN-KIT-S1 FHE-ROUGH-IN-KIT-S2 FHE-ROUGH-IN-KIT-S3]The nJoin power module (USG5-4000 / USG-4000) appears last in the comparison table
(sort_by_price: false preserves insertion order) because it is not a thermostat — it
extends a paired thermostat’s capacity for high-load zones.
Cross-Reference Section (SEO / Anti-Cannibalization)
Section titled “Cross-Reference Section (SEO / Anti-Cannibalization)”The #shop-by-system section provides 24 internal links grouped by:
- By Floor Type (8 links) — tile, LVT, engineered wood, laminate, bamboo, nailed hardwood, concrete, carpet
- By Room (8 links) — bathroom, kitchen, bedroom, living room, basement, shower, laundry/mudroom, sunroom
- By System Type (5 links) — mats, cable, custom mats, Prodeso, underlayment
This signals to Google that /floor-heating/thermostats is about controls and the
linked pages are the authoritative source for each floor type / room — preventing keyword
overlap and reinforcing the topical hierarchy under /floor-heating.
The pattern mirrors towel-warmer/controls → /towel-warmer/* cross-references.
Tagline (Short Description)
Section titled “Tagline (Short Description)”The component reads item.short_description from the product database and surfaces it as a
muted subtitle beneath each product title in the comparison header:
tagline: presenter.item&.short_description.presenceThe tagline row is omitted entirely when no product in the comparison has a short description, keeping the header clean for legacy SKUs.