Show this section IF (State is WA OR CA) AND (Income < $50,000).
Government forms need complex conditional logic. The Logic Engine evaluates expression trees to determine which fields are visible based on current form values.
Simple visibility rules like “show X when Y equals Z” don’t scale. Real forms have conditions like:
“Show disability section if age > 65 OR has disability”
“Show income verification if employment = ‘self-employed’ AND income > $50k”
“Hide bank section if (payment method = ‘check’) OR (no bank account)”
We need composable conditions: rules that combine with AND/OR operators into arbitrarily deep trees.This tree evaluates to: (State = WA OR State = CA) AND (Income < 50000)
type LogicRule = { id: string; type: "rule"; fieldId: string; // Which field to check operator: LogicOperator; // How to compare value?: string | number | boolean | array; // What to compare against};
function evaluateGroup(group: LogicGroup, formData: FormData): boolean { if (group.children.length === 0) return true; if (group.operator === "AND") { // All children must be true return group.children.every((child) => evaluateLogic(child, formData)); } else { // At least one child must be true return group.children.some((child) => evaluateLogic(child, formData)); }}
function getFieldValue(formData: FormData, fieldId: string): unknown { const parts = fieldId.split("."); let value = formData; for (const part of parts) { if (value === null || value === undefined) return undefined; value = value[part]; } return value;}
The old visibility format is still supported for backwards compatibility:
// Old format (deprecated){ "visibility": { "when": "has-dependents", "is": "yes" }}// New format (preferred){ "logic": { "type": "rule", "fieldId": "has-dependents", "operator": "eq", "value": "yes" }}
The evaluateElementVisibility function handles both:
export function evaluateElementVisibility(element, formData): boolean { // Prefer new logic system if (element.logic) { const result = evaluateLogic(element.logic, formData); const action = element.logicAction || "show"; return action === "show" ? result : !result; } // Fall back to legacy visibility if (element.visibility) { return evaluateVisibility(element.visibility, formData); } // No conditions = always visible return true;}
The logic engine re-evaluates on every form value change. For most forms this is instant, but deep nesting could become slow.Optimizations applied:
Short-circuit evaluation — AND stops at first false, OR stops at first true
Direct property access — no recursive tree walking for simple paths
Type coercion caching — number/string conversions memoized per render
Worst case: A form with 100 fields, each with 10-deep nested logic, re-evaluated 60x per second. In practice, forms have 20-50 fields with 2-3 level nesting.