diff --git a/README.md b/README.md
index cd7a8da9d369b83d11bef0721d6a9dbd0aa7be8c..93ce9458425ab670c45166e271e8afaccc3c0a08 100644
--- a/README.md
+++ b/README.md
@@ -51,6 +51,7 @@ This module requires no modules outside of Drupal core.
     this dump runs inside the form_alter, it can result in incomplete page
     load. This is expected.
 
+
 ## Using Label Help to create form fields programmatically
 
 The Label Help field defines a theme option named 'description at top' which
@@ -63,9 +64,7 @@ function mymodule_form($form, &$form_state) {
   $form['example'] = [
     '#type' => 'textfield',
     '#title' => t('Example'),
-    '#theme_options' => [
-      'description at top' => t('Label help text for the example field.'),
-    ],
+    '#label_help' => t('Label help text for the example field.'),
   ];
   return $form;
 }
@@ -73,28 +72,17 @@ function mymodule_form($form, &$form_state) {
 
 ## Modifying form fields using hook_form_alter()
 
-Drupal's hook_form_alter() functions may be used in a custom module or theme
-to add Label Help text to existing form fields. For example, the following
-function uses hook_form_user_login_alter() to change Drupal's user login form
-so that it displays label text top of field 'example':
+Drupal's hook_form_alter() and hook_FORM_ID_alter() functions may be used
+in a custom module or theme to add Label Help text to existing form fields.
+The following example adds help text to the Article content type's Title field.
 
 ```php
-function mymodule_form_user_login_alter(&$form, &$form_state, $form_id) {
-  $form['name']['#theme_options'] = [
-    'description at top' => t('Enter your @s username.', [
-      '@s' => variable_get('site_name', 'Drupal')
-    ]),
-  ];
-  unset($form['name']['#description']);
-  $form['pass']['#theme_options'] = [
-    'description at top' => t(
-      'Enter the password that accompanies your username.'
-    ),
-  ];
-  unset($form['pass']['#description']);
+function mymodule_form_node_article_alter(&$form, &$form_state, $form_id) {
+  $form['title']['#label_help'] = t('Label help message for the Title field.');
 }
 ```
 
+
 ## Contributing
 
 Local development is done via DDEV and the `ddev-drupal-contrib` add-on.
diff --git a/label_help.module b/label_help.module
index 1ec1bb6346498ec2209342e43f8f06116f69ea32..87514f27150f746dcffc5c3b981132c3450827c9 100644
--- a/label_help.module
+++ b/label_help.module
@@ -47,23 +47,30 @@ function label_help_theme() {
  * Implements hook_form_alter().
  */
 function label_help_form_alter(&$form, &$form_state, $form_id) {
+  $form['#process'][] = 'label_help_process_form';
+}
+
+/**
+ * Custom process callback for modifying form elements with Label Help.
+ */
+function label_help_process_form($element, FormStateInterface $form_state, &$form) {
   $children = array_intersect_key($form, array_flip(Element::children($form)));
   $form_object = $form_state->getFormObject();
   if (!method_exists($form_object, 'getEntity')) {
-    return;
+    return $element;
   }
   $method = new ReflectionMethod($form_object, 'getEntity');
   if (!$method->isPublic()) {
-    return;
+    return $element;
   }
 
   $form_entity = $form_object->getEntity();
   if (!method_exists($form_entity, 'getFieldDefinition')) {
-    return;
+    return $element;
   }
   $method = new ReflectionMethod($form_entity, 'getFieldDefinition');
   if (!$method->isPublic()) {
-    return;
+    return $element;
   }
 
   $debug = Settings::get('label_help_debug', FALSE);
@@ -75,9 +82,16 @@ function label_help_form_alter(&$form, &$form_state, $form_id) {
 
     $content = NULL;
     $fallback_use_case = FALSE;
+
+    // There are two possible ways to add text content for Label Help:
+    // Option 1) via the Field UI module in the Drupal web interface.
     if ($field && method_exists($field, 'getThirdPartySetting')) {
       $content = $field->getThirdPartySetting('label_help', 'label_help_description');
     }
+    // Option 2) via code, using a custom #label_help Form API property.
+    if (!empty($item['#label_help'])) {
+      $content = $item['#label_help'];
+    }
     if (is_null($content) || strlen($content) === 0) {
       continue;
     }
@@ -260,8 +274,16 @@ function label_help_form_alter(&$form, &$form_state, $form_id) {
         $fallback_use_case = TRUE;
       }
     }
-    else {
+
+    // Custom fields may not be defined with a container or a widget wrapper.
+    // To keep things simple, place the label in the field prefix.
+    elseif (isset($item['#type']) && empty($item['widget'])) {
       $use_case = 16;
+      $element = &$form[$key];
+      _label_help_append_label_suffix($element, $content, $use_case);
+    }
+    else {
+      $use_case = 17;
       $fallback_use_case = TRUE;
     }
 
@@ -291,6 +313,8 @@ function label_help_form_alter(&$form, &$form_state, $form_id) {
       dump($item);
     }
   }
+
+  return $form;
 }
 
 /**