# Iframes, shadow DOM and embedded widgets for AI agents

> What AI agents miss inside booking engines, review widgets and other iframes or shadow DOM, and the fallbacks that keep key facts within their reach.

By Ghost Agent Lab · Navigability · https://ghostagentlab.com/articles/iframes-shadow-dom-widgets-ai-agents/

## Key takeaways

- Booking engines, review widgets, chat and size finders often sit in iframes or shadow roots that agents reading your HTML can't see into.
- Browser agents can see embedded widgets on screen, but frames and shadow roots still get in the way of agents that work from the page's structure.
- Payment fields belong in the provider's frame; make everything before payment reachable instead.
- The fix is a fallback, not removal: key facts in your own HTML and a real link to a full page for each widget.
- This week, list the widgets on your top product or booking page and check that the rating, price and main button also appear in the page source.

Booking engines, review widgets, chat, payment fields and size finders are often built by another company and dropped into your page inside an iframe or a shadow root. People barely notice the seam. AI agents often do. Here's what agents miss inside embedded widgets, what browser agents can and can't do across frames, and how to give them a way around.

## Where widgets hide content

Most stores and booking sites carry several third-party widgets. Each is a small application of its own, loaded from the vendor's servers and placed in your page in one of two ways:

- **An iframe** is a separate web page shown inside a frame on yours. Its content lives at another URL, often on another domain, and loads after your page does.
- **A shadow root** (part of web components) is a walled-off piece of your own page. A script attaches it to an element and fills it, and the page's own styles and scripts can't easily reach inside. MDN notes that in "closed" mode, the page's own scripts don't even get a handle to reach inside.

Both are good engineering. They stop your styles breaking the widget and the widget breaking your page, and an iframe keeps card details away from your site entirely. The trouble is that they also keep the content away from anything reading your page.

**Widgets that often live in a frame or shadow root**

- ! **Booking engine:** Rooms, tables, appointments and their prices
- ! **Reviews:** Star ratings, counts and review text
- ! **Size finder:** The question that picks the right variant
- **Chat:** Help and answers, behind a launcher button
- **Payment fields:** Card number, expiry and security code

**Easy to embed, hard to read.** Each of these can hold facts or steps an agent needs, inside a boundary built to keep things out.

## What agents that read HTML miss

Many AI agents never render your page. AI search crawlers and assistants such as ChatGPT, Claude or Perplexity fetching a page for someone often read the HTML your server sends and stop there. To them:

- **An iframe is a tag and a URL.** `<iframe src="https://book.vendor.example/…">` says there's something there, not what. The agent would have to fetch that URL separately, and the page there is usually an empty shell that a script fills.
- **A shadow root built by script isn't there at all.** The served HTML holds only the empty host element, such as `<review-widget></review-widget>`. The reviews appear only after the vendor's script runs.
- **Declarative shadow DOM is in the HTML, but wrapped.** A widget can ship its shadow content in a `<template shadowrootmode="open">` element. Browsers that support it display it, but a tool that reads the HTML as text may treat a template as hidden and skip it.

The result is a page that looks complete to a person and has holes in it for an agent: a hotel page with no rates, a product page with no rating, a service page with no way to see available times. This is the same problem as any content that needs JavaScript; see [why AI agents can't see your JavaScript-only content](https://ghostagentlab.com/articles/javascript-content-ai-agents/).

`harborinn.example/rooms/sea-view (as an HTML reader sees it)`

- **Sea View Double:** King bed, balcony, breakfast available
- \<iframe src="book.vendor.example/…"> (✗ No rates or dates)
- \<review-widget>\</review-widget> (✗ No rating)

**Holes where the widgets were.** The room description is there; the price, availability and reviews are somewhere the agent can't see.

## What browser agents can and can't do

Browser agents load your page in a real browser, so the widgets run and appear on screen. That helps, but frames and shadow roots still get in the way, depending on how the agent reads the page:

- **Agents that work from screenshots** see the widget as a person does and can click on it. They struggle with the same things people do, only more: a small embedded calendar inside a scrolling frame, or a launcher icon with no words.
- **Agents that work from the page's structure** (the DOM or the accessibility tree) have to handle each frame as a separate document and step inside shadow roots. Some agent tools do this well; others see only the top page. You can't control which one a shopper uses.
- **Cross-origin frames are walled off by design.** Under the browser's same-origin policy, scripts from one origin can't read most of a frame from another. Your own page can't inspect a vendor's booking frame, and code running on your page on an agent's behalf can't either. The two sides can only talk through messages they choose to send.

Payment fields are the special case. Card fields sit in the payment provider's frame so that card numbers never touch your site, and that's exactly right. Many agents stop at payment and hand back to the person, or pay through a wallet or protocol built for it. See [payments for AI agents](https://ghostagentlab.com/articles/agentic-payments-ai-agents/). Don't try to make card fields more reachable; make sure everything before them is.

> **Unnamed launchers are the most common trap.** A chat or size-finder widget often starts as a round icon button. If that button has no text or label, an agent has no idea what it does, whatever is behind it. In [our study of 255 stores](https://ghostagentlab.com/blog/agentscore-store-study-2026/), 58% had buttons or links with no name an agent can read.

## Give agents a way around

You don't need to remove widgets. You need to make sure nothing important exists only inside one.

1. **Put the key facts in your own HTML.** The average rating and review count, a starting price ("Rooms from $180 a night"), the opening hours or service list. Render them on the server alongside the widget, and add them to your structured data. See [reviews and ratings AI agents can read](https://ghostagentlab.com/articles/reviews-ratings-ai-agents/).
2. **Link to a full page.** Give the booking engine, size guide or reviews a page of their own at a stable URL, and link to it with a real link: "See all 212 reviews", "Check dates and book". An agent that can't work the embedded version can open the full one. Ask your vendor whether they offer a hosted page as well as the embed.
3. **Name the frame and the launcher.** MDN explains that an iframe's `title` labels its content for assistive technology, so give every iframe one that says what's inside ("Room booking calendar"). Give launcher buttons visible text or an `aria-label` ("Open size finder").
4. **Keep the main action outside the widget.** Your add-to-cart, booking or sign-up button should be a real button in your own page. If a widget must handle it, make sure there's a plain link to the same step.
5. **Don't open widgets over the page on arrival.** A chat panel or survey that pops open covers the content and the buttons. See [cookie banners and pop-ups that trap AI agents](https://ghostagentlab.com/articles/cookie-banners-popups-ai-agents/).

**Widget only:**

- ✗ Rating exists only in the review widget
- ✗ Rates exist only in the booking frame
- ✗ Size advice behind an unnamed icon

**Widget plus fallback:**

- ✓ "4.7 out of 5 from 212 reviews" in the HTML, with a link to all reviews
- ✓ "From $180 a night" on the page, with a link to the full booking page
- ✓ "Open size finder" button, plus a size guide page

**Keep the widget, add the facts.** The embedded version stays for people; the plain version is what agents can read and follow.

When you're choosing or renewing a vendor, ask: Is there a hosted page for this, with its own URL? Can the key numbers be rendered server-side or exported to our structured data? Are controls inside the widget labeled? Does the widget work for a visitor who doesn't run JavaScript, or at least link somewhere that does?

## What AgentScore sees

AgentScore doesn't have a check for widgets, but it's worth knowing how they affect your report. Its checks read your page's own document, as many agents do:

- When it measures how much of your home page's text is in the HTML, text inside iframes isn't counted.
- The checks for named buttons and links, labeled form fields and real controls look at your home page's own buttons, links and fields. Controls inside an iframe or a shadow root aren't counted, for better or worse.
- When it looks for the add-to-cart or sign-up button on a product or pricing page, it searches that page's own document. A buy button that lives inside a widget's frame or shadow root isn't found, and the check warns that no button was found, which is what a simple agent would conclude too.

So a good score doesn't prove your widgets work for agents. To see that, run the journey: in the app, a Ghost Agent works through your booking, checkout or sign-up the way a real agent would, always stops before paying, and gives you a step-by-step replay showing where it got stuck. See [how to test your key journeys with AI agents](https://ghostagentlab.com/articles/test-journeys-with-ai-agents/).

## Where to start

List every widget on your product, booking and checkout pages, and for each one, answer three questions.

| Question | If the answer is no |
| --- | --- |
| Are the key facts it shows also in our own HTML? | Render the rating, price or hours on the server and in structured data |
| Is there a plain link to a full page for it? | Ask the vendor for a hosted page and link to it |
| Are its frame and launcher button named? | Add an iframe title and button text or aria-label |

> **Try it this week:** open your most important product or booking page, view the source and search for the rating, the price and the main button's words. Anything you can see on screen but can't find in the source is living inside a widget or a script. Start with whichever one a shopper's question depends on.

## Sources and further reading

- [\<iframe>: HTML inline frame element](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe) (MDN Web Docs)
- [Using shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) (MDN Web Docs)
- [Same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Same-origin_policy) (MDN Web Docs)
