⚠️ Issue — Accordion B content is invisible to Googlebot

Two accordions. One correct, one broken. Accordion A hides its content with CSS — the text is always in the HTML source. Accordion B's content doesn't exist until you click — it's injected by JavaScript on demand. Open view-source (Ctrl+U / Cmd+U) and search for text from each accordion to see the difference.

What this demonstrates

Two accordions sit side by side on this page. They look identical in the browser. They behave similarly when you click them. But they are completely different in how their content exists in the HTML — and that difference is what determines whether Google can index the content inside them.

Accordion A uses the correct pattern: the content is present in the raw HTML, and JavaScript simply toggles its visibility. The text has always been there — the click just reveals it.

Accordion B uses the problematic pattern: the content doesn't exist in the HTML at all. Clicking the button triggers a JavaScript function that injects the text into an empty <div>. Before anyone clicks, that div is empty — and that's exactly what Googlebot sees.

Live demo

Click both accordions to expand them. Then open view-source and search for text from each — you'll find Accordion A's text in the source, and Accordion B's text will not be there at all.

✅ Accordion A — Content in HTML source
⚠️ Accordion B — Content loaded by JavaScript

Why it matters

A common assumption is that "hiding content behind a click hides it from Google." That's wrong. The opposite mistake is equally common: developers assume that if content lives in a collapsed accordion, Google can always index it. Both assumptions are sometimes right — depending on how the accordion is actually built.

The distinction that matters to Google is not whether content is visible on screen. It's whether the content nodes exist in the HTML source. A collapsed accordion built correctly — Accordion A's pattern — is fully indexable. The content is there, it's just not shown. A demand-loaded accordion — Accordion B's pattern — is not indexable, because the content doesn't exist until a user clicks, and Googlebot does not simulate user interactions.

This pattern is widespread in CMS-built sites and page builders. FAQs, product specifications, pricing tables, and feature descriptions are commonly built as accordions that inject content on demand. The developers are often focused on performance — why load content users may never see? The SEO implication is missed entirely until an audit turns up pages with unexpectedly thin content counts despite appearing content-rich in the browser.

The code

The two patterns look almost identical from the outside. The difference is whether text nodes exist in the HTML before the button is clicked.

Accordion A — content exists in HTML source (correct):

<!-- Content EXISTS in HTML. JS only toggles visibility. --> <button id="accordion-a-toggle" aria-expanded="false">What is CSS-toggled content?</button> <div id="accordion-a-content" hidden> <p>This content is present in the raw HTML source...</p> </div> <!-- JS only removes/adds the hidden attribute on click --> <script> btn.addEventListener('click', function () { var expanded = btn.getAttribute('aria-expanded') === 'true'; btn.setAttribute('aria-expanded', expanded ? 'false' : 'true'); content.hidden = expanded; // toggles — content always existed }); </script>

Accordion B — content does NOT exist in HTML; injected on click (issue):

<!-- Content does NOT exist in HTML. JS builds it on click. --> <button id="accordion-b-toggle">What is JS-injected content?</button> <div id="accordion-b-content"> <!-- Empty until clicked --> </div> <!-- JS builds and injects the content on first click --> <script> btn.addEventListener('click', function () { // Content built here — not in HTML source div.innerHTML = '<p>This content was injected by JavaScript...</p>'; div.hidden = false; }); </script>

What Google does

  1. Wave 1 — raw HTML fetch: Googlebot fetches the page. Accordion A's content nodes are in the DOM — the text is readable in full. Accordion B's <div> is empty — there is nothing to read.
  2. Wave 2 — JavaScript rendering: Googlebot's headless Chromium renderer executes JavaScript. The accordion toggle functions are registered. But Googlebot does not simulate user interactions — no click event fires on Accordion B's button. The div remains empty.
  3. Indexation outcome: Accordion A's content is available for indexing. Accordion B's content is not — it has never existed in a form Google can read, because it only comes into existence when a human (or a tool that simulates clicks) physically interacts with the page.

This is why "it looks fine in the browser" is an unreliable SEO signal. The browser renders the full interactive experience — Google does not.

How to detect it

  • view-source Open view-source (Ctrl+U / Cmd+U) and search for a unique phrase from each accordion. Accordion A's text will be present in the source. Accordion B's text will not appear anywhere — because it doesn't exist in the HTML yet. view-source always shows the original delivered HTML, not the DOM after JavaScript has run.
  • Browser DevTools — Elements panel Inspect #accordion-b-content before clicking. The div is empty. After clicking, inspect again — the content has been injected. This confirms the JS-injection pattern. Note that view-source will remain empty regardless of how many times you click — it reflects the original HTML, not the live DOM.
  • curl Open Command Prompt (Windows) or Terminal (Mac) and run: curl -L https://sallymills.com/seo-javascript-issues/interaction-gated-content/ | grep "accordion-alpha" → Returns the unique phrase from Accordion A's content. Run the equivalent command for any phrase from Accordion B's injected content — it returns nothing, because the content isn't in the HTML. (Windows: replace | grep "accordion-alpha" with | findstr "accordion-alpha".)
  • Screaming Frog Crawl the page with JS rendering disabled. The word count reflects Accordion A's words but not Accordion B's. Enabling JS rendering increases the word count as scripts execute — but Screaming Frog does not simulate button clicks, so Accordion B's content typically remains undetected even with JS rendering enabled. This is the same behaviour as Googlebot.
  • Google Search Console Pages that rely on interaction-gated content for their primary body copy may appear as "Crawled — currently not indexed" or be flagged for thin content in the Indexing report — even though they look complete in a browser.

How to fix it

Include all content in the HTML and use CSS or the hidden attribute — toggled by JavaScript — only to control visibility. The content nodes themselves must be present in the source. The click should reveal content, not create it.

If content is being loaded on demand for performance reasons (reducing initial payload), consider server-side rendering it into the HTML with a CSS class applied to collapse it visually. The content exists in the source from first load; the visual collapsed state is handled by CSS. This is the Accordion A pattern, and it is fully indexable.

If the content is genuinely dynamic — user-specific, real-time, or dependent on an API call — it is unlikely to be indexable by nature. But that should be an explicit design decision, not an accidental side effect of how a page builder or CMS constructs its components.