Page awareness
The headline reason to use the NPM package over the script tag: the agent can see your page and fill your forms. Two primitives make this work — extractPageContext (which walks the DOM automatically) and LiveLayerRegion (for curated content sections). Forms need NO special markup — every <form> is auto-discovered.
How it works (30 seconds)
- The agent emits
request_page_contextover its data channel when it needs to "look around." - The widget walks the DOM, extracts headings, links, visible text, form fields, and any explicitly marked regions.
- The walked snapshot is sent back to the agent as JSON.
- When the agent emits a
fill_fieldorsubmit_formcommand, the widget targets the right element by ID.
The DOM walk respects two opt-out signals: data-ll-private="true" excludes a subtree, and password fields are always excluded.
extractPageContext
The default DOM walker. You can call it directly or override it via the getPageContext prop.
import { extractPageContext } from "@livelayer/react";
const context = extractPageContext({
// Optional extras passed straight through
currentUser: "user_abc",
cartTotal: 12.99,
});
// → { headings, links, visibleText, formFields, regions, extras }
For most apps, the default extraction is enough. Override it when:
- Your app's "real" state lives in a store, not the DOM (e.g., virtualized lists where most items aren't rendered).
- You want to feed the agent richer context (current route's title, user role, etc.).
- The default walk is too slow on a complex page.
import { AvatarWidget } from "@livelayer/react";
<AvatarWidget
agentId="agt_abc123"
getPageContext={async () => ({
headings: [{ level: 1, text: "Dashboard" }],
visibleText: useDashboardStore.getState().summarizeForAgent(),
formFields: [],
links: [],
regions: [],
})}
pageContextExtras={{
currentUser: useUserStore.getState().email,
plan: useUserStore.getState().plan,
}}
/>
<LiveLayerRegion> — mark important regions
Wrap a region of your UI in <LiveLayerRegion> to give it a stable name the agent can address.
import { LiveLayerRegion } from "@livelayer/react";
export function PricingPage() {
return (
<main>
<LiveLayerRegion name="hero" description="Top hero with primary CTA">
<h1>Pricing</h1>
<p>Simple, transparent.</p>
</LiveLayerRegion>
<LiveLayerRegion name="plans" description="Pricing tiers">
<PlanCard tier="starter" />
<PlanCard tier="pro" />
<PlanCard tier="enterprise" />
</LiveLayerRegion>
<LiveLayerRegion name="faq" description="Frequently asked questions">
<FaqList />
</LiveLayerRegion>
</main>
);
}
Now the agent's view of the page includes named regions:
{
"regions": [
{ "name": "hero", "description": "Top hero with primary CTA", "text": "Pricing. Simple, transparent." },
{ "name": "plans", "description": "Pricing tiers", "text": "Starter $9/mo... Pro $29/mo... Enterprise..." },
{ "name": "faq", "description": "Frequently asked questions", "text": "..." }
]
}
The agent can then say "scroll to plans" and the widget knows where to scroll.
Agent-fillable forms
Just write regular HTML. Every <form> on the page is auto-discovered and surfaced to the agent. The agent can fill, validate, and submit it — or run a guided voice sub-conversation through it via collect_from_page.
export function ContactForm() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [message, setMessage] = useState("");
return (
<form onSubmit={() => sendMessage({ name, email, message })}>
<label>Your name
<input name="name" value={name}
onChange={(e) => setName(e.target.value)} required />
</label>
<label>Email address
<input name="email" type="email" value={email}
onChange={(e) => setEmail(e.target.value)} required />
</label>
<label>Message
<textarea name="message" value={message}
onChange={(e) => setMessage(e.target.value)} />
</label>
<button type="submit">Send</button>
</form>
);
}
What this enables:
- Visitor: "Help me fill this out — my name is Jordan, email jordan@acme.com, message: When are you launching?"
- Agent: calls
fill_formwith all three values at once, thensubmit_formwhen the user confirms. - OR — for a voice-first walk-through — the agent calls
collect_from_pageand asks each question one at a time with per-kind normalization.
The agent infers each field's label from the wrapping <label> / aria-label / placeholder, and infers the kind from the type= attribute. Pass <AvatarWidget onCollect={...} /> to receive the typed payload when the agent finishes a guided collection.
Opt-out — keep forms or fields invisible
<form data-ll-skip>...</form> // exclude an entire form
<input data-ll-private /> // exclude a single input
And, always-on (browser-native) privacy guards: type="password", autocomplete="cc-*", and autocomplete="off" are ALWAYS excluded — you don't need to mark them.
Disambiguation hint
When a page has multiple forms and the LLM can't tell them apart, add data-ll-intent:
<form data-ll-intent="request a demo">...</form>
<form data-ll-intent="newsletter signup">...</form>
data-ll-private="true" — opt-out subtree
Mark any subtree as off-limits to context extraction:
<section data-ll-private="true">
{/* This entire section is invisible to the agent's page context */}
<CreditCardField />
<SocialSecurityField />
</section>
Use this for:
- Sensitive PII (medical records, financial details, government IDs)
- Internal admin UI you don't want the agent surfacing
- Diagnostic / debug panels
Password fields (<input type="password">) are excluded automatically — you don't need to mark them.
Capability gating
Page awareness is gated behind capabilities. To allow form filling, include fill_forms and submit_forms:
<AvatarWidget
agentId="agt_abc123"
capabilities={["read_page", "fill_forms", "submit_forms", "navigate"]}
/>
Without the right capabilities, the agent can request page context but won't be able to act on what it sees. See AvatarWidget capabilities.
Performance notes
- The default DOM walk is cached. Successive
request_page_contextcalls within the same route hit the cache. - The cache is invalidated when
pathnamechanges (which is why passing it for SPAs matters). - For pages over ~5000 elements, write a custom
getPageContextthat returns a structured summary instead of walking the whole DOM. - Use
clearPageContextCache()(exported) if you mutate the page meaningfully without a route change.
Read next
- Routing —
pathname,showOn,hideOnfor SPAs AvatarWidgetprops — full reference forgetPageContext,pageContextExtras,getRoutes,capabilities