You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by hn...@apache.org on 2023/01/05 15:42:38 UTC

[myfaces-tobago] 01/02: refactor: Bootstrap SCSS for validation

This is an automated email from the ASF dual-hosted git repository.

hnoeth pushed a commit to branch tobago-5.x
in repository https://gitbox.apache.org/repos/asf/myfaces-tobago.git

commit d3d7d1abd81f629719b2e13f6fd30baa4952a467
Author: Henning Noeth <hn...@apache.org>
AuthorDate: Mon Jan 2 13:44:14 2023 +0100

    refactor: Bootstrap SCSS for validation
    
    * Generate validation CSS classes with Bootstrap to replace custom SCSS.
      New classes are: is-error, is-warning, is-info
      Those classes replace: border-danger, border-warning, border-info
    * adjust tests
    * cleanup
---
 .../internal/renderkit/renderer/DateRenderer.java  |   2 +-
 .../internal/renderkit/renderer/FileRenderer.java  |   2 +-
 .../internal/renderkit/renderer/InRenderer.java    |   2 +-
 .../renderer/SelectBooleanRendererBase.java        |   2 +-
 .../renderer/SelectManyCheckboxRenderer.java       |   2 +-
 .../renderer/SelectManyListboxRenderer.java        |   5 +-
 .../renderer/SelectManyShuttleRenderer.java        |   2 +-
 .../renderer/SelectOneChoiceRenderer.java          |   2 +-
 .../renderer/SelectOneListboxRenderer.java         |   2 +-
 .../renderkit/renderer/SelectOneRadioRenderer.java |   2 +-
 .../renderkit/renderer/TextareaRenderer.java       |   2 +-
 .../tobago/renderkit/css/BootstrapClass.java       |  10 ++
 .../myfaces/tobago/renderkit/css/TobagoClass.java  |  52 ----------
 .../resources/renderer/date/error-message.html     |   4 +-
 .../test/resources/renderer/in/error-message.html  |   4 +-
 .../selectBooleanCheckboxError.html                |   2 +-
 .../selectBooleanCheckboxFatal.html                |   2 +-
 .../selectBooleanCheckboxInfo.html                 |   2 +-
 .../selectBooleanCheckboxWarning.html              |   2 +-
 .../selectManyCheckboxFatal.html                   |   4 +-
 .../renderer/selectManyShuttle/error-message.html  |   4 +-
 .../content/170-validation/02/Severity.test.js     |   8 +-
 tobago-theme/src/main/scss/_tobago.scss            | 108 +++++----------------
 23 files changed, 63 insertions(+), 164 deletions(-)

diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/DateRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/DateRenderer.java
index 7e6987a7e1..b3d96c6bc1 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/DateRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/DateRenderer.java
@@ -129,7 +129,7 @@ public class DateRenderer<T extends AbstractUIDate> extends MessageLayoutRendere
     writer.writeAttribute(HtmlAttributes.MAX, convertToString(date.getMax()), true);
 
     writer.writeClassAttribute(
-        BootstrapClass.borderColor(ComponentUtils.getMaximumSeverity(date)),
+        BootstrapClass.validationColor(ComponentUtils.getMaximumSeverity(date)),
         BootstrapClass.FORM_CONTROL,
         date.getCustomClass());
 
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/FileRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/FileRenderer.java
index ee03bb2dd6..702d5a192e 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/FileRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/FileRenderer.java
@@ -154,7 +154,7 @@ public class FileRenderer<T extends AbstractUIFile>
     writer.writeIdAttribute(fieldId);
     writer.writeClassAttribute(
         BootstrapClass.FORM_CONTROL,
-        BootstrapClass.borderColor(ComponentUtils.getMaximumSeverity(component)));
+        BootstrapClass.validationColor(ComponentUtils.getMaximumSeverity(component)));
     writer.writeNameAttribute(clientId);
     // readonly seems not making sense in browsers.
     writer.writeAttribute(HtmlAttributes.DISABLED, disabled || readonly);
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/InRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/InRenderer.java
index abbf2cc12e..95447b53d2 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/InRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/InRenderer.java
@@ -145,7 +145,7 @@ public class InRenderer<T extends AbstractUIIn> extends MessageLayoutRendererBas
 
     writer.writeClassAttribute(
         markup != null && markup.contains(Markup.NUMBER) ? TobagoClass.NUMBER : null,
-        BootstrapClass.borderColor(ComponentUtils.getMaximumSeverity(component)),
+        BootstrapClass.validationColor(ComponentUtils.getMaximumSeverity(component)),
         BootstrapClass.FORM_CONTROL,
         component.getCustomClass());
 
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectBooleanRendererBase.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectBooleanRendererBase.java
index 65c54b1543..fb9b8573bc 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectBooleanRendererBase.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectBooleanRendererBase.java
@@ -101,7 +101,7 @@ public abstract class SelectBooleanRendererBase<T extends AbstractUISelectBoolea
     writer.startElement(HtmlElements.INPUT);
     writer.writeClassAttribute(
         BootstrapClass.FORM_CHECK_INPUT,
-        BootstrapClass.borderColor(ComponentUtils.getMaximumSeverity(component)));
+        BootstrapClass.validationColor(ComponentUtils.getMaximumSeverity(component)));
     writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.CHECKBOX);
     writer.writeAttribute(HtmlAttributes.VALUE, "true", false);
     writer.writeNameAttribute(clientId);
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectManyCheckboxRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectManyCheckboxRenderer.java
index 2ab6faf352..786ca883e4 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectManyCheckboxRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectManyCheckboxRenderer.java
@@ -84,7 +84,7 @@ public class SelectManyCheckboxRenderer<T extends AbstractUISelectManyCheckbox>
         writer.startElement(HtmlElements.INPUT);
         writer.writeClassAttribute(
             BootstrapClass.FORM_CHECK_INPUT,
-            BootstrapClass.borderColor(ComponentUtils.getMaximumSeverity(component)));
+            BootstrapClass.validationColor(ComponentUtils.getMaximumSeverity(component)));
         writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.CHECKBOX);
         final String formattedValue = getFormattedValue(facesContext, component, item.getValue());
         final boolean checked;
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectManyListboxRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectManyListboxRenderer.java
index 8968a90d19..85a35407b0 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectManyListboxRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectManyListboxRenderer.java
@@ -73,7 +73,7 @@ public class SelectManyListboxRenderer<T extends AbstractUISelectManyListbox> ex
 
     writer.writeClassAttribute(
         BootstrapClass.FORM_CONTROL,
-        BootstrapClass.borderColor(ComponentUtils.getMaximumSeverity(component)),
+        BootstrapClass.validationColor(ComponentUtils.getMaximumSeverity(component)),
         component.getCustomClass(),
         markup != null && markup.contains(Markup.SPREAD) ? TobagoClass.SPREAD : null);
     writer.writeAttribute(HtmlAttributes.MULTIPLE, true);
@@ -82,8 +82,7 @@ public class SelectManyListboxRenderer<T extends AbstractUISelectManyListbox> ex
     final Object[] values = component.getSelectedValues();
     final String[] submittedValues = getSubmittedValues(component);
 
-    renderSelectItems(
-        component, TobagoClass.SELECT_MANY_LISTBOX__OPTION, items, values, submittedValues, writer, facesContext);
+    renderSelectItems(component, null, items, values, submittedValues, writer, facesContext);
   }
 
   @Override
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectManyShuttleRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectManyShuttleRenderer.java
index e4a3027dbc..60bd20d1cf 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectManyShuttleRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectManyShuttleRenderer.java
@@ -120,7 +120,7 @@ public class SelectManyShuttleRenderer<T extends AbstractUISelectManyShuttle> ex
     writer.writeAttribute(HtmlAttributes.TABINDEX, component.getTabIndex());
     writer.writeClassAttribute(
         TobagoClass.SELECTED,
-        BootstrapClass.borderColor(ComponentUtils.getMaximumSeverity(component)),
+        BootstrapClass.validationColor(ComponentUtils.getMaximumSeverity(component)),
         BootstrapClass.FORM_CONTROL);
     writer.writeAttribute(HtmlAttributes.MULTIPLE, true);
     writer.writeAttribute(HtmlAttributes.SIZE, size);
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectOneChoiceRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectOneChoiceRenderer.java
index 589f6af454..df8ca39435 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectOneChoiceRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectOneChoiceRenderer.java
@@ -81,7 +81,7 @@ public class SelectOneChoiceRenderer<T extends AbstractUISelectOneChoice> extend
 
     writer.writeClassAttribute(
         BootstrapClass.FORM_SELECT,
-        BootstrapClass.borderColor(ComponentUtils.getMaximumSeverity(component)),
+        BootstrapClass.validationColor(ComponentUtils.getMaximumSeverity(component)),
         component.getCustomClass());
     if (title != null) {
       writer.writeAttribute(HtmlAttributes.TITLE, title, true);
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectOneListboxRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectOneListboxRenderer.java
index 244f841759..efaed809b3 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectOneListboxRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectOneListboxRenderer.java
@@ -72,7 +72,7 @@ public class SelectOneListboxRenderer<T extends AbstractUISelectOneListbox> exte
 
     writer.writeClassAttribute(
         BootstrapClass.FORM_CONTROL,
-        BootstrapClass.borderColor(ComponentUtils.getMaximumSeverity(component)),
+        BootstrapClass.validationColor(ComponentUtils.getMaximumSeverity(component)),
         component.getCustomClass(),
         markup != null && markup.contains(Markup.SPREAD) ? TobagoClass.SPREAD : null);
     final String title = HtmlRendererUtils.getTitleFromTipAndMessages(facesContext, component);
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectOneRadioRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectOneRadioRenderer.java
index 4dee4d00cd..648e6ed9d6 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectOneRadioRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectOneRadioRenderer.java
@@ -134,7 +134,7 @@ public class SelectOneRadioRenderer<T extends AbstractUISelectOneRadio> extends
         writer.startElement(HtmlElements.INPUT);
         writer.writeClassAttribute(
             BootstrapClass.FORM_CHECK_INPUT,
-            BootstrapClass.borderColor(ComponentUtils.getMaximumSeverity(component)));
+            BootstrapClass.validationColor(ComponentUtils.getMaximumSeverity(component)));
         writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.RADIO);
         final String formattedValue = getFormattedValue(facesContext, component, item.getValue());
         final boolean checked;
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TextareaRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TextareaRenderer.java
index 4494e8aa3f..19bd0499a1 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TextareaRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TextareaRenderer.java
@@ -103,7 +103,7 @@ public class TextareaRenderer<T extends AbstractUITextarea> extends MessageLayou
     }
 
     writer.writeClassAttribute(
-        BootstrapClass.borderColor(ComponentUtils.getMaximumSeverity(component)),
+        BootstrapClass.validationColor(ComponentUtils.getMaximumSeverity(component)),
         BootstrapClass.FORM_CONTROL,
         component.getCustomClass(),
         markup != null && markup.contains(Markup.SPREAD) ? TobagoClass.SPREAD : null);
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/BootstrapClass.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/BootstrapClass.java
index f41e2921db..a495197a0c 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/BootstrapClass.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/BootstrapClass.java
@@ -405,6 +405,9 @@ public enum BootstrapClass implements CssItem {
   INPUT_GROUP_PREPEND("input-group-prepend"),
   INPUT_GROUP_TEXT("input-group-text"),
   INVISIBLE("invisible"),
+  IS_ERROR("is-error"),
+  IS_INFO("is-info"),
+  IS_WARNING("is-warning"),
   JUSTIFY_CONTENT_AROUND("justify-content-around"),
   JUSTIFY_CONTENT_BETWEEN("justify-content-between"),
   JUSTIFY_CONTENT_CENTER("justify-content-center"),
@@ -658,6 +661,13 @@ public enum BootstrapClass implements CssItem {
     }
   }
 
+  public static CssItem validationColor(final FacesMessage.Severity severity) {
+    if (severity == null) {
+      return null;
+    }
+    return getSeverityCssItem(severity, IS_INFO, IS_WARNING, IS_ERROR);
+  }
+
   public static CssItem borderColor(final FacesMessage.Severity severity) {
     if (severity == null) {
       return null;
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/TobagoClass.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/TobagoClass.java
index 91af83d7ed..224a6cb706 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/TobagoClass.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/TobagoClass.java
@@ -28,13 +28,11 @@ public enum TobagoClass implements CssItem {
 
   ASCENDING("tobago-ascending"),
   AUTO__SPACING("tobago-auto-spacing"),
-  //  BADGE("tobago-badge"),
   BAR("tobago-bar"),
   BODY("tobago-body"),
   BOX__HEADER("tobago-box-header"),
   // tbd: can this be removed, when using <tobago-button>?
   BUTTON("tobago-button"),
-  //  BUTTONS("tobago-buttons"),
   CIRCLE__1("tobago-circle-1"),
   CIRCLE__2("tobago-circle-2"),
   CIRCLE__3("tobago-circle-3"),
@@ -45,25 +43,16 @@ public enum TobagoClass implements CssItem {
   CIRCLE__8("tobago-circle-8"),
   CIRCLE__9("tobago-circle-9"),
   COLLAPSED("tobago-collapsed"),
-  //  DATE("tobago-date"),
-//  DATE__PICKER("tobago-date-picker"),
   DELETED("tobago-deleted"),
   DESCENDING("tobago-descending"),
   DISABLED("tobago-disabled"),
   DISPLAY__INLINE__BLOCK("tobago-display-inline-block"),
   DROPDOWN__SUBMENU("tobago-dropdown-submenu"),
   EXPANDED("tobago-expanded"),
-  //  FILE("tobago-file"),
-//  FIGURE("tobago-figure"),
   FOCUS("tobago-focus"),
   FOLDER("tobago-folder"),
   FILTER("tobago-filter"),
   HEADER("tobago-header"),
-  //  IMAGE("tobago-image"),
-  // tbd: can be removed?
-//  IN("tobago-in"),
-//  INPUT__GROUP__OUTER("tobago-input-group-outer"),
-//  LABEL("tobago-label"),
   LABEL__CONTAINER("tobago-label-container"),
   LEVEL("tobago-level"),
   LINK("tobago-link"),
@@ -85,38 +74,8 @@ public enum TobagoClass implements CssItem {
   RESIZE("tobago-resize"),
   SECTION__CONTENT("tobago-section-content"),
   SELECT__FIELD("tobago-select-field"),
-  SELECT_MANY_LISTBOX__OPTION("tobago-selectManyListbox-option"),
-  //  SELECT_MANY_SHUTTLE("tobago-selectManyShuttle"),
-//  SELECT_MANY_SHUTTLE__ADD("tobago-selectManyShuttle-add"),
-//  SELECT_MANY_SHUTTLE__ADD_ALL("tobago-selectManyShuttle-addAll"),
-//  SELECT_MANY_SHUTTLE__HIDDEN("tobago-selectManyShuttle-hidden"),
-//  SELECT_MANY_SHUTTLE__OPTION("tobago-selectManyShuttle-option"),
-//  SELECT_MANY_SHUTTLE__REMOVE("tobago-selectManyShuttle-remove"),
-//  SELECT_MANY_SHUTTLE__REMOVE_ALL("tobago-selectManyShuttle-removeAll"),
-//  SELECT_MANY_SHUTTLE__SELECTED("tobago-selectManyShuttle-selected"),
-//  SELECT_MANY_SHUTTLE__SELECTED_LABEL("tobago-selectManyShuttle-selectedLabel"),
-//  SELECT_MANY_SHUTTLE__TOOL_BAR("tobago-selectManyShuttle-toolBar"),
-//  SELECT_MANY_SHUTTLE__UNSELECTED("tobago-selectManyShuttle-unselected"),
-//  SELECT_MANY_SHUTTLE__UNSELECTED_LABEL("tobago-selectManyShuttle-unselectedLabel"),
-  SELECT_ONE_LISTBOX("tobago-selectOneListbox"),
-  SELECT_ONE_LISTBOX__OPTION("tobago-selectOneListbox-option"),
   SELECTED("tobago-selected"),
   SEPARATOR("tobago-separator"),
-  //  SHEET("tobago-sheet"),
-//  SHEET__CELL("tobago-sheet-cell"),
-//  SHEET__FOOTER("tobago-sheet-footer"),
-//  SHEET__BODY("tobago-sheet-body"),
-//  SHEET__HEADER_CELL("tobago-sheet-headerCell"),
-//  SHEET__HEADER_RESIZE("tobago-sheet-headerResize"),
-//  SHEET__EXPANDED("tobago-sheet-expanded"),
-//  SHEET__PAGING_TEXT("tobago-sheet-pagingText"),
-//  SHEET__HEADER("tobago-sheet-header"),
-//  SHEET__BODY_TABLE("tobago-sheet-bodyTable"),
-//  SHEET__COLUMN_SELECTOR("tobago-sheet-columnSelector"),
-//  SHEET__HEADER_TABLE("tobago-sheet-headerTable"),
-//  SHEET__PAGING_INPUT("tobago-sheet-pagingInput"),
-//  SHEET__PAGING_OUTPUT("tobago-sheet-pagingOutput"),
-//  SHEET__ROW("tobago-sheet-row"),
   SORTABLE("tobago-sortable"),
   SPREAD("tobago-spread"),
   STARS("tobago-stars"),
@@ -127,21 +86,10 @@ public enum TobagoClass implements CssItem {
   STARS__SLIDER("tobago-stars-slider"),
   STARS__TOOLTIP("tobago-stars-tooltip"),
   STARS__UNSELECTED("tobago-stars-unselected"),
-  //  TAB("tobago-tab"),
-//  TAB__BAR_FACET("tobago-tab-barFacet"),
-//  TAB__CONTENT("tobago-tab-content"),
-//  TAB_GROUP("tobago-tabGroup"),
   TABLE_LAYOUT__FIXED("tobago-tableLayout-fixed"),
   TEXT__JUSTIFY("tobago-text-justify"),
   TOGGLE("tobago-toggle"),
   TOOLTIP("tobago-tooltip"),
-  //  TREE_LABEL("tobago-treeLabel"),
-//  TREE_LISTBOX("tobago-treeListbox");
-//  TREE_LISTBOX__LEVEL("tobago-treeListbox-level"),
-//  TREE_LISTBOX__SELECT("tobago-treeListbox-select"),
-//  TREE_NODE("tobago-treeNode"),
-//  TREE_SELECT("tobago-treeSelect");
-//  TREE_SELECT__LABEL("tobago-treeSelect-label");
   UNSELECTED("tobago-unselected");
 
   private final String name;
diff --git a/tobago-core/src/test/resources/renderer/date/error-message.html b/tobago-core/src/test/resources/renderer/date/error-message.html
index 1fb1c1325f..160c282281 100644
--- a/tobago-core/src/test/resources/renderer/date/error-message.html
+++ b/tobago-core/src/test/resources/renderer/date/error-message.html
@@ -18,10 +18,10 @@
 <tobago-date id='id' class='tobago-auto-spacing'>
   <div class='tobago-messages-container'>
     <div class='input-group'>
-      <input type='date' name='id' id='id::field' title='a test' class='border-danger form-control' autofocus='autofocus'>
+      <input type='date' name='id' id='id::field' title='a test' class='is-error form-control' autofocus='autofocus'>
     </div>
     <tobago-popover data-bs-toggle='popover' title='Error' data-bs-content='a test' data-bs-trigger='focus'>
       <a tabindex='0' role='button' class='btn btn-danger'><i class='bi-exclamation-lg'></i></a>
     </tobago-popover>
   </div>
-</tobago-date>
\ No newline at end of file
+</tobago-date>
diff --git a/tobago-core/src/test/resources/renderer/in/error-message.html b/tobago-core/src/test/resources/renderer/in/error-message.html
index 537a7fabd5..132f9be199 100644
--- a/tobago-core/src/test/resources/renderer/in/error-message.html
+++ b/tobago-core/src/test/resources/renderer/in/error-message.html
@@ -18,9 +18,9 @@
 <tobago-in id='id' class='tobago-label-container tobago-auto-spacing'>
   <label for='id::field' class='col-form-label'>label</label>
   <div class='tobago-messages-container'>
-    <input type='text' name='id' id='id::field' title='a test' class='border-danger form-control' autofocus='autofocus'>
+    <input type='text' name='id' id='id::field' title='a test' class='is-error form-control' autofocus='autofocus'>
     <tobago-popover data-bs-toggle='popover' title='Error' data-bs-content='a test' data-bs-trigger='focus'>
       <a tabindex='0' role='button' class='btn btn-danger'><i class='bi-exclamation-lg'></i></a>
     </tobago-popover>
   </div>
-</tobago-in>
\ No newline at end of file
+</tobago-in>
diff --git a/tobago-core/src/test/resources/renderer/selectBooleanCheckbox/selectBooleanCheckboxError.html b/tobago-core/src/test/resources/renderer/selectBooleanCheckbox/selectBooleanCheckboxError.html
index 87d416b58e..23684c6ff8 100644
--- a/tobago-core/src/test/resources/renderer/selectBooleanCheckbox/selectBooleanCheckboxError.html
+++ b/tobago-core/src/test/resources/renderer/selectBooleanCheckbox/selectBooleanCheckboxError.html
@@ -18,7 +18,7 @@
 <tobago-select-boolean-checkbox id='id' class='tobago-auto-spacing'>
   <div class='tobago-messages-container'>
     <div class='form-check col-form-label' title='This is a custom error'>
-      <input class='form-check-input border-danger' type='checkbox' value='true' name='id' id='id::field' autofocus='autofocus'>
+      <input class='form-check-input is-error' type='checkbox' value='true' name='id' id='id::field' autofocus='autofocus'>
       <label class='form-check-label' for='id::field'></label>
     </div>
     <tobago-popover data-bs-toggle='popover' title='Error' data-bs-content='This is a custom error' data-bs-trigger='focus'>
diff --git a/tobago-core/src/test/resources/renderer/selectBooleanCheckbox/selectBooleanCheckboxFatal.html b/tobago-core/src/test/resources/renderer/selectBooleanCheckbox/selectBooleanCheckboxFatal.html
index 8f299d4cd6..18fa4c6bfc 100644
--- a/tobago-core/src/test/resources/renderer/selectBooleanCheckbox/selectBooleanCheckboxFatal.html
+++ b/tobago-core/src/test/resources/renderer/selectBooleanCheckbox/selectBooleanCheckboxFatal.html
@@ -18,7 +18,7 @@
 <tobago-select-boolean-checkbox id='id' class='tobago-auto-spacing'>
   <div class='tobago-messages-container'>
     <div class='form-check col-form-label' title='This is a custom fatal error'>
-      <input class='form-check-input border-danger' type='checkbox' value='true' name='id' id='id::field' autofocus='autofocus'>
+      <input class='form-check-input is-error' type='checkbox' value='true' name='id' id='id::field' autofocus='autofocus'>
       <label class='form-check-label' for='id::field'></label>
     </div>
     <tobago-popover data-bs-toggle='popover' title='Fatal' data-bs-content='This is a custom fatal error' data-bs-trigger='focus'>
diff --git a/tobago-core/src/test/resources/renderer/selectBooleanCheckbox/selectBooleanCheckboxInfo.html b/tobago-core/src/test/resources/renderer/selectBooleanCheckbox/selectBooleanCheckboxInfo.html
index 5a7360c712..8f14af4d7c 100644
--- a/tobago-core/src/test/resources/renderer/selectBooleanCheckbox/selectBooleanCheckboxInfo.html
+++ b/tobago-core/src/test/resources/renderer/selectBooleanCheckbox/selectBooleanCheckboxInfo.html
@@ -18,7 +18,7 @@
 <tobago-select-boolean-checkbox id='id' class='tobago-auto-spacing'>
   <div class='tobago-messages-container'>
     <div class='form-check col-form-label' title='This is a custom information'>
-      <input class='form-check-input border-info' type='checkbox' value='true' name='id' id='id::field' autofocus='autofocus'>
+      <input class='form-check-input is-info' type='checkbox' value='true' name='id' id='id::field' autofocus='autofocus'>
       <label class='form-check-label' for='id::field'></label>
     </div>
     <tobago-popover data-bs-toggle='popover' title='Information' data-bs-content='This is a custom information' data-bs-trigger='focus'>
diff --git a/tobago-core/src/test/resources/renderer/selectBooleanCheckbox/selectBooleanCheckboxWarning.html b/tobago-core/src/test/resources/renderer/selectBooleanCheckbox/selectBooleanCheckboxWarning.html
index 2060485c52..8bf12390c3 100644
--- a/tobago-core/src/test/resources/renderer/selectBooleanCheckbox/selectBooleanCheckboxWarning.html
+++ b/tobago-core/src/test/resources/renderer/selectBooleanCheckbox/selectBooleanCheckboxWarning.html
@@ -18,7 +18,7 @@
 <tobago-select-boolean-checkbox id='id' class='tobago-auto-spacing'>
   <div class='tobago-messages-container'>
     <div class='form-check col-form-label' title='This is a custom warning'>
-      <input class='form-check-input border-warning' type='checkbox' value='true' name='id' id='id::field' autofocus='autofocus'>
+      <input class='form-check-input is-warning' type='checkbox' value='true' name='id' id='id::field' autofocus='autofocus'>
       <label class='form-check-label' for='id::field'></label>
     </div>
     <tobago-popover data-bs-toggle='popover' title='Warning' data-bs-content='This is a custom warning' data-bs-trigger='focus'>
diff --git a/tobago-core/src/test/resources/renderer/selectManyCheckbox/selectManyCheckboxFatal.html b/tobago-core/src/test/resources/renderer/selectManyCheckbox/selectManyCheckboxFatal.html
index 6f672527fd..80620beab9 100644
--- a/tobago-core/src/test/resources/renderer/selectManyCheckbox/selectManyCheckboxFatal.html
+++ b/tobago-core/src/test/resources/renderer/selectManyCheckbox/selectManyCheckboxFatal.html
@@ -19,11 +19,11 @@
   <div class='tobago-messages-container'>
     <div title='This is a custom fatal error'>
       <div class='form-check'>
-        <input class='form-check-input border-danger' type='checkbox' name='id' id='id::0' value='' autofocus='autofocus'>
+        <input class='form-check-input is-error' type='checkbox' name='id' id='id::0' value='' autofocus='autofocus'>
         <label class='form-check-label' for='id::0'>Entry One</label>
       </div>
       <div class='form-check'>
-        <input class='form-check-input border-danger' type='checkbox' name='id' id='id::1' value=''>
+        <input class='form-check-input is-error' type='checkbox' name='id' id='id::1' value=''>
         <label class='form-check-label' for='id::1'>Entry Two</label>
       </div>
     </div>
diff --git a/tobago-core/src/test/resources/renderer/selectManyShuttle/error-message.html b/tobago-core/src/test/resources/renderer/selectManyShuttle/error-message.html
index 27621f4660..3ad5a23987 100644
--- a/tobago-core/src/test/resources/renderer/selectManyShuttle/error-message.html
+++ b/tobago-core/src/test/resources/renderer/selectManyShuttle/error-message.html
@@ -31,7 +31,7 @@
           <button type='button' class='btn btn-secondary' id='id::removeAll'><i class='bi-chevron-double-left'></i></button>
         </div>
       </div>
-      <select id='id::selected' class='tobago-selected border-danger form-control' multiple='multiple' size='2'></select>
+      <select id='id::selected' class='tobago-selected is-error form-control' multiple='multiple' size='2'></select>
       <select class='d-none' id='id::hidden' name='id' multiple='multiple'>
         <option value=''>Value 1
         </option>
@@ -42,4 +42,4 @@
       <a tabindex='0' role='button' class='btn btn-danger'><i class='bi-exclamation-lg'></i></a>
     </tobago-popover>
   </div>
-</tobago-select-many-shuttle>
\ No newline at end of file
+</tobago-select-many-shuttle>
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/170-validation/02/Severity.test.js b/tobago-example/tobago-example-demo/src/main/webapp/content/170-validation/02/Severity.test.js
index 9e3f6fbf11..cc989ded57 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/170-validation/02/Severity.test.js
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/170-validation/02/Severity.test.js
@@ -32,10 +32,10 @@ it("Check severity CSS classes", function (done) {
 
   let test = new JasmineTestTool(done);
   test.setup(() => alertsFn().length > 0, null, "click", submitFn);
-  test.do(() => expect(fatalInputFieldFn().classList).toContain("border-danger"));
-  test.do(() => expect(errorInputFieldFn().classList).toContain("border-danger"));
-  test.do(() => expect(warnInputFieldFn().classList).toContain("border-warning"));
-  test.do(() => expect(infoInputFieldFn().classList).toContain("border-info"));
+  test.do(() => expect(fatalInputFieldFn().classList).toContain("is-error"));
+  test.do(() => expect(errorInputFieldFn().classList).toContain("is-error"));
+  test.do(() => expect(warnInputFieldFn().classList).toContain("is-warning"));
+  test.do(() => expect(infoInputFieldFn().classList).toContain("is-info"));
   test.do(() => expect(fatalButtonFn().classList).toContain("btn-danger"));
   test.do(() => expect(errorButtonFn().classList).toContain("btn-danger"));
   test.do(() => expect(warnButtonFn().classList).toContain("btn-warning"));
diff --git a/tobago-theme/src/main/scss/_tobago.scss b/tobago-theme/src/main/scss/_tobago.scss
index ecb0d9346c..dd6234c28b 100644
--- a/tobago-theme/src/main/scss/_tobago.scss
+++ b/tobago-theme/src/main/scss/_tobago.scss
@@ -16,6 +16,7 @@
  */
 
 @import "node_modules/bootstrap/scss/variables";
+@import "node_modules/bootstrap/scss/mixins/forms";
 
 /* used bootstrap icons ---------------------------------------------------- */
 
@@ -58,7 +59,7 @@ $form-select-disabled-color: rgba($form-select-color, $tobago-form-disabled-alph
   display: inline-block;
 }
 
-/* main ---------------------------------------------------------- */
+/* mixins ---------------------------------------------------------- */
 @mixin adjustCustomControlLabel() {
   .form-check-label {
     &:after {
@@ -150,20 +151,6 @@ $form-select-disabled-color: rgba($form-select-color, $tobago-form-disabled-alph
   }
 }
 
-@mixin messageShadow() {
-  &.border-danger:focus {
-    box-shadow: 0 0 $input-btn-focus-blur $input-focus-width rgba($danger, $input-btn-focus-color-opacity);
-  }
-
-  &.border-warning:focus {
-    box-shadow: 0 0 $input-btn-focus-blur $input-focus-width rgba($warning, $input-btn-focus-color-opacity);
-  }
-
-  &.border-info:focus {
-    box-shadow: 0 0 $input-btn-focus-blur $input-focus-width rgba($info, $input-btn-focus-color-opacity);
-  }
-}
-
 @mixin radioCheckboxInline() {
   label + .form-check-inline {
     display: block; // must be 'block' to make labelLayout=top work for inline=true
@@ -317,8 +304,6 @@ tobago-date {
 
   input {
     @include formControlDisabled();
-    @include messageShadow();
-
     min-width: 7em;
 
     &::-ms-clear {
@@ -373,35 +358,13 @@ tobago-dropdown {
   // deprecated; must be added for test
 }
 
-/* figure -------------------------------------------------------------- */
-
-/* file -------------------------------------------------------------- */
-
 tobago-file {
   display: block;
 
   > .input-group {
     flex: 1 0 0px;
   }
-
-  input[type='file'] {
-    @include messageShadow();
-  }
-}
-
-/*
-tobago-file[drop-zone] > .input-group {
-  input, label {
-    display: none !important;
-  }
-  border: 3px dotted $gray-500;
-  border-radius: 0.25rem;
-  text-align: center;
-  &:hover {
-    background-color: $gray-200;
-  }
 }
-*/
 
 /* flexLayout -------------------------------------------------------------- */
 
@@ -544,7 +507,6 @@ tobago-in {
   display: block;
 
   input[type='text'] {
-    @include messageShadow();
     @include formControlDisabled();
   }
 
@@ -717,7 +679,30 @@ button {
 }
 
 /* messages / help text ----------------------------------------------- */
+$enable-validation-icons: false;
 .tobago-messages-container {
+  @function exclamation-circle-icon($color) {
+    @return url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='#{$color}'><circle cx='6' cy='6' r='4.5'/><path stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/><circle cx='6' cy='8.2' r='.6' fill='#{$color}' stroke='none'/></svg>");
+  }
+  $tobago-form-validation-states: (
+      "error": (
+          "color": $danger,
+          "icon": exclamation-circle-icon($danger)
+      ),
+      "warning": (
+          "color": $warning,
+          "icon": exclamation-circle-icon($warning)
+      ),
+      "info": (
+          "color": $info,
+          "icon": exclamation-circle-icon($info)
+      )
+  );
+
+  @each $state, $data in $tobago-form-validation-states {
+    @include form-validation-state($state, $data...);
+  }
+
   display: flex;
   align-items: flex-start;
 
@@ -1121,49 +1106,27 @@ tobago-stars {
 tobago-select-boolean-checkbox {
   display: block;
   @include adjustCustomControlLabel();
-
-  input[type='checkbox'] {
-    @include messageShadow();
-  }
 }
 
 /* selectBooleanToggle ---------------------------------------------------- */
 tobago-select-boolean-toggle {
   display: block;
   @include adjustCustomControlLabel();
-
-  input[type='checkbox'] {
-    @include messageShadow();
-  }
 }
 
-/* selectOneChoice --------------------------------------------------------- */
 tobago-select-one-choice {
   display: block;
 
   select {
     @include formControlDisabled();
-    @include messageShadow();
   }
 }
 
-/* selectOneListbox -------------------------------------------------------- */
-
-//xxx remove me
-.tobago-selectOneListbox {
-  @include messageShadow();
-}
-
-//xxx remove me
-.tobago-selectOneListbox-option {
-}
-
 tobago-select-one-listbox {
   display: block;
 
   select {
     @include formControlSelectListDisabled();
-    @include messageShadow();
   }
 }
 
@@ -1178,10 +1141,6 @@ tobago-select-one-radio {
       @include inlinePadding();
     }
   }
-
-  input[type='radio'] {
-    @include messageShadow();
-  }
 }
 
 tobago-select-many-list {
@@ -1323,7 +1282,6 @@ tobago-select-many-list {
   }
 }
 
-/* selectManyCheckbox ----------------------------------------------------- */
 tobago-select-many-checkbox {
   display: block;
   @include adjustCustomControlLabel();
@@ -1334,33 +1292,19 @@ tobago-select-many-checkbox {
       @include inlinePadding();
     }
   }
-
-  input[type='checkbox'] {
-    @include messageShadow();
-  }
 }
 
 .tobago-selectManyCheckbox {
 }
 
-/* selectManyListbox ----------------------------------------------------------- */
 tobago-select-many-listbox {
   display: block;
 
   select {
     @include formControlSelectListDisabled();
-    @include messageShadow();
   }
 }
 
-.tobago-selectManyListbox,
-.tobago-selectManyListbox-option {
-}
-
-.tobago-selectManyListbox {
-  @include messageShadow();
-}
-
 /* selectManyShuttle ----------------------------------------------------------- */
 tobago-select-many-shuttle {
   display: block;
@@ -1379,7 +1323,6 @@ tobago-select-many-shuttle {
     }
 
     .tobago-selected {
-      @include messageShadow();
       @include formControlSelectListDisabled();
     }
   }
@@ -1698,7 +1641,6 @@ tobago-textarea {
   display: block;
 
   textarea {
-    @include messageShadow();
     @include formControlDisabled();
   }
 }