diff --git a/composer.json b/composer.json
index bafd6fe..28c9623 100644
--- a/composer.json
+++ b/composer.json
@@ -6,6 +6,6 @@
   "license": "GPL-2.0+",
   "minimum-stability": "dev",
   "require": {
-    "drupal/core": "^8.8 || ^9.0 || ^10.0 || ^11"
+    "drupal/core": "^10.1 || ^11 || ^12"
   }
 }
diff --git a/heading.info.yml b/heading.info.yml
index 3ee82b4..49c4ecf 100644
--- a/heading.info.yml
+++ b/heading.info.yml
@@ -2,6 +2,6 @@ name: Heading
 type: module
 description: "Provides a text field that will be formatted as a heading (h1-h6)."
 package: Fields
-core_version_requirement: ^8 || ^9 || ^10 || ^11
+core_version_requirement: ^10.1 || ^11 || ^12
 dependencies:
   - drupal:field
diff --git a/heading.module b/heading.module
index fd2b536..afbecce 100644
--- a/heading.module
+++ b/heading.module
@@ -1,5 +1,12 @@
 <?php
 
+/**
+ * @file
+ */
+
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\heading\Hook\HeadingHooks;
+
 /**
  * @file
  * Module hooks.
@@ -8,17 +15,9 @@
 /**
  * Implements hook_theme().
  */
+#[LegacyHook]
 function heading_theme($existing, $type, $theme, $path) {
-  return [
-    'heading' => [
-      'template' => 'heading',
-      'path' => $path . '/templates',
-      'variables' => [
-        'size' => NULL,
-        'text' => NULL,
-      ],
-    ],
-  ];
+  return \Drupal::service(HeadingHooks::class)->theme($existing, $type, $theme, $path);
 }
 
 /**
@@ -26,23 +25,7 @@ function heading_theme($existing, $type, $theme, $path) {
  *
  * This adds the missing token info for automatically detected tokens.
  */
+#[LegacyHook]
 function heading_token_info_alter(&$info) {
-  $entities = \Drupal::service('entity_field.manager')->getFieldMap();
-  foreach ($entities as $entity_key => $entity) {
-    foreach ($entity as $field_key => $field) {
-      if ($field['type'] !== 'heading') {
-        continue;
-      }
-
-      $token_key = sprintf('%s-%s', $entity_key, $field_key);
-      $info['tokens'][$token_key]['size'] = [
-        'name' => t('Heading: Size'),
-        'description' => t('The size (h1, h2, ...) of the heading.'),
-      ];
-      $info['tokens'][$token_key]['text'] = [
-        'name' => t('Heading: Text'),
-        'description' => t('The text (content) of the heading.'),
-      ];
-    }
-  }
+  \Drupal::service(HeadingHooks::class)->tokenInfoAlter($info);
 }
diff --git a/heading.services.yml b/heading.services.yml
new file mode 100644
index 0000000..1bdfa78
--- /dev/null
+++ b/heading.services.yml
@@ -0,0 +1,5 @@
+
+services:
+  Drupal\heading\Hook\HeadingHooks:
+    class: Drupal\heading\Hook\HeadingHooks
+    autowire: true
diff --git a/src/Hook/HeadingHooks.php b/src/Hook/HeadingHooks.php
new file mode 100644
index 0000000..3a14711
--- /dev/null
+++ b/src/Hook/HeadingHooks.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Drupal\heading\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for heading.
+ */
+class HeadingHooks {
+  use StringTranslationTrait;
+  /**
+   * @file
+   * Module hooks.
+   */
+
+  /**
+   * Implements hook_theme().
+   */
+  #[Hook('theme')]
+  public static function theme($existing, $type, $theme, $path) {
+    return [
+      'heading' => [
+        'template' => 'heading',
+        'path' => $path . '/templates',
+        'variables' => [
+          'size' => NULL,
+          'text' => NULL,
+        ],
+      ],
+    ];
+  }
+
+  /**
+   * Implements hook_token_info_alter().
+   *
+   * This adds the missing token info for automatically detected tokens.
+   */
+  #[Hook('token_info_alter')]
+  public function tokenInfoAlter(&$info) {
+    $entities = \Drupal::service('entity_field.manager')->getFieldMap();
+    foreach ($entities as $entity_key => $entity) {
+      foreach ($entity as $field_key => $field) {
+        if ($field['type'] !== 'heading') {
+          continue;
+        }
+        $token_key = sprintf('%s-%s', $entity_key, $field_key);
+        $info['tokens'][$token_key]['size'] = [
+          'name' => $this->t('Heading: Size'),
+          'description' => $this->t('The size (h1, h2, ...) of the heading.'),
+        ];
+        $info['tokens'][$token_key]['text'] = [
+          'name' => $this->t('Heading: Text'),
+          'description' => $this->t('The text (content) of the heading.'),
+        ];
+      }
+    }
+  }
+
+}
