Volume 3: Human-System Collaboration

Pattern 15: Smart Dependencies

Part II: Interaction Patterns - Relationship Patterns


Opening Scenario: The Form That Knew What You Needed

Rachel was ordering custom business cards online. The form started simple:

Card Type: [Business  ▼]

When she selected "Business," new fields appeared:

Card Type: [Business  ▼]

Material: [Standard ▼]
Finish: [Matte ▼]
Quantity: [500 ▼]

She selected "Premium" for material. Instantly, the form adapted:

Material: [Premium ▼]  ← Changed

Finish: [Matte ▼] [Glossy] [Embossed ▼] [Foil] ← New options enabled
       (Premium materials support embossing and foil)

Quantity: [500 ▼]

"Embossed" option wasn't available with standard materials. The form understood the dependency and only showed valid choices.

She selected "Embossed" finish:

Finish: [Embossed ▼]

Embossing Color: [Gold ▼] [Silver] [Black] ← New field appeared
                 (Required for embossed finish)

Rush Delivery: [ ] Not available for embossed cards ← Disabled with explanation

The form knew: - Rush delivery wasn't possible with embossing (requires extra processing time) - Embossing requires selecting a color - Only certain quantities work with embossing (minimums apply)


Meanwhile, across town, Derek was using a different card printing service. Their form didn't understand dependencies:

Material: [Standard ▼]
Finish: [Embossed ▼]  ← Can select this
Rush Delivery: [✓]     ← Can check this

Quantity: [100]        ← Can enter this

Derek could select any combination. He chose: - Standard material - Embossed finish - Rush delivery - 100 cards

He submitted the order. Two days later, he got an email:

Your order cannot be processed:
- Embossing requires Premium material (not Standard)
- Rush delivery not available for embossed orders
- Embossing requires minimum 250 cards (you ordered 100)

Please reorder with correct options.

Derek was frustrated. "Why did the form let me select these options if they're not compatible?"


Rachel's experience was different. Her form guided her toward valid choices:

When she selected Standard material: - Embossing option was grayed out with tooltip: "Requires Premium material" - Foil option was grayed out with tooltip: "Requires Premium material" - Clear visual feedback about what was possible

When she wanted rush delivery: - Tooltip appeared: "Rush delivery available for standard finishes only" - Form suggested: "Choose Matte or Glossy finish to enable rush delivery"

When she selected a quantity: - Minimum quantities were enforced based on finish - Tooltip showed: "Embossed cards require minimum 250 quantity" - Price updated in real-time based on all selections

The form didn't just validate - it educated and guided. Rachel completed her order in 3 minutes with zero errors. Derek spent 30 minutes across two attempts and still wasn't sure if his new configuration would work.

Context

Smart Dependencies applies when:

Complex product configurations: Options depend on other options (materials, features, quantities)

Technical requirements: Some combinations are technically impossible

Business rules: Policies restrict certain combinations

Sequential logic: Later choices depend on earlier ones

Resource constraints: Availability affects what's possible

User expertise varies: Some understand dependencies, others don't

Error prevention critical: Invalid combinations cause real problems

Problem Statement

Most forms treat dependencies poorly, allowing invalid combinations or providing late feedback:

No dependency awareness:

<!-- Bad: All options always available -->
<select name="material">
  <option>Standard</option>
  <option>Premium</option>
</select>

<select name="finish">
  <option>Matte</option>
  <option>Glossy</option>
  <option>Embossed</option>  <!-- Requires Premium! -->
  <option>Foil</option>       <!-- Requires Premium! -->
</select>

<!-- Form allows selecting Standard + Embossed -->

Late error discovery:

// Bad: Validation at submission
function validateOrder(order) {
  if (order.material === 'Standard' && order.finish === 'Embossed') {
    return "Error: Embossing requires Premium material";
  }
}

// User discovers after filling entire form

No guidance:

<!-- Bad: Options disabled with no explanation -->
<select name="finish">
  <option>Matte</option>
  <option>Glossy</option>
  <option disabled>Embossed</option>  <!-- Why disabled? -->
</select>

<!-- User doesn't know what's needed to enable it -->

Hidden relationships:

// Bad: Dependency logic buried in code
if (material === 'Premium' && finish === 'Embossed') {
  // Can't do rush delivery
  // But this isn't visible to user until they try
}

No progressive disclosure:

<!-- Bad: Show all fields even when irrelevant -->
Embossing Color: [     ]  <!-- Shown even for non-embossed -->
Foil Type: [     ]        <!-- Shown even for non-foil -->

<!-- Clutters form with irrelevant options -->

We need forms that understand dependencies, guide users toward valid combinations, and explain constraints clearly.

Forces

Simplicity vs Power

  • Simple forms are easy to understand
  • But complex products need many options
  • Balance configurability with clarity

Freedom vs Guidance

  • Users want flexibility
  • But need guardrails against errors
  • Balance choice with direction

Immediate vs Progressive

  • Show all options upfront?
  • Or reveal them progressively?
  • Balance discoverability with focus

Explicit vs Implicit

  • Explain every dependency?
  • Or just prevent invalid states?
  • Balance education with simplicity

Strict vs Flexible

  • Enforce all dependencies rigidly?
  • Or allow overrides in edge cases?
  • Balance correctness with exceptions

Solution

Create intelligent dependency graphs that enable/disable options dynamically, provide clear explanations for constraints, guide users through valid configuration paths, and learn from common patterns to suggest optimal choices.

The pattern has five key strategies:

1. Dependency Graph Definition

Define what depends on what:

class DependencyGraph {
  constructor() {
    this.nodes = new Map();  // Field definitions
    this.edges = new Map();  // Dependencies between fields
  }

  defineField(fieldName, config) {
    this.nodes.set(fieldName, {
      name: fieldName,
      label: config.label,
      type: config.type,
      options: config.options || [],
      defaultValue: config.defaultValue,
      alwaysVisible: config.alwaysVisible || false
    });
  }

  // Field B depends on Field A having specific value(s)
  dependsOn(dependentField, sourceField, condition) {
    const key = `${dependentField}->${sourceField}`;

    this.edges.set(key, {
      dependent: dependentField,
      source: sourceField,
      condition: condition,
      type: 'enables'
    });
  }

  // Field B requires Field A to have specific value(s)
  requires(field, requiredField, values) {
    this.dependsOn(field, requiredField, {
      type: 'value-match',
      values: Array.isArray(values) ? values : [values],
      explanation: `Requires ${requiredField} to be ${values}`
    });
  }

  // Field B is incompatible with Field A having specific value(s)
  incompatibleWith(field, conflictField, values) {
    const key = `${field}-!->${conflictField}`;

    this.edges.set(key, {
      dependent: field,
      source: conflictField,
      condition: {
        type: 'value-mismatch',
        values: Array.isArray(values) ? values : [values],
        explanation: `Not compatible with ${conflictField} = ${values}`
      },
      type: 'disables'
    });
  }

  // Example: Business card dependencies
  defineBusinessCardDependencies() {
    // Define fields
    this.defineField('cardType', {
      label: 'Card Type',
      type: 'select',
      options: ['Business', 'Personal', 'Event'],
      alwaysVisible: true
    });

    this.defineField('material', {
      label: 'Material',
      type: 'select',
      options: ['Standard', 'Premium', 'Luxury'],
      alwaysVisible: true
    });

    this.defineField('finish', {
      label: 'Finish',
      type: 'select',
      options: ['Matte', 'Glossy', 'Embossed', 'Foil'],
      alwaysVisible: true
    });

    this.defineField('embossingColor', {
      label: 'Embossing Color',
      type: 'select',
      options: ['Gold', 'Silver', 'Black', 'White']
    });

    this.defineField('foilType', {
      label: 'Foil Type',
      type: 'select',
      options: ['Gold Foil', 'Silver Foil', 'Holographic']
    });

    this.defineField('rushDelivery', {
      label: 'Rush Delivery',
      type: 'checkbox'
    });

    // Define dependencies

    // Embossing requires Premium or Luxury material
    this.requires('finish', 'material', ['Premium', 'Luxury']);

    // Foil requires Premium or Luxury material
    this.requires('finish', 'material', ['Premium', 'Luxury']);

    // Embossing color only relevant when finish is Embossed
    this.requires('embossingColor', 'finish', 'Embossed');

    // Foil type only relevant when finish is Foil
    this.requires('foilType', 'finish', 'Foil');

    // Rush delivery incompatible with Embossed or Foil finishes
    this.incompatibleWith('rushDelivery', 'finish', ['Embossed', 'Foil']);
  }
}

2. Dependency Evaluator

Determine what should be enabled/disabled:

class DependencyEvaluator {
  constructor(graph) {
    this.graph = graph;
    this.currentState = new Map();
  }

  evaluate(formData) {
    const evaluations = new Map();

    // Evaluate each field's availability
    this.graph.nodes.forEach((config, fieldName) => {
      const evaluation = this.evaluateField(fieldName, formData);
      evaluations.set(fieldName, evaluation);
    });

    return evaluations;
  }

  evaluateField(fieldName, formData) {
    const field = this.graph.nodes.get(fieldName);

    if (field.alwaysVisible) {
      return {
        visible: true,
        enabled: true,
        options: this.evaluateOptions(fieldName, formData)
      };
    }

    // Check dependencies
    const dependencies = this.getDependencies(fieldName);

    if (dependencies.length === 0) {
      return {
        visible: true,
        enabled: true,
        options: field.options
      };
    }

    // Evaluate all dependencies
    let visible = true;
    let enabled = true;
    const reasons = [];

    dependencies.forEach(dep => {
      const result = this.evaluateDependency(dep, formData);

      if (!result.satisfied) {
        if (dep.type === 'enables') {
          visible = false;
          reasons.push(result.reason);
        } else if (dep.type === 'disables') {
          enabled = false;
          reasons.push(result.reason);
        }
      }
    });

    return {
      visible,
      enabled,
      reasons,
      options: this.evaluateOptions(fieldName, formData)
    };
  }

  evaluateDependency(dependency, formData) {
    const sourceValue = formData[dependency.source];
    const condition = dependency.condition;

    switch(condition.type) {
      case 'value-match':
        if (condition.values.includes(sourceValue)) {
          return { satisfied: true };
        }
        return {
          satisfied: false,
          reason: condition.explanation
        };

      case 'value-mismatch':
        if (!condition.values.includes(sourceValue)) {
          return { satisfied: true };
        }
        return {
          satisfied: false,
          reason: condition.explanation
        };

      default:
        return { satisfied: true };
    }
  }

  evaluateOptions(fieldName, formData) {
    const field = this.graph.nodes.get(fieldName);
    const availableOptions = [];

    field.options.forEach(option => {
      const available = this.isOptionAvailable(
        fieldName,
        option,
        formData
      );

      availableOptions.push({
        value: option,
        available: available.available,
        reason: available.reason
      });
    });

    return availableOptions;
  }

  isOptionAvailable(fieldName, optionValue, formData) {
    // Check if this specific option value is valid
    // given current form state

    // For example: "Embossed" option only available
    // when material is "Premium" or "Luxury"

    const testData = { ...formData, [fieldName]: optionValue };

    // Check all dependencies
    const dependencies = this.graph.edges;
    let available = true;
    let reason = null;

    dependencies.forEach((dep, key) => {
      if (dep.dependent === fieldName) {
        const result = this.evaluateDependency(dep, testData);
        if (!result.satisfied) {
          available = false;
          reason = result.reason;
        }
      }
    });

    return { available, reason };
  }

  getDependencies(fieldName) {
    const deps = [];

    this.graph.edges.forEach((edge, key) => {
      if (edge.dependent === fieldName) {
        deps.push(edge);
      }
    });

    return deps;
  }
}

3. Smart UI Updates

Update interface based on dependencies:

class SmartDependencyUI {
  constructor(evaluator) {
    this.evaluator = evaluator;
  }

  update(formData) {
    const evaluations = this.evaluator.evaluate(formData);

    evaluations.forEach((evaluation, fieldName) => {
      this.updateField(fieldName, evaluation);
    });
  }

  updateField(fieldName, evaluation) {
    const fieldElement = document.getElementById(fieldName);
    const container = fieldElement?.closest('.field-container');

    if (!container) return;

    // Update visibility
    if (evaluation.visible) {
      container.style.display = 'block';
      container.classList.add('fade-in');
    } else {
      container.style.display = 'none';
    }

    // Update enabled state
    if (evaluation.enabled) {
      fieldElement.disabled = false;
      container.classList.remove('disabled');
      this.removeDisabledHint(container);
    } else {
      fieldElement.disabled = true;
      container.classList.add('disabled');
      this.showDisabledHint(container, evaluation.reasons);
    }

    // Update options (for select/radio)
    if (evaluation.options && fieldElement.tagName === 'SELECT') {
      this.updateSelectOptions(fieldElement, evaluation.options);
    }
  }

  updateSelectOptions(selectElement, options) {
    // Clear existing options
    selectElement.innerHTML = '';

    // Add placeholder
    const placeholder = document.createElement('option');
    placeholder.value = '';
    placeholder.textContent = 'Select...';
    selectElement.appendChild(placeholder);

    // Add options with availability info
    options.forEach(opt => {
      const option = document.createElement('option');
      option.value = opt.value;
      option.textContent = opt.value;

      if (!opt.available) {
        option.disabled = true;
        option.textContent += ` (${opt.reason})`;
        option.classList.add('unavailable-option');
      }

      selectElement.appendChild(option);
    });
  }

  showDisabledHint(container, reasons) {
    const existing = container.querySelector('.disabled-hint');
    if (existing) existing.remove();

    const hint = document.createElement('div');
    hint.className = 'disabled-hint';
    hint.innerHTML = `
      <span class="icon">ℹ️</span>
      <span class="message">${reasons.join('. ')}</span>
    `;

    container.appendChild(hint);
  }

  removeDisabledHint(container) {
    const hint = container.querySelector('.disabled-hint');
    if (hint) hint.remove();
  }

  // Show suggestion to enable a field
  showEnablementSuggestion(fieldName, requiredChanges) {
    const container = document.getElementById(fieldName)
      ?.closest('.field-container');

    if (!container) return;

    const suggestion = document.createElement('div');
    suggestion.className = 'enablement-suggestion';
    suggestion.innerHTML = `
      <div class="suggestion-header">
        <span class="icon">💡</span>
        <span class="title">Want to enable this option?</span>
      </div>
      <div class="suggestion-body">
        ${requiredChanges.map(change => `
          <button 
            class="suggestion-action"
            onclick="applyChange('${change.field}', '${change.value}')"
          >
            Set ${change.field} to ${change.value}
          </button>
        `).join('')}
      </div>
    `;

    container.appendChild(suggestion);
  }
}

4. Guided Configuration

Help users navigate complex dependencies:

class ConfigurationGuide {
  constructor(graph, evaluator) {
    this.graph = graph;
    this.evaluator = evaluator;
  }

  suggestNextStep(formData) {
    // Determine what user should configure next
    const incomplete = this.getIncompleteFields(formData);

    if (incomplete.length === 0) {
      return {
        message: 'Configuration complete!',
        action: 'submit'
      };
    }

    // Find next logical field to fill
    const next = this.findNextLogicalField(incomplete, formData);

    return {
      message: `Next: Configure ${next.label}`,
      field: next.name,
      suggestions: this.getSuggestions(next.name, formData)
    };
  }

  findNextLogicalField(incompleteFields, formData) {
    // Prioritize fields with fewest dependencies
    // (easier to fill first)

    return incompleteFields.sort((a, b) => {
      const aDeps = this.evaluator.getDependencies(a).length;
      const bDeps = this.evaluator.getDependencies(b).length;
      return aDeps - bDeps;
    })[0];
  }

  getSuggestions(fieldName, formData) {
    const field = this.graph.nodes.get(fieldName);
    const options = this.evaluator.evaluateOptions(fieldName, formData);

    // Filter to available options
    const available = options.filter(opt => opt.available);

    if (available.length === 0) {
      return {
        type: 'prerequisites',
        message: 'Complete previous fields first',
        required: this.getPrerequisites(fieldName, formData)
      };
    }

    // Suggest most popular or default
    return {
      type: 'options',
      message: `Choose from ${available.length} options`,
      options: available.map(opt => opt.value),
      recommended: this.getRecommendedOption(fieldName, formData)
    };
  }

  getPrerequisites(fieldName, formData) {
    const deps = this.evaluator.getDependencies(fieldName);
    const missing = [];

    deps.forEach(dep => {
      const result = this.evaluator.evaluateDependency(dep, formData);
      if (!result.satisfied) {
        missing.push({
          field: dep.source,
          required: dep.condition.values,
          reason: result.reason
        });
      }
    });

    return missing;
  }

  getRecommendedOption(fieldName, formData) {
    // Use historical data, popular choices, or defaults
    // to recommend an option

    const field = this.graph.nodes.get(fieldName);

    // Simple: return default value
    if (field.defaultValue) {
      return field.defaultValue;
    }

    // Could enhance with:
    // - Most popular choice
    // - Choice that enables most other options
    // - Machine learning prediction

    return null;
  }

  getConfigurationPath(formData) {
    // Generate step-by-step guide through configuration
    const steps = [];
    const fields = Array.from(this.graph.nodes.keys());

    // Sort by dependency order
    const sorted = this.topologicalSort(fields);

    sorted.forEach((fieldName, index) => {
      const evaluation = this.evaluator.evaluateField(fieldName, formData);

      steps.push({
        step: index + 1,
        field: fieldName,
        label: this.graph.nodes.get(fieldName).label,
        status: formData[fieldName] ? 'complete' : 'pending',
        available: evaluation.enabled,
        prerequisites: evaluation.reasons || []
      });
    });

    return steps;
  }

  topologicalSort(fields) {
    // Sort fields by dependency order
    // (fields with no dependencies first)

    const sorted = [];
    const visited = new Set();

    const visit = (field) => {
      if (visited.has(field)) return;

      visited.add(field);

      // Visit dependencies first
      const deps = this.evaluator.getDependencies(field);
      deps.forEach(dep => visit(dep.source));

      sorted.push(field);
    };

    fields.forEach(field => visit(field));

    return sorted;
  }
}

5. Dependency Visualization

Show relationships visually:

class DependencyVisualizer {
  constructor(graph) {
    this.graph = graph;
  }

  renderDependencyMap() {
    const map = document.createElement('div');
    map.className = 'dependency-map';

    // Show all fields and their dependencies
    this.graph.nodes.forEach((config, fieldName) => {
      const fieldCard = this.createFieldCard(fieldName, config);
      map.appendChild(fieldCard);
    });

    // Draw connection lines
    this.drawConnections(map);

    return map;
  }

  createFieldCard(fieldName, config) {
    const card = document.createElement('div');
    card.className = 'dependency-field-card';
    card.id = `dep-card-${fieldName}`;

    // Get dependencies
    const deps = [];
    this.graph.edges.forEach((edge, key) => {
      if (edge.dependent === fieldName) {
        deps.push(edge);
      }
    });

    card.innerHTML = `
      <div class="card-header">
        <h4>${config.label}</h4>
      </div>
      <div class="card-body">
        ${deps.length > 0 ? `
          <div class="dependencies">
            <strong>Depends on:</strong>
            <ul>
              ${deps.map(dep => `
                <li>${dep.source} = ${dep.condition.values?.join(' or ')}</li>
              `).join('')}
            </ul>
          </div>
        ` : ''}
      </div>
    `;

    return card;
  }

  drawConnections(container) {
    // Use SVG to draw lines between dependent fields
    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.classList.add('dependency-connections');
    svg.style.position = 'absolute';
    svg.style.top = '0';
    svg.style.left = '0';
    svg.style.width = '100%';
    svg.style.height = '100%';
    svg.style.pointerEvents = 'none';

    this.graph.edges.forEach((edge, key) => {
      const fromCard = container.querySelector(`#dep-card-${edge.source}`);
      const toCard = container.querySelector(`#dep-card-${edge.dependent}`);

      if (fromCard && toCard) {
        const line = this.createConnectionLine(fromCard, toCard, edge);
        svg.appendChild(line);
      }
    });

    container.appendChild(svg);
  }

  createConnectionLine(fromElement, toElement, edge) {
    const fromRect = fromElement.getBoundingClientRect();
    const toRect = toElement.getBoundingClientRect();

    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');

    // Calculate connection points
    const x1 = fromRect.right;
    const y1 = fromRect.top + fromRect.height / 2;
    const x2 = toRect.left;
    const y2 = toRect.top + toRect.height / 2;

    // Create curved path
    const midX = (x1 + x2) / 2;
    path.setAttribute('d', `M ${x1},${y1} C ${midX},${y1} ${midX},${y2} ${x2},${y2}`);
    path.setAttribute('stroke', edge.type === 'enables' ? '#27ae60' : '#e74c3c');
    path.setAttribute('stroke-width', '2');
    path.setAttribute('fill', 'none');
    path.setAttribute('marker-end', 'url(#arrow)');

    return path;
  }

  showCurrentPath(formData) {
    // Highlight the current configuration path
    const evaluations = this.evaluator.evaluate(formData);

    evaluations.forEach((evaluation, fieldName) => {
      const card = document.getElementById(`dep-card-${fieldName}`);
      if (!card) return;

      if (formData[fieldName]) {
        card.classList.add('configured');
      } else if (evaluation.enabled) {
        card.classList.add('available');
      } else {
        card.classList.add('locked');
      }
    });
  }
}

Implementation Details

Complete Smart Dependencies System

class SmartDependenciesSystem {
  constructor() {
    this.graph = new DependencyGraph();
    this.evaluator = new DependencyEvaluator(this.graph);
    this.ui = new SmartDependencyUI(this.evaluator);
    this.guide = new ConfigurationGuide(this.graph, this.evaluator);
    this.visualizer = new DependencyVisualizer(this.graph);
  }

  initialize(config) {
    // Define fields
    config.fields?.forEach(field => {
      this.graph.defineField(field.name, field);
    });

    // Define dependencies
    config.dependencies?.forEach(dep => {
      if (dep.type === 'requires') {
        this.graph.requires(dep.field, dep.requires, dep.values);
      } else if (dep.type === 'incompatible') {
        this.graph.incompatibleWith(dep.field, dep.incompatible, dep.values);
      }
    });

    // Set up change listeners
    this.setupListeners();

    // Initial evaluation
    this.update({});
  }

  setupListeners() {
    document.querySelectorAll('input, select').forEach(element => {
      element.addEventListener('change', () => {
        this.handleChange();
      });
    });
  }

  handleChange() {
    const formData = this.getFormData();
    this.update(formData);
  }

  update(formData) {
    // Update UI based on dependencies
    this.ui.update(formData);

    // Show next step suggestion
    const nextStep = this.guide.suggestNextStep(formData);
    this.showNextStepSuggestion(nextStep);

    // Update visual map if visible
    if (this.isMapVisible()) {
      this.visualizer.showCurrentPath(formData);
    }
  }

  showNextStepSuggestion(nextStep) {
    const suggestionBox = document.getElementById('next-step-suggestion');
    if (!suggestionBox) return;

    if (nextStep.action === 'submit') {
      suggestionBox.innerHTML = `
        <div class="success-message">
          ✓ ${nextStep.message}
        </div>
      `;
    } else {
      suggestionBox.innerHTML = `
        <div class="next-step">
          <strong>${nextStep.message}</strong>
          ${nextStep.suggestions ? `
            <div class="suggestions">
              ${this.formatSuggestions(nextStep.suggestions)}
            </div>
          ` : ''}
        </div>
      `;
    }
  }

  formatSuggestions(suggestions) {
    if (suggestions.type === 'prerequisites') {
      return `
        <p>${suggestions.message}</p>
        <ul>
          ${suggestions.required.map(req => `
            <li>Set ${req.field} to ${req.required.join(' or ')}</li>
          `).join('')}
        </ul>
      `;
    } else {
      return `
        <p>${suggestions.message}</p>
        ${suggestions.recommended ? `
          <button onclick="selectOption('${suggestions.recommended}')">
            Use recommended: ${suggestions.recommended}
          </button>
        ` : ''}
      `;
    }
  }

  getFormData() {
    const form = document.querySelector('form');
    const data = new FormData(form);
    return Object.fromEntries(data.entries());
  }

  isMapVisible() {
    const map = document.querySelector('.dependency-map');
    return map && map.style.display !== 'none';
  }
}

// Example usage
const system = new SmartDependenciesSystem();

system.initialize({
  fields: [
    {
      name: 'material',
      label: 'Material',
      type: 'select',
      options: ['Standard', 'Premium', 'Luxury'],
      alwaysVisible: true
    },
    {
      name: 'finish',
      label: 'Finish',
      type: 'select',
      options: ['Matte', 'Glossy', 'Embossed', 'Foil'],
      alwaysVisible: true
    },
    {
      name: 'rushDelivery',
      label: 'Rush Delivery',
      type: 'checkbox',
      alwaysVisible: true
    }
  ],

  dependencies: [
    {
      type: 'requires',
      field: 'finish',
      requires: 'material',
      values: ['Premium', 'Luxury']
    },
    {
      type: 'incompatible',
      field: 'rushDelivery',
      incompatible: 'finish',
      values: ['Embossed', 'Foil']
    }
  ]
});

Consequences

Benefits

Guided Experience: - Users navigate complex options confidently - Clear what's possible at each step - Reduced decision paralysis

Error Prevention: - Invalid combinations impossible - Real-time feedback - No submission errors

Better UX: - Progressive disclosure - Contextual help - Smart suggestions

Improved Completion: - Users understand what's needed - Clear path to completion - Higher success rate

Reduced Support: - Self-explanatory constraints - Visual dependency feedback - Fewer "why can't I" questions

Liabilities

Complexity: - Dependency graphs can be intricate - Testing all paths challenging - Maintenance burden

Performance: - Real-time evaluation expensive - Complex graphs slow - Need optimization

Rigidity: - Enforced dependencies may be too strict - Edge cases not handled - Need override mechanisms

User Learning Curve: - Progressive disclosure can confuse - "Where did this field come from?" - Need clear communication

Over-Guidance: - Too much help feels patronizing - Power users want freedom - Balance guidance with autonomy

Domain Examples

E-commerce: Product Configuration

// Computer configuration dependencies
defineComputerConfig() {
  // RAM depends on motherboard
  this.graph.requires('ram', 'motherboard', ['DDR5 Board']);

  // Graphics card depends on power supply
  this.graph.requires('graphicsCard', 'powerSupply', ['750W', '850W']);

  // Cooling depends on CPU
  this.graph.requires('cooling', 'cpu', ['High-End CPU']);
}

Healthcare: Treatment Planning

// Treatment dependencies
defineTreatmentDeps() {
  // Certain medications require lab work
  this.graph.requires('medication', 'labResults', ['Complete']);

  // Surgery requires pre-op clearance
  this.graph.requires('surgery', 'preOpClearance', ['Approved']);

  // Physical therapy incompatible with certain conditions
  this.graph.incompatibleWith('physicalTherapy', 'condition', ['Acute Injury']);
}

Travel: Booking Flow

// Travel booking dependencies
defineTravelDeps() {
  // Hotel requires destination
  this.graph.requires('hotel', 'destination', ['Selected']);

  // Car rental requires arrival date
  this.graph.requires('carRental', 'arrivalDate', ['Set']);

  // Activities depend on destination
  this.graph.requires('activities', 'destination', ['Selected']);
}

Prerequisites: - Volume 3, Pattern 7: Calculated Dependencies (computed values) - Volume 3, Pattern 11: Cascading Updates (changes propagate)

Synergies: - Volume 3, Pattern 1: Progressive Disclosure (show fields as relevant) - Volume 3, Pattern 9: Contextual Constraints (context affects availability) - Volume 3, Pattern 13: Conditional Requirements (requirements drive dependencies)

Conflicts: - Simple forms (dependencies add complexity) - Expert users (may find guidance restrictive)

Alternatives: - Wizard workflows (step-by-step instead of smart form) - Static validation (catch errors at submit) - Expert mode (disable all guidance)

Known Uses

PC Part Picker: Compatible components based on selections

Car Configurators: Options depend on model, trim, packages

Insurance Applications: Coverage options based on selections

Product Customization (Dell, Apple): Features depend on base configuration

Travel Booking (Expedia, Kayak): Hotels/cars/activities based on destination

Software Licensing: Features/tiers have dependencies

Cable/Internet Packages: Add-ons depend on base package


Further Reading

Academic Foundations

  • Constraint Satisfaction: Russell, S., & Norvig, P. (2020). Artificial Intelligence: A Modern Approach (4th ed.). Pearson. ISBN: 978-0134610993 - Chapter on CSP
  • Product Configuration: Felfernig, A., et al. (2014). "Knowledge-based configuration: A survey." IEEE Intelligent Systems 29(2): 16-25.
  • Dependency Networks: Heckerman, D., et al. (2000). "Dependency Networks for Inference." ICML '00.

Practical Implementation

Standards & Patterns

  • Pattern 12: Calculated Dependencies - Calculations follow dependencies
  • Pattern 14: Contextual Constraints - Context defines dependencies
  • Pattern 16: Cascading Updates - Dependencies trigger cascades
  • Pattern 19: Cross-Field Validation - Validate dependencies
  • Volume 2, Pattern 7: Multi-Dimensional Risk Assessment - Dependency rules
  • Volume 1, Chapter 10: Domain Knowledge Acquisition - Configuration patterns

Tools & Services

Implementation Examples