Skyra Survey Widget Integration Version: 2.0 (2026-01) Load script: === TYPESCRIPT TYPES === declare global { interface Window { SKYRA_CONFIG?: SkyraConfig skyra: SkyraAPI } } type SkyraConfig = { org: string // REQUIRED: organization slug testMode?: boolean // bypass targeting rules traits?: Record // user traits for segmentation cssSelector?: string // mount point (default: 'body') consent?: boolean // alias for cookieConsent cookieConsent?: boolean // allow cookies (default: true) cookieDomain?: string // share cookies across subdomains autoStart?: boolean // auto-init on load (default: true) version?: 'v1' | 'v2' // v2 for multiple inline surveys surveyOverrides?: Record priority?: number only?: boolean // show only this survey }> } type SkyraAPI = { start(config?: Partial): void reload(): void setConsent(value: boolean): void setLanguage(code: string): void getLanguage(): string | undefined getAvailableLanguages(): Array<{ code: string; name: string }> hideSurveys(criteria: { id?: string; slug?: string; renderType?: 'Popup' | 'Inline' }): void showSurveys(criteria: { id?: string; slug?: string; renderType?: 'Popup' | 'Inline' }): void preview(params: { slug: string; target?: string }): void redactPathname(pattern: string): void redactSearchParam(param: string, options?: { path?: string; paths?: string[] }): void on(event: 'ready' | 'surveyStarted' | 'surveyCompleted' | 'surveyRejected' | 'cardSaved', handler: (e: any) => void): void off(event: string, handler: (e: any) => void): void debugInfo(options?: { format?: 'json' | 'console'; survey?: string }): any _debugEnabled: boolean } type CardSavedEvent = { type: 'cardSaved' sessionId: string visitorId: string surveyId: string surveyName: string cardId: string cardType: 'TopTaskCard' | 'SegmentCard' | 'LikertCard' | 'RecruitmentCard' | 'InputCard' | 'MessageCard' | 'FindabilityCard' | 'MultiSelectCard' | 'CompletionCard' | 'SingleSelectCard' value: any } === POPUP SURVEY === === INLINE SURVEY === Custom element attributes: - slug: REQUIRED, format "org/survey-name" - inline: presence makes it inline (no value needed) - consent: "true" | "false" - lang: language code override === CONFIG OPTIONS === window.SKYRA_CONFIG = { org: 'your-org', // REQUIRED consent: false, // set true when user accepts cookies testMode: false, // bypass targeting rules for dev cookieConsent: true, // allow cookies cookieDomain: '.example.com', // share cookies across subdomains autoStart: true, // set false for manual init cssSelector: 'body', // popup mount point version: 'v2', // use v2 for multiple inline surveys traits: { userId: '123', plan: 'pro' }, surveyOverrides: { 'your-org/specific-survey': { testMode: true, priority: 1 } } }; === SCRIPT LOAD DETECTION === The script creates window.skyra when loaded. Handle async loading: // Pattern 1: Wait for script load function waitForSkyra(callback, timeout = 5000) { const start = Date.now(); const check = () => { if (window.skyra) { callback(window.skyra); } else if (Date.now() - start < timeout) { requestAnimationFrame(check); } else { console.error('Skyra failed to load'); } }; check(); } // Pattern 2: Use skyraStart callback (called automatically after load) window.skyraStart = function() { // Called after window.skyra is available window.skyra.on('ready', () => console.log('Surveys loaded')); }; // Pattern 3: Check before use (safe for SPAs) window.skyra?.reload(); === COOKIE CONSENT === Best practice: read existing consent state, then listen for changes. // Generic pattern window.skyra.setConsent(cookieManager.hasAnalyticsConsent()); cookieManager.onConsentChange(() => { window.skyra.setConsent(cookieManager.hasAnalyticsConsent()); }); Cookiebot: window.skyra.setConsent(Cookiebot.consent?.statistics ?? false); window.addEventListener('CookiebotOnAccept', () => window.skyra.setConsent(Cookiebot.consent.statistics)); window.addEventListener('CookiebotOnDecline', () => window.skyra.setConsent(false)); OneTrust: window.skyra.setConsent(OnetrustActiveGroups?.includes('C0002') ?? false); function OptanonWrapper() { window.skyra.setConsent(OnetrustActiveGroups.includes('C0002')); } CookieYes: window.skyra.setConsent(getCookieYesConsent()); document.addEventListener('cookieyes_consent_update', () => { window.skyra.setConsent(getCookieYesConsent()); }); === JAVASCRIPT API === window.skyra.start({ org: 'x' }) // manual init if autoStart: false window.skyra.reload() // call after SPA navigation window.skyra.setConsent(true) window.skyra.setLanguage('nb') window.skyra.getLanguage() window.skyra.hideSurveys({ renderType: 'Popup' }) window.skyra.showSurveys({ renderType: 'Popup' }) window.skyra.hideSurveys({ slug: 'org/survey' }) window.skyra.preview({ slug: 'org/survey' }) window.skyra.redactPathname('/users/:id/profile') // privacy: redacts /users/123/profile window.skyra.redactSearchParam('token') === EVENTS === window.skyra.on('ready', () => {}); window.skyra.on('surveyStarted', (e) => console.log(e.slug)); window.skyra.on('surveyCompleted', (e) => console.log(e.slug)); window.skyra.on('surveyRejected', (e) => console.log(e.slug)); window.skyra.on('cardSaved', (e) => { // e.cardType: 'LikertCard' | 'TopTaskCard' | 'SegmentCard' | 'InputCard' | 'RecruitmentCard' | ... // e.value: response data // e.sessionId, e.visitorId, e.surveyId, e.cardId }); Analytics example: window.skyra.on('surveyCompleted', (e) => { gtag('event', 'survey_completed', { survey: e.slug }); }); === SPA INTEGRATION === IMPORTANT: Call reload() after route changes to re-evaluate survey targeting. React (with script load check): useEffect(() => { window.skyra?.reload(); }, [location.pathname]); Next.js App Router: 'use client'; import { usePathname } from 'next/navigation'; import { useEffect } from 'react'; export function SkyraReloader() { const pathname = usePathname(); useEffect(() => { window.skyra?.reload(); }, [pathname]); return null; } Next.js Pages Router: import { useRouter } from 'next/router'; import { useEffect } from 'react'; export function SkyraReloader() { const router = useRouter(); useEffect(() => { window.skyra?.reload(); }, [router.asPath]); return null; } Vue Router: router.afterEach(() => window.skyra?.reload()); Angular: this.router.events.pipe( filter(event => event instanceof NavigationEnd) ).subscribe(() => window.skyra?.reload()); === STYLING === All optional. Surveys render with defaults. skyra-survey { --skyra-bg-color: #ffffff; --skyra-text-color: #1a1a1a; --skyra-action-color: #0066cc; --skyra-action-text-color: #ffffff; --skyra-secondary-color: #f5f5f5; --skyra-interface-color: #666666; --skyra-z-index: 100; } Dark mode: [data-theme="dark"] skyra-survey { --skyra-bg-color: #1a1a2e; --skyra-text-color: #f5f5f5; } @media (prefers-color-scheme: dark) { skyra-survey { --skyra-bg-color: #1a1a2e; --skyra-text-color: #f5f5f5; } } Shadow DOM parts for deeper styling: skyra-survey::part(heading) { } skyra-survey::part(button-primary) { } skyra-survey::part(button-secondary) { } skyra-survey::part(description) { } === EDGE CASES === Script load failure: CSP (Content Security Policy) - add these directives: script-src: https://survey.skyra.no connect-src: https://ingest.skyra.no https://ingest.staging.skyra.no https://survey.skyra.no style-src: 'unsafe-inline' (for shadow DOM styles) frame-src: https://survey.skyra.no (if using iframe mode) Common errors and fixes: "[Skyra] No organization specified" -> Set window.SKYRA_CONFIG = { org: 'your-org' } before script loads "[Skyra] Could not find DOM element with selector" -> Ensure cssSelector element exists in DOM when script runs -> For SPAs, use autoStart: false and call start() after mount "skyra.reload() called before skyra.start()" -> Script hasn't initialized yet, use optional chaining: window.skyra?.reload() Surveys not showing: 1. Check consent: window.skyra.setConsent(true) 2. Check targeting rules in Skyra dashboard 3. Enable debug: window.skyra._debugEnabled = true; window.skyra.debugInfo() Multiple inline surveys on same page: -> Use version: 'v2' in SKYRA_CONFIG === DEBUG === window.skyra._debugEnabled = true; window.skyra.debugInfo(); // pretty console output window.skyra.debugInfo({ format: 'json' }); // returns data object window.skyra.debugInfo({ survey: 'org/survey' }); // filter to specific survey