diff --git a/src/click/types.py b/src/click/types.py
--- a/src/click/types.py
+++ b/src/click/types.py
@@ -304,16 +304,21 @@ def convert(
         if normed_value in normed_choices:
             return normed_choices[normed_value]
 
+        self.fail(self.get_invalid_choice_message(value), param, ctx)
+
+    def get_invalid_choice_message(self, value: t.Any) -> str:
+        """Get the error message when the given choice is invalid.
+
+        :param value: The invalid value.
+
+        .. versionadded:: 8.2
+        """
         choices_str = ", ".join(map(repr, self.choices))
-        self.fail(
-            ngettext(
-                "{value!r} is not {choice}.",
-                "{value!r} is not one of {choices}.",
-                len(self.choices),
-            ).format(value=value, choice=choices_str, choices=choices_str),
-            param,
-            ctx,
-        )
+        return ngettext(
+            "{value!r} is not {choice}.",
+            "{value!r} is not one of {choices}.",
+            len(self.choices),
+        ).format(value=value, choice=choices_str, choices=choices_str)
 
     def __repr__(self) -> str:
         return f"Choice({list(self.choices)})"

diff --git a/tests/test_types.py b/tests/test_types.py
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -244,3 +244,9 @@ def test_invalid_path_with_esc_sequence():
             click.Path(dir_okay=False).convert(tempdir, None, None)
 
     assert "my\\ndir" in exc_info.value.message
+
+
+def test_choice_get_invalid_choice_message():
+    choice = click.Choice(["a", "b", "c"])
+    message = choice.get_invalid_choice_message("d")
+    assert message == "'d' is not one of 'a', 'b', 'c'."

Allow customizing fail message for invalid choice in `click.types.Choice`
Move the fail art of `convert()` in the `Choice` class to a new method `get_invalid_choice_fail_message(self)` so in my subclass of `click.Choice` I can customize the way it shows the failing value for the choice

Some CLI option choices are multi-part meaning they are separated by some value, like a comma separated list for example. Maybe each section of this value represents a different part of a schema and it is nice to show a custom message about the specific part.

Contrived version:
Let says my choices are `["maine::goat", "maine::themepark"]` and I give it "maine::toast"`, it'd be nice to say the `maine` part was correct and the `toast` part was not.



