# SurveyJS Form Library ## Introduction SurveyJS Form Library is a free, open-source MIT-licensed JavaScript library that renders dynamic JSON-based forms in web applications. It provides a client-side component that allows developers to create multi-page forms, surveys, quizzes, calculator forms, and survey pop-ups without server-side dependencies for form rendering. The library is framework-agnostic with native support for React, Angular, Vue, and Knockout, plus jQuery support through a Knockout wrapper. Forms are defined using JSON schemas, enabling complete separation between form structure and application logic. The library offers comprehensive form-building capabilities including 20+ accessible input types, input validation, conditional logic, dynamic questions, data aggregation, expression language support, custom widgets, file uploads, e-signatures, and auto-localization to 50+ languages. All form submission data and files are stored on your own servers with no limits on forms, submissions, or uploads. The library features built-in themes, CSS customization options, TypeScript support, and integrates seamlessly with third-party libraries and payment systems. Weekly updates ensure the library stays current with modern web development practices. ## Core API ### Initialize Survey Model Create a survey instance from a JSON configuration. ```javascript import { Model } from 'survey-core'; import 'survey-core/survey-core.css'; const surveyJson = { title: "Customer Feedback Survey", description: "Help us improve our service", showProgressBar: "top", progressBarType: "pages", pages: [{ name: "page1", elements: [{ type: "text", name: "firstName", title: "First Name:", isRequired: true }, { type: "text", name: "email", title: "Email Address:", inputType: "email", isRequired: true, validators: [{ type: "email", text: "Please enter a valid email address" }] }] }, { name: "page2", elements: [{ type: "rating", name: "satisfaction", title: "How satisfied are you with our service?", rateMin: 1, rateMax: 5, minRateDescription: "Not satisfied", maxRateDescription: "Very satisfied" }] }], showQuestionNumbers: "on", completedHtml: "

Thank you for your feedback!

" }; const survey = new Model(surveyJson); // Handle survey completion survey.onComplete.add((sender, options) => { const results = sender.data; console.log("Survey results:", results); // Send to server fetch("https://api.example.com/surveys", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(results) }) .then(response => response.json()) .then(data => { options.showSaveSuccess("Data saved successfully!"); }) .catch(error => { options.showSaveError("Error saving data. Please try again."); }); }); // Render in container (vanilla JS) survey.render(document.getElementById("surveyContainer")); ``` ### Access and Modify Survey Data Get and set question values programmatically. ```javascript import { Model } from 'survey-core'; const survey = new Model(surveyJson); // Set individual question value survey.setValue("firstName", "John"); survey.setValue("email", "john@example.com"); // Get individual question value const firstName = survey.getValue("firstName"); console.log("First name:", firstName); // Set multiple values at once survey.data = { firstName: "Jane", email: "jane@example.com", age: 28 }; // Get all survey data const allData = survey.data; console.log("All survey data:", allData); // Pre-populate form from existing data (e.g., user profile) survey.data = { firstName: userData.firstName, lastName: userData.lastName, email: userData.email }; // Clear all data survey.clear(true, true); // clearData=true, gotoFirstPage=true // Work with variables (for expressions, not included in results) survey.setVariable("currentYear", new Date().getFullYear()); survey.setVariable("discount", 0.15); // Check if survey is completed if (survey.state === "completed") { console.log("Survey already completed"); } ``` ### Handle Survey Events Subscribe to lifecycle and data change events. ```javascript import { Model } from 'survey-core'; const survey = new Model(surveyJson); // Value changed event survey.onValueChanged.add((sender, options) => { const { name, value, oldValue, question } = options; console.log(`Question "${name}" changed from "${oldValue}" to "${value}"`); // Conditional logic based on value change if (name === "country" && value === "USA") { sender.setValue("currency", "USD"); } // Auto-save on value change localStorage.setItem("surveyData", JSON.stringify(sender.data)); }); // Before value changes (can modify or cancel) survey.onValueChanging.add((sender, options) => { if (options.name === "email") { // Normalize email to lowercase options.value = options.value.toLowerCase().trim(); } }); // Page navigation events survey.onCurrentPageChanging.add((sender, options) => { const { oldCurrentPage, newCurrentPage } = options; // Prevent navigation if condition not met if (oldCurrentPage.name === "terms" && !sender.getValue("acceptTerms")) { alert("Please accept terms to continue"); options.allowChanging = false; } }); survey.onCurrentPageChanged.add((sender, options) => { console.log("Moved to page:", options.newCurrentPage.title); // Track page views analytics.trackPageView(options.newCurrentPage.name); }); // Before completing survey survey.onCompleting.add((sender, options) => { const requiredFields = ["email", "phone"]; const missingFields = requiredFields.filter(field => !sender.getValue(field)); if (missingFields.length > 0) { alert("Please fill all required fields"); options.allowComplete = false; } }); // Survey started survey.onStarted.add((sender, options) => { console.log("Survey started at:", new Date()); sender.setVariable("startTime", Date.now()); }); // Question visibility changed survey.onQuestionVisibleChanged.add((sender, options) => { const { question, visible } = options; console.log(`Question "${question.name}" is now ${visible ? "visible" : "hidden"}`); }); // After rendering question survey.onAfterRenderQuestion.add((sender, options) => { const { question, htmlElement } = options; // Add custom tooltip if (question.name === "specialField") { const tooltip = document.createElement("span"); tooltip.innerHTML = " ℹ️"; tooltip.title = "Additional help text"; htmlElement.querySelector(".sv-question__title").appendChild(tooltip); } }); ``` ## Question Types ### Text Input Questions Create single-line and multi-line text inputs with validation. ```javascript const surveyJson = { elements: [ // Basic text input { type: "text", name: "fullName", title: "Full Name:", isRequired: true, placeholder: "Enter your full name" }, // Email input with validation { type: "text", name: "email", title: "Email Address:", inputType: "email", isRequired: true, validators: [{ type: "email", text: "Invalid email format" }] }, // Number input with range { type: "text", name: "age", title: "Age:", inputType: "number", validators: [{ type: "numeric", minValue: 18, maxValue: 120, text: "Age must be between 18 and 120" }] }, // Password input { type: "text", name: "password", title: "Password:", inputType: "password", validators: [{ type: "regex", regex: "^(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*]).{8,}$", text: "Password must contain uppercase, number, and special character" }] }, // Multi-line text area { type: "comment", name: "feedback", title: "Your Feedback:", rows: 5, maxLength: 1000, placeholder: "Tell us what you think..." }, // Multiple text inputs in one question { type: "multipletext", name: "personalInfo", title: "Personal Information:", items: [ { name: "firstName", title: "First Name", isRequired: true }, { name: "lastName", title: "Last Name", isRequired: true }, { name: "phone", title: "Phone", inputType: "tel" } ] } ] }; const survey = new Model(surveyJson); ``` ### Choice-Based Questions Create dropdowns, radio buttons, checkboxes, and selection controls. ```javascript const surveyJson = { elements: [ // Dropdown with simple choices { type: "dropdown", name: "country", title: "Select Your Country:", placeholder: "Choose a country...", choices: ["USA", "UK", "Canada", "Australia", "Germany"], isRequired: true, allowClear: true }, // Radio group with custom values { type: "radiogroup", name: "plan", title: "Choose Your Plan:", choices: [ { value: "basic", text: "Basic - $9/month" }, { value: "pro", text: "Professional - $29/month" }, { value: "enterprise", text: "Enterprise - $99/month" } ], isRequired: true }, // Checkboxes with "Select All" option { type: "checkbox", name: "features", title: "Select Features You Need:", choices: [ "Email Support", "Priority Support", "API Access", "Custom Branding", "Advanced Analytics" ], showSelectAllItem: true, maxSelectedChoices: 3, minSelectedChoices: 1 }, // Boolean toggle { type: "boolean", name: "subscribe", title: "Subscribe to Newsletter", defaultValue: false, labelTrue: "Yes, sign me up", labelFalse: "No, thanks" }, // Tag box (multi-select searchable) { type: "tagbox", name: "skills", title: "Your Skills:", placeholder: "Type to search...", choices: [ "JavaScript", "Python", "Java", "C++", "Ruby", "Go", "Rust", "TypeScript", "PHP", "Swift" ], searchEnabled: true, closeOnSelect: false }, // Image picker { type: "imagepicker", name: "avatar", title: "Choose Your Avatar:", choices: [ { value: "lion", imageLink: "https://example.com/lion.png" }, { value: "tiger", imageLink: "https://example.com/tiger.png" }, { value: "bear", imageLink: "https://example.com/bear.png" } ], imageFit: "cover", imageHeight: 150, imageWidth: 150 }, // Ranking question { type: "ranking", name: "priorities", title: "Rank These Priorities (drag to reorder):", choices: [ "Performance", "Security", "User Experience", "Cost", "Scalability" ] }, // Button group { type: "buttongroup", name: "size", title: "Select Size:", choices: ["Small", "Medium", "Large", "X-Large"] } ] }; const survey = new Model(surveyJson); ``` ### Dynamic Choice Loading from API Load dropdown/radiogroup choices from REST API. ```javascript const surveyJson = { elements: [ { type: "dropdown", name: "country", title: "Select Country:", choicesByUrl: { url: "https://surveyjs.io/api/CountriesExample", valueName: "name", titleName: "name" }, placeholder: "Loading countries..." }, // Dependent dropdown - load cities based on selected country { type: "dropdown", name: "city", title: "Select City:", visibleIf: "{country} notempty", choicesByUrl: { url: "https://api.example.com/cities?country={country}", valueName: "id", titleName: "name" } } ] }; const survey = new Model(surveyJson); // Custom choices loading with authentication survey.onLoadChoicesFromServer.add((sender, options) => { const { question, serverResult } = options; // Transform API response if (question.name === "products") { const transformedChoices = serverResult.data.map(item => ({ value: item.id, text: `${item.name} - $${item.price}` })); options.choices = transformedChoices; } }); // Handle loading errors survey.onChoicesLoaded.add((sender, options) => { const { question, choices, error } = options; if (error) { console.error(`Failed to load choices for ${question.name}:`, error); question.choices = [{ value: "error", text: "Failed to load options" }]; } }); ``` ### Rating and Slider Questions Create rating scales and numeric sliders. ```javascript const surveyJson = { elements: [ // Star rating { type: "rating", name: "serviceRating", title: "Rate Our Service:", rateMin: 1, rateMax: 5, rateStep: 1, minRateDescription: "Poor", maxRateDescription: "Excellent", displayMode: "buttons" }, // Numeric scale { type: "rating", name: "satisfaction", title: "How likely are you to recommend us? (0-10)", rateMin: 0, rateMax: 10, displayMode: "buttons" }, // Slider with labels { type: "slider", name: "budget", title: "Your Budget Range:", min: 0, max: 10000, step: 500, defaultValue: 2500, rangeMin: 0, rangeMax: 10000 }, // Range slider { type: "nouislider", name: "priceRange", title: "Select Price Range:", rangeMin: 0, rangeMax: 1000, step: 10, defaultValue: [100, 500], tooltips: true, pipsMode: "positions", pipsValues: [0, 25, 50, 75, 100], pipsText: ["$0", "$250", "$500", "$750", "$1000"] } ] }; const survey = new Model(surveyJson); // Calculate NPS score from rating survey.onComplete.add((sender) => { const score = sender.getValue("satisfaction"); let npsCategory; if (score >= 9) npsCategory = "Promoter"; else if (score >= 7) npsCategory = "Passive"; else npsCategory = "Detractor"; console.log(`NPS Category: ${npsCategory}`); }); ``` ### Matrix Questions Create grid-based questions with rows and columns. ```javascript const surveyJson = { elements: [ // Single-select matrix { type: "matrix", name: "satisfaction", title: "Rate each aspect:", columns: [ { value: 1, text: "Poor" }, { value: 2, text: "Fair" }, { value: 3, text: "Good" }, { value: 4, text: "Excellent" } ], rows: [ "Product Quality", "Customer Service", "Delivery Speed", "Value for Money" ], isAllRowRequired: true }, // Multi-select matrix with different cell types { type: "matrixdropdown", name: "employees", title: "Employee Information:", columns: [ { name: "name", title: "Name", cellType: "text", isRequired: true }, { name: "position", title: "Position", cellType: "dropdown", choices: ["Developer", "Designer", "Manager", "Analyst"] }, { name: "salary", title: "Salary", cellType: "text", inputType: "number" } ], rows: ["Employee 1", "Employee 2", "Employee 3"] }, // Dynamic matrix (add/remove rows) { type: "matrixdynamic", name: "tasks", title: "Project Tasks:", columns: [ { name: "task", title: "Task Description", cellType: "text" }, { name: "priority", title: "Priority", cellType: "dropdown", choices: ["Low", "Medium", "High"] }, { name: "hours", title: "Estimated Hours", cellType: "text", inputType: "number" } ], rowCount: 2, minRowCount: 1, maxRowCount: 10, addRowText: "Add Task", removeRowText: "Remove", confirmDelete: true, confirmDeleteText: "Are you sure you want to delete this task?" } ] }; const survey = new Model(surveyJson); // Handle matrix cell value changes survey.onMatrixCellValueChanged.add((sender, options) => { const { question, row, columnName, value } = options; if (columnName === "priority" && value === "High") { console.log(`High priority task added: ${row.getValue("task")}`); } }); // Calculate total hours from dynamic matrix survey.onValueChanged.add((sender, options) => { if (options.name === "tasks") { const tasks = sender.getValue("tasks") || []; const totalHours = tasks.reduce((sum, task) => { return sum + (parseFloat(task.hours) || 0); }, 0); sender.setVariable("totalHours", totalHours); console.log(`Total estimated hours: ${totalHours}`); } }); ``` ### File Upload Questions Handle file uploads with custom server integration. ```javascript const surveyJson = { elements: [ { type: "file", name: "resume", title: "Upload Your Resume:", allowMultiple: false, maxSize: 5242880, // 5MB in bytes acceptedTypes: ".pdf,.doc,.docx", isRequired: true, storeDataAsText: false, waitForUpload: true }, { type: "file", name: "photos", title: "Upload Photos (max 3):", allowMultiple: true, maxSize: 10485760, // 10MB acceptedTypes: "image/*", maxFiles: 3, imageHeight: 150, imageWidth: 150 } ] }; const survey = new Model(surveyJson); // Handle file upload survey.onUploadFiles.add((sender, options) => { const formData = new FormData(); options.files.forEach((file, index) => { formData.append(`file${index}`, file); }); fetch("https://api.example.com/upload", { method: "POST", headers: { "Authorization": `Bearer ${authToken}` }, body: formData }) .then(response => { if (!response.ok) throw new Error("Upload failed"); return response.json(); }) .then(data => { // Call success callback with file URLs/IDs const uploadedFiles = data.files.map(f => ({ file: f, content: f.url })); options.callback("success", uploadedFiles); }) .catch(error => { console.error("Upload error:", error); options.callback("error", error.message); }); }); // Handle file download survey.onDownloadFile.add((sender, options) => { const { fileValue, callback } = options; fetch(`https://api.example.com/download/${fileValue.content}`, { headers: { "Authorization": `Bearer ${authToken}` } }) .then(response => response.blob()) .then(blob => { callback("success", blob); }) .catch(() => { callback("error"); }); }); // Handle file removal survey.onClearFiles.add((sender, options) => { const { fileName, callback } = options; fetch(`https://api.example.com/delete/${fileName}`, { method: "DELETE", headers: { "Authorization": `Bearer ${authToken}` } }) .then(() => { callback("success"); }) .catch(() => { callback("error"); }); }); ``` ### Signature Pad Question Capture digital signatures. ```javascript const surveyJson = { elements: [ { type: "signaturepad", name: "customerSignature", title: "Please sign below:", isRequired: true, height: 200, width: 500, penColor: "#000000", backgroundColor: "#ffffff", signatureWidth: 2, allowClear: true, dataFormat: "png" // or "jpeg", "svg" } ] }; const survey = new Model(surveyJson); survey.onComplete.add((sender) => { const signature = sender.getValue("customerSignature"); // signature is base64 encoded image data console.log("Signature data:", signature); // Send to server fetch("https://api.example.com/save-signature", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ signature }) }); }); ``` ## Data Validation ### Built-in Validators Apply validation rules to questions. ```javascript const surveyJson = { elements: [ // Required field { type: "text", name: "username", title: "Username:", isRequired: true, requiredErrorText: "Username cannot be empty" }, // Text length validation { type: "text", name: "password", title: "Password:", inputType: "password", validators: [{ type: "text", minLength: 8, maxLength: 20, text: "Password must be 8-20 characters" }] }, // Numeric range validation { type: "text", name: "age", title: "Age:", inputType: "number", validators: [{ type: "numeric", minValue: 18, maxValue: 65, text: "Age must be between 18 and 65" }] }, // Email validation { type: "text", name: "email", title: "Email:", inputType: "email", validators: [{ type: "email", text: "Please enter a valid email address" }] }, // Regular expression validation { type: "text", name: "phone", title: "Phone Number:", validators: [{ type: "regex", regex: "^\\+?[1-9]\\d{9,14}$", text: "Please enter valid phone format: +1234567890" }] }, // Expression validator (cross-field validation) { type: "text", name: "confirmEmail", title: "Confirm Email:", validators: [{ type: "expression", expression: "{email} = {confirmEmail}", text: "Emails do not match" }] }, // Answer count validator (for checkboxes) { type: "checkbox", name: "interests", title: "Select 2-4 interests:", choices: ["Sports", "Music", "Reading", "Travel", "Gaming", "Cooking"], validators: [{ type: "answercount", minCount: 2, maxCount: 4, text: "Please select between 2 and 4 options" }] } ], checkErrorsMode: "onValueChanged", // Validate immediately textUpdateMode: "onTyping" // Update as user types }; const survey = new Model(surveyJson); ``` ### Custom Validation Implement custom validation logic. ```javascript import { Model } from 'survey-core'; const surveyJson = { elements: [ { type: "text", name: "email", title: "Company Email:" }, { type: "text", name: "promoCode", title: "Promo Code:" } ] }; const survey = new Model(surveyJson); // Client-side custom validation survey.onValidateQuestion.add((sender, options) => { const { name, value } = options; // Validate company email domain if (name === "email") { if (!value.endsWith("@company.com")) { options.error = "Please use your company email address"; } } // Validate promo code format if (name === "promoCode" && value) { if (!/^[A-Z]{4}\d{4}$/.test(value)) { options.error = "Promo code must be 4 letters followed by 4 digits (e.g., ABCD1234)"; } } }); // Async server-side validation survey.onServerValidateQuestions.add((sender, options) => { const { data, errors, complete } = options; fetch("https://api.example.com/validate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }) .then(response => response.json()) .then(result => { // Add errors for invalid fields if (result.errors) { Object.keys(result.errors).forEach(field => { errors[field] = result.errors[field]; }); } // MUST call complete() when done complete(); }) .catch(error => { console.error("Validation error:", error); complete(); }); }); // Custom error messages survey.onErrorCustomText.add((sender, options) => { const { name, error, text } = options; if (name === "age" && error.getErrorType() === "required") { options.text = "We need to know your age to continue"; } }); // Validate entire panel survey.onValidatePanel.add((sender, options) => { const { name, data, errors } = options; if (name === "addressPanel") { if (!data.street || !data.city || !data.zipCode) { errors["addressPanel"] = "Please complete all address fields"; } } }); ``` ## Conditional Logic ### Visibility Conditions Show/hide questions based on answers. ```javascript const surveyJson = { elements: [ { type: "radiogroup", name: "hasAccount", title: "Do you have an account?", choices: ["Yes", "No"], isRequired: true }, // Show login fields if user has account { type: "text", name: "username", title: "Username:", visibleIf: "{hasAccount} = 'Yes'", isRequired: true }, { type: "text", name: "password", title: "Password:", inputType: "password", visibleIf: "{hasAccount} = 'Yes'", isRequired: true }, // Show registration fields if no account { type: "text", name: "email", title: "Email:", visibleIf: "{hasAccount} = 'No'", isRequired: true }, { type: "text", name: "newPassword", title: "Create Password:", inputType: "password", visibleIf: "{hasAccount} = 'No'", isRequired: true }, // Conditional page visibility { type: "rating", name: "satisfaction", title: "How satisfied are you?", rateMin: 1, rateMax: 5 }, { type: "comment", name: "positiveFeeback", title: "What did you like?", visibleIf: "{satisfaction} >= 4" }, { type: "comment", name: "negativeFeeback", title: "What can we improve?", visibleIf: "{satisfaction} <= 2" }, // Complex conditions with AND/OR { type: "text", name: "discount", title: "Enter discount code:", visibleIf: "{satisfaction} >= 4 and {hasAccount} = 'Yes'" } ] }; const survey = new Model(surveyJson); ``` ### Dynamic Text and Placeholders Use question values in text with piping. ```javascript const surveyJson = { elements: [ { type: "text", name: "firstName", title: "First Name:" }, { type: "text", name: "product", title: "Product Name:" }, { type: "html", name: "greeting", html: "

Hello, {firstName}!

Thank you for your interest in {product}.

" }, { type: "radiogroup", name: "recommend", title: "Would you recommend {product} to others?", choices: ["Yes", "No", "Maybe"] } ] }; const survey = new Model(surveyJson); // Custom text processing survey.onProcessTextValue.add((sender, options) => { const { name, returnDisplayValue } = options; if (name === "firstName") { // Capitalize first name in displayed text options.value = options.value.charAt(0).toUpperCase() + options.value.slice(1); } }); ``` ### Calculated Values Create computed values based on expressions. ```javascript const surveyJson = { elements: [ { type: "text", name: "price", title: "Price:", inputType: "number" }, { type: "text", name: "quantity", title: "Quantity:", inputType: "number", defaultValue: 1 }, { type: "dropdown", name: "taxRate", title: "Tax Rate:", choices: [ { value: 0.05, text: "5%" }, { value: 0.10, text: "10%" }, { value: 0.15, text: "15%" } ] }, { type: "expression", name: "subtotal", title: "Subtotal:", expression: "{price} * {quantity}", displayStyle: "currency", currency: "USD" }, { type: "expression", name: "tax", title: "Tax:", expression: "{subtotal} * {taxRate}", displayStyle: "currency", currency: "USD" }, { type: "expression", name: "total", title: "Total:", expression: "{subtotal} + {tax}", displayStyle: "currency", currency: "USD" } ], calculatedValues: [ { name: "discount", expression: "iif({total} > 100, {total} * 0.1, 0)", includeIntoResult: true }, { name: "finalTotal", expression: "{total} - {discount}", includeIntoResult: true } ] }; const survey = new Model(surveyJson); ``` ### Skip Logic and Triggers Execute actions based on conditions. ```javascript const surveyJson = { pages: [{ name: "page1", elements: [{ type: "radiogroup", name: "userType", title: "Are you a new or existing customer?", choices: ["New", "Existing"] }] }, { name: "newCustomerPage", elements: [{ type: "text", name: "referralCode", title: "Referral Code:" }] }, { name: "existingCustomerPage", elements: [{ type: "text", name: "customerId", title: "Customer ID:" }] }], triggers: [ // Skip to specific page { type: "skip", expression: "{userType} = 'New'", gotoName: "newCustomerPage" }, { type: "skip", expression: "{userType} = 'Existing'", gotoName: "existingCustomerPage" }, // Complete survey early { type: "complete", expression: "{qualified} = 'No'" }, // Set value trigger { type: "setvalue", expression: "{country} = 'USA'", setToName: "currency", setValue: "USD" }, // Copy value trigger { type: "copyvalue", expression: "{sameAddress} = true", setToName: "shippingAddress", fromName: "billingAddress" }, // Run expression trigger { type: "runexpression", expression: "{score} >= 80", runExpression: "iif({score} >= 90, 'A', iif({score} >= 80, 'B', 'C'))", setToName: "grade" } ] }; const survey = new Model(surveyJson); ``` ## Themes and Styling ### Apply Predefined Themes Use built-in themes for instant styling. ```javascript import { Model } from 'survey-core'; import { DefaultLight, DefaultDark, LayeredLight, LayeredDark, FlatLight, BorderlessDark } from 'survey-core/themes'; import 'survey-core/survey-core.css'; const survey = new Model(surveyJson); // Apply a theme survey.applyTheme(LayeredDark); // Switch themes dynamically document.getElementById("themeSelector").addEventListener("change", (e) => { const themes = { "default-light": DefaultLight, "default-dark": DefaultDark, "layered-light": LayeredLight, "layered-dark": LayeredDark, "flat-light": FlatLight, "borderless-dark": BorderlessDark }; survey.applyTheme(themes[e.target.value]); }); ``` ### Create Custom Theme Define custom theme with CSS variables. ```javascript const customTheme = { "themeName": "corporate", "colorPalette": "light", "isPanelless": false, "cssVariables": { // Primary colors "--sjs-primary-backcolor": "#1e88e5", "--sjs-primary-backcolor-dark": "#1565c0", "--sjs-primary-backcolor-light": "#64b5f6", "--sjs-primary-forecolor": "#ffffff", // Secondary colors "--sjs-secondary-backcolor": "#f5f5f5", "--sjs-secondary-backcolor-light": "#fafafa", "--sjs-secondary-backcolor-semi-light": "#f8f8f8", "--sjs-secondary-forecolor": "#161616", // Base unit and spacing "--sjs-base-unit": "8px", "--sjs-corner-radius": "4px", // Typography "--sjs-font-family": "'Roboto', 'Helvetica', 'Arial', sans-serif", "--sjs-font-size": "16px", "--sjs-font-questiontitle-size": "18px", "--sjs-font-questiontitle-weight": "600", // Question spacing "--sjs-question-vertical-margin": "16px", // Borders "--sjs-border-default": "#e0e0e0", "--sjs-border-light": "#eeeeee", // Special colors "--sjs-special-red": "#d32f2f", "--sjs-special-green": "#388e3c", "--sjs-special-yellow": "#f57c00", // Shadow "--sjs-shadow-small": "0 1px 3px rgba(0, 0, 0, 0.12)", "--sjs-shadow-medium": "0 2px 6px rgba(0, 0, 0, 0.16)", "--sjs-shadow-large": "0 4px 12px rgba(0, 0, 0, 0.24)" } }; const survey = new Model(surveyJson); survey.applyTheme(customTheme); ``` ### Custom CSS Classes Override CSS classes for specific elements. ```javascript import { Model } from 'survey-core'; const survey = new Model(surveyJson); // Override specific CSS classes survey.onUpdateQuestionCssClasses.add((sender, options) => { const { question, cssClasses } = options; // Add custom class to all required questions if (question.isRequired) { cssClasses.root += " required-question"; cssClasses.title += " required-question-title"; } // Custom styling for specific question if (question.name === "importantField") { cssClasses.root += " highlighted-question"; } // Different styles based on question type if (question.getType() === "rating") { cssClasses.root += " custom-rating"; } }); // Panel CSS classes survey.onUpdatePanelCssClasses.add((sender, options) => { const { panel, cssClasses } = options; if (panel.name === "premiumSection") { cssClasses.container += " premium-panel"; } }); // Page CSS classes survey.onUpdatePageCssClasses.add((sender, options) => { const { page, cssClasses } = options; cssClasses.root += " custom-page"; }); // Choice item CSS classes survey.onUpdateChoiceItemCss.add((sender, options) => { const { question, item, css } = options; if (item.value === "premium") { css += " premium-choice"; } }); ``` ## Framework Integration ### React Integration Use SurveyJS in React applications. ```jsx // Installation: npm install survey-react-ui survey-core import { useState, useCallback } from 'react'; import { Model } from 'survey-core'; import { Survey } from 'survey-react-ui'; import { DefaultLight } from 'survey-core/themes'; import 'survey-core/survey-core.css'; const surveyJson = { title: "Customer Satisfaction Survey", pages: [{ elements: [{ type: "rating", name: "satisfaction", title: "How satisfied are you with our product?", rateMax: 5, isRequired: true }, { type: "comment", name: "feedback", title: "Additional feedback:", rows: 4 }] }] }; export default function SurveyComponent() { const [survey] = useState(() => { const surveyModel = new Model(surveyJson); surveyModel.applyTheme(DefaultLight); return surveyModel; }); const surveyComplete = useCallback((sender) => { const results = sender.data; console.log("Survey completed:", results); // Send to API fetch("/api/survey-results", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(results) }) .then(response => response.json()) .then(data => { alert("Thank you! Your response has been recorded."); }) .catch(error => { console.error("Error saving survey:", error); }); }, []); const valueChanged = useCallback((sender, options) => { // Auto-save to localStorage localStorage.setItem("survey-data", JSON.stringify(sender.data)); }, []); survey.onComplete.add(surveyComplete); survey.onValueChanged.add(valueChanged); // Restore incomplete survey useState(() => { const savedData = localStorage.getItem("survey-data"); if (savedData) { survey.data = JSON.parse(savedData); } }); return (
); } ``` ### Angular Integration Use SurveyJS in Angular applications. ```typescript // Installation: npm install survey-angular-ui survey-core @angular/cdk // app.module.ts import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { SurveyModule } from 'survey-angular-ui'; import { AppComponent } from './app.component'; @NgModule({ declarations: [AppComponent], imports: [ BrowserModule, SurveyModule ], bootstrap: [AppComponent] }) export class AppModule { } // survey.component.ts import { Component, OnInit } from '@angular/core'; import { Model } from 'survey-core'; import { DefaultLight } from 'survey-core/themes'; import 'survey-core/survey-core.css'; @Component({ selector: 'app-survey', template: `
`, styleUrls: ['./survey.component.css'] }) export class SurveyComponent implements OnInit { surveyModel: Model; surveyJson = { title: "Employee Feedback", pages: [{ elements: [{ type: "radiogroup", name: "department", title: "Department:", choices: ["Engineering", "Sales", "Marketing", "Support"], isRequired: true }, { type: "rating", name: "workEnvironment", title: "Work environment rating:", rateMax: 5 }, { type: "comment", name: "suggestions", title: "Suggestions for improvement:" }] }] }; ngOnInit() { const survey = new Model(this.surveyJson); survey.applyTheme(DefaultLight); survey.onComplete.add(this.onSurveyComplete); survey.onValueChanged.add(this.onValueChanged); this.surveyModel = survey; } onSurveyComplete(sender: Model) { const results = sender.data; console.log("Survey results:", results); // POST to API fetch("/api/feedback", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(results) }); } onValueChanged(sender: Model, options: any) { console.log(`Value changed: ${options.name} = ${options.value}`); } } ``` ### Vue Integration Use SurveyJS in Vue applications. ```vue ``` ### Vanilla JavaScript Integration Use SurveyJS without frameworks. ```html SurveyJS Form
``` ## Summary SurveyJS Form Library provides a comprehensive solution for creating dynamic, interactive forms and surveys in JavaScript applications. The library's JSON-based configuration approach enables developers to define complex form structures declaratively, separating form logic from application code. With support for 20+ question types including text inputs, dropdowns, matrices, file uploads, and signature pads, the library handles virtually any data collection requirement from simple contact forms to complex multi-page surveys with conditional branching. The library excels in enterprise scenarios where data sovereignty and customization are critical. All form data remains on your servers, eliminating third-party service dependencies and associated costs. Built-in validation engines, expression language support for calculations, and comprehensive event systems enable sophisticated form behaviors without external dependencies. The framework-agnostic architecture with first-class React, Angular, and Vue support ensures seamless integration into existing applications. Features like auto-save, conditional logic, dynamic text piping, REST API integration for choice loading, and 40+ built-in theme variations make SurveyJS suitable for everything from customer feedback forms to quiz applications to calculator forms requiring complex computations. Weekly updates and active community support ensure the library remains current with evolving web standards.