Conditional Schema Branching (Display Logic)¶
Conditional schema branching shows or hides annotation schemas according to what the annotator answered elsewhere on the page. This is useful for:
- Follow-up questions when specific answers are selected
- Branching survey-style annotation flows
- Requiring additional details only when relevant
- Creating cleaner interfaces by hiding irrelevant options
Quick Start¶
Add a display_logic block to any annotation scheme:
annotation_schemes:
# Primary question - always visible
- annotation_type: radio
name: contains_pii
description: "Does this text contain PII?"
labels:
- name: "Yes"
- name: "No"
# Follow-up - only shown when "Yes" is selected above
- annotation_type: text
name: pii_explanation
description: "Describe the PII found:"
display_logic:
show_when:
- schema: contains_pii
operator: equals
value: "Yes"
Configuration Reference¶
Basic Structure¶
display_logic:
show_when:
- schema: <schema_name> # Name of the schema to watch
operator: <operator> # Comparison operator
value: <value> # Value(s) to compare against
case_sensitive: false # Optional, default: false
logic: all # Optional: 'all' (AND) or 'any' (OR)
Supported Operators¶
Value Comparison¶
| Operator | Description | Example |
|---|---|---|
equals |
Exact value match | value: "Yes" or value: ["Yes", "Maybe"] |
not_equals |
Value doesn't match | value: "No" |
Collection Operators¶
| Operator | Description | Example |
|---|---|---|
contains |
List/text contains value | value: "keyword" |
not_contains |
Doesn't contain | value: "spam" |
Regex Matching¶
| Operator | Description | Example |
|---|---|---|
matches |
Regex pattern match | value: "^[A-Z]{2}\\d{4}$" |
Numeric Comparison (for sliders, number inputs)¶
| Operator | Description | Example |
|---|---|---|
gt |
Greater than | value: 5 |
gte |
Greater than or equal | value: 5 |
lt |
Less than | value: 5 |
lte |
Less than or equal | value: 5 |
in_range |
Within range (inclusive) | value: [3, 7] |
not_in_range |
Outside range | value: [3, 7] |
Emptiness Checks¶
| Operator | Description | Example |
|---|---|---|
empty |
Field is empty/not set | (no value needed) |
not_empty |
Field has a value | (no value needed) |
Text Length¶
| Operator | Description | Example |
|---|---|---|
length_gt |
Text length > value | value: 50 |
length_lt |
Text length < value | value: 10 |
length_in_range |
Length within range | value: [10, 100] |
Examples¶
1. Single Condition¶
Show a text box when "Other" is selected:
- annotation_type: multiselect
name: categories
description: "Select categories:"
labels: [Category A, Category B, Other]
- annotation_type: text
name: other_category
description: "Describe the other category:"
display_logic:
show_when:
- schema: categories
operator: contains
value: "Other"
2. Multiple Values (OR within condition)¶
Show when ANY of the specified values is selected:
display_logic:
show_when:
- schema: rating
operator: equals
value: ["Bad", "Very Bad", "Terrible"] # Matches any of these
3. Multiple Conditions with AND Logic¶
Show only when ALL conditions are met:
display_logic:
show_when:
- schema: sentiment
operator: equals
value: "Negative"
- schema: confidence
operator: gte
value: 7
logic: all # Both conditions must be true (default)
4. Multiple Conditions with OR Logic¶
Show when ANY condition is met:
display_logic:
show_when:
- schema: urgent
operator: equals
value: "Yes"
- schema: priority
operator: in_range
value: [8, 10]
logic: any # Either condition can be true
5. Numeric Range Branching¶
Show different questions based on slider value:
# Low score follow-up
- annotation_type: text
name: improvement_suggestions
description: "What could be improved?"
display_logic:
show_when:
- schema: quality_score
operator: in_range
value: [1, 3]
# High score follow-up
- annotation_type: text
name: positive_feedback
description: "What worked well?"
display_logic:
show_when:
- schema: quality_score
operator: in_range
value: [8, 10]
6. Text Length Trigger¶
Show when the user provides a detailed response:
- annotation_type: text
name: initial_feedback
description: "Brief feedback:"
- annotation_type: radio
name: wants_detailed_review
description: "Would you like a detailed review of your feedback?"
labels: [Yes, No]
display_logic:
show_when:
- schema: initial_feedback
operator: length_gt
value: 50
7. Regex Matching¶
Show a follow-up for specific patterns:
display_logic:
show_when:
- schema: user_input
operator: matches
value: "error|exception|bug"
case_sensitive: false
8. Chained Conditions (Multi-level Branching)¶
annotation_schemes:
# Level 1
- annotation_type: radio
name: main_category
description: "Select main category:"
labels: [Product, Service, General]
# Level 2 - appears for "Product" selection
- annotation_type: radio
name: product_type
description: "Product type:"
labels: [Hardware, Software, Other]
display_logic:
show_when:
- schema: main_category
operator: equals
value: "Product"
# Level 3 - appears for "Software" product type
- annotation_type: multiselect
name: software_issues
description: "Software issue types:"
labels: [Bug, Feature Request, Performance, UI/UX]
display_logic:
show_when:
- schema: product_type
operator: equals
value: "Software"
Behavior Details¶
Initial Visibility¶
Schemas with display_logic are hidden by default when the page loads. They become visible only when their conditions are met.
Value Preservation¶
When a schema becomes hidden because its conditions are no longer met, the annotation values are preserved (not cleared). This allows users to change their primary answer and return to the same state without re-entering data.
Stale Annotations¶
In the output, annotations for hidden schemas are tracked separately as "stale" to indicate they may no longer be relevant to the current selections.
Smooth Animations¶
Show/hide transitions use smooth CSS animations (300ms). Users who prefer reduced motion will see instant transitions.
Validation¶
- Schemas that are hidden are excluded from required field validation
- The configuration is validated at startup to detect:
- Invalid operators
- Missing referenced schemas
- Circular dependencies
Troubleshooting¶
Schema Not Showing¶
- Check the schema name: The
schemafield in conditions must exactly match thenameof another schema. - Check the value: String comparisons are case-insensitive by default. Set
case_sensitive: trueif needed. - Check the operator: Use
containsfor multiselect (checking if a value is in the list of selected items), useequalsfor radio/select. - Browser console: Open browser developer tools and look for
[DisplayLogic]messages.
Circular Dependency Error¶
Display logic validation errors:
- Circular dependency detected: schema_a -> schema_b -> schema_a
This means schema_a depends on schema_b AND schema_b depends on schema_a. Remove one of the dependencies to fix this.
Debugging¶
Enable debug mode in your browser console:
displayLogicManager.enableDebug();
This will log all condition evaluations to help diagnose issues.
SurveyFlow / Phase Questions¶
The same display_logic grammar works for questions in SurveyFlow phases —
consent, prestudy, and poststudy surveys loaded from a JSON/JSONL file (or a survey
instrument). This lets you build adaptive surveys without splitting the workflow into extra
phases: show a follow-up only when relevant, reveal a free-text box when "Other" is picked,
ask for an explanation only on a low rating, and so on.
Because SurveyFlow questions are ordinary annotation schemes, you reference other questions by
their name (exactly as on the annotation page), not by id:
[
{
"name": "prior_experience",
"annotation_type": "radio",
"description": "Have you previously worked on a text-annotation task?",
"labels": ["Yes", "No"],
"label_requirement": {"required": true}
},
{
"name": "experience_details",
"annotation_type": "text",
"description": "Please briefly describe your previous annotation experience.",
"label_requirement": {"required": true},
"display_logic": {
"show_when": [
{"schema": "prior_experience", "operator": "equals", "value": "Yes"}
],
"logic": "all"
}
}
]
experience_details stays hidden — and is excluded from required-field validation, so it
never blocks the Continue button — until the participant answers "Yes". Answer values are
preserved (not cleared) if a question is hidden again, matching annotation-page behavior.
Cross-page conditions¶
A question may condition on an answer given on an earlier phase, not just a sibling on the same page. For example, a poststudy question can be shown only when a prestudy answer was "Yes":
{
"name": "experience_match",
"annotation_type": "radio",
"description": "You told us you had prior experience. Did this task match your expectations?",
"labels": ["Yes", "No"],
"display_logic": {
"show_when": [
{"schema": "prior_experience", "operator": "equals", "value": "Yes"}
]
}
}
Earlier-phase answers are injected into each phase page by the server, so the condition is evaluated on load. References resolve against questions on any phase.
Server-side enforcement¶
Because the form preserves hidden answers (so nothing is lost if a participant toggles a
trigger back and forth), a question that was answered and then hidden could otherwise linger in
the exported data. When export_include_phase_data: true, Potato re-evaluates each
participant's answers server-side (cross-page aware) and excludes answers to questions they
never actually saw from the exported phase responses. Opt out with
exclude_hidden_survey_answers: false (answers are then kept, each tagged with a hidden
boolean instead).
Scope and limitations¶
- JSON/instrument questions only. An
instructionsphase that points at a raw.htmlfile bypasses the schema pipeline, sodisplay_logicdoes not apply there. Put conditional questions in a JSON survey file instead. - Validated at startup. Invalid survey
display_logic(unknown operator, a reference to a question that exists on no phase, or a circular dependency) fails fast when the server starts, with a clear error. - Reference by
name. Conditions reference other questions by theirname, the same key used on the annotation page — not byid.
SurveyFlow example project¶
examples/advanced/surveyflow-conditional-logic/ demonstrates conditional consent, a prestudy
survey with two branches (a follow-up gated on prior experience, and an "Other → please
specify" free-text field), and a poststudy survey that asks for improvement feedback only on a
low rating.
python potato/flask_server.py start examples/advanced/surveyflow-conditional-logic/config.yaml -p 8000
Complete Example¶
See the full example project at:
examples/advanced/conditional-logic/
Run it with:
python potato/flask_server.py start examples/advanced/conditional-logic/config.yaml -p 8000
Technical Notes¶
Files Involved¶
potato/server_utils/display_logic.py- Core validation and evaluation logicpotato/static/display-logic.js- Frontend condition evaluationpotato/static/display-logic.css- Show/hide animationspotato/server_utils/schemas/registry.py- Wraps schema HTML with display_logic attributes
Performance¶
- Conditions are only evaluated when a relevant schema changes (not on every keystroke)
- Large forms with many conditional schemas perform well due to efficient dependency tracking