Skip to main content
The raw HTML element is your escape hatch for unlimited customization. When FunnelFox’s built-in elements don’t fit your needs, RAW lets you write custom code to create exactly what you want.
RAW element in Visual Editor

Use cases

Most raw HTML element implementations combine several capabilities: rendering custom HTML/CSS UI, running custom logic, animations, or timers, saving values to hidden fields, and more. Here are the most common visual experience categories you can build:
Learn how to use the Fox API inside raw HTML elements to capture emails from URLs, calculate values from user inputs, route users with dynamic navigation logic, and send custom tracking events.

Create raw HTML

1

Add element

  1. Click + Add and go to Media & Display.
  2. Select Raw HTML.
2

Add HTML code

Go to the Element tab and add your code in the HTML Content editor. It supports full HTML5 syntax with embedded styles and scripts.
<script>
const url = new URL(window.location.href);
const emailParamValue = url.searchParams.get('email');
if (!!emailParamValue) {
  fox.inputs.setEmail(emailParamValue);
}
</script>
3

Preserve formatting

Use the Preserve Formatting setting to control how the element inherits your funnel’s theme:
  • No (default): The element renders with its own isolated styling.
  • Yes: Inherits theme styles (fonts, colors) from your funnel settings.
Due to element limitations:
  • No server-side execution: All code runs in the browser.
  • No file system access: Can’t read or write local files.
  • No Node.js: Browser JavaScript only.

Examples

Subscribes to weight and height inputs, recalculates BMI on every change, and writes the result both as a named variable and as a typed number write to a hidden field. Handles metric and imperial unit systems, converts inputs to a common base, and clears the output for unrealistic values.
BMI calculator
<script>
const BMI_HIDDEN_ID = 'el_FnbIP'; // your hidden input id

const WATCH_INPUTS = [
  'measurement_system',
  'current_weight_kgs',
  'current_weight_lbs',
  'height_cm',
  'height_ft',
  'height_in',
];

fox.inputs.subscribe((name) => {
  if (WATCH_INPUTS.includes(name)) updateBMI();
});

updateBMI();

function toMetricWeight(weight, system) {
  const w = parseFloat(weight);
  if (!Number.isFinite(w)) return null;
  return system === 'imperial' ? w * 0.453592 : w;
}

function toMetricHeight(height, inches, system) {
  if (system === 'imperial') {
    const ft = parseFloat(height);
    const inch = parseFloat(inches);
    const totalIn = (Number.isFinite(ft) ? ft : 0) * 12 + (Number.isFinite(inch) ? inch : 0);
    if (totalIn <= 0) return null;
    return totalIn * 2.54; // cm
  } else {
    const cm = parseFloat(height);
    return Number.isFinite(cm) && cm > 0 ? cm : null;
  }
}

function calcBMI(kg, cm) {
  const m = cm / 100;
  if (!Number.isFinite(kg) || !Number.isFinite(m) || m <= 0) return null;
  return Math.round((kg / (m * m)) * 10) / 10; // 1 decimal
}

function setBMIValue(val) {
  // keep both primitive + typed write
  fox.inputs.set('bmi', val === null ? '' : val);
  fox.inputs.set(BMI_HIDDEN_ID, val === null ? '' : val);
}

function updateBMI() {
  const system = fox.inputs.get('measurement_system')?.value === 'imperial' ? 'imperial' : 'metric';

  const weightRaw = system === 'metric'
    ? fox.inputs.get('current_weight_kgs')?.value
    : fox.inputs.get('current_weight_lbs')?.value;

  const heightRaw = system === 'metric'
    ? fox.inputs.get('height_cm')?.value
    : fox.inputs.get('height_ft')?.value;

  const inchesRaw = fox.inputs.get('height_in')?.value;

  const kg = toMetricWeight(weightRaw, system);
  const cm = toMetricHeight(heightRaw, inchesRaw, system);

  if (!kg || !cm) {
    setBMIValue(null);
    return;
  }

  const bmi = calcBMI(kg, cm);

  // guardrails: unrealistic values -> clear
  if (!bmi || bmi < 10 || bmi > 80) {
    setBMIValue(null);
    return;
  }

  setBMIValue(bmi);
  fox.inputs.set(BMI_HIDDEN_ID, { value: bmi, type: 'number' }); // typed write for hidden field
}
</script>
Fetches the visitor’s country from the ipinfo.io API and renders the country name inline using Intl.DisplayNames. If the request fails or returns no data, falls back to a CSS-blurred placeholder so the screen still looks intentional.
Geolocation personalization
<span id="country" style="color:#2DD285;font-family:Roboto, sans-serif;font-size:18px;font-weight:500;line-height:150%;">...</span>

<style>
  .ffx-blur { 
    filter: blur(var(--ffx-blur, 6px)); 
    transition: filter .2s; 
  }
</style>

<script>
(async function () {
  const el = document.getElementById("country");
  try {
    const res = await fetch("https://ipinfo.io/json?token=YOUR_IPINFO_TOKEN", { headers: { "Accept": "application/json" } });
    if (!res.ok) throw new Error("HTTP " + res.status);
    const data = await res.json();
    const code = (data && data.country) || "";
    if (code) {
      const name = new Intl.DisplayNames(["en"], { type: "region" }).of(code) || code;
      el.textContent = name;
      return;
    }
  } catch (e) {
    console.warn("ipinfo country error:", e);
  }
  // fallback — show blurred placeholder text
  el.innerHTML = '<span class="ffx-blur" style="--ffx-blur:6px">location</span>';
})();
</script>

<style>
  .ffx-blur { filter: blur(var(--ffx-blur, 6px)); transition: filter .2s; }
</style>

<p> <span class="ffx-blur" style="--ffx-blur:6px">location</span>
</p>
Renders a canvas-based signature pad with clear and confirm buttons. Once the user draws their signature, a celebration popup appears. Confirming the popup navigates to a specific screen using fox.navigation.goToId. Replace screen_lbtuWI7r with your target screen ID.
Signature pad
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sign Your Commitment</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', sans-serif;
            background: linear-gradient(135deg, #FFF5E6 0%, #FFE4B5 100%);
            min-height: 100vh;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            padding: 2rem;
        }

        .container {
            max-width: 500px;
            width: 100%;
            text-align: center;
        }

        .headline {
            font-size: 2rem;
            font-weight: 700;
            color: #1a1a1a;
            margin-bottom: 0.5rem;
        }

        .subtext {
            font-size: 1.1rem;
            color: #666;
            margin-bottom: 3rem;
        }

        .signature-container {
            background: white;
            border-radius: 16px;
            padding: 2rem;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
            margin-bottom: 2rem;
            position: relative;
        }

        .instruction {
            font-size: 0.9rem;
            color: #999;
            margin-bottom: 1rem;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 0.5rem;
        }

        .pen-icon {
            font-size: 1.2rem;
            animation: float 2s ease-in-out infinite;
        }

        @keyframes float {
            0%, 100% { transform: translateY(0px); }
            50% { transform: translateY(-10px); }
        }

        #signature-canvas {
            border: 2px dashed #ddd;
            border-radius: 12px;
            cursor: crosshair;
            touch-action: none;
            width: 100%;
            max-width: 400px;
            height: 200px;
            display: block;
            margin: 0 auto;
            background: #fafafa;
        }

        .buttons {
            display: flex;
            gap: 1rem;
            justify-content: center;
            margin-top: 1.5rem;
        }

        .btn {
            padding: 0.75rem 2rem;
            border: none;
            border-radius: 50px;
            font-size: 1rem;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
        }

        .btn-clear {
            background: #f0f0f0;
            color: #666;
        }

        .btn-clear:hover {
            background: #e0e0e0;
        }

        .btn-continue {
            background: #6B7FFF;
            color: white;
            opacity: 0.5;
            pointer-events: none;
        }

        .btn-continue.active {
            opacity: 1;
            pointer-events: all;
        }

        .btn-continue.active:hover {
            transform: translateY(-2px);
            box-shadow: 0 10px 25px rgba(107, 127, 255, 0.3);
        }

        /* Popup overlay */
        .popup-overlay {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.8);
            z-index: 1000;
            align-items: center;
            justify-content: center;
            animation: fadeIn 0.3s;
        }

        .popup-overlay.show {
            display: flex;
        }

        @keyframes fadeIn {
            from { opacity: 0; }
            to { opacity: 1; }
        }

        .popup-content {
            background: white;
            border-radius: 20px;
            padding: 2rem;
            max-width: 400px;
            width: 90%;
            text-align: center;
            animation: slideUp 0.3s;
        }

        @keyframes slideUp {
            from { 
                opacity: 0;
                transform: translateY(50px);
            }
            to { 
                opacity: 1;
                transform: translateY(0);
            }
        }

        .popup-image {
            width: 100%;
            max-width: 300px;
            height: auto;
            border-radius: 12px;
            margin: 1rem 0;
        }

        .popup-text {
            font-size: 1.2rem;
            font-weight: 600;
            color: #1a1a1a;
            margin-bottom: 1.5rem;
        }

        .popup-btn {
            background: #6B7FFF;
            color: white;
            padding: 1rem 3rem;
            border: none;
            border-radius: 50px;
            font-size: 1.1rem;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
        }

        .popup-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 10px 25px rgba(107, 127, 255, 0.3);
        }

        @media (max-width: 480px) {
            .headline {
                font-size: 1.6rem;
            }
            .subtext {
                font-size: 1rem;
            }
            #signature-canvas {
                height: 150px;
            }
        }
    </style>
</head>
<body>
    <div class="container">

        <div class="signature-container">
            <div class="instruction">
                <span class="pen-icon">✍️</span>
                <span>Draw your signature below</span>
            </div>
            
            <canvas id="signature-canvas"></canvas>

            <div class="buttons">
                <button class="btn btn-clear" id="clear-btn">Clear</button>
                <button class="btn btn-continue" id="continue-btn">I'm Committed</button>
            </div>
        </div>
    </div>

    <!-- Popup -->
    <div class="popup-overlay" id="popup">
        <div class="popup-content">
            <img 
                src="https://assets.fnlfx.com/01K7QWS36ZFETYPT7QCXWCHP2H/AOzXXTWT.webp" 
                alt="Commitment celebration" 
                class="popup-image"
            />
            <p class="popup-text">Your journey to sobriety starts now! 🌻</p>
            <button class="popup-btn" id="close-popup">Continue to Your Plan</button>
        </div>
    </div>

    <script>
        const canvas = document.getElementById('signature-canvas');
        const ctx = canvas.getContext('2d');
        const clearBtn = document.getElementById('clear-btn');
        const continueBtn = document.getElementById('continue-btn');
        const popup = document.getElementById('popup');
        const closePopupBtn = document.getElementById('close-popup');

        // Set canvas size
        function resizeCanvas() {
            const rect = canvas.getBoundingClientRect();
            canvas.width = rect.width;
            canvas.height = rect.height;
        }
        resizeCanvas();
        window.addEventListener('resize', resizeCanvas);

        // Drawing variables
        let isDrawing = false;
        let hasDrawn = false;

        // Drawing settings
        ctx.strokeStyle = '#1a1a1a';
        ctx.lineWidth = 2;
        ctx.lineCap = 'round';
        ctx.lineJoin = 'round';

        // Get coordinates
        function getCoordinates(e) {
            const rect = canvas.getBoundingClientRect();
            if (e.touches) {
                return {
                    x: e.touches[0].clientX - rect.left,
                    y: e.touches[0].clientY - rect.top
                };
            }
            return {
                x: e.clientX - rect.left,
                y: e.clientY - rect.top
            };
        }

        // Start drawing
        function startDrawing(e) {
            isDrawing = true;
            hasDrawn = true;
            const coords = getCoordinates(e);
            ctx.beginPath();
            ctx.moveTo(coords.x, coords.y);
            
            // Enable continue button
            continueBtn.classList.add('active');
        }

        // Draw
        function draw(e) {
            if (!isDrawing) return;
            e.preventDefault();
            
            const coords = getCoordinates(e);
            ctx.lineTo(coords.x, coords.y);
            ctx.stroke();
        }

        // Stop drawing
        function stopDrawing() {
            isDrawing = false;
        }

        // Mouse events
        canvas.addEventListener('mousedown', startDrawing);
        canvas.addEventListener('mousemove', draw);
        canvas.addEventListener('mouseup', stopDrawing);
        canvas.addEventListener('mouseout', stopDrawing);

        // Touch events
        canvas.addEventListener('touchstart', startDrawing);
        canvas.addEventListener('touchmove', draw);
        canvas.addEventListener('touchend', stopDrawing);

        // Clear button
        clearBtn.addEventListener('click', () => {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            hasDrawn = false;
            continueBtn.classList.remove('active');
        });

        // Continue button - show popup
        continueBtn.addEventListener('click', () => {
            if (hasDrawn) {
                popup.classList.add('show');
            }
        });

        // Close popup and navigate to next screen
        closePopupBtn.addEventListener('click', () => {
            popup.classList.remove('show');
            
            // Navigate using fox.navigation
            if (typeof fox !== 'undefined' && fox.navigation) {
                fox.navigation.goToId('screen_lbtuWI7r');
            } else {
                console.error('Fox navigation not available');
                alert('Navigation would go to: screen_lbtuWI7r');
            }
        });

        // Close popup on overlay click
        popup.addEventListener('click', (e) => {
            if (e.target === popup) {
                popup.classList.remove('show');
            }
        });
    </script>
</body>
</html>

Next steps