Skip to main content

CTA Popup from Search Bar

Overview​

The ai12z CTA (Call-to-Action) component with toolbar search integration provides a seamless way to combine traditional search UI patterns with AI-powered conversational interfaces. This implementation places a search bar directly on your page that, when submitted, opens a modal dialog containing the ai12z chat assistant with the user's question automatically sent.

This pattern is ideal for:

  • Maintaining familiar search UI while adding AI capabilities
  • Creating hero sections with contextual AI assistance
  • Providing non-intrusive access to AI help throughout your site
  • Enhancing user experience without replacing existing navigation

Visual Example​

Search Toolbar​

The search bar appears in-context on your page, often positioned over hero images or in prominent locations:

Search toolbar popup to cta

When users submit a query, it opens a modal dialog with AI-generated responses:

Search results

Configuration​

The CTA popup behavior is controlled through the configuration interface in your ai12z dashboard:

Modal dialog setting

Display Settings​

The Display tab controls the modal dialog appearance and behavior:

SettingDescriptionDefaultExample
HeadingTitle displayed at the top of the modal-"Ask AI Assistant", "Search Help"
Dialog WidthWidth of the modal as a percentage of viewport80%"70%", "90%"
Dialog Max WidthMaximum width constraint70rem"60rem", "900px"
Dialog Top MarginSpace from top of viewport5rem"3rem", "80px"
Dialog Minimum HeightMinimum height of the dialog900px"700px", "80vh"

Options​

Control various features of the modal dialog:

  • Auto Search: Automatically processes the search query when dialog opens
  • Categorize: Enables categorization of responses
  • Allow Close: Displays close button (X) in the modal header
  • Enable Mobile View: Optimizes layout for mobile devices
  • Clear Memory: Clears conversation history when dialog reopens
  • Image Upload: Allows users to upload images in the conversation
  • Mic: Enables voice input functionality
  • Show Message Icons: Displays icons alongside messages

Theme Color​

Choose from predefined themes or create a custom theme to match your brand:

  • Default: Standard ai12z styling
  • Custom: Define your own color scheme using CSS variables

See the Style tab for advanced customization options.

Implementation​

Basic Setup​

First, include the ai12z web components in your HTML:

<head>
<!-- ai12z web components (CDN) -->
<script
type="module"
src="https://cdn.ai12z.net/pkg/ai12z@latest/dist/esm/library.js"
></script>
<link
rel="stylesheet"
href="https://cdn.ai12z.net/pkg/ai12z@latest/dist/library/library.css"
/>
</head>

Component Structure​

The <ai12z-cta> component uses slots to customize the trigger element. Replace the default button with a search bar:

<ai12z-cta id="searchCta" data-key="YOUR_API_KEY">
<!-- Custom search bar trigger (slot="cta") -->
<div slot="cta" class="search-wrapper">
<form id="searchForm" class="search-form">
<input
id="searchInput"
class="search-input"
type="search"
placeholder="Tell me about the resort"
aria-label="Search"
/>
<button class="search-button" type="submit" aria-label="Submit">➤</button>
</form>
</div>

<!-- Optional custom privacy notice -->
<div slot="custom-privacy" class="privacy-note">
Privacy Statement:
<a href="/privacy" target="_blank">Click to view</a>
</div>
</ai12z-cta>

JavaScript Integration​

The JavaScript handles opening the modal and sending the search query:

document.addEventListener("DOMContentLoaded", () => {
const cta = document.getElementById("searchCta")
const form = document.getElementById("searchForm")
const input = document.getElementById("searchInput")

// Configure CTA options
cta.options = {
allowClose: true,
autoSearch: true,
}

// Add contextual data
cta.dataAttributes = {
origin: window.location.href,
path: window.location.pathname,
title: document.title,
}

// Handle form submission
form.addEventListener("submit", async (e) => {
e.preventDefault()

const query = input.value.trim()
if (!query) return

// Open the modal
await openCta(cta)

// Send the question
await sendToCta(cta, query)

// Clear input
input.value = ""
})
})

// Helper function to open CTA modal
async function openCta(cta) {
await customElements.whenDefined("ai12z-cta")

return new Promise((resolve) => {
cta.addEventListener("open", () => resolve(true), { once: true })

if (typeof cta.action === "function") {
cta.action("open", { expanded: false })
}
})
}

// Helper function to send message
async function sendToCta(cta, text) {
if (typeof cta.sendMessage === "function") {
await cta.sendMessage(text)
return
}

// Fallback: find chat component in shadow DOM
const chat = cta.shadowRoot?.querySelector("ai12z-chat")
if (chat && typeof chat.sendMessage === "function") {
await chat.sendMessage(text)
}
}

Complete Example: Resort Website​

This example demonstrates a complete implementation for a beach resort website with a hero section and search bar:

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Kalauni Resort — Search Bar → Open CTA + Send</title>

<!-- ai12z web components (CDN) -->
<script
type="module"
src="https://cdn.ai12z.net/pkg/ai12z@next/dist/esm/library.js"
></script>
<link
rel="stylesheet"
href="https://cdn.ai12z.net/pkg/ai12z@next/dist/library/library.css"
/>

<style>
:root {
color-scheme: light;
}
body {
margin: 0;
font-family:
ui-sans-serif,
system-ui,
-apple-system,
Segoe UI,
Roboto,
Arial,
sans-serif;
}

.topbar {
position: sticky;
top: 0;
z-index: 20;
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px 22px;
background: rgba(255, 255, 255, 0.92);
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
}
.brand {
display: flex;
align-items: center;
gap: 12px;
font-weight: 800;
letter-spacing: 0.08em;
}
.brandMark {
width: 28px;
height: 28px;
border-radius: 999px;
border: 2px solid #2bb8b4;
box-shadow: 0 0 0 4px rgba(43, 184, 180, 0.12) inset;
}
nav a {
color: #0f2d3a;
text-decoration: none;
font-weight: 600;
margin-left: 18px;
font-size: 13px;
letter-spacing: 0.14em;
}

.hero {
min-height: calc(100vh - 64px);
display: grid;
place-items: center;
position: relative;
background: linear-gradient(
to bottom,
rgba(0, 0, 0, 0.22),
rgba(0, 0, 0, 0.55)
),
url("https://images.unsplash.com/photo-1500375592092-40eb2168fd21?auto=format&fit=crop&w=2400&q=80");
background-size: cover;
background-position: center;
}
.heroInner {
text-align: center;
padding: 48px 18px;
max-width: 980px;
color: #fff;
}
.eyebrow {
font-size: 12px;
letter-spacing: 0.45em;
text-transform: uppercase;
opacity: 0.9;
}
h1 {
margin: 14px 0 22px;
font-family: ui-serif, Georgia, "Times New Roman", Times, serif;
font-weight: 500;
font-size: clamp(42px, 6vw, 84px);
letter-spacing: 0.1em;
line-height: 1.05;
text-transform: uppercase;
}

/* ---------- Search bar (replaces "ASK AI" button) ---------- */
.askWrap {
width: min(980px, calc(100vw - 36px));
margin: 20px auto 0;
display: flex;
justify-content: center;
}

.askBar {
width: min(980px, calc(100vw - 36px));
background: rgba(0, 0, 0, 0.26);
padding: 16px;
border-radius: 18px;
backdrop-filter: blur(10px);
box-shadow: 0 18px 70px rgba(0, 0, 0, 0.3);
}

.askForm {
display: flex;
align-items: center;
gap: 14px;
background: #fff;
border-radius: 999px;
padding: 10px 14px;
box-shadow: 0 14px 40px rgba(0, 0, 0, 0.22);
}

.askIconLeft {
width: 36px;
height: 36px;
display: grid;
place-items: center;
border-radius: 999px;
background: rgba(170, 92, 255, 0.12);
color: rgba(120, 40, 220, 1);
font-weight: 900;
user-select: none;
}

.askInput {
flex: 1;
border: none;
outline: none;
font-size: 20px;
padding: 12px 6px;
color: #111;
min-width: 0;
}
.askInput::placeholder {
color: #8a8a8a;
}

.askSend {
width: 52px;
height: 52px;
border: none;
border-radius: 999px;
background: rgba(170, 92, 255, 1);
color: #fff;
cursor: pointer;
display: grid;
place-items: center;
font-size: 20px;
}
.askSend:hover {
filter: brightness(1.05);
}
.askSend:disabled {
opacity: 0.65;
cursor: not-allowed;
}

.privacyNote {
font-size: 12px;
color: rgba(255, 255, 255, 0.85);
}
.privacyNote a {
color: #ffffff;
text-decoration: underline;
}
</style>
</head>

<body>
<header class="topbar">
<div class="brand">
<span class="brandMark" aria-hidden="true"></span>
<span>KALAUNI RESORT</span>
</div>
<nav aria-label="Primary">
<a href="#rooms">ROOMS</a>
<a href="#restaurant">RESTAURANT</a>
<a href="#spa">SPA</a>
<a href="#activities">ACTIVITIES</a>
<a href="#resources">RESOURCES</a>
</nav>
</header>

<main class="hero">
<div class="heroInner">
<div class="eyebrow">RELAXATION &amp; COMFORT</div>
<h1>BEACH RESORT<br />HOTEL</h1>

<!-- ai12z-cta is still the thing that opens the modal -->
<ai12z-cta id="kalauniAsk" data-key="Enter your key">
<!-- Replace CTA button with a SEARCH BAR (slot="cta") -->
<div slot="cta" class="askWrap">
<div class="askBar">
<form id="kalauniAskForm" class="askForm" autocomplete="off">
<div class="askIconLeft" aria-hidden="true">✦</div>

<input
id="kalauniAskInput"
class="askInput"
type="search"
placeholder="Ask something…"
aria-label="Ask Kalauni"
/>

<button
id="kalauniAskSend"
class="askSend"
type="submit"
aria-label="Send"
>
➤
</button>
</form>
</div>

<!-- Hidden opener (more reliable than cta.action in some builds) -->
<button
id="kalauniHiddenOpen"
type="button"
style="position:fixed; left:-9999px; top:-9999px; width:1px; height:1px; opacity:0;"
aria-hidden="true"
tabindex="-1"
>
open
</button>
</div>

<div slot="custom-privacy" class="privacyNote">
Privacy Statement:
<a href="/privacy" target="_blank" rel="noopener">Click to view</a>
</div>
</ai12z-cta>
</div>
</main>

<script>
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))

async function waitForSelector(root, selector, timeoutMs = 8000) {
const start = Date.now()
while (Date.now() - start < timeoutMs) {
const el = root?.querySelector?.(selector)
if (el) return el
await sleep(120)
}
return null
}

async function openCta(cta) {
await customElements.whenDefined("ai12z-cta")

// wait for "open" event (or time out)
const opened = new Promise((resolve) => {
const t = setTimeout(() => resolve(false), 4500)
cta.addEventListener(
"open",
() => {
clearTimeout(t)
resolve(true)
},
{ once: true }
)
})

// best effort: use API if present, otherwise click hidden opener
if (typeof cta.action === "function") {
try {
await cta.action("open", { expanded: false })
const ok = await opened
if (ok) return true
} catch (e) {
// fall through
}
}

document.getElementById("kalauniHiddenOpen")?.click()
return await opened
}

async function sendToCta(cta, text) {
const msg = (text || "").trim()
if (!msg) return

// If the component exposes sendMessage, use it
if (typeof cta.sendMessage === "function") {
await cta.sendMessage(msg)
return
}

// Otherwise try ai12z-chat inside the CTA shadow DOM
const chat = await waitForSelector(cta.shadowRoot, "ai12z-chat", 8000)
if (chat && typeof chat.sendMessage === "function") {
await chat.sendMessage(msg)
return
}

// Last resort: set input + click send inside chat shadow root
if (chat?.shadowRoot) {
const input = chat.shadowRoot.querySelector("textarea, input")
if (input) {
input.focus()
input.value = msg
input.dispatchEvent(new Event("input", { bubbles: true }))
}
const sendBtn =
chat.shadowRoot.querySelector('button[type="submit"]') ||
chat.shadowRoot.querySelector("button.send") ||
chat.shadowRoot.querySelector("button")
sendBtn?.click()
}
}

document.addEventListener("DOMContentLoaded", () => {
const cta = document.getElementById("kalauniAsk")
const form = document.getElementById("kalauniAskForm")
const input = document.getElementById("kalauniAskInput")
const sendBtn = document.getElementById("kalauniAskSend")

if (!cta || !form || !input || !sendBtn) return

// Close button in ai12z modal
cta.options = { allowClose: true }

// Context you can use via {attributes} / {origin}
cta.dataAttributes = {
origin: window.location.href,
path: window.location.pathname,
title: document.title,
section: "home",
lang: document.documentElement.lang || "en",
}

// Prevent clicking/focusing in the search bar from auto-opening the modal.
// We only open on submit (Enter or arrow button).
form.addEventListener("click", (e) => e.stopPropagation())
input.addEventListener("click", (e) => e.stopPropagation())
input.addEventListener("focus", (e) => e.stopPropagation())

form.addEventListener("submit", async (e) => {
e.preventDefault()

const q = (input.value || "").trim()
if (!q) return

sendBtn.disabled = true

// Open ai12z modal (reliable)
const ok = await openCta(cta)
if (!ok) {
console.error("ai12z-cta did not open (no 'open' event).")
sendBtn.disabled = false
return
}

// Give the modal a beat to render the chat input
await sleep(150)

// Send the question so it answers immediately
await sendToCta(cta, q)

// Clear field for next question
input.value = ""

sendBtn.disabled = false
})
})
</script>
</body>
</html>

This creates a fully functional search bar that opens an AI-powered modal dialog.

Styling Guidelines​

Search Bar Styling​

Create an attractive search bar that fits your design:

.search-wrapper {
width: min(980px, calc(100vw - 36px));
margin: 20px auto 0;
}

.search-form {
display: flex;
align-items: center;
gap: 14px;
background: #fff;
border-radius: 999px;
padding: 10px 14px;
box-shadow: 0 14px 40px rgba(0, 0, 0, 0.22);
}

.search-input {
flex: 1;
border: none;
outline: none;
font-size: 20px;
padding: 12px 6px;
color: #111;
}

.search-input::placeholder {
color: #8a8a8a;
}

.search-button {
width: 52px;
height: 52px;
border: none;
border-radius: 999px;
background: rgba(170, 92, 255, 1);
color: #fff;
cursor: pointer;
font-size: 20px;
}

.search-button:hover {
filter: brightness(1.05);
}

Control the modal appearance through the Style tab in your configuration or using CSS:

/* Custom theme variables */
:root {
--ai12z-cta-dialog-width: 80%;
--ai12z-cta-dialog-max-width: 70rem;
--ai12z-cta-dialog-bg: #ffffff;
--ai12z-cta-overlay-bg: rgba(0, 0, 0, 0.5);
}

/* Responsive adjustments */
@media (max-width: 768px) {
:root {
--ai12z-cta-dialog-width: 95%;
--ai12z-cta-dialog-max-width: 100%;
}
}

Advanced Features​

Preventing Auto-Open on Focus​

By default, you may want to prevent the modal from opening when users click on the search bar (only opening on submit):

// Prevent clicking/focusing in the search bar from auto-opening the modal
form.addEventListener("click", (e) => e.stopPropagation())
input.addEventListener("click", (e) => e.stopPropagation())
input.addEventListener("focus", (e) => e.stopPropagation())

Adding Context Data​

Pass contextual information to the AI for more relevant responses:

cta.dataAttributes = {
origin: window.location.href,
path: window.location.pathname,
title: document.title,
section: "home",
lang: document.documentElement.lang || "en",
userType: "guest", // or detect from your auth system
}

This data can be referenced in your AI prompts using template variables like {origin}, {title}, etc.

Voice Input Integration​

If the "Mic" option is enabled in your configuration, users can use voice input in the modal dialog. No additional code is required—it's automatically available.

Image Upload Support​

Enable the "Image Upload" option in configuration to allow users to include images in their questions. This is useful for:

  • Visual search queries
  • Product identification
  • Troubleshooting with screenshots
  • Accessibility needs

Use Cases​

Place the search bar prominently in your hero section to immediately offer AI assistance:

<section class="hero">
<h1>BEACH RESORT HOTEL</h1>
<p>RELAXATION & COMFORT</p>

<ai12z-cta data-key="YOUR_KEY" config-id="hero-search">
<div slot="cta">
<input type="search" placeholder="Tell me about the resort" />
</div>
</ai12z-cta>
</section>

2. Help Center Integration​

Add contextual help without disrupting the user's flow:

cta.dataAttributes = {
page: "checkout",
step: "payment",
category: "support",
}

3. Product Search Enhancement​

Combine traditional product search with AI-powered recommendations:

form.addEventListener("submit", async (e) => {
e.preventDefault()

// Show traditional results
loadTraditionalResults(query)

// Also open AI assistant for conversational help
await openCta(cta)
await sendToCta(cta, `Help me find: ${query}`)
})

4. Navigation Assistant​

Help users find content or navigate complex sites:

<ai12z-cta data-key="YOUR_KEY">
<div slot="cta">
<input type="search" placeholder="How can I help you today?" />
</div>
</ai12z-cta>

Best Practices​

User Experience​

  1. Clear Placeholder Text: Use descriptive placeholders that indicate what users can ask
  2. Visual Feedback: Provide loading states when opening the modal
  3. Keyboard Support: Ensure Enter key works for submission
  4. Mobile Optimization: Enable "Enable Mobile View" in configuration for responsive design
  5. Accessibility: Include proper ARIA labels and keyboard navigation

Performance​

  1. Lazy Loading: Consider loading the ai12z component only when needed
  2. Debouncing: If implementing auto-suggest, debounce input events
  3. Caching: Enable "Clear Memory" option if you don't need conversation persistence
  4. Image Optimization: If using background images, optimize for web

Content Strategy​

  1. Context Awareness: Pass relevant page context to improve AI responses
  2. Predefined Queries: Consider showing suggested questions below the search bar
  3. Conversation Design: Configure your agent to handle common search patterns
  4. Fallback Options: Provide links to traditional search if AI doesn't meet needs

Troubleshooting​

Problem: The modal dialog doesn't appear when submitting the search.

Solutions:

  • Verify your API key is correct
  • Check browser console for errors
  • Ensure the ai12z-cta component is fully loaded before calling action()
  • Use the customElements.whenDefined() promise to wait for component registration
await customElements.whenDefined("ai12z-cta")

Search Query Not Sent​

Problem: The modal opens but the question isn't automatically sent.

Solutions:

  • Verify "Auto Search" is enabled in configuration
  • Check that the sendMessage() method is available
  • Add a small delay (100-200ms) after opening before sending
  • Use the fallback method to find the chat component in shadow DOM

Styling Not Applied​

Problem: Custom styles don't appear to work.

Solutions:

  • Remember that ai12z components use Shadow DOM
  • Use CSS custom properties (variables) for theming
  • Apply styles through the Style tab in configuration
  • Check CSS specificity and selector syntax

Mobile Layout Issues​

Problem: Dialog doesn't display properly on mobile devices.

Solutions:

  • Enable "Enable Mobile View" in configuration options
  • Adjust Dialog Width to 95% or higher for mobile
  • Set Dialog Max Width appropriately
  • Test on actual devices, not just browser DevTools

Configuration Reference​

CTA Component Attributes​

AttributeTypeDescriptionRequired
data-keyStringYour ai12z API keyYes
config-idStringSpecific configuration ID to useNo
data-modeStringServer mode: dev or prodNo (default: prod)

JavaScript API​

MethodParametersDescription
cta.action()'open', { expanded: boolean }Opens the modal dialog
cta.sendMessage()stringSends a message to the chat
cta.optionsobjectSet component options
cta.dataAttributesobjectPass contextual data to AI

Events​

Event NameDescriptionEvent Detail
openFired when modal opens-
closeFired when modal closes-
message-sentFired when message is sent{ message: string }
answer-receivedFired when AI responds{ response: string }

Example Repository​

For more examples and starter templates, visit our GitHub examples repository.