diff --git a/custom_classes.info.yml b/custom_classes.info.yml
index 48ed098..cba0b93 100644
--- a/custom_classes.info.yml
+++ b/custom_classes.info.yml
@@ -2,7 +2,7 @@ name: Custom Classes
 type: module
 description: Custom classes can be added to form elements.
 package: Form
-core_version_requirement: ^10 || ^11
+core_version_requirement: ^10.1 || ^11 || ^12
 configure: custom_classes.settings_form
 dependencies:
   - drupal:path_alias
diff --git a/custom_classes.module b/custom_classes.module
index edad3c9..2f5dc20 100644
--- a/custom_classes.module
+++ b/custom_classes.module
@@ -4,44 +4,27 @@
  * @file
  * Implements hooks for custom_classes module.
  */
-
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\custom_classes\Hook\CustomClassesHooks;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 
 /**
  * Implements hook_help().
  */
-function custom_classes_help($route_name, RouteMatchInterface $route_match) {
-  // To avoid phpmd UnusedFormalParameter errors since there is a fix for that
-  // https://github.com/phpmd/phpmd/issues/24
-  unset($route_match);
-  switch ($route_name) {
-    // Main module help for the blizz_table_field module.
-    case 'help.page.custom_classes':
-      $text = file_get_contents(dirname(__FILE__) . '/README.md');
-      if (!\Drupal::moduleHandler()->moduleExists('markdown')) {
-        return '<pre>' . $text . '</pre>';
-      }
-      else {
-        // Use the Markdown filter to render the README.
-        $filter_manager = \Drupal::service('plugin.manager.filter');
-        $settings = \Drupal::configFactory()->get('markdown.settings')->getRawData();
-        $config = ['settings' => $settings];
-        $filter = $filter_manager->createInstance('markdown', $config);
-        return $filter->process($text, 'en');
-      }
-
-    default:
-  }
+#[LegacyHook]
+function custom_classes_help($route_name, RouteMatchInterface $route_match)
+{
+    return \Drupal::service(CustomClassesHooks::class)->help($route_name, $route_match);
 }
 
 /**
  * Implements hook_form_alter().
  */
-function custom_classes_form_alter(&$form, FormStateInterface $form_state, $form_id) {
-  /** @var \Drupal\custom_classes\Controller\CustomClassController $customClassController */
-  $customClassController = \Drupal::service('custom_classes.controller');
-  $customClassController->customClassesFormAlter($form, $form_state, $form_id);
+#[LegacyHook]
+function custom_classes_form_alter(&$form, FormStateInterface $form_state, $form_id)
+{
+    \Drupal::service(CustomClassesHooks::class)->formAlter($form, $form_state, $form_id);
 }
 
 /**
diff --git a/custom_classes.services.yml b/custom_classes.services.yml
index 3ece2b4..654027e 100644
--- a/custom_classes.services.yml
+++ b/custom_classes.services.yml
@@ -2,3 +2,7 @@ services:
   custom_classes.controller:
     class: Drupal\custom_classes\Controller\CustomClassController
     arguments: ['@path_alias.manager', '@path.matcher', '@request_stack', '@path.current', '@config.factory', '@current_route_match']
+
+  Drupal\custom_classes\Hook\CustomClassesHooks:
+    class: Drupal\custom_classes\Hook\CustomClassesHooks
+    autowire: true
diff --git a/src/Hook/CustomClassesHooks.php b/src/Hook/CustomClassesHooks.php
new file mode 100644
index 0000000..ced2d42
--- /dev/null
+++ b/src/Hook/CustomClassesHooks.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\custom_classes\Hook;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+/**
+ * Hook implementations for custom_classes.
+ */
+class CustomClassesHooks
+{
+    /**
+     * Implements hook_help().
+     */
+    #[Hook('help')]
+    public function help($route_name, \Drupal\Core\Routing\RouteMatchInterface $route_match)
+    {
+        // To avoid phpmd UnusedFormalParameter errors since there is a fix for that
+        // https://github.com/phpmd/phpmd/issues/24
+        unset($route_match);
+        switch ($route_name) {
+            // Main module help for the blizz_table_field module.
+            case 'help.page.custom_classes':
+                $text = file_get_contents(dirname(__FILE__) . '/README.md');
+                if (!\Drupal::moduleHandler()->moduleExists('markdown')) {
+                    return '<pre>' . $text . '</pre>';
+                } else {
+                    // Use the Markdown filter to render the README.
+                    $filter_manager = \Drupal::service('plugin.manager.filter');
+                    $settings = \Drupal::configFactory()->get('markdown.settings')->getRawData();
+                    $config = [
+                        'settings' => $settings,
+                    ];
+                    $filter = $filter_manager->createInstance('markdown', $config);
+                    return $filter->process($text, 'en');
+                }
+            default:
+        }
+    }
+    /**
+     * Implements hook_form_alter().
+     */
+    #[Hook('form_alter')]
+    public function formAlter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id)
+    {
+        /** @var \Drupal\custom_classes\Controller\CustomClassController $customClassController */
+        $customClassController = \Drupal::service('custom_classes.controller');
+        $customClassController->customClassesFormAlter($form, $form_state, $form_id);
+    }
+}
