Pattern 13: Conditional Requirements
Part II: Interaction Patterns - Relationship Patterns
Opening Scenario: The Form That Demanded Too Much
Angela was filling out a job application. The form had a field marked as required:
* Spouse's Name: [________________]
Required field
Angela wasn't married. She left it blank and clicked Submit.
Error: Spouse's Name is required
She stared at the screen, frustrated. "I don't have a spouse!"
She tried typing "N/A" just to get past the validation. It worked, and she submitted the form.
The HR manager, Carol, reviewed the application and was confused. "Why did Angela write 'N/A' for spouse's name? Is she being sarcastic?"
Carol looked at the form definition:
// Bad: Field is always required
{
name: 'spouseName',
label: "Spouse's Name",
required: true // Always!
}
"This is wrong," Carol said. "Spouse name should only be required if the person is married."
Carol redesigned the form:
// Better: Conditional requirement
{
name: 'maritalStatus',
type: 'select',
options: ['Single', 'Married', 'Divorced', 'Widowed'],
required: true
},
{
name: 'spouseName',
label: "Spouse's Name",
required: {
when: 'maritalStatus',
equals: 'Married'
}
}
Now the form behaved intelligently:
User selects "Single": - Spouse name field is hidden (not required) - No validation error
User selects "Married": - Spouse name field appears - Marked as required (red asterisk) - Validation enforces requirement
But Carol discovered more conditional requirements throughout the application:
Employment section:
If "Currently Employed" = Yes:
→ "Current Employer" is REQUIRED
→ "Start Date" is REQUIRED
→ "Supervisor Name" is REQUIRED
If "Currently Employed" = No:
→ These fields are OPTIONAL (or hidden)
Education section:
If "Degree Type" = "Bachelor's" or higher:
→ "Major" is REQUIRED
→ "Graduation Year" is REQUIRED
If "Degree Type" = "High School":
→ "Major" is NOT APPLICABLE
→ "Graduation Year" is REQUIRED
References section:
If "Years of Experience" >= 5:
→ 3 professional references REQUIRED
If "Years of Experience" < 5:
→ 1 professional reference REQUIRED
Carol built a system to handle all these cases:
class ConditionalRequirementEngine {
constructor() {
this.rules = new Map();
}
defineRule(fieldName, requirement) {
this.rules.set(fieldName, requirement);
}
isRequired(fieldName, formData) {
const rule = this.rules.get(fieldName);
if (!rule) {
return false; // No rule = not required
}
if (rule === true) {
return true; // Unconditionally required
}
// Conditional requirement
return this.evaluateCondition(rule, formData);
}
evaluateCondition(condition, formData) {
if (condition.when) {
const watchedField = condition.when;
const watchedValue = formData[watchedField];
if (condition.equals !== undefined) {
return watchedValue === condition.equals;
}
if (condition.in !== undefined) {
return condition.in.includes(watchedValue);
}
if (condition.greaterThan !== undefined) {
return watchedValue > condition.greaterThan;
}
}
return false;
}
}
The new form was dynamic: - Fields appeared only when relevant - Requirements changed based on answers - No "N/A" workarounds needed - Clear visual indication of required fields
Application completion rate improved 35%. Error rates dropped 60%. And Angela's next application went smoothly.
Context
Conditional Requirements applies when:
Relevance depends on previous answers: Field only matters if user answered a certain way
Complexity must be hidden: Don't show irrelevant fields upfront
Business rules vary: Requirements change based on scenario
User fatigue is a risk: Long forms need to hide unnecessary fields
Data quality matters: Collecting data that doesn't apply leads to garbage
Workflow branches: Different paths require different information
Regulatory compliance: Some fields required only in specific jurisdictions
Problem Statement
Most forms handle requirements poorly, creating frustration and errors:
Always-required fields that sometimes don't apply:
// Bad: Always required, regardless of context
{
name: 'spouseName',
required: true
}
// User who isn't married gets error
// Forces fake data entry ("N/A", "None", etc.)
Hidden complexity in validation:
// Bad: Logic buried in validation function
function validate(form) {
if (form.maritalStatus === 'Married' && !form.spouseName) {
return "Spouse name is required for married applicants";
}
}
// User doesn't know field is required until submit
// No visual indication in the form
Static forms that don't adapt:
<!-- Bad: All fields always visible and required -->
<label>Spouse's Name *</label>
<input name="spouseName" required>
<label>Spouse's Employer *</label>
<input name="spouseEmployer" required>
<label>Spouse's Income *</label>
<input name="spouseIncome" required>
<!-- Single people forced to fill these out -->
No visual feedback:
// Bad: Requirement changes but UI doesn't update
if (maritalStatus === 'Married') {
// Spouse name is now required
// But form still shows it as optional
}
Inconsistent behavior:
// Bad: Some fields hide, others just become optional
if (employmentStatus === 'Unemployed') {
// Current employer field is hidden
// But supervisor field just becomes optional
// Inconsistent!
}
We need forms that show only relevant fields, clearly indicate dynamic requirements, and adapt based on user input.
Forces
Simplicity vs Completeness
- Hide irrelevant fields to reduce clutter
- But need all necessary information
- Balance minimal form with comprehensive data
Immediate vs Delayed Indication
- Show required indicator immediately?
- Or wait until field becomes relevant?
- Balance early warning with clarity
Hiding vs Disabling
- Hide irrelevant fields entirely?
- Or show them disabled?
- Balance clean interface with context
Strict vs Forgiving
- Enforce all conditional rules strictly?
- Or allow some flexibility?
- Balance data quality with user experience
Progressive vs All-at-Once
- Add requirements progressively as user answers?
- Or show all possible requirements upfront?
- Balance discoverability with overwhelm
Solution
Define field requirements as conditional rules that evaluate based on form state, update visual indicators dynamically, and communicate changes clearly to users - making relevance obvious through progressive disclosure and adaptive validation.
The pattern has four key strategies:
1. Declarative Requirement Rules
Define requirements as data, not code:
class RequirementRuleEngine {
constructor() {
this.fieldRules = new Map();
}
defineField(fieldName, config) {
this.fieldRules.set(fieldName, {
label: config.label,
type: config.type,
required: config.required || false,
visible: config.visible !== false,
validation: config.validation
});
}
// Simple conditional requirement
whenEquals(fieldName, watchField, value) {
const field = this.fieldRules.get(fieldName);
field.required = {
type: 'equals',
watch: watchField,
value: value
};
}
// Multiple condition requirement
whenAny(fieldName, watchField, values) {
const field = this.fieldRules.get(fieldName);
field.required = {
type: 'in',
watch: watchField,
values: values
};
}
// Complex condition requirement
whenCondition(fieldName, conditionFn) {
const field = this.fieldRules.get(fieldName);
field.required = {
type: 'function',
evaluate: conditionFn
};
}
// Compound conditions (AND/OR)
whenAll(fieldName, conditions) {
const field = this.fieldRules.get(fieldName);
field.required = {
type: 'and',
conditions: conditions
};
}
whenAnyOf(fieldName, conditions) {
const field = this.fieldRules.get(fieldName);
field.required = {
type: 'or',
conditions: conditions
};
}
// Example: Job application rules
defineJobApplicationRules() {
// Spouse name required only if married
this.defineField('spouseName', {
label: "Spouse's Name",
type: 'text'
});
this.whenEquals('spouseName', 'maritalStatus', 'Married');
// Current employer required if employed
this.defineField('currentEmployer', {
label: 'Current Employer',
type: 'text'
});
this.whenEquals('currentEmployer', 'employmentStatus', 'Employed');
// References required based on experience
this.defineField('references', {
label: 'Professional References',
type: 'list'
});
this.whenCondition('references', (form) => {
const years = parseInt(form.yearsExperience);
return years >= 5 ? 3 : 1; // 3 refs if 5+ years, else 1
});
// Degree details required for college education
this.defineField('major', {
label: 'Major',
type: 'text'
});
this.whenAny('major', 'degreeType', [
"Bachelor's",
"Master's",
"Doctorate"
]);
}
}
2. Dynamic Requirement Evaluation
Continuously evaluate requirements as form changes:
class DynamicRequirementEvaluator {
constructor(ruleEngine) {
this.ruleEngine = ruleEngine;
this.currentRequirements = new Map();
}
evaluate(formData) {
const updates = [];
this.ruleEngine.fieldRules.forEach((config, fieldName) => {
const wasRequired = this.currentRequirements.get(fieldName);
const isRequired = this.isRequired(fieldName, formData, config);
if (wasRequired !== isRequired) {
updates.push({
field: fieldName,
wasRequired,
isRequired,
reason: this.getRequirementReason(config, formData)
});
this.currentRequirements.set(fieldName, isRequired);
}
});
return updates;
}
isRequired(fieldName, formData, config) {
const requirement = config.required;
// Always required
if (requirement === true) {
return true;
}
// Never required
if (!requirement || requirement === false) {
return false;
}
// Conditional requirement
return this.evaluateCondition(requirement, formData);
}
evaluateCondition(requirement, formData) {
switch(requirement.type) {
case 'equals':
return formData[requirement.watch] === requirement.value;
case 'in':
return requirement.values.includes(formData[requirement.watch]);
case 'function':
return requirement.evaluate(formData);
case 'and':
return requirement.conditions.every(cond =>
this.evaluateCondition(cond, formData)
);
case 'or':
return requirement.conditions.some(cond =>
this.evaluateCondition(cond, formData)
);
case 'not':
return !this.evaluateCondition(requirement.condition, formData);
case 'comparison':
return this.evaluateComparison(
formData[requirement.watch],
requirement.operator,
requirement.value
);
default:
return false;
}
}
evaluateComparison(fieldValue, operator, compareValue) {
switch(operator) {
case '>': return fieldValue > compareValue;
case '>=': return fieldValue >= compareValue;
case '<': return fieldValue < compareValue;
case '<=': return fieldValue <= compareValue;
case '!=': return fieldValue !== compareValue;
case 'contains': return fieldValue.includes(compareValue);
case 'startsWith': return fieldValue.startsWith(compareValue);
default: return false;
}
}
getRequirementReason(config, formData) {
const req = config.required;
if (req.type === 'equals') {
return `Required when ${req.watch} is "${req.value}"`;
}
if (req.type === 'in') {
return `Required when ${req.watch} is one of: ${req.values.join(', ')}`;
}
if (req.type === 'function') {
return 'Required based on your answers';
}
return 'Required';
}
// Get all currently required fields
getRequiredFields() {
const required = [];
this.currentRequirements.forEach((isReq, fieldName) => {
if (isReq) required.push(fieldName);
});
return required;
}
// Validate form based on current requirements
validateRequired(formData) {
const errors = [];
this.currentRequirements.forEach((isRequired, fieldName) => {
if (isRequired && !formData[fieldName]) {
const config = this.ruleEngine.fieldRules.get(fieldName);
errors.push({
field: fieldName,
message: `${config.label} is required`,
reason: this.getRequirementReason(config, formData)
});
}
});
return errors;
}
}
3. Visual Requirement Indicators
Update UI to show dynamic requirements:
class RequirementVisualizer {
constructor(evaluator) {
this.evaluator = evaluator;
}
// Update required indicator on field
updateFieldIndicator(fieldName, isRequired, animated = true) {
const fieldElement = document.getElementById(fieldName);
const labelElement = document.querySelector(`label[for="${fieldName}"]`);
if (!labelElement) return;
// Remove existing indicator
const existing = labelElement.querySelector('.required-indicator');
if (existing) existing.remove();
if (isRequired) {
// Add required indicator
const indicator = document.createElement('span');
indicator.className = 'required-indicator';
indicator.textContent = '*';
indicator.setAttribute('aria-label', 'required');
if (animated) {
indicator.classList.add('fade-in');
}
labelElement.appendChild(indicator);
// Update field attributes
fieldElement.setAttribute('required', 'required');
fieldElement.setAttribute('aria-required', 'true');
// Add visual styling
fieldElement.classList.add('required');
} else {
// Remove required state
fieldElement.removeAttribute('required');
fieldElement.setAttribute('aria-required', 'false');
fieldElement.classList.remove('required');
}
}
// Show notification about requirement change
showRequirementChange(update) {
const message = update.isRequired
? `${this.getFieldLabel(update.field)} is now required`
: `${this.getFieldLabel(update.field)} is no longer required`;
this.showToast(message, 'info', {
duration: 3000,
reason: update.reason
});
// Scroll field into view if newly required
if (update.isRequired) {
const fieldElement = document.getElementById(update.field);
if (fieldElement && !this.isInViewport(fieldElement)) {
this.scrollToField(update.field, {
highlight: true,
delay: 500
});
}
}
}
// Highlight required fields that are empty
highlightMissingRequired(errors) {
errors.forEach(error => {
const fieldElement = document.getElementById(error.field);
const container = fieldElement.closest('.field-container');
// Add error styling
container.classList.add('error');
// Show inline error message
const errorMsg = document.createElement('div');
errorMsg.className = 'field-error';
errorMsg.textContent = error.message;
if (error.reason) {
const reason = document.createElement('div');
reason.className = 'field-error-reason';
reason.textContent = error.reason;
errorMsg.appendChild(reason);
}
container.appendChild(errorMsg);
});
}
// Show summary of required fields
showRequiredSummary() {
const required = this.evaluator.getRequiredFields();
if (required.length === 0) {
return null;
}
const summary = document.createElement('div');
summary.className = 'required-fields-summary';
summary.innerHTML = `
<div class="summary-header">
<span class="icon">📋</span>
<span class="title">Required Fields (${required.length})</span>
</div>
<ul class="required-list">
${required.map(field => `
<li>
<a href="#${field}" onclick="scrollToField('${field}')">
${this.getFieldLabel(field)}
</a>
</li>
`).join('')}
</ul>
`;
return summary;
}
// Progressive disclosure: show fields as they become relevant
showField(fieldName, reason) {
const container = document.getElementById(`${fieldName}-container`);
if (container && container.style.display === 'none') {
// Slide in animation
container.style.display = 'block';
container.classList.add('slide-in');
// Add context message
const contextMsg = document.createElement('div');
contextMsg.className = 'field-context';
contextMsg.textContent = reason;
container.insertBefore(contextMsg, container.firstChild);
// Remove context message after delay
setTimeout(() => contextMsg.remove(), 5000);
}
}
hideField(fieldName) {
const container = document.getElementById(`${fieldName}-container`);
if (container) {
container.classList.add('slide-out');
setTimeout(() => {
container.style.display = 'none';
container.classList.remove('slide-out');
}, 300);
}
}
getFieldLabel(fieldName) {
const labelElement = document.querySelector(`label[for="${fieldName}"]`);
return labelElement ? labelElement.textContent.replace('*', '').trim() : fieldName;
}
}
4. Conditional Visibility
Show/hide fields based on relevance:
class ConditionalVisibility {
constructor(ruleEngine, evaluator) {
this.ruleEngine = ruleEngine;
this.evaluator = evaluator;
this.visibilityRules = new Map();
}
defineVisibility(fieldName, condition) {
this.visibilityRules.set(fieldName, condition);
}
// Example: Show spouse fields only if married
showSpouseFields() {
const spouseFields = [
'spouseName',
'spouseEmployer',
'spouseIncome'
];
spouseFields.forEach(field => {
this.defineVisibility(field, {
when: 'maritalStatus',
equals: 'Married'
});
});
}
// Example: Show international address fields for non-US
showInternationalFields() {
const intlFields = [
'province',
'postalCode',
'country'
];
intlFields.forEach(field => {
this.defineVisibility(field, {
when: 'addressType',
equals: 'International'
});
});
}
evaluateVisibility(formData) {
const updates = [];
this.visibilityRules.forEach((condition, fieldName) => {
const wasVisible = this.isVisible(fieldName);
const shouldBeVisible = this.evaluator.evaluateCondition(
condition,
formData
);
if (wasVisible !== shouldBeVisible) {
updates.push({
field: fieldName,
visible: shouldBeVisible
});
}
});
return updates;
}
isVisible(fieldName) {
const container = document.getElementById(`${fieldName}-container`);
return container && container.style.display !== 'none';
}
applyVisibilityUpdates(updates) {
updates.forEach(update => {
const container = document.getElementById(`${update.field}-container`);
if (container) {
if (update.visible) {
container.style.display = 'block';
container.classList.add('fade-in');
} else {
container.classList.add('fade-out');
setTimeout(() => {
container.style.display = 'none';
container.classList.remove('fade-out');
// Clear field value when hidden
const field = document.getElementById(update.field);
if (field) field.value = '';
}, 300);
}
}
});
}
}
Implementation Details
Complete Conditional Requirements System
class ConditionalRequirementsSystem {
constructor() {
this.ruleEngine = new RequirementRuleEngine();
this.evaluator = new DynamicRequirementEvaluator(this.ruleEngine);
this.visualizer = new RequirementVisualizer(this.evaluator);
this.visibility = new ConditionalVisibility(this.ruleEngine, this.evaluator);
}
initialize(formDefinition) {
// Define all field rules
formDefinition.fields.forEach(field => {
this.ruleEngine.defineField(field.name, field);
// Set up conditional requirements
if (field.requiredWhen) {
this.applyRequirementCondition(field.name, field.requiredWhen);
}
// Set up conditional visibility
if (field.visibleWhen) {
this.visibility.defineVisibility(field.name, field.visibleWhen);
}
});
// Initial evaluation
this.evaluateAll({});
// Listen for changes
this.setupChangeListeners();
}
applyRequirementCondition(fieldName, condition) {
if (condition.equals) {
this.ruleEngine.whenEquals(fieldName, condition.field, condition.equals);
} else if (condition.in) {
this.ruleEngine.whenAny(fieldName, condition.field, condition.in);
} else if (condition.function) {
this.ruleEngine.whenCondition(fieldName, condition.function);
}
}
setupChangeListeners() {
document.addEventListener('change', (e) => {
const formData = this.getFormData();
this.evaluateAll(formData);
});
// Also listen on input for immediate feedback
document.addEventListener('input', (e) => {
const formData = this.getFormData();
this.evaluateAll(formData);
});
}
evaluateAll(formData) {
// Evaluate requirement changes
const reqUpdates = this.evaluator.evaluate(formData);
// Update visual indicators
reqUpdates.forEach(update => {
this.visualizer.updateFieldIndicator(
update.field,
update.isRequired,
true
);
// Optionally show notification
if (update.isRequired && !update.wasRequired) {
this.visualizer.showRequirementChange(update);
}
});
// Evaluate visibility changes
const visUpdates = this.visibility.evaluateVisibility(formData);
this.visibility.applyVisibilityUpdates(visUpdates);
}
validateForm() {
const formData = this.getFormData();
const errors = this.evaluator.validateRequired(formData);
if (errors.length > 0) {
this.visualizer.highlightMissingRequired(errors);
return false;
}
return true;
}
getFormData() {
const form = document.querySelector('form');
const data = new FormData(form);
return Object.fromEntries(data.entries());
}
}
// Example usage
const system = new ConditionalRequirementsSystem();
system.initialize({
fields: [
{
name: 'maritalStatus',
label: 'Marital Status',
type: 'select',
options: ['Single', 'Married', 'Divorced', 'Widowed'],
required: true
},
{
name: 'spouseName',
label: "Spouse's Name",
type: 'text',
requiredWhen: {
field: 'maritalStatus',
equals: 'Married'
},
visibleWhen: {
when: 'maritalStatus',
equals: 'Married'
}
},
{
name: 'employmentStatus',
label: 'Employment Status',
type: 'select',
options: ['Employed', 'Unemployed', 'Self-Employed', 'Retired'],
required: true
},
{
name: 'currentEmployer',
label: 'Current Employer',
type: 'text',
requiredWhen: {
field: 'employmentStatus',
in: ['Employed', 'Self-Employed']
},
visibleWhen: {
when: 'employmentStatus',
in: ['Employed', 'Self-Employed']
}
}
]
});
Consequences
Benefits
Reduced Form Clutter: - Only show relevant fields - Progressive disclosure - Less overwhelming for users
Improved Data Quality: - No fake "N/A" entries - Fields collect data only when applicable - Clearer user intent
Better User Experience: - Clear what's required when - No surprise requirements at submit - Form adapts to user's situation
Faster Completion: - Users skip irrelevant sections - Less time on form - Higher completion rates
Enforced Business Rules: - Requirements match policies - Consistent application - Automated compliance
Liabilities
Complexity: - Conditional logic can get intricate - Testing all paths challenging - Debugging harder than static forms
User Confusion: - "Why did this become required?" - Need clear communication - Visual changes must be obvious
Performance: - Real-time evaluation adds overhead - Complex conditions can be slow - Need optimization for large forms
Maintenance: - Business rules change - Conditions need updating - Documentation critical
Accessibility: - Screen readers need notification of changes - Keyboard navigation affected by show/hide - Must maintain focus management
Domain Examples
Healthcare: Patient Intake
// Conditional requirements based on insurance
defineHealthcareRules() {
// Insurance fields
this.whenEquals('insurancePolicyNumber', 'hasInsurance', 'Yes');
this.whenEquals('insuranceGroupNumber', 'hasInsurance', 'Yes');
// Secondary insurance only if primary exists
this.whenEquals('secondaryInsurance', 'hasInsurance', 'Yes');
// Pharmacy required for prescription medications
this.whenEquals('preferredPharmacy', 'needsPrescriptions', 'Yes');
// Emergency contact required for minors
this.whenCondition('emergencyContact', (form) => {
const age = this.calculateAge(form.birthDate);
return age < 18;
});
}
Legal: Case Intake
// Conditional requirements based on case type
defineLegalCaseRules() {
// Opposing party required for litigation
this.whenAny('opposingParty', 'caseType', [
'Civil Litigation',
'Family Law',
'Employment Dispute'
]);
// Children details required for family law
this.whenEquals('childrenNames', 'caseType', 'Family Law');
this.whenEquals('custodyArrangement', 'caseType', 'Family Law');
// Corporate details for business cases
this.whenAny('companyEIN', 'caseType', [
'Business Formation',
'Corporate Law',
'Commercial Litigation'
]);
}
Real Estate: Property Listing
// Conditional requirements based on property type
defineRealEstateRules() {
// HOA information required for condos/townhouses
this.whenAny('hoaFees', 'propertyType', ['Condo', 'Townhouse']);
this.whenAny('hoaName', 'propertyType', ['Condo', 'Townhouse']);
// Lot size required for land/single-family
this.whenAny('lotSize', 'propertyType', ['Land', 'Single Family']);
// Rental history required for investment properties
this.whenEquals('rentalIncome', 'listingType', 'Investment');
this.whenEquals('leaseTerms', 'listingType', 'Investment');
}
Related Patterns
Prerequisites: - Volume 3, Pattern 1: Progressive Disclosure (hide/show based on progress)
Synergies: - Volume 3, Pattern 9: Contextual Constraints (what's valid depends on requirements) - Volume 3, Pattern 12: Mutual Exclusivity (exclusive choices affect requirements) - Volume 3, Pattern 14: Cross-Field Validation (validate conditional requirements)
Conflicts: - All-at-once forms (some contexts need everything visible) - Print forms (can't dynamically show/hide)
Alternatives: - Wizard workflows (one step per condition) - Multiple forms (separate form for each scenario) - All fields optional (let backend sort it out)
Known Uses
Tax Software (TurboTax, H&R Block): Show tax forms based on income types
Insurance Applications: Require different fields based on coverage type
Job Applications: Require references based on experience level
Loan Applications: Require co-signer based on credit score
Survey Tools (SurveyMonkey, Typeform): Skip logic and conditional questions
E-commerce: Require shipping address only for physical products
Healthcare Portals (Epic MyChart): Require different info based on appointment type
Further Reading
Academic Foundations
- Adaptive Systems: Oppermann, R., & Rasher, R. (1997). "Adaptability and Adaptivity in Learning Systems." Knowledge Transfer 2: 173-179.
- Business Rules: von Halle, B. (2001). Business Rules Applied: Building Better Systems Using the Business Rules Approach. Wiley. ISBN: 978-0471412939
- Form Design Research: Jarrett, C., & Gaffney, G. (2009). Forms that Work: Designing Web Forms for Usability. Morgan Kaufmann. ISBN: 978-1558607101
Practical Implementation
- React Hook Form Conditional Validation: https://react-hook-form.com/advanced-usage#ConditionalValidation
- Yup when(): https://github.com/jquense/yup#mixedwhenkeys-string--arraystring-builder-object--value-schema-schema-schema - Conditional schema
- JSON Schema if/then/else: https://json-schema.org/understanding-json-schema/reference/conditionals.html
- Formik Dynamic Requirements: https://formik.org/docs/guides/validation#dependent-fields
Standards & Specifications
- XForms Relevance: https://www.w3.org/TR/xforms11/#structure-relevant - Context-dependent requirements
- BPMN Gateways: https://www.omg.org/spec/BPMN/ - Conditional workflow paths
Related Trilogy Patterns
- Pattern 11: Validation Rules - Requirements are validation rules
- Pattern 14: Contextual Constraints - Context determines requirements
- Pattern 17: Mutual Exclusivity - Exclusive choices change requirements
- Pattern 19: Cross-Field Validation - Validate conditional requirements
- Pattern 26: Dynamic UI Adaptation - Show/hide required fields
- Volume 2, Chapter 5: Privacy-Preserving Observation - Context triggers requirements
- Volume 1, Chapter 7: Cross-Domain Analysis - Rule-driven requirements
Tools & Libraries
- Vest Validation: https://vestjs.dev/ - Declarative conditional validation
- ajv Conditional Keywords: https://ajv.js.org/guide/combining-schemas.html#if-then-else - JSON Schema validation
- Superstruct Refiners: https://docs.superstructjs.org/guides/refining-validation - Conditional refinement
Implementation Examples
- Conditional Form Fields: https://css-tricks.com/dynamic-page-replacing-content/ - Progressive disclosure
- Skip Logic Tutorial: https://www.surveymonkey.com/mp/skip-logic/ - Survey skip patterns
- Typeform Logic Jump: https://www.typeform.com/help/conditional-logic/ - Conditional branching