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 2019/01/03 14:00:24 UTC

[myfaces-tobago] branch master updated: TOBAGO-1898 Add a context message to form-components

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 70e69b1  TOBAGO-1898 Add a context message to form-components
70e69b1 is described below

commit 70e69b1499d5aa0aaac394808805f993adeef250
Author: Henning Noeth <hn...@apache.org>
AuthorDate: Thu Jan 3 14:59:48 2019 +0100

    TOBAGO-1898 Add a context message to form-components
    
    * help text can be added for input and select components
    * improve demo for tc:in
---
 .../myfaces/tobago/component/Attributes.java       |  1 +
 .../SupportsHelp.java}                             | 25 ++-------
 .../tobago/internal/component/AbstractUIFile.java  |  3 +-
 .../tobago/internal/component/AbstractUIInput.java |  3 +-
 .../component/AbstractUISelectBoolean.java         |  3 +-
 .../component/AbstractUISelectManyBase.java        |  3 +-
 .../component/AbstractUISelectOneBase.java         |  3 +-
 .../renderer/MessageLayoutRendererBase.java        | 61 ++++++++++++++++++----
 .../taglib/component/DateTagDeclaration.java       |  3 +-
 .../taglib/component/FileTagDeclaration.java       |  4 +-
 .../taglib/component/InTagDeclaration.java         |  3 +-
 .../SelectBooleanCheckboxTagDeclaration.java       |  3 +-
 .../SelectBooleanToggleTagDeclaration.java         |  3 +-
 .../SelectManyCheckboxTagDeclaration.java          |  3 +-
 .../component/SelectManyListboxTagDeclaration.java |  3 +-
 .../component/SelectManyShuttleTagDeclaration.java |  3 +-
 .../component/SelectOneChoiceTagDeclaration.java   |  3 +-
 .../component/SelectOneListboxTagDeclaration.java  |  3 +-
 .../component/SelectOneRadioTagDeclaration.java    |  5 +-
 .../taglib/component/TextareaTagDeclaration.java   |  3 +-
 .../declaration/HasHelp.java}                      | 31 ++++-------
 .../tobago/renderkit/css/BootstrapClass.java       |  1 +
 .../apache/myfaces/tobago/renderkit/css/Icons.java |  1 +
 .../myfaces/tobago/renderkit/css/TobagoClass.java  |  1 +
 .../tobago/context/TobagoResourceBundle.properties |  1 +
 .../context/TobagoResourceBundle_de.properties     |  1 +
 .../context/TobagoResourceBundle_es.properties     |  1 +
 tobago-core/src/main/resources/scss/_tobago.scss   | 10 ++--
 .../myfaces/tobago/example/demo/InController.java  |  4 ++
 .../content/20-component/010-input/10-in/In.xhtml  |  6 +++
 30 files changed, 123 insertions(+), 75 deletions(-)

diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Attributes.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Attributes.java
index cfeb6e3..a762abc 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Attributes.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Attributes.java
@@ -121,6 +121,7 @@ public enum Attributes {
   gridRow,
   gridTemplateColumns,
   gridTemplateRows,
+  help,
   height,
   hidden,
   hover,
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectManyBase.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/component/SupportsHelp.java
similarity index 53%
copy from tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectManyBase.java
copy to tobago-core/src/main/java/org/apache/myfaces/tobago/component/SupportsHelp.java
index eae8539..3f2329d 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectManyBase.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/component/SupportsHelp.java
@@ -17,28 +17,9 @@
  * under the License.
  */
 
-package org.apache.myfaces.tobago.internal.component;
+package org.apache.myfaces.tobago.component;
 
-import org.apache.myfaces.tobago.component.SupportsLabelLayout;
-import org.apache.myfaces.tobago.component.Visual;
+public interface SupportsHelp {
 
-import javax.faces.component.UISelectMany;
-import javax.faces.component.behavior.ClientBehaviorHolder;
-import java.util.Collection;
-
-/**
- * Base class for multi select.
- */
-public abstract class AbstractUISelectManyBase extends UISelectMany
-    implements Visual, SupportsLabelLayout, ClientBehaviorHolder {
-
-  @Override
-  public Object[] getSelectedValues() {
-    final Object value = getValue();
-    if (value instanceof Collection) {
-      return ((Collection) value).toArray();
-    } else {
-      return (Object[]) value;
-    }
-  }
+  String getHelp();
 }
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIFile.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIFile.java
index f93d4fb..3a4e670 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIFile.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIFile.java
@@ -21,6 +21,7 @@ package org.apache.myfaces.tobago.internal.component;
 
 import org.apache.myfaces.tobago.component.LabelLayout;
 import org.apache.myfaces.tobago.component.SupportFieldId;
+import org.apache.myfaces.tobago.component.SupportsHelp;
 import org.apache.myfaces.tobago.component.SupportsLabelLayout;
 import org.apache.myfaces.tobago.component.Visual;
 import org.apache.myfaces.tobago.util.ComponentUtils;
@@ -36,7 +37,7 @@ import javax.servlet.http.Part;
  * {@link org.apache.myfaces.tobago.internal.taglib.component.FileTagDeclaration}
  */
 public abstract class AbstractUIFile extends UIInput implements SupportsLabelLayout, Visual, ClientBehaviorHolder,
-    SupportFieldId {
+    SupportFieldId, SupportsHelp {
 
   @Override
   public void validate(final FacesContext facesContext) {
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIInput.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIInput.java
index 057fed6..2a03142 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIInput.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIInput.java
@@ -23,6 +23,7 @@ import org.apache.myfaces.tobago.component.Facets;
 import org.apache.myfaces.tobago.component.LabelLayout;
 import org.apache.myfaces.tobago.component.SupportFieldId;
 import org.apache.myfaces.tobago.component.SupportsAccessKey;
+import org.apache.myfaces.tobago.component.SupportsHelp;
 import org.apache.myfaces.tobago.component.SupportsLabelLayout;
 import org.apache.myfaces.tobago.component.Visual;
 import org.apache.myfaces.tobago.util.ComponentUtils;
@@ -35,7 +36,7 @@ import javax.faces.context.FacesContext;
  * Base class for some inputs.
  */
 public abstract class AbstractUIInput extends javax.faces.component.UIInput
-    implements SupportsAccessKey, SupportsLabelLayout, Visual, ClientBehaviorHolder, SupportFieldId {
+    implements SupportsAccessKey, SupportsLabelLayout, Visual, ClientBehaviorHolder, SupportFieldId, SupportsHelp {
 
   public abstract Integer getTabIndex();
 
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectBoolean.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectBoolean.java
index 816bb40..9fa97a3 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectBoolean.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectBoolean.java
@@ -22,6 +22,7 @@ package org.apache.myfaces.tobago.internal.component;
 import org.apache.myfaces.tobago.component.LabelLayout;
 import org.apache.myfaces.tobago.component.SupportFieldId;
 import org.apache.myfaces.tobago.component.SupportsAccessKey;
+import org.apache.myfaces.tobago.component.SupportsHelp;
 import org.apache.myfaces.tobago.component.SupportsLabelLayout;
 import org.apache.myfaces.tobago.component.Visual;
 import org.apache.myfaces.tobago.util.ComponentUtils;
@@ -33,7 +34,7 @@ import javax.faces.component.behavior.ClientBehaviorHolder;
 import javax.faces.context.FacesContext;
 
 public abstract class AbstractUISelectBoolean extends UISelectBoolean
-    implements Visual, ClientBehaviorHolder, SupportFieldId, SupportsAccessKey, SupportsLabelLayout {
+    implements Visual, ClientBehaviorHolder, SupportFieldId, SupportsAccessKey, SupportsLabelLayout, SupportsHelp {
 
   @Override
   public boolean isSelected() {
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectManyBase.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectManyBase.java
index eae8539..aae3e95 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectManyBase.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectManyBase.java
@@ -19,6 +19,7 @@
 
 package org.apache.myfaces.tobago.internal.component;
 
+import org.apache.myfaces.tobago.component.SupportsHelp;
 import org.apache.myfaces.tobago.component.SupportsLabelLayout;
 import org.apache.myfaces.tobago.component.Visual;
 
@@ -30,7 +31,7 @@ import java.util.Collection;
  * Base class for multi select.
  */
 public abstract class AbstractUISelectManyBase extends UISelectMany
-    implements Visual, SupportsLabelLayout, ClientBehaviorHolder {
+    implements Visual, SupportsLabelLayout, ClientBehaviorHolder, SupportsHelp {
 
   @Override
   public Object[] getSelectedValues() {
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectOneBase.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectOneBase.java
index 0bef935..15a412b 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectOneBase.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectOneBase.java
@@ -19,6 +19,7 @@
 
 package org.apache.myfaces.tobago.internal.component;
 
+import org.apache.myfaces.tobago.component.SupportsHelp;
 import org.apache.myfaces.tobago.component.SupportsLabelLayout;
 import org.apache.myfaces.tobago.component.Visual;
 import org.apache.myfaces.tobago.util.MessageUtils;
@@ -31,7 +32,7 @@ import javax.faces.context.FacesContext;
  * Base class for select one.
  */
 public abstract class AbstractUISelectOneBase extends javax.faces.component.UISelectOne
-    implements Visual, SupportsLabelLayout, ClientBehaviorHolder {
+    implements Visual, SupportsLabelLayout, ClientBehaviorHolder, SupportsHelp {
 
   public static final String MESSAGE_VALUE_REQUIRED = "org.apache.myfaces.tobago.UISelectOne.REQUIRED";
 
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/MessageLayoutRendererBase.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/MessageLayoutRendererBase.java
index 8607383..67e81ff 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/MessageLayoutRendererBase.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/MessageLayoutRendererBase.java
@@ -20,7 +20,9 @@
 package org.apache.myfaces.tobago.internal.renderkit.renderer;
 
 import org.apache.myfaces.tobago.component.LabelLayout;
+import org.apache.myfaces.tobago.component.SupportsHelp;
 import org.apache.myfaces.tobago.component.SupportsLabelLayout;
+import org.apache.myfaces.tobago.internal.util.StringUtils;
 import org.apache.myfaces.tobago.renderkit.css.BootstrapClass;
 import org.apache.myfaces.tobago.renderkit.css.Icons;
 import org.apache.myfaces.tobago.renderkit.css.TobagoClass;
@@ -29,6 +31,7 @@ import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
 import org.apache.myfaces.tobago.renderkit.html.HtmlButtonTypes;
 import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
 import org.apache.myfaces.tobago.util.ComponentUtils;
+import org.apache.myfaces.tobago.util.ResourceUtils;
 import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
 
 import javax.faces.application.FacesMessage;
@@ -50,7 +53,7 @@ public abstract class MessageLayoutRendererBase extends LabelLayoutRendererBase
       return; // skip, because this component is the label
     }
 
-    encodeBeginSurroundingMessage(facesContext, component);
+    encodeBeginMessagesContainer(facesContext, component);
     encodeBeginField(facesContext, component);
   }
 
@@ -66,29 +69,42 @@ public abstract class MessageLayoutRendererBase extends LabelLayoutRendererBase
     }
 
     encodeEndField(facesContext, component);
-    encodeEndSurroundingMessage(facesContext, component);
+    encodeEndMessagesContainer(facesContext, component);
   }
 
-  protected void encodeBeginSurroundingMessage(final FacesContext facesContext, final UIComponent component)
+  private void encodeBeginMessagesContainer(final FacesContext facesContext, final UIComponent component)
       throws IOException {
+    final TobagoResponseWriter writer = getResponseWriter(facesContext);
+
     final String clientId = component.getClientId();
     final List<FacesMessage> messages = facesContext.getMessageList(clientId);
-    final TobagoResponseWriter writer = getResponseWriter(facesContext);
+    final String help = component instanceof SupportsHelp ? ((SupportsHelp) component).getHelp() : null;
+    final boolean hasMessage = !messages.isEmpty();
+    final boolean hasHelp = !StringUtils.isEmpty(help);
 
-    if (!messages.isEmpty()) {
+    if (hasMessage || hasHelp) {
       writer.startElement(HtmlElements.DIV);
       writer.writeClassAttribute(TobagoClass.MESSAGES__CONTAINER, TobagoClass.FLEX_LAYOUT, BootstrapClass.D_FLEX);
     }
   }
 
-  protected void encodeEndSurroundingMessage(final FacesContext facesContext, final UIComponent component)
+  private void encodeEndMessagesContainer(final FacesContext facesContext, final UIComponent component)
       throws IOException {
+    final TobagoResponseWriter writer = getResponseWriter(facesContext);
+
     final String clientId = component.getClientId();
     final List<FacesMessage> messages = facesContext.getMessageList(clientId);
-    final TobagoResponseWriter writer = getResponseWriter(facesContext);
+    final String help = component instanceof SupportsHelp ? ((SupportsHelp) component).getHelp() : null;
+    final boolean hasMessage = !messages.isEmpty();
+    final boolean hasHelp = !StringUtils.isEmpty(help);
 
-    if (!messages.isEmpty()) {
-      encodeMessages(writer, messages);
+    if (hasMessage || hasHelp) {
+      if (hasMessage) {
+        encodeFacesMessagesButton(writer, messages);
+      }
+      if (hasHelp) {
+        encodeHelpButton(facesContext, writer, help);
+      }
       writer.endElement(HtmlElements.DIV);
     }
   }
@@ -97,7 +113,7 @@ public abstract class MessageLayoutRendererBase extends LabelLayoutRendererBase
 
   protected abstract void encodeEndField(FacesContext facesContext, UIComponent component) throws IOException;
 
-  private void encodeMessages(
+  private void encodeFacesMessagesButton(
       final TobagoResponseWriter writer, final List<FacesMessage> messages) throws IOException {
     writer.startElement(HtmlElements.A);
     writer.writeAttribute(HtmlAttributes.TABINDEX, "0", false);
@@ -189,10 +205,33 @@ public abstract class MessageLayoutRendererBase extends LabelLayoutRendererBase
 
   private String getMessage(final List<FacesMessage> messages) {
     final StringBuilder stringBuilder = new StringBuilder();
+    boolean firstMessage = true;
     for (final FacesMessage message : messages) {
+      if (firstMessage) {
+        firstMessage = false;
+      } else {
+        stringBuilder.append("\n\n");
+      }
       stringBuilder.append(message.getDetail());
-      stringBuilder.append("\n\n");
     }
     return stringBuilder.toString();
   }
+
+  private void encodeHelpButton(final FacesContext facesContext, final TobagoResponseWriter writer, final String help)
+      throws IOException {
+    writer.startElement(HtmlElements.A);
+    writer.writeAttribute(HtmlAttributes.TABINDEX, "0", false);
+    writer.writeAttribute(HtmlAttributes.ROLE, HtmlButtonTypes.BUTTON);
+    writer.writeClassAttribute(
+        TobagoClass.HELP__BUTTON,
+        BootstrapClass.BTN,
+        BootstrapClass.BTN_OUTLINE_INFO);
+    writer.writeAttribute(DataAttributes.TOGGLE, "popover", false);
+    writer.writeAttribute(DataAttributes.TITLE, ResourceUtils.getString(facesContext, "help.title"), true);
+    writer.writeAttribute(DataAttributes.CONTENT, help, true);
+    writer.startElement(HtmlElements.I);
+    writer.writeClassAttribute(Icons.FA, Icons.QUESTION);
+    writer.endElement(HtmlElements.I);
+    writer.endElement(HtmlElements.A);
+  }
 }
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/DateTagDeclaration.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/DateTagDeclaration.java
index a8d3ef4..121fbfe 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/DateTagDeclaration.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/DateTagDeclaration.java
@@ -27,6 +27,7 @@ import org.apache.myfaces.tobago.component.RendererTypes;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasAccessKey;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverter;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverterMessage;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasHelp;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasIdBindingAndRendered;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabel;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabelLayout;
@@ -77,7 +78,7 @@ import javax.faces.component.UIInput;
 public interface DateTagDeclaration
     extends HasAccessKey, HasValidator, HasValue, HasValueChangeListener, HasTabIndex, IsFocus, IsVisual,
     HasValidatorMessage, HasConverterMessage, HasRequiredMessage, HasIdBindingAndRendered, IsReadonly,
-    IsDisabled, HasConverter, HasLabel, HasLabelLayout,
+    IsDisabled, HasConverter, HasLabel, HasHelp, HasLabelLayout,
     HasTip, IsRequired, HasPlaceholder {
 
 }
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/FileTagDeclaration.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/FileTagDeclaration.java
index 923989f..1754cc5 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/FileTagDeclaration.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/FileTagDeclaration.java
@@ -29,6 +29,7 @@ import org.apache.myfaces.tobago.component.ClientBehaviors;
 import org.apache.myfaces.tobago.component.RendererTypes;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasAccessKey;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverterMessage;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasHelp;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasIdBindingAndRendered;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabel;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabelLayout;
@@ -80,7 +81,8 @@ import javax.faces.component.UIInput;
 public interface FileTagDeclaration
     extends HasValidator, HasValidatorMessage, HasRequiredMessage, HasConverterMessage,
     HasValueChangeListener, HasIdBindingAndRendered, IsDisabled, IsFocus, IsMultiple,
-    HasLabel, HasLabelLayout, HasAccessKey, HasTip, IsReadonly, IsRequired, HasTabIndex, IsVisual, HasPlaceholder {
+    HasLabel, HasLabelLayout, HasAccessKey, HasTip, HasHelp, IsReadonly, IsRequired, HasTabIndex, IsVisual,
+    HasPlaceholder {
 
   /**
    * Value binding expression pointing to a
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/InTagDeclaration.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/InTagDeclaration.java
index d96262b..a4f8f4f 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/InTagDeclaration.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/InTagDeclaration.java
@@ -32,6 +32,7 @@ import org.apache.myfaces.tobago.internal.taglib.declaration.HasAccessKey;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasAutocomplete;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverter;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverterMessage;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasHelp;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasIdBindingAndRendered;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabel;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabelLayout;
@@ -94,7 +95,7 @@ import javax.faces.component.UIInput;
         )
     })
 public interface InTagDeclaration
-    extends HasIdBindingAndRendered, HasConverter, IsReadonly, IsDisabled, IsRequired, HasTip, IsPassword,
+    extends HasIdBindingAndRendered, HasConverter, IsReadonly, IsDisabled, IsRequired, HasHelp, HasTip, IsPassword,
     HasAccessKey, HasValidator, HasValue, HasValueChangeListener, HasTabIndex, IsFocus, IsVisual,
     HasValidatorMessage, HasConverterMessage, HasRequiredMessage, HasLabel, HasLabelLayout,
     HasAutocomplete, HasPlaceholder {
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectBooleanCheckboxTagDeclaration.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectBooleanCheckboxTagDeclaration.java
index 573fb33..8d0a3f3 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectBooleanCheckboxTagDeclaration.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectBooleanCheckboxTagDeclaration.java
@@ -28,6 +28,7 @@ import org.apache.myfaces.tobago.component.RendererTypes;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasAccessKey;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverter;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverterMessage;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasHelp;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasIdBindingAndRendered;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasItemLabel;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabel;
@@ -75,7 +76,7 @@ import javax.faces.component.UISelectBoolean;
 
 public interface SelectBooleanCheckboxTagDeclaration extends HasValidator,
     HasValueChangeListener, HasIdBindingAndRendered, HasValue, IsDisabled,
-    HasTip, IsReadonly, HasTabIndex, IsRequiredForSelect, HasConverter, IsFocus,
+    HasTip, HasHelp, IsReadonly, HasTabIndex, IsRequiredForSelect, HasConverter, IsFocus,
     HasValidatorMessage, HasRequiredMessageForSelect, HasConverterMessage, IsVisual,
     HasAccessKey, HasItemLabel, HasLabel, HasLabelLayout {
 }
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectBooleanToggleTagDeclaration.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectBooleanToggleTagDeclaration.java
index b774e94..51960bd 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectBooleanToggleTagDeclaration.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectBooleanToggleTagDeclaration.java
@@ -28,6 +28,7 @@ import org.apache.myfaces.tobago.component.RendererTypes;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasAccessKey;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverter;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverterMessage;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasHelp;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasIdBindingAndRendered;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasItemLabel;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabel;
@@ -75,7 +76,7 @@ import javax.faces.component.UISelectBoolean;
 
 public interface SelectBooleanToggleTagDeclaration extends HasValidator,
     HasValueChangeListener, HasIdBindingAndRendered, HasValue, IsDisabled,
-    HasTip, IsReadonly, HasTabIndex, IsRequiredForSelect, HasConverter, IsFocus,
+    HasTip, HasHelp, IsReadonly, HasTabIndex, IsRequiredForSelect, HasConverter, IsFocus,
     HasValidatorMessage, HasRequiredMessageForSelect, HasConverterMessage, IsVisual,
     HasAccessKey, HasItemLabel, HasLabel, HasLabelLayout {
 }
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectManyCheckboxTagDeclaration.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectManyCheckboxTagDeclaration.java
index f9c0532..f24ed40 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectManyCheckboxTagDeclaration.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectManyCheckboxTagDeclaration.java
@@ -30,6 +30,7 @@ import org.apache.myfaces.tobago.component.RendererTypes;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasBinding;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverter;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverterMessage;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasHelp;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasId;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabel;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabelLayout;
@@ -78,7 +79,7 @@ import javax.faces.component.UISelectMany;
             name = ClientBehaviors.BLUR)
     })
 public interface SelectManyCheckboxTagDeclaration extends
-    IsDisabled, HasId, HasTip, IsInline, HasRenderRange, IsRendered, IsRequiredForSelect,
+    IsDisabled, HasId, HasTip, HasHelp, IsInline, HasRenderRange, IsRendered, IsRequiredForSelect,
     HasBinding, IsReadonly, HasConverter, HasLabelLayout,
     HasLabel, HasValidator, HasValueChangeListener,
     HasValidatorMessage, HasConverterMessage, HasRequiredMessageForSelect, HasTabIndex, IsFocus, IsVisual {
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectManyListboxTagDeclaration.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectManyListboxTagDeclaration.java
index 026374a..47c8c37 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectManyListboxTagDeclaration.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectManyListboxTagDeclaration.java
@@ -30,6 +30,7 @@ import org.apache.myfaces.tobago.component.RendererTypes;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasBinding;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverter;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverterMessage;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasHelp;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasId;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabel;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabelLayout;
@@ -81,7 +82,7 @@ import javax.faces.component.UISelectMany;
     })
 
 public interface SelectManyListboxTagDeclaration
-    extends HasId, IsDisabled, IsRendered, HasBinding, HasTip,
+    extends HasId, IsDisabled, IsRendered, HasBinding, HasTip, HasHelp,
     IsReadonly, HasConverter, IsRequiredForSelect, HasLabel, HasValidator, HasValueChangeListener, HasLabelLayout,
     HasValidatorMessage, HasConverterMessage, HasRequiredMessageForSelect, HasTabIndex, IsFocus, IsVisual, HasSize {
 
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectManyShuttleTagDeclaration.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectManyShuttleTagDeclaration.java
index b947c5f..deda5a1 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectManyShuttleTagDeclaration.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectManyShuttleTagDeclaration.java
@@ -31,6 +31,7 @@ import org.apache.myfaces.tobago.component.RendererTypes;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasBinding;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverter;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverterMessage;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasHelp;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasId;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabel;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabelLayout;
@@ -81,7 +82,7 @@ import javax.faces.component.UISelectMany;
         )
     })
 public interface SelectManyShuttleTagDeclaration extends
-    IsDisabled, HasId, HasTip, IsRendered, IsRequiredForSelect, HasBinding, IsReadonly, HasConverter,
+    IsDisabled, HasId, HasTip, HasHelp, IsRendered, IsRequiredForSelect, HasBinding, IsReadonly, HasConverter,
     HasLabel, HasValidator, HasValueChangeListener, HasLabelLayout,
     HasValidatorMessage, HasConverterMessage, HasRequiredMessageForSelect, HasTabIndex, IsFocus, IsVisual, HasSize {
 
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectOneChoiceTagDeclaration.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectOneChoiceTagDeclaration.java
index 6ce768f..104fece 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectOneChoiceTagDeclaration.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectOneChoiceTagDeclaration.java
@@ -30,6 +30,7 @@ import org.apache.myfaces.tobago.component.RendererTypes;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasBinding;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverter;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverterMessage;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasHelp;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasId;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabel;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabelLayout;
@@ -79,7 +80,7 @@ import javax.faces.component.UISelectOne;
 public interface SelectOneChoiceTagDeclaration
     extends HasValidator, HasValue, HasValueChangeListener, HasTabIndex, IsFocus, IsVisual,
     HasValidatorMessage, HasConverterMessage, HasRequiredMessage, HasId, IsDisabled, IsReadonly, HasLabel,
-    IsRendered, HasConverter, HasBinding, HasTip, HasLabelLayout {
+    IsRendered, HasConverter, HasBinding, HasTip, HasHelp, HasLabelLayout {
 
   /**
    * Flag indicating that selecting an Item representing a value is required.
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectOneListboxTagDeclaration.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectOneListboxTagDeclaration.java
index 8f18666..5218fb7 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectOneListboxTagDeclaration.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectOneListboxTagDeclaration.java
@@ -31,6 +31,7 @@ import org.apache.myfaces.tobago.component.RendererTypes;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasBinding;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverter;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverterMessage;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasHelp;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasId;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabel;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabelLayout;
@@ -87,7 +88,7 @@ import javax.faces.component.UISelectOne;
 public interface SelectOneListboxTagDeclaration
     extends HasValidator, HasValue, HasValueChangeListener, HasTabIndex, IsFocus, IsVisual,
     HasValidatorMessage, HasConverterMessage, HasRequiredMessage, HasId, IsDisabled, IsReadonly, HasLabel, IsRendered,
-    HasBinding, HasTip, HasConverter, HasLabelLayout, HasSize {
+    HasBinding, HasTip, HasHelp, HasConverter, HasLabelLayout, HasSize {
 
   /**
    * Flag indicating that selecting an Item representing a Value is Required.
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectOneRadioTagDeclaration.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectOneRadioTagDeclaration.java
index cade91d..fcfe741 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectOneRadioTagDeclaration.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectOneRadioTagDeclaration.java
@@ -30,6 +30,7 @@ import org.apache.myfaces.tobago.component.RendererTypes;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasBinding;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverter;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverterMessage;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasHelp;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasId;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabel;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabelLayout;
@@ -80,8 +81,8 @@ import javax.faces.component.UISelectOne;
     })
 public interface SelectOneRadioTagDeclaration
     extends HasValidator, HasValue, HasValueChangeListener, HasTabIndex, IsFocus, IsVisual,
-    HasValidatorMessage, HasConverterMessage, HasRequiredMessage, IsDisabled, IsReadonly, HasId, HasTip, IsInline,
-    HasRenderRange, IsRendered, HasBinding, HasConverter, HasLabel, HasLabelLayout {
+    HasValidatorMessage, HasConverterMessage, HasRequiredMessage, IsDisabled, IsReadonly, HasId, HasTip, HasHelp,
+    IsInline, HasRenderRange, IsRendered, HasBinding, HasConverter, HasLabel, HasLabelLayout {
 
   /**
    * Flag indicating that selecting an Item representing a Value is Required.
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/TextareaTagDeclaration.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/TextareaTagDeclaration.java
index 7296d48..8f851be 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/TextareaTagDeclaration.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/TextareaTagDeclaration.java
@@ -30,6 +30,7 @@ import org.apache.myfaces.tobago.component.RendererTypes;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasAccessKey;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverter;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverterMessage;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasHelp;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasIdBindingAndRendered;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabel;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabelLayout;
@@ -82,7 +83,7 @@ import javax.faces.component.UIInput;
     })
 public interface TextareaTagDeclaration
     extends HasIdBindingAndRendered, HasConverter, IsReadonly, IsDisabled, IsRequired, HasLabel, HasLabelLayout, HasTip,
-    HasAccessKey, HasValidator, HasValue, HasValueChangeListener, HasTabIndex, IsFocus, IsVisual,
+    HasHelp, HasAccessKey, HasValidator, HasValue, HasValueChangeListener, HasTabIndex, IsFocus, IsVisual,
     HasValidatorMessage, HasConverterMessage, HasRequiredMessage, HasSanitize, HasPlaceholder {
 
   /**
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectManyBase.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/declaration/HasHelp.java
similarity index 53%
copy from tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectManyBase.java
copy to tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/declaration/HasHelp.java
index eae8539..6d8f68c 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectManyBase.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/declaration/HasHelp.java
@@ -17,28 +17,17 @@
  * under the License.
  */
 
-package org.apache.myfaces.tobago.internal.component;
+package org.apache.myfaces.tobago.internal.taglib.declaration;
 
-import org.apache.myfaces.tobago.component.SupportsLabelLayout;
-import org.apache.myfaces.tobago.component.Visual;
+import org.apache.myfaces.tobago.apt.annotation.TagAttribute;
+import org.apache.myfaces.tobago.apt.annotation.UIComponentTagAttribute;
 
-import javax.faces.component.UISelectMany;
-import javax.faces.component.behavior.ClientBehaviorHolder;
-import java.util.Collection;
+public interface HasHelp {
 
-/**
- * Base class for multi select.
- */
-public abstract class AbstractUISelectManyBase extends UISelectMany
-    implements Visual, SupportsLabelLayout, ClientBehaviorHolder {
-
-  @Override
-  public Object[] getSelectedValues() {
-    final Object value = getValue();
-    if (value instanceof Collection) {
-      return ((Collection) value).toArray();
-    } else {
-      return (Object[]) value;
-    }
-  }
+  /**
+   * Text value to display as a help.
+   */
+  @TagAttribute
+  @UIComponentTagAttribute
+  void setHelp(String help);
 }
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 bd1b208..b62151e 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
@@ -90,6 +90,7 @@ public enum BootstrapClass implements CssItem {
   BTN_LIGHT("btn-light"),
   BTN_LINK("btn-link"),
   BTN_PRIMARY("btn-primary"),
+  BTN_OUTLINE_INFO("btn-outline-info"),
   BTN_SECONDARY("btn-secondary"),
   BTN_SUCCESS("btn-success"),
   BTN_TOOLBAR("btn-toolbar"),
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/Icons.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/Icons.java
index 19df9e0..a5ea3db 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/Icons.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/Icons.java
@@ -46,6 +46,7 @@ public enum Icons implements CssItem {
   FORWARD,
   MINUS_SQUARE_O,
   PLUS_SQUARE_O,
+  QUESTION,
   SQUARE_O,
   STEP_BACKWARD,
   STEP_FORWARD;
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 7ea0a4f..a01be4d 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
@@ -131,6 +131,7 @@ public enum TobagoClass implements CssItem {
   FORM("tobago-form"),
   GRID_LAYOUT("tobago-gridLayout"),
   HEADER("tobago-header"),
+  HELP__BUTTON("tobago-help-button"),
   IMAGE("tobago-image"),
   IN("tobago-in"),
   INPUT__GROUP__OUTER("tobago-input-group-outer"),
diff --git a/tobago-core/src/main/resources/org/apache/myfaces/tobago/context/TobagoResourceBundle.properties b/tobago-core/src/main/resources/org/apache/myfaces/tobago/context/TobagoResourceBundle.properties
index a5baeff..c89fdd5 100644
--- a/tobago-core/src/main/resources/org/apache/myfaces/tobago/context/TobagoResourceBundle.properties
+++ b/tobago-core/src/main/resources/org/apache/myfaces/tobago/context/TobagoResourceBundle.properties
@@ -36,3 +36,4 @@ sheet.prev=Previous Page
 sheet.sorting=Click to sort this column
 sheet.toPage=Page {0}
 file.selected={} files selected
+help.title=Help
diff --git a/tobago-core/src/main/resources/org/apache/myfaces/tobago/context/TobagoResourceBundle_de.properties b/tobago-core/src/main/resources/org/apache/myfaces/tobago/context/TobagoResourceBundle_de.properties
index 47ea7bf..bbdb4b6 100644
--- a/tobago-core/src/main/resources/org/apache/myfaces/tobago/context/TobagoResourceBundle_de.properties
+++ b/tobago-core/src/main/resources/org/apache/myfaces/tobago/context/TobagoResourceBundle_de.properties
@@ -36,3 +36,4 @@ sheet.prev=vorherige Seite
 sheet.sorting=nach dieser Spalte sortieren
 sheet.toPage=Seite {0}
 file.selected={} Dateien ausgew\u00E4hlt
+help.title=Hilfe
diff --git a/tobago-core/src/main/resources/org/apache/myfaces/tobago/context/TobagoResourceBundle_es.properties b/tobago-core/src/main/resources/org/apache/myfaces/tobago/context/TobagoResourceBundle_es.properties
index 95d5637..fcb8f7a 100644
--- a/tobago-core/src/main/resources/org/apache/myfaces/tobago/context/TobagoResourceBundle_es.properties
+++ b/tobago-core/src/main/resources/org/apache/myfaces/tobago/context/TobagoResourceBundle_es.properties
@@ -36,3 +36,4 @@ sheet.prev=P\u00E1gina Anterior
 sheet.sorting=Ordenar por esta columna
 sheet.toPage=P\u00E1gina {0}
 file.selected={} archivos seleccionados
+help.title=Ayudar
diff --git a/tobago-core/src/main/resources/scss/_tobago.scss b/tobago-core/src/main/resources/scss/_tobago.scss
index e62af0a..c0ba8ad 100644
--- a/tobago-core/src/main/resources/scss/_tobago.scss
+++ b/tobago-core/src/main/resources/scss/_tobago.scss
@@ -470,8 +470,7 @@ span.dropdown {
   width: 100%;
 }
 
-/* messages ----------------------------------------------------------- */
-
+/* messages / help text ----------------------------------------------- */
 .tobago-messages-container {
   align-items: flex-start;
 
@@ -483,7 +482,7 @@ span.dropdown {
   }
 }
 
-a.tobago-messages-button {
+a.tobago-messages-button, a.tobago-help-button {
   padding-left: 0.4em;
   padding-right: 0.4em;
 }
@@ -531,6 +530,11 @@ a.tobago-messages-button {
   }
 }
 
+.popover .popover-body {
+  // allow linebreaks for messages / help text
+  white-space: pre-line;
+}
+
 /* nav ----------------------------------------------------------- */
 
 /* styles for drop down menu first level */
diff --git a/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/InController.java b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/InController.java
index bb96589..a823948 100644
--- a/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/InController.java
+++ b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/InController.java
@@ -56,4 +56,8 @@ public class InController {
   public void setRequiredValue(String requiredValue) {
     this.requiredValue = requiredValue;
   }
+
+  public String getHelpText() {
+    return "Help text with a new\nline character.";
+  }
 }
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/010-input/10-in/In.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/010-input/10-in/In.xhtml
index ca37db6..d9f32b1 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/010-input/10-in/In.xhtml
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/010-input/10-in/In.xhtml
@@ -50,6 +50,12 @@
     <tc:button label="Submit"/>
   </tc:section>
 
+  <tc:section label="Help">
+    <p>A help text can be added with the <code>help</code> attribute.</p>
+    <pre><code class="language-markup">&lt;tc:in help="\#{inController.helpText}"/></code></pre>
+    <tc:in help="#{inController.helpText}"/>
+  </tc:section>
+
   <tc:section label="Password">
     <p>To create an inputfield for passwords, set the
       <code>password</code> attribute to 'true'. It will hide the text as shown in the following example.</p>