9 Landing Page Sections That Build Trust
Landing page sections like these are the trust-building pieces that usually end up scattered across templates.
Auto-plays · click a tile to jump to its section · all nine in one zip
- 01 Key-metric counter row
- 02 Logo cloud / trusted-by bar
- 03 Feature comparison table
- 04 Team members grid
- 05 Newsletter signup band
- 06 Integration partners grid
- 07 Timeline / roadmap
- 08 Awards / press mentions
- 09 Contact form (split layout)
The order below follows how a visitor's trust actually builds, not a popularity ranking. A metric counter opens with scale, a logo cloud shows who else already uses the product, a comparison table lays out the plan differences, a team grid puts real people behind the brand, a newsletter band asks for a lightweight next step, an integration grid lists what it already connects to, a roadmap shows where the product has been and where it's going, a press/awards section adds third-party proof, and a contact form closes with an actual question. Every avatar, logo, and outlet name here is a shape drawn from this site's own ink, blue, and cream tokens — swapping in real assets later is a straight drop-in that leaves the grid and the JavaScript untouched.
01Key-metric counter row
Three numbers count from zero to their real target with requestAnimationFrame the instant the page loads, each one starting a beat after the last. The trend icon above each number keeps a gentle bob going so the row reads as alive rather than static.
document.querySelectorAll('.stats__n').forEach(function (el, i) {
var target = Number(el.getAttribute('data-target'));
var DUR = 1100, start = null, delay = i * 140;
function frame(ts) {
if (!start) start = ts;
var t = Math.max(0, ts - start - delay);
var p = Math.min(1, t / DUR);
var eased = 1 - Math.pow(1 - p, 3);
el.textContent = Math.round(eased * target).toLocaleString('en-US');
if (p < 1) requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
});
02Logo cloud / trusted-by bar
The logo row scrolls without a seam, and pressing the button flips a real aria-pressed value that actually pauses the scroll and lets it run again. The track is the same six logos pasted twice, so the loop point never shows a jump.
.lc__track {
display: flex;
width: max-content;
animation: lc-scroll 9s linear infinite;
}
.lc__track li {
flex: 0 0 auto;
margin-right: 24px; // margin-right instead of gap
}
@keyframes lc-scroll {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
03Feature comparison table
Clicking a plan name flips aria-pressed and genuinely scales up and highlights that column's check and cross icons. The two columns also alternate every second on their own, so the difference is visible even before anyone clicks.
function select(plan) {
document.querySelectorAll('.fc__plan').forEach(function (b) {
var on = b.getAttribute('data-plan') === plan;
b.classList.toggle('is-on', on);
b.setAttribute('aria-pressed', String(on));
});
document.querySelectorAll('.fc__cell').forEach(function (c) {
c.classList.toggle('is-on', c.getAttribute('data-plan') === plan);
});
}
04Team members grid
Clicking a member card or pressing Enter flips aria-expanded and actually expands a one-line bio through grid-template-rows. The avatar scales and tilts slightly while its card is open, so it's obvious at a glance which member is expanded.
document.querySelectorAll('.tm__head').forEach(function (btn) {
btn.addEventListener('click', function () {
var card = btn.closest('.tm__card');
var open = btn.getAttribute('aria-expanded') === 'true';
btn.setAttribute('aria-expanded', String(!open));
card.classList.toggle('is-open', !open);
});
});
05Newsletter signup band
Typing an email and submitting runs it through a real regular expression, and on a pass the checkmark actually draws itself via stroke-dashoffset while the button relabels to "Subscribed". A bad format shakes the input left and right instead of failing silently.
var RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
form.addEventListener('submit', function (e) {
e.preventDefault();
if (RE.test(input.value)) {
form.classList.add('is-done');
msg.textContent = 'We sent a confirmation to ' + input.value + '.';
} else {
input.classList.add('is-shake');
msg.textContent = 'Please check the email format.';
}
});
06Integration partners grid
Pressing a connect button flips aria-pressed and genuinely slides the dot to the right while the label switches to "Connected". The dot only moves inside its own track, so it never overlaps the label text next to it.
document.querySelectorAll('.ig__toggle').forEach(function (btn) {
btn.addEventListener('click', function () {
var on = btn.getAttribute('aria-pressed') === 'true';
btn.setAttribute('aria-pressed', String(!on));
btn.querySelector('.ig__state').textContent = on ? 'Connect' : 'Connected';
});
});
07Timeline / roadmap
The spotlight advances to the next quarter on its own, and clicking a step or tabbing to it moves the spotlight by a real, measured translateY distance instead of a guessed one. Finished and upcoming quarters use two different icons so the split is obvious without reading the text.
function moveTo(idx) {
i = idx;
var top = steps[idx].getBoundingClientRect().top - list.getBoundingClientRect().top;
spot.style.transform = 'translateY(' + top + 'px)';
steps.forEach(function (s, n) { s.classList.toggle('is-active', n === idx); });
}
08Awards / press mentions
Clicking an outlet name flips aria-expanded and genuinely expands that outlet's quote, one at a time. The outlets also cycle open on their own, so every quote surfaces even if nobody clicks anything.
function openOnly(idx) {
btns.forEach(function (b, n) {
var on = n === idx;
b.setAttribute('aria-expanded', String(on));
b.closest('.ap__badge').classList.toggle('is-open', on);
});
}
09Contact form (split layout)
Filling in name, email, and message and sending it makes the paper-plane icon actually fly off and fade out while the button relabels to "Sent". Only the field that's empty or fails the regex check shakes, not the whole form.
inputs.forEach(function (inp) {
if (!inp.value.trim() || (inp.type === 'email' && !RE.test(inp.value))) {
ok = false;
inp.classList.add('is-shake');
}
});
if (ok) {
form.classList.add('is-sent');
msg.textContent = 'Got it — we reply within one business day.';
}
Where it breaks — the trap
Three real snags came up while building these nine. The first was the logo cloud (02). The track pastes six logos twice and loops with translateX(-50%), and the first pass spaced the items with gap. Twelve items only have eleven gaps between them, so -50% lands half a gap short and the row visibly jumps once per loop. Swapping gap for margin-right on each item removed the seam completely.
The second came from the 320×200 phone check. The team grid (04) and the contact form (09) originally switched to flex-direction: column under the narrow breakpoint, which stacked the panels and made the layout taller, not shorter — the measured height came back over the viewport. Keeping the row layout and shrinking font size and padding instead brought both back under the limit.
The third was the connect toggle in the integration grid (06). The dot originally sat inline with the "Connect" / "Connected" label and moved with transform: translateX alone, so at the "on" position it visually overlapped the first letter of the label. Wrapping the dot in its own small track and positioning the dot inside that track removed the overlap.
Accessibility
All nine, in the exact code you get from the zip once you open it with rghhpe4v, turn off decorative motion under prefers-reduced-motion: reduce while keeping the state itself — the counter row (01) drops the icon bob but keeps counting up since the number is information, and the logo bar (02) stops scrolling while the pause button still works. The comparison table (03) and integration grid (06) carry state through aria-pressed, while the team grid (04) and press mentions (08) carry their expanded state through aria-expanded. The newsletter band (05) and contact form (09) report their result through role="status" so a screen reader announces success or failure right away. The grid-template-rows: 0fr → 1fr reveal follows the standard track-sizing syntax documented on MDN's grid-template-rows page, and prefers-reduced-motion itself follows MDN's reference.
If event and press-room sections are what you're after, the template collection covers those, and the about page has more on who builds this site.
FAQ
Does the logo bar (02) actually scroll, or is it a static image?
It actually scrolls — a translateX(-50%) CSS animation loops it, and the logo list is pasted exactly one extra time so the loop point never shows a seam. Pressing the pause button flips animation-play-state to paused for real.
Does the React version behave the same as the CSS version?
Yes. Every piece of state — expanded, connected, highlighted — lives in useState, and the three variables (duration, easing, color) come in as props and get passed straight through as CSS variables. The roadmap's (07) auto-advance and the contact form's (09) auto-focus cycle both run the same logic inside useEffect.
Does the contact form (09) actually send an email anywhere?
No — the demo itself only runs the format check and shows the sent state; there's no real delivery server behind it. Intercepting the form's submit event and validating with a regular expression is as far as this template goes, and wiring in a real API call at that point lets you keep the rest of the validation and display logic as-is.