Marketing site examples
The questions people ask about acme.com: who clicks Book a demo, which FAQs get read, how far people scroll. For each one: what autocapture already answers, the custom event that answers it exactly, and where to read it in the app.
Updated
Every snippet assumes the script tag is on the page. Put it, and the queue stub, in the <head>. Browser custom events carry the page automatically, so every one can be filtered and broken down by Page. The data-… attributes in the HTML are for your own script to read. ClickClacks doesn’t read them.
Who clicks “Book a demo”, on which page, and do they book?
Fastest: autocapture
If “Book a demo” is an <a> or a <button>, every click already arrives as $click with its label as Clicked text (up to 40 characters) and the page as Page. Nothing to add.
Precise: a custom event
Labels change when someone rewrites the copy, and two buttons with the same label look alike. A custom event with a placement property survives both:
<a href="/demo" class="book-demo" data-placement="hero">Book a demo</a>
<!-- … further down the page … -->
<a href="/demo" class="book-demo" data-placement="footer">Book a demo</a>
<script>
document.querySelectorAll('.book-demo').forEach(function (button) {
button.addEventListener('click', function () {
window.clickclacks?.('event', 'Demo booking started', {
placement: button.dataset.placement,
})
})
})
</script> For the booking itself, the simplest signal is a page. Most scheduling tools can send people to a page on your site after they book, such as /demo/booked, and its $pageview is your “booked” event. Scheduling widgets that open in an iframe are a separate page, so clicks inside them never reach your tracker.
Read the answer
Clicks by page, in Insights:
- Click New insight. Set series A to
$clickunder Autocaptured (or toDemo booking started), counted as Unique people. - For
$click, click Where and add Clicked text containsbook a demo. contains ignores case and any arrow or icon text. - Click Add breakdown › Event property › Page, Top 10. For the custom event, break down by Placement instead.
- Switch the chart to Bar to rank pages over the whole range. Save it as Book a demo clicks.
Clicks to bookings, in Funnels:
- Click New funnel. Step 1:
$click, then + Filter › Clicked text containsbook a demo. - Step 2:
$pageview, + Filter › Page is/demo/booked. - Set Conversion window to 7 days and save it as Demo clicks to bookings.
- Choose Break down by › Page. A funnel breakdown reads step 1, so this is the page the button was clicked on.
Which FAQs do people open, and do they book afterwards?
Fastest: autocapture, if the questions are buttons
Accordions built from <button> elements send each question as Clicked text. The limits: only the first 40 characters are kept, and a click that closes a question counts the same as one that opens it.
The common <details> and <summary> pattern doesn’t work this way. A <summary> is neither a link nor a button, so its clicks carry no text, and every one has the same selector, summary:nth-of-type(1). Use the custom event.
Precise: a custom event with the question
<details class="faq">
<summary>Can I import contacts from HubSpot?</summary>
<p>Yes. Open Contacts › Import and choose HubSpot.</p>
</details>
<script>
document.querySelectorAll('details.faq').forEach(function (faq) {
faq.addEventListener('toggle', function () {
if (!faq.open) return // count opens, not closes
window.clickclacks?.('event', 'FAQ opened', {
question: faq.querySelector('summary').textContent.trim(),
})
})
})
</script>Read the answer
Most-opened questions:
- In Insights, set series A to
FAQ opened, counted as Total events (every open) or Unique people (who opened it). - Click Add breakdown › Event property › Question, Top 25.
- Switch the chart to Bar or Table. Save it as FAQ opens by question.
Questions opened before booking:
- In Funnels, build
FAQ opened→$pageviewwhere Page is/demo/booked, with a 7 days window. - Choose Break down by › Question.
Pricing: who switches to annual, and which plan do they pick?
Fastest: autocapture
A toggle made of two buttons reading Monthly and Annual sends those words as Clicked text. A radio-input toggle doesn’t: clicks on form fields never carry text. Plan buttons usually all say Start free trial, so autocapture can only tell them apart by an id on each (Clicked element ID).
Precise: custom events for the toggle and the plan
<fieldset class="billing-period">
<label><input type="radio" name="billing-period" value="monthly" checked> Monthly</label>
<label><input type="radio" name="billing-period" value="annual"> Annual</label>
</fieldset>
<a href="/signup?plan=starter" class="plan-cta" data-plan="starter">Start free trial</a>
<a href="/signup?plan=team" class="plan-cta" data-plan="team">Start free trial</a>
<script>
function billingPeriod() {
return document.querySelector('[name="billing-period"]:checked').value
}
document.querySelectorAll('[name="billing-period"]').forEach(function (input) {
input.addEventListener('change', function () {
window.clickclacks?.('event', 'Billing period switched', { period: input.value })
})
})
document.querySelectorAll('.plan-cta').forEach(function (button) {
button.addEventListener('click', function () {
window.clickclacks?.('event', 'Plan selected', {
plan: button.dataset.plan,
period: billingPeriod(),
})
})
})
</script>Read the answer
- In Insights, chart
Plan selectedas Unique people. Add breakdown › Event property › Plan, and switch to Bar. - Change the breakdown to Period to see how many choose annual.
- For the toggle, chart
Billing period switchedbroken down by Period. - For plan to sign-up, build a funnel
Plan selected→Signed upand choose Break down by › Plan.
Where do outbound, phone and email links go?
Fastest: autocapture, partly
Every link click is a $click with Clicked element a and its Clicked text. The tracker doesn’t record where a link points, though, so autocapture can’t tell an outbound link, a tel: link or a mailto: link from any other. If your phone link shows the number, its text is the number, and that’s as far as autocapture goes.
Precise: one listener for every link
<script>
document.addEventListener('click', function (event) {
var link = event.target.closest && event.target.closest('a[href]')
if (!link) return
var url = new URL(link.href, location.href)
if (url.protocol === 'tel:') {
window.clickclacks?.('event', 'Contact link clicked', { type: 'phone' })
} else if (url.protocol === 'mailto:') {
window.clickclacks?.('event', 'Contact link clicked', { type: 'email' })
} else if (/^https?:$/.test(url.protocol) && url.hostname !== location.hostname) {
// The domain only: a full URL can carry tokens or personal data.
window.clickclacks?.('event', 'Outbound link clicked', { domain: url.hostname })
}
})
</script> The event carries only the type or the destination’s domain, never the number, the address or a full URL. Links into your own other domains, such as app.acme.com, count as outbound here, and their domain says so.
Read the answer
- In Insights, chart
Outbound link clickedas Total events, break it down by Domain, Top 10, as a Bar chart. - Chart
Contact link clickedas Unique people, broken down by Type, as a Line by Weekly interval. - To see which pages people call or email from, change the breakdown to Page.
How many people play the product video, and how many watch to the end?
Fastest: autocapture can’t answer this
Pressing play on a <video> isn’t a link or button click, and embedded players (YouTube, Vimeo, Wistia) run in an iframe, whose clicks never reach your page. Nothing tells ClickClacks that a video played or ended.
Precise: two custom events
<video src="/media/product-tour.mp4" data-video="product-tour" controls></video>
<script>
document.querySelectorAll('video[data-video]').forEach(function (video) {
var name = video.dataset.video
var played = false
video.addEventListener('play', function () {
if (played) return // the first play only, not every resume
played = true
window.clickclacks?.('event', 'Video played', { video: name })
})
video.addEventListener('ended', function () {
window.clickclacks?.('event', 'Video finished', { video: name })
})
})
</script>For an embedded player, send the same two events from the player’s own JavaScript API when it reports playing and ended.
Read the answer
- In Insights, set series A to
Video played, Unique people, Where Video isproduct-tour. - Click Add series. Set series B to
Video finishedwith the same filter. - Click Add formula, keep
B / A * 100and name it Watched to the end. - Use Big number for the whole range, or Line by week to follow it.
How far do people scroll, and do they reach the plans?
Fastest: the heatmap’s scroll depth
Autocapture sends one $scroll per page view, when the visitor leaves the page, with the lowest point they saw in pixels. Heatmaps turn that into answers with no setup: open the page, pick a device and choose Scroll depth. You get the share of people who reach 25, 50, 75 and 100% of the page, with the median fold marked.
Precise: one event per section
Percentages move whenever the page gets longer, and pixels differ per device. To know whether people reached a particular section, send an event when it comes into view:
<section data-section="plans">…</section>
<section data-section="faq">…</section>
<script>
var seen = {}
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
var section = entry.target.dataset.section
if (!entry.isIntersecting || seen[section]) return
seen[section] = true // once per page load
window.clickclacks?.('event', 'Section viewed', { section: section })
})
}, { rootMargin: '0px 0px -50% 0px' }) // its top has reached the middle of the screen
document.querySelectorAll('[data-section]').forEach(function (el) {
observer.observe(el)
})
</script>Read the answer
- In Insights, set series A to
$pageview, Unique people, Where Page is/pricing. - Set series B to
Section viewed, Unique people, Where Section isplans. - Add the formula
B / A * 100, named Reached the plans. - To compare every section, chart
Section viewedalone, Where Page is/pricing, broken down by Section.
Which CTA placement gets more clicks, the hero or the footer?
Fastest: autocapture, with an id on each
Two buttons with the same label look the same in Clicked text. Give each one an id and autocapture tells them apart as Clicked element ID. It’s a change to your HTML, not your JavaScript:
<a href="/signup" id="cta-hero">Start free trial</a>
<!-- … -->
<a href="/signup" id="cta-footer">Start free trial</a> Or use the placement property from the Book a demo event above, which reads the same way.
Read the answer
- In Insights, chart
$clickas Unique people, Where Clicked element ID is any ofcta-hero,cta-footer. - Add breakdown › Event property › Clicked element ID, and switch to Bar.
- In Heatmaps, open the same page’s Scroll depth to see what share of people ever reach the footer.
Is a button broken? Finding rage clicks
Fastest: nothing to add
Rage clicks come from autocapture. Friction lists an element once people click it 5 or more times within 10 seconds, and at least 25 different people have done so in the last 7 days. For a single page, the Elements view in Heatmaps shows every clicked element with its dead clicks and rage clicks, however few.
Precise: say when the action fails
When the element runs your own code, send an event when it fails. Now you count the failures themselves, not just the frustration they cause:
applyCoupon(code).catch(function (error) {
window.clickclacks?.('event', 'Coupon failed', { reason: error.code || 'unknown' })
showCouponError(error)
})Read the answer
- Open Friction on Happening. Rows name the element and the page, for example People are rage-clicking “Apply coupon” on /checkout.
- For one page, open it in Heatmaps, choose Elements and sort by rage clicks.
- For the custom event, chart
Coupon failedin Insights, broken down by Reason.
Which 404 pages do people hit, and from where?
Fastest: autocapture can’t tell
A missing page still sends a normal $pageview for the address that was asked for. The tracker doesn’t see the HTTP status or the page title, so nothing marks it as a 404.
Precise: a custom event on the 404 template
<!-- On your 404 template only -->
<script>
var from = 'direct'
try {
var ref = new URL(document.referrer)
// A broken link on your own site: its page. Anywhere else: the site's host.
from = ref.host === location.host ? ref.pathname : ref.host
} catch (e) {}
window.clickclacks?.('event', 'Page not found', { from: from })
</script>In a single-page app, send the same event from your not-found route.
Read the answer
- In Insights, chart
Page not foundas Total events. - Add breakdown › Event property › Page, Top 25, and switch to Table.
- Change the breakdown to From to find the broken links: a path is a page on your site, a host is another site.