Embedding a Survey

Learn how to embed SenseFolks surveys across common frameworks and environments. Examples include production-ready patterns.

Choose Your Frontend

HTML

The simplest approach, with no build tools required:

html
<!DOCTYPE html>
<html>
<head>
  <title>My Page</title>
  <!-- Load the component -->
  <script type="module" src="https://unpkg.com/@sensefolks/fastpoll"></script>
</head>
<body>
  <h1>We'd love your feedback</h1>
  
  <!-- Place the survey -->
  <sf-fastpoll 
    survey-key="your-survey-uuid"
    completion-message="Thanks for your feedback!">
  </sf-fastpoll>
</body>
</html>

The type="module" attribute is required for ES module loading. For legacy browser support, add the nomodule fallback shown in the component reference.

Script URLs by Survey Type

Use the package URL that matches the component you embed:

Component Script URL
sf-fastpoll https://unpkg.com/@sensefolks/fastpoll
sf-userchoice https://unpkg.com/@sensefolks/userchoice
sf-pricepoint https://unpkg.com/@sensefolks/pricepoint
sf-openfeedback https://unpkg.com/@sensefolks/openfeedback
sf-featurepriority https://unpkg.com/@sensefolks/featurepriority
sf-reaction https://unpkg.com/@sensefolks/reaction

React

Use dynamic imports to load the component on mount:

jsx
// SurveyComponent.jsx
import { useEffect } from 'react';

export function SurveyComponent({ surveyKey }) {
  useEffect(() => {
    // Import the component on mount
    import('@sensefolks/fastpoll');
  }, []);

  return (
    <div className="survey-wrapper">
      <h2>Quick Poll</h2>
      <sf-fastpoll 
        survey-key={surveyKey}
        completion-message="Thanks for voting!" 
      />
    </div>
  );
}

TypeScript Support

Add type declarations for all SenseFolks components:

typescript
// types/sf-components.d.ts
declare namespace JSX {
  interface IntrinsicElements {
    'sf-fastpoll': React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLElement> & {
        'survey-key': string;
        'completion-message'?: string;
      },
      HTMLElement
    >;
    'sf-pricepoint': React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLElement> & {
        'survey-key': string;
        'completion-message'?: string;
      },
      HTMLElement
    >;
    'sf-userchoice': React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLElement> & {
        'survey-key': string;
        'completion-message'?: string;
      },
      HTMLElement
    >;
    'sf-featurepriority': React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLElement> & {
        'survey-key': string;
        'completion-message'?: string;
      },
      HTMLElement
    >;
    'sf-openfeedback': React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLElement> & {
        'survey-key': string;
        'completion-message'?: string;
      },
      HTMLElement
    >;
    'sf-reaction': React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLElement> & {
        'survey-key': string;
      },
      HTMLElement
    >;
  }
}

Create this file at types/sf-components.d.ts and include it in your tsconfig.json.

Vue 3

vue
<!-- SurveyComponent.vue -->
<template>
  <div class="survey-wrapper">
    <h2>Quick Poll</h2>
    <sf-fastpoll 
      :survey-key="surveyKey"
      completion-message="Thanks for voting!" 
    />
  </div>
</template>

<script setup lang="ts">
import { onMounted } from 'vue';

defineProps<{
  surveyKey: string;
}>();

onMounted(() => {
  import('@sensefolks/fastpoll');
});
</script>

Vite Configuration

Tell Vue to treat sf-* tags as custom elements:

typescript
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          // Treat sf-* tags as custom elements
          isCustomElement: (tag) => tag.startsWith('sf-')
        }
      }
    })
  ]
});

Angular

First, add the custom elements schema to your module:

typescript
// app.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  schemas: [CUSTOM_ELEMENTS_SCHEMA], // Required for custom elements
  bootstrap: [AppComponent]
})
export class AppModule {}

Then create a component that loads the survey:

typescript
// survey.component.ts
import { Component, Input, OnInit } from '@angular/core';

@Component({
  selector: 'app-survey',
  template: `
    <div class="survey-wrapper">
      <h2>Quick Poll</h2>
      <sf-fastpoll 
        [attr.survey-key]="surveyKey"
        [attr.completion-message]="completionMessage">
      </sf-fastpoll>
    </div>
  `
})
export class SurveyComponent implements OnInit {
  @Input() surveyKey: string = '';
  completionMessage = 'Thanks for voting!';

  ngOnInit() {
    import('@sensefolks/fastpoll');
  }
}

Use [attr.survey-key] for dynamic attribute binding in Angular templates.

Next.js

Web components require client-side rendering. Use 'use client' and dynamic imports:

tsx
// components/Survey.tsx
'use client';

import { useEffect } from 'react';

interface SurveyProps {
  surveyKey: string;
  type?: 'fastpoll' | 'pricepoint' | 'userchoice' | 'featurepriority' | 'openfeedback' | 'reaction';
}

export function Survey({ surveyKey, type = 'fastpoll' }: SurveyProps) {
  useEffect(() => {
    // Dynamic import for client-side only
    import(`@sensefolks/${type}`);
  }, [type]);

  const Tag = `sf-${type}` as keyof JSX.IntrinsicElements;

  return (
    <Tag 
      survey-key={surveyKey}
      completion-message="Thank you for your feedback!"
    />
  );
}

// Usage in a page:
// <Survey surveyKey="your-uuid" type="fastpoll" />

This pattern works with both the App Router and Pages Router.

Svelte

svelte
<!-- Survey.svelte -->
<script lang="ts">
  import { onMount } from 'svelte';
  
  export let surveyKey: string;
  export let type: 'fastpoll' | 'pricepoint' | 'userchoice' | 'featurepriority' | 'openfeedback' | 'reaction' = 'fastpoll';
  
  onMount(async () => {
    await import(`@sensefolks/${type}`);
  });
</script>

<div class="survey-wrapper">
  {#if type === 'fastpoll'}
    <sf-fastpoll 
      survey-key={surveyKey}
      completion-message="Thanks!">
    </sf-fastpoll>
  {:else if type === 'pricepoint'}
    <sf-pricepoint 
      survey-key={surveyKey}
      completion-message="Thanks!">
    </sf-pricepoint>
  {:else if type === 'userchoice'}
    <sf-userchoice 
      survey-key={surveyKey}
      completion-message="Thanks!">
    </sf-userchoice>
  {/if}
</div>

Svelte natively supports custom elements — no additional configuration needed.

Astro

astro
---
// Survey.astro
interface Props {
  surveyKey: string;
  type?: 'fastpoll' | 'pricepoint' | 'userchoice' | 'featurepriority' | 'openfeedback' | 'reaction';
}

const { surveyKey, type = 'fastpoll' } = Astro.props;
const componentUrl = `https://unpkg.com/@sensefolks/${type}`;
---

<div class="survey-wrapper">
  <sf-fastpoll 
    survey-key={surveyKey}
    completion-message="Thank you!">
  </sf-fastpoll>
</div>

<script define:vars={{ componentUrl }}>
  // Load component dynamically
  import(componentUrl);
</script>

For Astro, use a client-side script to load the component dynamically.

Advanced Patterns

Conditional Loading

Load surveys based on user behavior (scroll, time on page, etc.):

javascript
// Show survey based on user action
function showSurveyOnScroll() {
  const surveyContainer = document.getElementById('survey');
  
  window.addEventListener('scroll', () => {
    // Show survey when user scrolls 50% down the page
    const scrollPercent = (window.scrollY / document.body.scrollHeight) * 100;
    
    if (scrollPercent > 50 && !surveyContainer.hasChildNodes()) {
      // Dynamically create and insert survey
      const survey = document.createElement('sf-fastpoll');
      survey.setAttribute('survey-key', 'your-survey-uuid');
      surveyContainer.appendChild(survey);
    }
  }, { once: true });
}

Custom Styling

Style embedded surveys to match your site:

css
/* Custom styling for embedded survey */
.survey-wrapper {
  max-width: 600px;
  margin: 2rem auto;
  padding: 1.5rem;
  border-radius: 12px;
  background: #f8fafc;
}

/* Style the survey component */
sf-fastpoll::part(survey-container) {
  font-family: inherit;
}

sf-fastpoll::part(button) {
  background: #6366f1;
  border-radius: 8px;
}

sf-fastpoll::part(choice-option) {
  border-radius: 8px;
  transition: all 0.2s;
}

sf-fastpoll::part(choice-option):hover {
  border-color: #6366f1;
}

Common Issues

Component not rendering

  • Ensure the script tag has type="module"
  • Check that the survey-key is a valid UUID from your dashboard
  • Verify the component is loaded before it's used (use dynamic imports)

TypeScript errors

  • Add the type declarations shown in the React section
  • For Vue, configure isCustomElement in your build config

Styles not applying

  • Use ::part() selectors — regular CSS won't penetrate Shadow DOM
  • Check the CSS Parts guide for available parts

SSR/Hydration errors

  • Web components must render client-side only
  • Use dynamic imports inside useEffect or onMounted
  • In Next.js, use the 'use client' directive

Next Steps