Skip to main content

Dynamic Question-Answer UI in Salesforce: Visualforce, Apex & Modern LWC Patterns

In plain words: A Dynamic Question-Answer UI generates intake forms, questionnaires, or audits dynamically from database records or metadata rather than hardcoding static form fields. In Salesforce, this is achieved by combining an Apex controller (which manages the questions, answers, and data models) with a dynamic UI template (like Visualforce or Lightning Web Components) to capture user input seamlessly.

Standard static pages fail when business requirements demand configurable questionnaires, dynamic intake audits, or interactive customer surveys. When questions frequently change or depend on user selections, hardcoding fields directly into a page requires continuous code deployments. Building a data-driven, dynamic question-and-answer architecture allows administrators to add or alter questions while the user interface renders the input controls automatically.

1. Architecture: How Dynamic Form Binding Works

Dynamic question generation relies on clean separation between the database schema, the Apex controller state, and the presentation layer:

  • Model Tier (Custom Objects / Wrapper Classes): Stores question definitions (e.g., prompt text, input type, required flag) in Custom Metadata or custom objects.
  • Controller Tier (Apex): Ingests the questions, initializes dynamic response maps (Map<String, String>), and processes submissions into target records.
  • View Tier (Visualforce / LWC): Iterates through collections using dynamic markup tags (<apex:repeat>, <apex:pageBlockTable>) to render the form fields dynamically.
360 Dynamic Q&A Architecture Card:
  • Data Storage: Custom Metadata Types, Custom Settings, or Custom Question Objects.
  • State Management: Apex Wrapper Classes (QuestionWrapper) binding question IDs to input response variables.
  • View Rendering: Dynamic Visualforce Iterators (<apex:repeat>) or LWC template directives (for:each).
  • Database Security: Enforced using with sharing and WITH USER_MODE on DML inserts.

2. Implementing the Apex Controller (Wrapper Pattern)

While simple maps can store string responses, using an Apex Wrapper Class is the enterprise best practice because it supports diverse input types (text, picklist options, checkboxes) and input validation.

Step 1: Create the Apex Controller Class
public with sharing class DynamicQAController {

    // Wrapper class to represent each dynamic question and its response
    public class QuestionItem {
        public String questionId { get; set; }
        public String promptText { get; set; }
        public String responseValue { get; set; }
        public Boolean isRequired { get; set; }

        public QuestionItem(String qId, String prompt, Boolean required) {
            this.questionId = qId;
            this.promptText = prompt;
            this.isRequired = required;
            this.responseValue = '';
        }
    }

    public List<QuestionItem> questionList { get; set; }

    public DynamicQAController() {
        loadQuestions();
    }

    private void loadQuestions() {
        questionList = new List<QuestionItem>();
        
        // In production, load these from Custom Metadata or a Custom Object
        questionList.add(new QuestionItem('Q1', 'What is your primary implementation timeline?', true));
        questionList.add(new QuestionItem('Q2', 'How many total user licenses are required?', true));
        questionList.add(new QuestionItem('Q3', 'Do you require third-party ERP integration?', false));
    }

    public PageReference processAnswers() {
        // Validate required fields
        for (QuestionItem item : questionList) {
            if (item.isRequired && String.isBlank(item.responseValue)) {
                ApexPages.addMessage(new ApexPages.Message(
                    ApexPages.Severity.ERROR, 
                    'Please provide an answer for: ' + item.promptText
                ));
                return null;
            }
        }

        // Process answers: Log or insert into target Salesforce objects
        List<Task> followUpTasks = new List<Task>();
        for (QuestionItem item : questionList) {
            System.debug(LoggingLevel.INFO, 'Processed ' + item.questionId + ': ' + item.responseValue);
        }

        ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.CONFIRM, 'Responses saved successfully!'));
        return null;
    }
}

3. Designing the Visualforce Presentation Page

Step 2: Build the Visualforce Form UI
Iterate over the wrapper collection and bind input elements to item.responseValue.
<apex:page controller="DynamicQAController" lightningStylesheets="true">
    <apex:sectionHeader title="Customer Intake Questionnaire" subtitle="Dynamic Form Wizard" />
    
    <apex:form id="qaForm">
        <apex:pageMessages />
        
        <apex:pageBlock title="Please Complete the Questionnaire" mode="edit">
            <apex:pageBlockButtons location="bottom">
                <apex:commandButton value="Submit Responses" action="{!processAnswers}" reRender="qaForm" status="savingStatus" />
                <apex:actionStatus id="savingStatus">
                    <apex:facet name="start">
                        <span style="color: #1f4e79; font-weight: bold; margin-left: 10px;">Saving...</span>
                    </apex:facet>
                </apex:actionStatus>
            </apex:pageBlockButtons>

            <apex:pageBlockTable value="{!questionList}" var="q" style="width:100%;">
                <apex:column headerValue="Question" style="width: 60%;">
                    <apex:outputLabel value="{!q.promptText}" style="font-weight: bold;" />
                    <apex:outputText value=" *" style="color: red;" rendered="{!q.isRequired}" />
                </apex:column>
                
                <apex:column headerValue="Your Response" style="width: 40%;">
                    <apex:inputText value="{!q.responseValue}" styleClass="slds-input" style="width: 90%;" />
                </apex:column>
            </apex:pageBlockTable>
        </apex:pageBlock>
    </apex:form>
</apex:page>

4. Modern Evolution: Migrating from Visualforce to LWC

While Visualforce provides legacy support, modern Salesforce architectures build dynamic questionnaires using Lightning Web Components (LWC) or Salesforce Flow:

  • Client-Side Reactivity: LWC renders conditional questions instantly in the browser without requiring full-page server round-trips (reRender).
  • Salesforce Screen Flows: Declarative administrators can use Screen Flows with Dynamic Form Screens and Component Visibility Rules to build zero-code dynamic questionnaires.
  • Lightning Design System (SLDS): LWC provides mobile-responsive, modern SLDS components (<lightning-input>, <lightning-radio-group>) out of the box.

5. Common Traps & Architectural Best Practices

Developer Trap: Direct Map Key Binding in Visualforce Forms
Attempting to bind direct map expressions like <apex:inputText value="{!answers[question]}" /> directly in markup often fails during postback if the key does not pre-exist in the controller's map instance. Always initialize wrapper collections or ensure all map keys are fully instantiated in the constructor before rendering the page.
Core Rule: Use Apex wrapper classes to encapsulate question metadata and responses, enforce input validation before database commits, and adopt Lightning Web Components (LWC) for modern responsive user experiences.
  • Enable lightningStylesheets: Always include lightningStylesheets="true" on the <apex:page> tag so legacy Visualforce pages match the modern Salesforce Lightning look and feel.
  • Enforce Object & Field Permissions: When saving responses to custom objects, use WITH USER_MODE or Security.stripInaccessible() to ensure field-level security is respected.
  • Sanitize User Inputs: Prevent Cross-Site Scripting (XSS) by using built-in Visualforce encoding or standard Apex string sanitization when redisplaying user answers.

Summary

Dynamic Question-Answer forms empower organizations to collect structured customer and audit data without requiring repetitive code deployments. By structuring question models inside Apex wrapper classes, binding fields dynamically in the view layer, and following secure data handling practices, developers can build scalable, interactive survey and intake workflows across both Visualforce and modern Lightning Web Components.