A tutorial and conceptual guide for building surveys with the SRP Survey DSL.
© 2026 SRP Solutions SRP LLC. All rights reserved.
The SRP Survey DSL and its parser are proprietary technology of SRP Solutions SRP LLC. This guide may be freely shared for non-commercial and educational purposes with attribution. For licensing inquiries or more information, contact SRP Surveys.
01 Introduction
1.1 What is the SRP Survey DSL?
SRP Survey DSL is a (S)imple, (R)eadable, (P)arsable language for defining surveys. You write survey structure in a .srp file using a clean, readable syntax, and the tool generates interactive HTML surveys from that description.
The DSL handles:
- Survey structure (pages, groups)
- Question types (34 types, from simple radio buttons to heatmap image interactions)
- Conditional logic (show questions/pages based on previous answers)
- Segmentation (tag respondents into groups based on responses)
- Survey flow control (skip logic, early termination, completion)
1.2 Key Concepts
| Concept | Description |
|---|---|
| Page | Container for questions; respondents see one page at a time |
| Question | An individual question within a page |
| Option / Item / Row / Column | The answer choices within a question |
| Segment | A tag applied to a respondent based on their answer |
| Action | An instruction that controls survey flow (skip, terminate, complete) |
| Group | A named collection of pages (for navigation) or segments (for AND logic) |
1.3 How a Survey Runs
- Respondent opens the survey HTML in a browser
- Pages are shown one at a time in definition order (unless skip logic applies)
- Segments are activated in real-time as the respondent makes selections
- Conditional pages and questions appear/disappear based on active segments
- Navigation actions (
skip_to,complete,terminate) route the respondent - Responses are auto-saved to IndexedDB; resumable across browser sessions
- Survey ends when a
completeorterminatepage is reached
02 Building Your First Survey
2.1 Minimal Survey
Every survey needs at least one page with a title, and best practice is to end with an explicit completion page.
# my_survey.srp
page "welcome" do
title "Welcome to Our Survey"
"Thank you for participating. This survey takes about 5 minutes."
end
page "questions" do
title "Your Opinion"
SingleSelect "satisfaction" do
text "How satisfied are you with our service?"
required
option "Very satisfied"
option "Satisfied"
option "Neutral"
option "Dissatisfied"
end
end
page "thank_you" do
title "Thank You"
action :complete
Notification "completion_message" do
text "Your responses have been recorded. Thank you!"
end
end
2.2 Adding Questions
Questions go inside page blocks. Every question requires a unique name as its first argument:
page "demographics" do
title "About You"
SingleSelect "age_group" do
text "What is your age group?"
required
option "18–24"
option "25–34"
option "35–44"
option "45–54"
option "55+"
end
MultiSelect "interests" do
text "What topics interest you? (Select all that apply)"
min_selections 1
option "Technology"
option "Business"
option "Design"
option "Science"
option "Arts"
end
OpenEnded "comments" do
text "Any other comments?"
end
end
2.3 Running and Validating
# Check for errors
ruby survey_dsl.rb --file my_survey.srp --validate
# Generate HTML and open in browser
ruby survey_dsl.rb --file my_survey.srp --outfile my_survey.html
open my_survey.html # macOS; use xdg-open on Linux
# Debug mode with full stack traces
DEBUG=1 ruby survey_dsl.rb --file my_survey.srp
03 Pages and Structure
Page Definition Rules
- Unique names — Each page name must be unique across the survey
- Define once — A page appears exactly once in the
.srpfile - Order matters — Default flow follows definition order
- Completion pages at the end — Pages with
action :completeoraction :terminateshould appear last
# ✅ Pages defined in logical flow order
page "intro" do ... end
page "screener" do ... end
page "main_questions" do ... end
page "thank_you" do
action :complete
...
end
page "disqualified" do
action :terminate
...
end
Page Attributes
page "my_page" do
title "Page Title" # Required — shown as heading
text "Optional description" # Creates a Notification question
randomized # Randomize question order on this page
show_only_if "segment_name" # Conditional display
action :complete # :complete or :terminate (optional)
# PageTimer — control how long respondents spend on this page
min_seconds 15 # Block Next until 15s elapsed
max_seconds 120 # Auto-advance after 120s
show_timer true # Display countdown/stopwatch
end
PageTimer attributes are independent — use any combination:
min_secondsprevents rushed responses (e.g., stimulus exposure tasks)max_secondsauto-advances after a fixed window (e.g., timed exercises)show_timermakes the timer visible; without it, the countdown runs silently
Groups
Groups bundle pages together for navigation and optional randomization.
# Sequential group — skip_to "onboarding" always goes to "welcome" first
group "onboarding", "welcome", "instructions", "practice"
# Randomized group — useful for A/B testing or reducing order bias
group "product_survey", "version_a", "version_b" do
randomize
end
# Navigate to a group
option "Start tutorial" do
action :skip_to, "onboarding"
end
04 Question Types
All questions share a common pattern:
QuestionType "unique_name" do
text "Question prompt"
required # optional
# type-specific attributes
end
For the complete syntax of each type, see DSL_REFERENCE.md §6.
4.1 Text Input
| Type | When to Use |
|---|---|
OpenEnded | Short free-text responses; supports numeric mode and if_value actions |
Number | Numeric input with unit display, decimal control, and explicit min/max |
Discussion | Long free-text responses (multi-line textarea) |
FillInTheBlank | Sentence completion tasks |
Use OpenEnded with numeric for simple age/income questions. Use Number when you need explicit unit labels or decimal control.
OpenEnded "age" do
text "What is your age?"
numeric :min_value, 18, :max_value, 120
required
action :terminate, :if_value, "< 18"
end
Number "weight" do
text "What is your current weight?"
min_value 0.1
max_value 500.0
unit_label "lbs"
placeholder "Enter weight"
end
FillInTheBlank "completion" do
text "I would describe our service as ___ and ___."
required
end
4.2 Selection
| Type | When to Use |
|---|---|
SingleSelect | Choose one from several; default radio buttons |
MultiSelect | Choose multiple from several |
Dropdown | Long lists of options (countries, states, etc.) |
ButtonCheckbox | Multi-select rendered as toggle buttons |
ButtonRating | Labeled scale rendered as buttons |
SingleSelect "preference" do
text "Which option do you prefer?"
randomized
add_other_option
option "Option A"
option "Option B"
option "Option C"
end
Dropdown "country" do
text "Select your country."
option "United States"
option "Canada"
option "United Kingdom"
add_other_option
required
end
When to use Dropdown vs SingleSelect
Use Dropdown when you have more than ~8 options or need a compact layout. Use SingleSelect (radio style) when you have fewer options and want them all visible at once.
SingleSelect Styles
SingleSelect supports three rendering styles via the style attribute: :radio (default — vertical radio buttons), :dropdown (compact select box), and :slider (continuous scale rendered as an inline slider). The slider style requires min_label and max_label to label the poles.
# Default radio style (no style declaration needed)
SingleSelect "channel" do
text "How did you hear about us?"
option "Social Media"
option "Friend or Colleague"
option "Search Engine"
option "Advertisement"
required
end
# Dropdown style — compact, good for long option lists
SingleSelect "country_single" do
text "Which country are you from?"
style :dropdown
option "United States"
option "Canada"
option "United Kingdom"
required
end
# Slider style — continuous scale rendered as inline slider
SingleSelect "agreement" do
text "How much do you agree with this statement?"
style :slider
min_label "Strongly Disagree"
max_label "Strongly Agree"
required
end
ButtonCheckbox and ButtonRating
Both types use button — not option — to define their choices. ButtonCheckbox allows multiple selections; ButtonRating is single-select and best used for labeled scales.
ButtonCheckbox "important_features" do
text "Which features matter most? (Select up to 3)"
min_selections 1
max_selections 3
randomize_buttons
button "Price"
button "Quality"
button "Speed"
button "Support"
required
end
ButtonRating "satisfaction" do
text "How satisfied are you with our service?"
button "Very Dissatisfied"
button "Dissatisfied"
button "Neutral"
button "Satisfied"
button "Very Satisfied"
add_why_field
required
end
4.3 Ranking and Sorting
| Type | When to Use |
|---|---|
Ranking | Order items by preference |
CardSort | Group items into categories (information architecture research) |
CardRating | Rate items one at a time |
MaxDiff | Best-worst scaling; most/least important |
Ranking "priorities" do
text "Rank these features from most to least important."
randomize_items
item "Performance"
item "Ease of Use"
item "Price"
item "Customer Support"
required
end
MaxDiff "feature_prefs" do
text "For each set, select the MOST and LEAST important feature."
items_per_set 4
randomize_sets
item "Price"
item "Quality"
item "Speed"
item "Support"
item "Innovation"
item "Brand"
required
end
CardRating
CardRating presents items one at a time on individual cards. Respondents rate each card on a numeric scale (1 to number_of_ranks). Use randomize_items to vary card order across respondents.
CardRating "concept_ratings" do
text "Rate each concept on the scale below."
number_of_ranks 5
randomize_items
item "Convenience"
item "Value for Money"
item "Innovation"
required
end
CardSort
CardSort requires both item entries (the cards respondents drag) and category entries (the buckets to sort into). Use allow_unsorted to let respondents leave cards unclassified.
CardSort "navigation_sort" do
text "Sort each page into the category where you'd expect to find it."
randomize_items
allow_unsorted
item "Our Team"
item "Pricing"
item "Blog"
item "API Documentation"
category "Company Info"
category "Products"
category "Resources"
required
end
4.4 Matrix Questions
Matrix questions show multiple items (rows) rated on shared criteria (columns).
| Type | When to Use |
|---|---|
SingleSelectMatrix | Rate multiple items on the same scale |
MultiSelectMatrix | Select multiple attributes for each item |
OpenEndedMatrix | Collect text for each row-column combination |
RatingMatrix | Multiple items on a shared numeric rating scale |
SingleSelectBipolar | Rate items on opposing-pole scales |
SingleSelectBipolarMatrix | Multiple items, each on its own bipolar scale |
SingleSelectMatrix "service_ratings" do
text "Rate each aspect of our service."
required
randomize_rows
row "Customer Support"
row "Product Quality"
row "Pricing"
row "Onboarding"
column "Excellent"
column "Good"
column "Fair"
column "Poor"
end
SingleSelectBipolarMatrix
SingleSelectBipolarMatrix is a matrix where each column has its own pair of opposing poles instead of shared labels. Define columns with a block containing left_label and right_label. Use scale to control the number of points on each bipolar scale.
SingleSelectBipolarMatrix "brand_perceptions" do
text "Rate our brand on each dimension."
scale 7
randomize_rows
required
row "Design"
row "Reliability"
row "Value"
column "Quality" do
left_label "Low Quality"
right_label "High Quality"
end
column "Personality" do
left_label "Boring"
right_label "Exciting"
end
end
4.5 Rating Questions
| Type | When to Use |
|---|---|
Rating | Star or numeric rating for one item |
Slider | Continuous or stepped scale; supports multiple rows |
NPS | Net Promoter Score (fixed 0–10 scale) |
ThisOrThat | Forced binary choice (A/B) |
Rating "overall_satisfaction" do
text "How would you rate your overall experience?"
number_of_ranks 5 # default; adjust for a different scale size
add_why_field
required
end
# Single-track slider
Slider "budget_comfort" do
text "How comfortable are you with our pricing?"
min_value 0
max_value 10
step 1
default_value 5
min_label "Not at all comfortable"
max_label "Very comfortable"
add_why_field
required
end
# Multi-row slider — one track per row
Slider "attribute_ratings" do
text "Rate each attribute on a 1–7 scale."
min_value 1
max_value 7
default_value 4
row "Ease of Use"
row "Performance"
row "Value for Money"
required
end
NPS "recommend_score" do
text "How likely are you to recommend us?"
min_label "Not at all likely"
max_label "Extremely likely"
add_why_field
required
end
ThisOrThat "style_preference" do
text "Which design style do you prefer?"
this "Minimal"
that "Feature-rich"
add_why_field
required
end
SingleSelectBipolar
SingleSelectBipolar is the single-item variant of a bipolar scale — one question with two opposing poles. Use scale to set the number of points, left_label / right_label for the pole labels, and add_none_option to allow a "Not applicable" opt-out.
SingleSelectBipolar "brand_impression" do
text "Rate your overall impression of our brand."
left_label "Very Negative"
right_label "Very Positive"
scale 7
add_none_option
required
end
4.6 Image-Based Questions
| Type | When to Use |
|---|---|
HeatMap | Capture where respondents click on an image |
TimedHeatMap | HeatMap with a countdown timer |
StickyNote | Respondents place labeled notes on an image |
HeatMap "ui_feedback" do
text "Click on areas of the interface that feel confusing."
image "https://example.com/interface-screenshot.png"
min_clicks 1
max_clicks 5
category "Confusing" do
color "#e74c3c"
end
category "Works well" do
color "#2ecc71"
end
required
end
TimedHeatMap
TimedHeatMap works like HeatMap but shows the image for a limited time before hiding it. The time_limit attribute (in seconds) is required — the image disappears after the countdown, then the respondent records their clicks.
TimedHeatMap "first_impression" do
text "Click on the first thing that catches your eye. You have 5 seconds."
image "https://example.com/landing-page.png"
time_limit 5 # seconds — required
min_clicks 1
max_clicks 3
required
end
StickyNote
StickyNote lets respondents place labeled sticky notes on an image. Each category block defines a note type; the color attribute sets the note's background color.
StickyNote "feedback_notes" do
text "Place notes on the areas of the page you'd like to comment on."
image "https://example.com/interface.png"
min_clicks 1
max_clicks 5
category "I like this" do
color "#2ecc71"
end
category "Needs improvement" do
color "#e74c3c"
end
required
end
4.7 Specialized Questions
| Type | When to Use |
|---|---|
ConstantSum | Budget allocation / point distribution |
DatePicker | Date input with range constraints |
Autosuggest | Typeahead text input with pre-defined suggestions |
TextHighlighter | Respondent highlights phrases in a passage |
USAddress | US address collection |
InternationalAddress | International address collection |
Age | Date-of-birth (month/day/year) |
PhoneNumber | Phone number input |
MediaUpload | File upload (image, video, document) |
DatePicker "event_date" do
text "When did the issue occur?"
min_date "2020-01-01" # ISO 8601 format: YYYY-MM-DD
max_date "2026-12-31"
required
end
# Static suggestion list
Autosuggest "brand" do
text "Which brand comes to mind first?"
suggestions ["Nike", "Adidas", "Puma", "Reebok", "New Balance"]
allow_custom false # restrict to listed suggestions only
placeholder "Start typing a brand..."
required
end
# Server-side suggestions (pass a URL path for live lookup)
Autosuggest "city" do
text "Which city do you live in?"
suggestions "/api/cities"
placeholder "Type your city..."
required
end
TextHighlighter "ad_reactions" do
text "Highlight phrases you like or dislike in this ad copy."
passage "Experience the future of coffee with our revolutionary BrewMaster Pro. Every morning deserves a perfect cup crafted just for you. Our patented SmartBrew technology learns your preferences."
categories ["Like", "Dislike", "Confusing"] # colors auto-assigned; up to 6 categories
min_highlights 1
max_highlights 5
required
end
ConstantSum "budget_allocation" do
text "Allocate 100 points across these priorities."
sum_to 100 # default is 100
randomize_rows
required
row "Quality"
row "Speed"
row "Price"
row "Support"
end
Address and demographic types have minimal syntax — they render structured, validated input fields automatically:
USAddress "home_address" do
text "Please enter your home address."
required
end
InternationalAddress "billing_address" do
text "Please enter your billing address."
required
end
Age "date_of_birth" do
text "Please enter your date of birth."
required
end
PhoneNumber "contact_phone" do
text "Please enter your phone number."
required
end
MediaUpload "supporting_docs" do
text "Upload any supporting documents (optional)."
accept ".pdf,.docx,.png,image/*" # MIME types or file extensions
max_files 3
multiple true # default; set false for single-file only
end
4.8 Choice Experiments (ConjointChoice)
ConjointChoice presents concept cards side-by-side; each card shows the same set of attributes with different values (levels). The respondent picks their preferred option.
ConjointChoice "car_pref" do
text "Which car would you be most likely to purchase?"
required
attribute "Brand"
attribute "Price"
attribute "Fuel Type"
attribute "Warranty"
profile "Option A" do
level "Brand", "Toyota"
level "Price", "$28,000"
level "Fuel Type", "Hybrid"
level "Warranty", "3 years"
end
profile "Option B" do
level "Brand", "Ford"
level "Price", "$24,000"
level "Fuel Type", "Gasoline"
level "Warranty", "5 years"
end
add_none_option # adds "None of these" card
randomize_profiles # shuffle card order to reduce position bias
end
Tips:
- Define at least 2 profiles; 2–5 is typical per task
- Use the same attributes across all profiles so the table layout is consistent
randomize_profilesreduces left/right position bias across respondents- Chain multiple
ConjointChoicequestions on separate pages to vary the trade-off presented
4.9 Display Elements
Notification displays text without requiring any input. It cannot be marked required.
Notification "instructions" do
text "Please read each question carefully before answering."
end
At the page level, a bare string is a shorthand for Notification:
page "welcome" do
title "Welcome"
"Thank you for participating in this survey."
end
4.10 EmotionSelector
EmotionSelector presents an emoji-based emotional response question. By default it shows five preset emotions (😄 Delighted, 🙂 Happy, 😐 Neutral, 🙁 Unhappy, 😠 Angry) — no configuration needed. Adding any emotion declaration replaces the entire default set with your custom emotions.
# Default emotions — works with no additional attributes
EmotionSelector "feel_about_product" do
text "How do you feel about this product?"
required
end
# Custom emotions — replaces all five defaults
EmotionSelector "brand_reaction" do
text "How does this brand make you feel?"
emotion "excited", label: "Excited", emoji: "🤩"
emotion "curious", label: "Curious", emoji: "🤔"
emotion "satisfied", label: "Satisfied", emoji: "😌"
emotion "bored", label: "Bored", emoji: "😑"
emotion "annoyed", label: "Annoyed", emoji: "😤"
add_why_field
required
end
Custom vs. default: Defining even one emotion replaces the full default set. If you want to keep the standard five emotions, omit all emotion declarations entirely.
05 Rich Text Formatting
The DSL processes markdown automatically in all text values.
| Markdown | Result |
|---|---|
**bold** | bold |
*italic* or _italic_ | italic |
__underline__ | underlined text |
`code` | inline code |
~~strikethrough~~ |
Works in: question text, option labels, row/column labels, page titles, notification text.
SingleSelect "app_rating" do
text "How **satisfied** are you with our _new_ app?"
option "**Very** satisfied"
option "*Somewhat* satisfied"
option "~~Dissatisfied~~ Not satisfied"
end
06 Collecting Specific Answer Types
Special Options
SingleSelect "experience" do
text "Rate your experience."
option "Excellent"
option "Good"
add_none_option # Appends "None" at the bottom
add_dont_know_option # Appends "Don't Know"
add_other_option # Appends "Other (please specify)"
add_why_field # Appends a free-text "Why?" field
end
Anchoring Options During Randomization
Use anchored inside an option block to keep it at the bottom when randomized is active:
SingleSelect "programming_language" do
text "What is your favorite programming language?"
randomized
option "Python"
option "JavaScript"
option "Ruby"
option "Java"
option "None of the above" do
anchored
end
end
Numeric Constraints
MultiSelect "team_tools" do
text "Which tools does your team use? (Select up to 3)"
max_selections 3
option "Slack"
option "Jira"
option "Confluence"
option "GitHub"
option "Figma"
end
MultiSelectMatrix "feature_usage" do
text "For each product, select all features you use."
min_selections_per_row 1
max_selections_per_row 3
row "Product A"
row "Product B"
column "Analytics"
column "Reporting"
column "Integration"
column "Automation"
end
07 Conditional Logic and Segmentation
7.1 The Concept: Segments
A segment is a tag that gets attached to a respondent when they make a specific selection. Once a segment is active, it stays active for the rest of the survey.
Segments are the foundation of conditional logic: pages and questions can be shown or hidden based on which segments are active.
7.2 Creating Segments
Segments are created inside option, row, or column blocks:
# Option segment — activates when this option is selected
SingleSelect "role" do
text "What is your role?"
option "Developer" do
segment "developer"
end
option "Manager" do
segment "manager"
segment "decision_maker" # A respondent can have multiple segments
end
end
# Matrix row segment — activates based on column selection
SingleSelectMatrix "ratings" do
text "Rate our services."
row "Customer Support" do
segment "support_issues", :if_column, "Poor"
segment "support_satisfied", :if_column, "Excellent", "Good" # OR logic
end
column "Excellent"
column "Good"
column "Fair"
column "Poor"
end
# Matrix column segment — activates based on row selection
SingleSelectMatrix "ratings" do
text "Rate our services."
row "Customer Support"
row "Product Quality"
column "Poor" do
segment "cs_issues", :if_row, "Customer Support"
segment "quality_issues", :if_row, "Product Quality"
end
end
7.3 Using Segments with show_only_if
Once segments exist, use show_only_if to make pages and questions conditional:
# Single segment condition
page "developer_questions" do
title "Developer Feedback"
show_only_if "developer"
SingleSelect "ide_preference" do
text "Which IDE do you prefer?"
option "VS Code"
option "JetBrains"
option "Vim/Neovim"
end
end
# Multiple segments — shown if respondent is in ANY (OR logic)
page "feedback_collection" do
title "Help Us Improve"
show_only_if "support_issues", "quality_issues"
OpenEnded "improvement_suggestions" do
text "What specific improvements would you like to see?"
required
end
end
Page-level vs. question-level show_only_if
The DSL syntax is identical at both levels, but the scope and consequences differ:
- Page-level — the entire page (title and all questions) is hidden as a unit. The respondent never visits the page; required questions on it are automatically bypassed.
- Question-level — only that one question is hidden. The page is still visited and its other questions render normally. The hidden question is excluded from required validation.
You can combine both: a page can have its own show_only_if and also contain questions with their own show_only_if. The page condition is the outer gate — a question's condition is only relevant if its page is already visible.
7.4 AND Logic: Group-Segments
Use group to require ALL listed segments to be active simultaneously (AND logic):
# Create segments
MultiSelect "pets" do
text "Which pets do you own?"
option "Dog" do
segment "dog_owner"
end
option "Cat" do
segment "cat_owner"
end
end
# Group requires BOTH dog_owner AND cat_owner
group "multi_pet_owner", "dog_owner", "cat_owner"
# Only shown to respondents who own BOTH a dog AND a cat
page "multi_pet_care" do
title "Caring for Multiple Pets"
show_only_if "multi_pet_owner"
...
end
How the parser tells page groups from segment groups: After the full survey file is parsed, the validator checks every group's members against all defined segment names. If every member is a known segment name, the group becomes a segment group (AND logic). If no member is a segment name, the group is treated as a page group and its members must all be valid page names. If a group mixes segment names and page names, a validation error is raised immediately.
7.5 Mixed AND/OR Logic
Combine groups and segments in show_only_if for complex targeting:
group "premium_candidate", "power_user", "high_satisfaction"
# Show to: (power_user AND high_satisfaction) OR bird_owner
page "upgrade_offer" do
show_only_if "premium_candidate", "bird_owner"
...
end
7.6 Practical Segmentation Recipe
Here is a complete, realistic example of segmentation for a customer satisfaction survey:
page "satisfaction" do
title "Your Experience"
SingleSelectMatrix "service_ratings" do
text "Rate each aspect of your experience."
required
row "Customer Support" do
segment "support_poor", :if_column, "Poor", "Fair"
segment "support_great", :if_column, "Excellent"
end
row "Product Quality" do
segment "quality_poor", :if_column, "Poor"
end
column "Excellent"
column "Good"
column "Fair"
column "Poor"
end
end
# Only shown to respondents with support concerns
page "support_improvement" do
title "Help Us Improve Support"
show_only_if "support_poor"
MultiSelect "support_improvements" do
text "What would improve your support experience?"
min_selections 1
option "Faster response times"
option "More knowledgeable staff"
option "Better documentation"
option "24/7 availability"
end
end
# Testimonial request for satisfied respondents
page "testimonial_request" do
title "Share Your Story"
show_only_if "support_great"
SingleSelect "testimonial_consent" do
text "Would you be willing to share your positive experience?"
option "Yes"
option "Maybe later"
option "No"
end
end
08 Survey Navigation and Actions
8.1 Default Flow
Without any actions, respondents move through pages in definition order. Pages with show_only_if conditions that are not met are automatically skipped.
8.2 Page Actions (:complete, :terminate)
Pages support action :complete (survey finished successfully) or action :terminate (disqualified or early exit). Pages with these actions should be defined at the end of the .srp file.
page "thank_you" do
title "Thank You"
action :complete
Notification "completion_message" do
text "Your responses have been recorded."
end
end
page "disqualified" do
title "Thank You for Your Time"
action :terminate
Notification "disqualify_message" do
text "Unfortunately, you do not meet the criteria for this survey."
end
end
8.3 Option Actions
Options can trigger navigation when selected:
SingleSelect "age_check" do
text "Are you 18 or older?"
required
option "Yes" do
action :skip_to, "main_survey"
end
option "No" do
action :skip_to, "disqualified"
end
end
8.4 Matrix Conditional Actions (:if_column, :if_row)
Matrix rows and columns can trigger actions based on their specific selections:
SingleSelectMatrix "feature_ratings" do
text "Rate these features."
row "Critical Feature" do
# Skip to detailed feedback if this row is rated Poor
action :skip_to, "detailed_feedback", :if_column, "Poor"
end
column "Poor" do
# Skip to improvement page if Customer Support is rated Poor
action :skip_to, "improvement_page", :if_row, "Customer Support"
end
column "Excellent"
column "Good"
column "Fair"
column "Poor"
end
Multi-Condition AND Logic (Matrix)
Combine :if_column and :if_row in a single action to require both conditions:
row "Technical Support" do
# Skip only if THIS row is rated Poor AND Customer Support is ALSO Poor
action :skip_to, "escalation", :if_column, "Poor", :if_row, "Customer Support", "Poor"
end
Count-Based Actions
Trigger an action based on how many rows have a particular column value:
row "Service Quality" do
# Skip if more than 2 rows are rated "Poor" overall
action :skip_to, "improvement_survey", :if_column_count, "Poor", :greater_than, 2
end
Count operators: :greater_than, :greater_than_or_equal, :equals, :less_than, :less_than_or_equal
Segment-Based Conditions (:if_segment, :if_group)
Matrix row blocks also support two segment-awareness conditions. :if_segment fires only when a specific named segment is active for the current respondent. :if_group fires only when all segments belonging to a named group are simultaneously active, enabling AND-logic across multiple matrix rows.
row "Critical Issue" do
# Skip to escalation only if the respondent is also in the "enterprise" segment
action :skip_to, "escalation_page", :if_segment, "enterprise_customer"
# Skip to priority page only if ALL segments in the group are active
action :skip_to, "priority_review", :if_group, "high_value_at_risk"
end
8.5 Value-Based Actions (:if_value on OpenEnded)
OpenEnded questions can trigger actions based on what the respondent types:
OpenEnded "age" do
text "What is your age?"
numeric :min_value, 0, :max_value, 120
required
action :terminate, :if_value, "< 18"
action :terminate, :if_value, "> 69"
action :skip_to, "senior_questions", :if_value, ">= 65"
end
# Text matching with lists
OpenEnded "programming_language" do
text "What is your primary programming language?"
action :skip_to, "web_dev", :if_value, "== JavaScript, TypeScript, HTML"
action :skip_to, "systems", :if_value, "== C, C++, Rust, Go"
end
Comparison operators: <, <=, >, >=, ==, !=
For == and !=, comma-separated lists use OR logic: "== val1, val2" means "equals val1 OR val2".
8.6 Multiple Conditional Actions on OpenEnded
OpenEnded questions support multiple action calls. Actions are evaluated in declaration order; the first matching condition fires and stops evaluation.
# ✓ All three actions are evaluated in order
OpenEnded "income" do
text "What is your annual household income?"
numeric :min_value, 0
action :terminate, :if_value, "< 10000"
action :skip_to, "high_income", :if_value, "> 150000"
action :skip_to, "standard", :if_value, ">= 10000"
end
Note: option, row, and column blocks each support exactly one action — a second action call overwrites the first.
8.7 Groups for Navigation
Navigate to a group to jump to a collection of pages:
group "product_section", "product_a", "product_b", "product_c" do
randomize # Random page from the group
end
option "Tell us about your product" do
action :skip_to, "product_section"
end
09 Dynamic Content
9.1 Text Piping ({{question_name}})
Use {{question_name}} in any text field to insert the respondent's previous answer at runtime:
SingleSelect "favorite_fruit" do
text "What is your favorite fruit?"
option "Apple"
option "Banana"
option "Cherry"
end
SingleSelect "fruit_rating" do
text "You selected {{favorite_fruit}}. How would you rate it?"
option "Excellent"
option "Good"
option "Poor"
end
Notification "summary" do
text "Thank you for your feedback about {{favorite_fruit}}!"
end
Rules:
- The referenced question must appear before the piping question in survey flow
- Before the respondent answers the source question, a placeholder
___is shown - Piped text updates reactively if the respondent goes back and changes their answer
- Works with markdown:
"You chose **{{favorite_fruit}}**"
Display formats by source type:
| Source Type | Display |
|---|---|
SingleSelect, Dropdown | Selected option text |
MultiSelect, ButtonCheckbox | Comma-separated with "and" before last item |
OpenEnded, Discussion | The typed text |
9.2 Dynamic Options (options_from, rows_from, columns_from)
Populate a question's options or rows from a previous question's selections:
MultiSelect "owned_products" do
text "Which products do you own?"
option "Laptop"
option "Phone"
option "Tablet"
option "Smartwatch"
end
# Only the selected products become rows
SingleSelectMatrix "product_satisfaction" do
text "Rate your satisfaction with each product you own."
rows_from "owned_products"
required
column "Very Satisfied"
column "Satisfied"
column "Neutral"
column "Dissatisfied"
end
Rules:
- Source question must appear before the dependent question in survey flow
- If the source has no selections, an informational message is shown
options_from,rows_from, andcolumns_fromeach accept one source question name
10 Hidden Questions
A question marked hidden exists in the survey's logic but is not rendered to the respondent. Useful for tracking variables, session metadata, or validation placeholders.
page "survey_start" do
title "Your Feedback"
# Visible question
SingleSelect "satisfaction" do
text "How satisfied are you overall?"
required
option "Satisfied" do
segment "satisfied"
end
option "Unsatisfied" do
segment "unsatisfied"
end
end
# Hidden — not shown to respondent, used for internal tracking
OpenEnded "session_timestamp" do
text "Session start timestamp"
hidden
end
end
Hidden questions:
- Do not appear in rendered HTML
- Still participate in segment evaluation and conditional logic
- Are excluded from required-field validation
- Do not count against recommended question limits per page
11 Best Practices
Survey Design
- Keep question text under 500 characters
- Limit radio button options to 10 or fewer for readability
- Use descriptive page titles that reflect what the page is about
- Set appropriate
min_selections/max_selectionsfor multi-select questions - Always end surveys with an explicit completion page containing a
Notificationquestion
Question Naming
- Use snake_case:
"satisfaction_level"not"SatisfactionLevel" - Use descriptive names:
"demo_age"not"q1" - Prefix by section for easier maintenance:
"demo_*","product_*","satisfaction_*" - Names must be unique across the entire survey
Page Organization
- Define pages in the order respondents will encounter them
- Pages with
action :completeoraction :terminatego at the end - Use
show_only_ifto skip pages that don't apply — don't useskip_tofor everything - Group related pages together for easier maintenance
Segmentation
- Only define segments that are referenced elsewhere in
show_only_ifor group conditions - Keep segment names descriptive:
"power_user"not"s1" - Use group-segments (AND logic) sparingly; OR logic is often sufficient
- Test segment activation by validating your
.srpfile
12 Common Patterns and Recipes
Screener Survey (Early Termination)
page "screener" do
title "Quick Qualification Check"
SingleSelect "age_check" do
text "Are you 18 or older?"
required
option "Yes"
option "No" do
action :skip_to, "disqualified"
end
end
SingleSelect "customer_check" do
text "Are you a current customer?"
required
option "Yes"
option "No" do
action :skip_to, "disqualified"
end
end
end
page "main_survey" do
title "Your Feedback"
# ... main questions
end
page "complete" do
title "Thank You"
action :complete
Notification "completion_message" do
text "Thank you for completing this survey!"
end
end
page "disqualified" do
title "Thank You for Your Interest"
action :terminate
Notification "disqualify_message" do
text "Thank you, but you do not qualify for this survey."
end
end
Matrix with Targeted Follow-Up
page "ratings" do
title "Product Ratings"
SingleSelectMatrix "product_ratings" do
text "Rate each product."
required
row "Product A" do
segment "product_a_poor", :if_column, "Poor", "Fair"
end
row "Product B" do
segment "product_b_poor", :if_column, "Poor", "Fair"
end
column "Excellent"
column "Good"
column "Fair"
column "Poor"
end
end
page "product_a_followup" do
title "Product A Feedback"
show_only_if "product_a_poor"
OpenEnded "product_a_issues" do
text "What issues did you have with Product A?"
required
end
end
page "product_b_followup" do
title "Product B Feedback"
show_only_if "product_b_poor"
OpenEnded "product_b_issues" do
text "What issues did you have with Product B?"
required
end
end
Dynamic Follow-Up (options_from + show_only_if)
page "selection" do
title "Your Preferences"
MultiSelect "selected_features" do
text "Which features have you used?"
required
option "Analytics" do
segment "analytics_user"
end
option "Reporting" do
segment "reporting_user"
end
option "Integration" do
segment "integration_user"
end
end
end
page "feature_ratings" do
title "Rate Your Experiences"
show_only_if "analytics_user", "reporting_user", "integration_user"
SingleSelectMatrix "feature_satisfaction" do
text "Rate each feature you have used."
rows_from "selected_features" # Only rows for features they selected
required
column "Very Satisfied"
column "Satisfied"
column "Neutral"
column "Dissatisfied"
end
end