diff --git a/include/class.forms.php b/include/class.forms.php
index 585772fdf9a914b937bcbc0501e582b58abf65ea..6552d5c13b0ebd135f99d29498cdbfc1b7045a7e 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -236,7 +236,9 @@ class FormField {
         if (!isset($this->_clean)) {
             $this->_clean = (isset($this->value))
                 ? $this->value : $this->parse($this->getWidget()->value);
-            $this->validateEntry($this->_clean);
+
+            if ($this->isVisible())
+                $this->validateEntry($this->_clean);
         }
         return $this->_clean;
     }
@@ -291,6 +293,21 @@ class FormField {
         }
     }
 
+    /**
+     * isVisible
+     *
+     * If this field has visibility configuration, then it will parse the
+     * constraints with the visibility configuration to determine if the
+     * field is visible and should be considered for validation
+     */
+    function isVisible() {
+        $config = $this->getConfiguration();
+        if ($this->get('visibility') instanceof VisibilityConstraint) {
+            return $this->get('visibility')->isVisible($this);
+        }
+        return true;
+    }
+
     /**
      * parse
      *
@@ -452,7 +469,11 @@ class FormField {
     }
 
     function render($mode=null) {
-        return $this->getWidget()->render($mode);
+        $rv = $this->getWidget()->render($mode);
+        if ($v = $this->get('visibility')) {
+            $v->emitJavascript($this);
+        }
+        return $rv;
     }
 
     function renderExtras($mode=null) {
@@ -1663,6 +1684,7 @@ class TextareaWidget extends Widget {
         <span style="display:inline-block;width:100%">
         <textarea <?php echo $rows." ".$cols." ".$maxlength." ".$class
                 .' placeholder="'.$config['placeholder'].'"'; ?>
+            id="<?php echo $this->name; ?>"
             name="<?php echo $this->name; ?>"><?php
                 echo Format::htmlchars($this->value);
             ?></textarea>
@@ -1676,7 +1698,7 @@ class PhoneNumberWidget extends Widget {
         $config = $this->field->getConfiguration();
         list($phone, $ext) = explode("X", $this->value);
         ?>
-        <input type="text" name="<?php echo $this->name; ?>" value="<?php
+        <input id="<?php echo $this->name; ?>" type="text" name="<?php echo $this->name; ?>" value="<?php
         echo Format::htmlchars($phone); ?>"/><?php
         // Allow display of extension field even if disabled if the phone
         // number being edited has an extension
@@ -1806,7 +1828,8 @@ class CheckboxWidget extends Widget {
         if (!isset($this->value))
             $this->value = $this->field->get('default');
         ?>
-        <input style="vertical-align:top;" type="checkbox" name="<?php echo $this->name; ?>[]" <?php
+        <input id="<?php echo $this->name; ?>" style="vertical-align:top;"
+            type="checkbox" name="<?php echo $this->name; ?>[]" <?php
             if ($this->value) echo 'checked="checked"'; ?> value="<?php
             echo $this->field->get('id'); ?>"/>
         <?php
@@ -1841,6 +1864,7 @@ class DatetimePickerWidget extends Widget {
         }
         ?>
         <input type="text" name="<?php echo $this->name; ?>"
+            id="<?php echo $this->name; ?>"
             value="<?php echo Format::htmlchars($this->value); ?>" size="12"
             autocomplete="off" class="dp" />
         <script type="text/javascript">
@@ -2035,4 +2059,157 @@ class FileUploadWidget extends Widget {
 
 class FileUploadError extends Exception {}
 
+class VisibilityConstraint {
+
+    const HIDDEN =      0x0001;
+    const VISIBLE =     0x0002;
+
+    var $initial;
+    var $constraint;
+
+    function __construct($constraint, $initial=self::VISIBLE) {
+        $this->constraint = $constraint;
+        $this->initial = $initial;
+    }
+
+    function emitJavascript($field) {
+        $func = 'recheck';
+        $form = $field->getForm();
+?>
+    <script type="text/javascript">
+      !(function() {
+        var <?php echo $func; ?> = function() {
+          var target = $('#field-<?php echo $field->getWidget()->name; ?>');
+
+<?php   $fields = $this->getAllFields($this->constraint);
+        foreach ($fields as $f) {
+            $field = $form->getField($f);
+            echo sprintf('var %1$s = $("#%1$s");',
+                $field->getWidget()->name);
+        }
+        $expression = $this->compileQ($this->constraint, $form);
+?>
+          target.slideToggle(<?php echo $expression; ?>);
+        };
+
+<?php   foreach ($fields as $f) {
+            $w = $form->getField($f)->getWidget();
+?>
+        $('#<?php echo $w->name; ?>').on('change', <?php echo $func; ?>);
+<?php   } ?>
+      })();
+    </script><?php
+    }
+
+    /**
+     * Determines if the field was visible when the form was submitted
+     */
+    function isVisible($field) {
+        return $this->compileQPhp($this->constraint, $field);
+    }
+
+    function compileQPhp(Q $Q, $field) {
+        $form = $field->getForm();
+        $expr = array();
+        foreach ($Q->constraints as $c=>$value) {
+            if ($value instanceof Q) {
+                $expr[] = $this->compileQPhp($value, $field);
+            }
+            else {
+                @list($f, $op) = explode('__', $c, 2);
+                $field = $form->getField($f);
+                $wval = $field->getClean();
+                switch ($op) {
+                case 'eq':
+                case null:
+                    $expr[] = $wval == $value;
+                }
+            }
+        }
+        $glue = $Q->isOred()
+            ? function($a, $b) { return $a || $b; }
+            : function($a, $b) { return $a && $b; };
+        $initial = !$Q->isOred();
+        $expression = array_reduce($expr, $glue, $initial);
+        if ($Q->isNegated)
+            $expression = !$expression;
+        return $expression;
+    }
+
+    function getAllFields(Q $Q, &$fields=array()) {
+        foreach ($Q->constraints as $c=>$value) {
+            if ($c instanceof Q) {
+                $this->getAllFields($c, $fields);
+            }
+            else {
+                list($f, $op) = explode('__', $c, 2);
+                $fields[$f] = true;
+            }
+        }
+        return array_keys($fields);
+    }
+
+    function compileQ($Q, $form) {
+        $expr = array();
+        foreach ($Q->constraints as $c=>$value) {
+            if ($value instanceof Q) {
+                $expr[] = $this->compileQ($value, $form);
+            }
+            else {
+                list($f, $op) = explode('__', $c, 2);
+                $name = $form->getField($f)->getWidget()->name;
+                switch ($op) {
+                case 'eq':
+                default:
+                    $expr[] = sprintf('%s.val() == %s', $name, JsonDataEncoder::encode($value));
+                }
+            }
+        }
+        $glue = $Q->isOred() ? ' || ' : ' && ';
+        $expression = implode($glue, $expr);
+        if (count($expr) > 1)
+            $expression = '('.$expression.')';
+        if ($Q->isNegated)
+            $expression = '!'.$expression;
+        return $expression;
+    }
+}
+
+class Q {
+    const NEGATED = 0x0001;
+    const ANY =     0x0002;
+
+    var $constraints;
+    var $flags;
+    var $negated = false;
+    var $ored = false;
+
+    function __construct($filter, $flags=0) {
+        $this->constraints = $filter;
+        $this->negated = $flags & self::NEGATED;
+        $this->ored = $flags & self::ANY;
+    }
+
+    function isNegated() {
+        return $this->negated;
+    }
+
+    function isOred() {
+        return $this->ored;
+    }
+
+    function negate() {
+        $this->negated = !$this->negated;
+        return $this;
+    }
+
+    static function not(array $constraints) {
+        return new static($constraints, self::NEGATED);
+    }
+
+    static function any(array $constraints) {
+        return new static($constraints, self::ORED);
+    }
+}
+
 ?>
diff --git a/include/staff/templates/dynamic-field-config.tmpl.php b/include/staff/templates/dynamic-field-config.tmpl.php
index af257465289439f8748ffc948911be5fc62e2f1c..104c14e643c38f38a07c6b24fd1a356a9f7d3b77 100644
--- a/include/staff/templates/dynamic-field-config.tmpl.php
+++ b/include/staff/templates/dynamic-field-config.tmpl.php
@@ -8,10 +8,15 @@
         $form = $field->getConfigurationForm();
         echo $form->getMedia();
         foreach ($form->getFields() as $name=>$f) { ?>
-            <div class="flush-left custom-field">
-            <div class="field-label">
+            <div class="flush-left custom-field" id="field-<?php echo $f->getWidget()->name;
+                ?>" <?php if (!$f->isVisible()) echo 'style="display:none;"'; ?>>
+            <div class="field-label <?php if ($f->get('required')) echo 'required'; ?>">
             <label for="<?php echo $f->getWidget()->name; ?>">
-                <?php echo Format::htmlchars($f->get('label')); ?>:</label>
+                <?php echo Format::htmlchars($f->get('label')); ?>:
+      <?php if ($f->get('required')) { ?>
+                <span class="error">*</span>
+      <?php } ?>
+            </label>
             <?php
             if ($f->get('hint')) { ?>
                 <br/><em style="color:gray;display:inline-block"><?php
@@ -21,10 +26,6 @@
             </div><div>
             <?php
             $f->render();
-            if ($f->get('required')) { ?>
-                <font class="error">*</font>
-            <?php
-            }
             ?>
             </div>
             <?php
diff --git a/scp/css/scp.css b/scp/css/scp.css
index 9389be034598f57f880b0124b14b8b79c31924be..5e3579d5ee8d48e692522b76ed039b0d09a132ae 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -1948,3 +1948,6 @@ table.custom-info td {
     direction: ltr;
     unicode-bidi: embed;
 }
+.required {
+    font-weight: bold;
+}