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:

index.html
<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:

  1. Click New insight. Set series A to $click under Autocaptured (or to Demo booking started), counted as Unique people.
  2. For $click, click Where and add Clicked text contains book a demo. contains ignores case and any arrow or icon text.
  3. Click Add breakdown › Event property › Page, Top 10. For the custom event, break down by Placement instead.
  4. Switch the chart to Bar to rank pages over the whole range. Save it as Book a demo clicks.

Clicks to bookings, in Funnels:

  1. Click New funnel. Step 1: $click, then + Filter › Clicked text contains book a demo.
  2. Step 2: $pageview, + Filter › Page is /demo/booked.
  3. Set Conversion window to 7 days and save it as Demo clicks to bookings.
  4. 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

faq.html
<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:

  1. In Insights, set series A to FAQ opened, counted as Total events (every open) or Unique people (who opened it).
  2. Click Add breakdown › Event property › Question, Top 25.
  3. Switch the chart to Bar or Table. Save it as FAQ opens by question.

Questions opened before booking:

  1. In Funnels, build FAQ opened → $pageview where Page is /demo/booked, with a 7 days window.
  2. 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

pricing.html
<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

  1. In Insights, chart Plan selected as Unique people. Add breakdown › Event property › Plan, and switch to Bar.
  2. Change the breakdown to Period to see how many choose annual.
  3. For the toggle, chart Billing period switched broken down by Period.
  4. For plan to sign-up, build a funnel Plan selected → Signed up and choose Break down by › Plan.

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.

links.html
<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.

  1. In Insights, chart Outbound link clicked as Total events, break it down by Domain, Top 10, as a Bar chart.
  2. Chart Contact link clicked as Unique people, broken down by Type, as a Line by Weekly interval.
  3. 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.html
<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

  1. In Insights, set series A to Video played, Unique people, Where Video is product-tour.
  2. Click Add series. Set series B to Video finished with the same filter.
  3. Click Add formula, keep B / A * 100 and name it Watched to the end.
  4. 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:

sections.html
<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

  1. In Insights, set series A to $pageview, Unique people, Where Page is /pricing.
  2. Set series B to Section viewed, Unique people, Where Section is plans.
  3. Add the formula B / A * 100, named Reached the plans.
  4. To compare every section, chart Section viewed alone, 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:

index.html
<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

  1. In Insights, chart $click as Unique people, Where Clicked element ID is any of cta-hero, cta-footer.
  2. Add breakdown › Event property › Clicked element ID, and switch to Bar.
  3. 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:

checkout.js
applyCoupon(code).catch(function (error) {
  window.clickclacks?.('event', 'Coupon failed', { reason: error.code || 'unknown' })
  showCouponError(error)
})

Read the answer

  1. Open Friction on Happening. Rows name the element and the page, for example People are rage-clicking “Apply coupon” on /checkout.
  2. For one page, open it in Heatmaps, choose Elements and sort by rage clicks.
  3. For the custom event, chart Coupon failed in 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

404.html
<!-- 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

  1. In Insights, chart Page not found as Total events.
  2. Add breakdown › Event property › Page, Top 25, and switch to Table.
  3. Change the breakdown to From to find the broken links: a path is a page on your site, a host is another site.