Skip to content

AI Sections

AI Sections is an embeddable component that displays predefined questions visitors can click to instantly trigger conversations with the Rose widget. It helps guide visitors toward relevant topics and increases engagement.

Overview

AI Sections provides a visual prompt section that:

  • Displays contextual questions relevant to the page content
  • Triggers the Rose widget when questions are clicked
  • Animates smoothly with staggered entrance and shimmer effects
  • Supports custom styling via props or CSS
  • Integrates with unified appearance config for consistent branding

Architecture

flowchart LR subgraph Sources["Data Sources"] DB[(config.ai_sections table)] Site[(appearance config)] end subgraph Components["Components"] AISections[AISections] Button[AISectionsButton] Sparkle[SparkleIcon] end Widget[Rose Widget] DB -->|questions, styling, customCSS| AISections Site -->|brand_color| AISections AISections --> Button Button --> Sparkle Button -->|onClick| Widget

Component Locations

Component Location Purpose
AISections frontend/shared/src/components/AISections/ Main container component
AISectionsButton frontend/shared/src/components/AISections/ Individual question button
RoseSectionsManager frontend/widget/src/ai-sections/ Widget-integrated manager

Single Bundle Architecture

AI Sections is integrated into the main widget bundle (inboundx-widget.js). There is no separate sections bundle - the widget auto-discovers and renders sections.

Configuration

Database Configuration (Supabase)

AI Sections can be configured per-domain in the config.ai_sections table:

Column Type Description
section_id string Unique identifier for the section
site_domain string Site domain this section belongs to
title jsonb Localized title object (e.g., {"en": "Ask us about pricing", "fr": "..."})
questions jsonb Localized questions object (e.g., {"en": ["Q1", "Q2"], "fr": ["Q1 fr", "Q2 fr"]})
styling jsonb Custom styling options
custom_css text Raw CSS for advanced theming
enabled boolean Whether the section is active
hides_main_bar boolean When true, hides the main Rose widget search bar while this section's search bar is visible on screen

Inline Configuration

Sections can also be configured inline when embedding:

<AISections
  config={{
    title: 'Ask us about pricing',
    questions: [
      'What plan fits my needs?',
      'Is there a free trial?',
      'How does billing work?',
    ],
    styling: {
      primaryColor: '#0A42C3',
      buttonBackground: '#F0F4FF',
    },
    hidesMainBar: true, // Hides main widget search bar when this section's search bar is visible
  }}
/>

Localization Support

Both title and questions support localized content with language keys (en, fr, de, es, it). The widget automatically selects the appropriate language based on visitor context, falling back to en.

Database format:

{
  "title": {"en": "Ask us about pricing", "fr": "Posez-nous vos questions sur les tarifs"},
  "questions": {
    "en": ["What plan fits my needs?", "Is there a free trial?"],
    "fr": ["Quel forfait me convient?", "Y a-t-il un essai gratuit?"]
  }
}

Styling

Style Resolution Architecture

AI Sections uses a three-layer styling architecture. Each property is resolved independently through this cascade:

flowchart TD subgraph Layer1["Layer 1: Explicit Props"] Props["config.styling.primaryColor = '#FF0000'"] end subgraph Layer2["Layer 2: Custom CSS"] CSS["config.customCSS"] Vars["--ai-sections-sparkle-color: #00FF00"] end subgraph Layer3["Layer 3: Defaults"] SiteColor["appearance.brand_color (resolver)"] Hardcoded["Hardcoded (#006967)"] end Props -->|"if set"| Final["Final Value"] CSS -->|"if prop not set"| Final SiteColor -->|"if no CSS override"| Final Hardcoded -->|"fallback"| Final style Layer1 fill:#e8f5e9 style Layer2 fill:#fff3e0 style Layer3 fill:#fce4ec

Priority (highest to lowest):

  1. Explicit styling props → Inline styles, always wins
  2. Custom CSS → Injected stylesheet with .ai-sections-* selectors
  3. Appearance config color → From appearance.brand_color (same as search bar)
  4. Hardcoded defaults → Built-in fallback values

How It Works

Per-Property Resolution: Each styling property is resolved independently. For example:

// Config with partial styling
config={{
  styling: {
    buttonBackground: '#FF0000',  // Explicit → Layer 1 wins
    // primaryColor not set → Layers 2 or 3 apply
  },
  customCSS: `
    .ai-sections-button {
      --ai-sections-sparkle-color: #00FF00;  /* Layer 2 for sparkle */
    }
  `
}}

In this example: - buttonBackground → Uses #FF0000 (Layer 1 - explicit prop) - primaryColor → Uses CSS variable if set, otherwise the resolved appearance color (Layers 2/3)

Layer 3 Default Chain:

Appearance Brand Color (resolver) → Hardcoded Default (#006967)

The sparkle icon color defaults to the site's resolved appearance color (the same color used in the search bar), ensuring brand consistency without explicit configuration.

Styling Options

Property Type Default Description
primaryColor string Appearance config color Icon color (sparkle)
backgroundColor string #E8F5F5 Section container background
buttonBackground string #FFFFFF Button background color
buttonTextColor string #1a1a1a Button text color
buttonFontSize string 20px Button text size
buttonFontWeight string 400 Button text weight
iconSize number 20 Sparkle icon size (px)
arrowSize number 24 Arrow icon size (px)
titleColor string #003E3F Section title color
gap string 12px Gap between buttons
layout string stacked stacked or inline
searchBarPosition string bottom Position of search bar: top or bottom
unhideParent boolean false Auto-show/hide the first hidden parent when section expands/collapses

Custom CSS

For advanced theming, use the customCSS property with CSS targeting these classes:

/* Container */
.ai-sections-container {
  background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
}

/* Title */
.ai-sections-title {
  color: #e0fc63;
  font-size: 40px;
}

/* Buttons */
.ai-sections-button {
  background: rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(10px);
}

/* Button text */
.ai-sections-button-text {
  color: #ffffff;
}

/* Sparkle icon color override */
.ai-sections-button {
  --ai-sections-sparkle-color: #e0fc63;
}

/* Arrow icon */
.ai-sections-arrow {
  opacity: 0.8;
}

CSS Variables

Variable Description
--ai-sections-bg Container background
--ai-sections-title-color Title text color
--ai-sections-primary-color Primary/accent color
--ai-sections-button-bg Button background
--ai-sections-button-text Button text color
--ai-sections-gap Gap between buttons
--ai-sections-sparkle-color Override sparkle icon color

Integration

AI Sections is built into the main widget bundle. Just load the widget and add containers with data-rose-section attributes:

<!-- Load the widget (includes AI Sections) -->
<script src="https://cdn.userose.ai/widget/{DOMAIN}/inboundx-widget.js"></script>

<!-- Add sections anywhere on the page - widget auto-discovers them -->
<div data-rose-section="homepage-hero"></div>
<div data-rose-section="pricing-faq"></div>

Conventional Container

Alternatively, use the conventional container ID for auto-loading all sections:

<!-- Widget auto-loads all enabled sections for the domain -->
<div id="rose-ai-sections-container"></div>
<script src="https://cdn.userose.ai/widget/{DOMAIN}/inboundx-widget.js"></script>

Global Config (Alternative)

For more control, define configuration before the widget loads:

<div id="my-section"></div>
<script>
  window.RoseSectionsConfig = {
    sections: [{
      containerId: 'my-section',
      title: 'How can we help?',
      questions: ['What is your product?', 'How much does it cost?']
    }]
  };
</script>
<script src="https://cdn.userose.ai/widget/{DOMAIN}/inboundx-widget.js"></script>

Manual API (Advanced)

For dynamic rendering after page load:

// Render a section manually
window.RoseSections.renderSection({
  containerId: 'my-container',
  sectionId: 'pricing-faq',  // or provide config directly
});

// Destroy a section
window.RoseSections.destroySection('my-container');

React Integration

For React applications within the Rose ecosystem:

import { AISections } from '@inboundx/shared';

function PricingPage() {
  return (
    <AISections
      config={{
        title: 'Pricing Questions',
        questions: ['What plan fits my needs?', 'Is there a free trial?'],
      }}
      onQuestionClick={(question) => console.log('Clicked:', question)}
    />
  );
}

Props Reference

AISections Props

Prop Type Default Description
sectionId string - Database section ID to fetch config
config AISectionConfig - Inline configuration object
showShimmer boolean true Show shimmer effect on first reveal
staggerDelay number 100 Delay (ms) between button animations
collapseTimeout number 10000 Timeout before collapsing if widget fails
onQuestionClick function - Callback when question is clicked
onExpand function - Callback when section expands
onCollapse function - Callback when section collapses
className string - Additional CSS class for container

AISectionConfig Options

Option Type Default Description
title string - Section title displayed above questions
questions string[] - Array of question strings to display as buttons
styling object - Styling options (see Styling Options)
customCSS string - Raw CSS for advanced theming
hidesMainBar boolean false When true, hides the main Rose widget search bar while this section's search bar is visible on screen
disableOnMobile boolean false When true, hides the entire section on mobile devices

Behavior

Widget Readiness

AI Sections automatically detects when the Rose widget is ready:

  1. Section starts collapsed (hidden)
  2. Waits for widget to emit rose:ready event
  3. Expands with animation when widget is available
  4. Collapses if widget fails to load within timeout

Question Click Flow

  1. User clicks a question button
  2. triggerQuestion() is called on the widget
  3. Widget opens and sends the question
  4. onQuestionClick callback fires (if provided)

Unhide Parent

When the widget bar is hidden (traffic control, URL exclusion, etc.), AI sections collapse. But the host page container wrapping the section element may remain visible, leaving empty space.

The unhideParent styling option solves this by automatically toggling the first hidden parent:

  1. On mount, traverses up to 10 parent elements to find the first one that is hidden
  2. Detects the hiding method(s) used: display: none, visibility: hidden, opacity: 0, clip-path, height: 0, hidden attribute, or aria-hidden="true"
  3. When the section expands (widget bar visible) → unhides the parent by reversing each detected method
  4. When the section collapses (widget bar hidden) → re-hides the parent using the original methods
  5. On destroy → restores the parent to its original hidden state
<!-- Client wraps the section in a hidden container -->
<div style="display: none">
  <div data-rose-section="faq"></div>
</div>

With unhideParent: true in the section's styling config, the hidden wrapper is automatically shown/hidden in sync with the widget bar.

JS works even when parent is hidden

The React root mounts and runs regardless of display: none. The useWidgetReadiness hook listens for window events (inboundx_bar_visible / inboundx_bar_hidden), not DOM visibility.

Animation

  • Entrance: Buttons fade in and slide up with staggered delay
  • Shimmer: One-time shimmer effect on first reveal
  • Exit: Smooth collapse if widget becomes unavailable

Main Bar Visibility

When hides_main_bar is enabled for a section, the main Rose widget search bar will automatically hide when the AI section's search bar scrolls into view:

  1. Uses IntersectionObserver to detect when the section's search bar is ≥20% visible
  2. Dispatches inboundx_section_searchbar_entered event when visible
  3. Dispatches inboundx_section_searchbar_exited event when hidden
  4. The main widget animates smoothly out of view to avoid duplicate search bars on screen

This prevents visual clutter when a page has both an embedded AI section search bar and the main floating widget search bar.

Preview Mode

The widget exposes preview() and exitPreview() methods on window.RoseSections to allow staff to preview disabled sections on the live site without enabling them for visitors.

Public API

// Show all sections including disabled ones
await window.RoseSections.preview()

// Revert to showing only enabled sections
await window.RoseSections.exitPreview()

How It Works

  1. preview() sets previewMode = true on the RoseSectionsManager singleton
  2. Destroys all currently rendered sections
  3. Re-runs auto-discovery, passing { includeDisabled: true } to getAISection() and getAISectionsForDomain()
  4. The service layer skips the .eq('enabled', true) filter when includeDisabled is set
  5. exitPreview() reverses the process — sets previewMode = false and re-renders with the default enabled-only filter

RLS Policy

The anon RLS policy on config.ai_sections allows reading all rows (including disabled). The enabled filter is enforced at the application layer in the JS service functions, not at the database level. This allows the preview mode to bypass the filter without needing authenticated access.

Testing

Use the preprod-ui to test AI Sections configurations:

  1. Navigate to /ai-sections in preprod-ui
  2. Select a client from the dropdown
  3. Toggle between database and sample configs
  4. Test different layout modes (stacked/inline)
  5. View the current configuration JSON
  6. Use the eye toggle button to hide/show the widget bar (tests unhideParent behavior)
  7. Select the "unhide-parent" sample config to see the hidden-parent demo

Analytics

AI Sections interactions are tracked via PostHog:

Event Description
rw_ai_section_impression Section displayed to user
rw_ai_section_question_click Question button clicked