diff --git a/pyproject.toml b/pyproject.toml
index 485c05b..4107cf8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -40,6 +40,7 @@ classifiers = [
 dependencies = [
   "django>=3.2",
   "nh3",
+  "typing-extensions",
 ]
 
 urls.Changelog = "https://github.com/marksweb/django-nh3/blob/main/CHANGELOG.rst"
diff --git a/src/django_nh3/models.py b/src/django_nh3/models.py
index 7ca0b20..e5b5b2d 100644
--- a/src/django_nh3/models.py
+++ b/src/django_nh3/models.py
@@ -1,5 +1,6 @@
 from __future__ import annotations
 
+import warnings
 from collections.abc import Callable
 from typing import Any
 
@@ -9,11 +10,12 @@
 from django.db.models import Expression, Model
 from django.forms import Field as FormField
 from django.utils.safestring import mark_safe
+from typing_extensions import deprecated
 
 from . import forms
 
 
-class Nh3Field(models.TextField):
+class Nh3FieldMixin:
     def __init__(
         self,
         attributes: dict[str, set[str]] = {},
@@ -42,28 +44,28 @@ def formfield(
         """Makes the field for a ModelForm"""
 
         # If field doesn't have any choices add kwargs expected by Nh3Field.
-        if not self.choices:
+        if not self.choices:  # type: ignore[attr-defined]
             kwargs.update(
                 {
-                    "max_length": self.max_length,
+                    "max_length": self.max_length,  # type: ignore[attr-defined]
                     "attributes": self.nh3_options.get("attributes"),
                     "attribute_filter": self.nh3_options.get("attribute_filter"),
                     "clean_content_tags": self.nh3_options.get("clean_content_tags"),
                     "link_rel": self.nh3_options.get("link_rel"),
                     "strip_comments": self.nh3_options.get("strip_comments"),
                     "tags": self.nh3_options.get("tags"),
-                    "required": not self.blank,
+                    "required": not self.blank,  # type: ignore[attr-defined]
                 }
             )
 
-        return super().formfield(form_class=form_class, **kwargs)
+        return super().formfield(form_class=form_class, **kwargs)  # type: ignore[misc]
 
     def pre_save(self, model_instance: Model, add: bool) -> Any:
-        data = getattr(model_instance, self.attname)
+        data = getattr(model_instance, self.attname)  # type: ignore[attr-defined]
         if data is None:
             return data
         clean_value = nh3.clean(data, **self.nh3_options) if data else ""
-        setattr(model_instance, self.attname, mark_safe(clean_value))
+        setattr(model_instance, self.attname, mark_safe(clean_value))  # type: ignore[attr-defined]
         return clean_value
 
     def from_db_value(
@@ -77,3 +79,28 @@ def from_db_value(
         # Values are sanitised before saving, so any value returned from the DB
         # is safe to render unescaped.
         return mark_safe(value)
+
+
+class Nh3TextField(Nh3FieldMixin, models.TextField):
+    pass
+
+
+class Nh3CharField(Nh3FieldMixin, models.CharField):
+    pass
+
+
+@deprecated("Use Nh3TextField instead")
+class Nh3Field(Nh3FieldMixin, models.TextField):
+    """
+    .. deprecated:: 0.2.0
+    Use :class:`Nh3TextField` instead.
+    """
+
+    def __init__(self, *args, **kwargs) -> None:  # type: ignore[no-untyped-def]
+        warnings.warn(
+            "Nh3Field is deprecated and will be removed in a future version. "
+            "Use Nh3TextField instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        super().__init__(*args, **kwargs)
diff --git a/tests/deprecated_models.py b/tests/deprecated_models.py
new file mode 100644
index 0000000..3e87f6c
--- /dev/null
+++ b/tests/deprecated_models.py
@@ -0,0 +1,9 @@
+from django.db import models
+
+from django_nh3.models import Nh3Field
+
+
+class Nh3FieldContent(models.Model):
+    """NH3 test model"""
+
+    content = Nh3Field()
diff --git a/tests/forms.py b/tests/forms.py
new file mode 100644
index 0000000..72c36ce
--- /dev/null
+++ b/tests/forms.py
@@ -0,0 +1,40 @@
+from django.forms import ModelForm
+
+from .models import (
+    Nh3CharFieldContent,
+    Nh3CharFieldNullableContent,
+    Nh3TextFieldContent,
+    Nh3TextFieldNullableContent,
+)
+
+
+class Nh3CharFieldContentModelForm(ModelForm):
+    """NH3 test model form"""
+
+    class Meta:
+        model = Nh3CharFieldContent
+        fields = ["content"]
+
+
+class Nh3CharFieldNullableContentModelForm(ModelForm):
+    """NH3 test model form"""
+
+    class Meta:
+        model = Nh3CharFieldNullableContent
+        fields = ["choice"]
+
+
+class Nh3TextFieldContentModelForm(ModelForm):
+    """NH3 test model form"""
+
+    class Meta:
+        model = Nh3TextFieldContent
+        fields = ["content"]
+
+
+class Nh3TextFieldNullableContentModelForm(ModelForm):
+    """NH3 test model form"""
+
+    class Meta:
+        model = Nh3TextFieldNullableContent
+        fields = ["choice"]
diff --git a/tests/migrations/0001_initial.py b/tests/migrations/0001_initial.py
index f6686b2..b06e700 100644
--- a/tests/migrations/0001_initial.py
+++ b/tests/migrations/0001_initial.py
@@ -11,7 +11,7 @@ class Migration(migrations.Migration):
 
     operations = [
         migrations.CreateModel(
-            name="Nh3Content",
+            name="Nh3CharFieldContent",
             fields=[
                 (
                     "id",
@@ -22,16 +22,46 @@ class Migration(migrations.Migration):
                         verbose_name="ID",
                     ),
                 ),
-                ("content", django_nh3.models.Nh3Field()),
-                ("blank_field", django_nh3.models.Nh3Field(blank=True)),
+                ("content", django_nh3.models.Nh3CharField(max_length=1000)),
+                (
+                    "blank_field",
+                    django_nh3.models.Nh3CharField(blank=True, max_length=1000),
+                ),
                 (
                     "null_field",
-                    django_nh3.models.Nh3Field(blank=True, null=True),
+                    django_nh3.models.Nh3CharField(
+                        blank=True, null=True, max_length=1000
+                    ),
                 ),
             ],
         ),
         migrations.CreateModel(
-            name="Nh3NullableContent",
+            name="Nh3TextFieldContent",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("content", django_nh3.models.Nh3TextField(max_length=1000)),
+                (
+                    "blank_field",
+                    django_nh3.models.Nh3TextField(blank=True, max_length=1000),
+                ),
+                (
+                    "null_field",
+                    django_nh3.models.Nh3TextField(
+                        blank=True, null=True, max_length=1000
+                    ),
+                ),
+            ],
+        ),
+        migrations.CreateModel(
+            name="Nh3CharFieldNullableContent",
             fields=[
                 (
                     "id",
@@ -44,11 +74,46 @@ class Migration(migrations.Migration):
                 ),
                 (
                     "choice",
-                    django_nh3.models.Nh3Field(
-                        choices=[("f", "first choice"), ("s", "second choice")]
+                    django_nh3.models.Nh3CharField(
+                        blank=True,
+                        choices=[("f", "first choice"), ("s", "second choice")],
+                        max_length=1000,
+                    ),
+                ),
+                (
+                    "content",
+                    django_nh3.models.Nh3CharField(
+                        blank=True, null=True, max_length=1000
+                    ),
+                ),
+            ],
+        ),
+        migrations.CreateModel(
+            name="Nh3TextFieldNullableContent",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "choice",
+                    django_nh3.models.Nh3TextField(
+                        blank=True,
+                        choices=[("f", "first choice"), ("s", "second choice")],
+                        max_length=1000,
+                    ),
+                ),
+                (
+                    "content",
+                    django_nh3.models.Nh3TextField(
+                        blank=True, null=True, max_length=1000
                     ),
                 ),
-                ("content", django_nh3.models.Nh3Field(blank=True, null=True)),
             ],
         ),
     ]
diff --git a/tests/models.py b/tests/models.py
new file mode 100644
index 0000000..0b44b22
--- /dev/null
+++ b/tests/models.py
@@ -0,0 +1,41 @@
+from django.db import models
+
+from django_nh3.models import Nh3CharField, Nh3TextField
+
+
+class Nh3CharFieldContent(models.Model):
+    """NH3 test model"""
+
+    content = Nh3CharField(
+        strip_comments=True,
+        max_length=1000,
+    )
+    blank_field = Nh3CharField(blank=True, max_length=1000)
+    null_field = Nh3CharField(blank=True, null=True, max_length=1000)
+
+
+class Nh3CharFieldNullableContent(models.Model):
+    """NH3 test model"""
+
+    CHOICES = (("f", "first choice"), ("s", "second choice"))
+    choice = Nh3CharField(choices=CHOICES, blank=True, max_length=1000)
+    content = Nh3CharField(blank=True, null=True, max_length=1000)
+
+
+class Nh3TextFieldContent(models.Model):
+    """NH3 test model"""
+
+    content = Nh3TextField(
+        strip_comments=True,
+        max_length=1000,
+    )
+    blank_field = Nh3TextField(blank=True, max_length=1000)
+    null_field = Nh3TextField(blank=True, null=True, max_length=1000)
+
+
+class Nh3TextFieldNullableContent(models.Model):
+    """NH3 test model"""
+
+    CHOICES = (("f", "first choice"), ("s", "second choice"))
+    choice = Nh3TextField(choices=CHOICES, blank=True, max_length=1000)
+    content = Nh3TextField(blank=True, null=True, max_length=1000)
diff --git a/tests/test_models.py b/tests/test_models_charfield.py
similarity index 64%
rename from tests/test_models.py
rename to tests/test_models_charfield.py
index 885abe5..e6b4e81 100644
--- a/tests/test_models.py
+++ b/tests/test_models_charfield.py
@@ -1,46 +1,11 @@
-from django.db import models
-from django.forms import ModelForm
 from django.test import TestCase
 from django.utils.safestring import SafeString
 
-from django_nh3.models import Nh3Field
+from .forms import Nh3CharFieldContentModelForm, Nh3CharFieldNullableContentModelForm
+from .models import Nh3CharFieldContent, Nh3CharFieldNullableContent
 
 
-class Nh3Content(models.Model):
-    """NH3 test model"""
-
-    content = Nh3Field(
-        strip_comments=True,
-    )
-    blank_field = Nh3Field(blank=True)
-    null_field = Nh3Field(blank=True, null=True)
-
-
-class Nh3ContentModelForm(ModelForm):
-    """NH3 test model form"""
-
-    class Meta:
-        model = Nh3Content
-        fields = ["content"]
-
-
-class Nh3NullableContent(models.Model):
-    """NH3 test model"""
-
-    CHOICES = (("f", "first choice"), ("s", "second choice"))
-    choice = Nh3Field(choices=CHOICES, blank=True)
-    content = Nh3Field(blank=True, null=True)
-
-
-class Nh3NullableContentModelForm(ModelForm):
-    """NH3 test model form"""
-
-    class Meta:
-        model = Nh3NullableContent
-        fields = ["choice"]
-
-
-class TestNh3ModelField(TestCase):
+class TestNh3ModelCharField(TestCase):
     """Test model field"""
 
     def test_cleaning(self):
@@ -57,32 +22,32 @@ def test_cleaning(self):
         }
 
         for key, value in test_data.items():
-            obj = Nh3Content.objects.create(content=value)
+            obj = Nh3CharFieldContent.objects.create(content=value)
             self.assertEqual(obj.content, expected_values[key])
 
     def test_retrieved_values_are_template_safe(self):
-        obj = Nh3Content.objects.create(content="some content")
+        obj = Nh3CharFieldContent.objects.create(content="some content")
         obj.refresh_from_db()
         self.assertIsInstance(obj.content, SafeString)
-        obj = Nh3Content.objects.create(content="")
+        obj = Nh3CharFieldContent.objects.create(content="")
         obj.refresh_from_db()
         self.assertIsInstance(obj.content, SafeString)
 
     def test_saved_values_are_template_safe(self):
-        obj = Nh3Content(content="some content")
+        obj = Nh3CharFieldContent(content="some content")
         obj.save()
         self.assertIsInstance(obj.content, SafeString)
-        obj = Nh3Content(content="")
+        obj = Nh3CharFieldContent(content="")
         obj.save()
         self.assertIsInstance(obj.content, SafeString)
 
     def test_saved_none_values_are_none(self):
-        obj = Nh3Content(null_field=None)
+        obj = Nh3CharFieldContent(null_field=None)
         obj.save()
         self.assertIsNone(obj.null_field)
 
 
-class TestNh3NullableModelField(TestCase):
+class TestNh3CharFieldNullableModelField(TestCase):
     """Test model field"""
 
     def test_cleaning(self):
@@ -101,11 +66,11 @@ def test_cleaning(self):
         }
 
         for key, value in test_data.items():
-            obj = Nh3NullableContent.objects.create(content=value)
+            obj = Nh3CharFieldNullableContent.objects.create(content=value)
             self.assertEqual(obj.content, expected_values[key])
 
 
-class TestNh3ModelFormField(TestCase):
+class TestNh3CharFieldModelFormField(TestCase):
     """Test model form field"""
 
     def test_cleaning(self):
@@ -122,7 +87,7 @@ def test_cleaning(self):
         }
 
         for key, value in test_data.items():
-            form = Nh3ContentModelForm(data={"content": value})
+            form = Nh3CharFieldContentModelForm(data={"content": value})
             self.assertTrue(form.is_valid())
             obj = form.save()
             self.assertEqual(obj.content, expected_values[key])
@@ -131,17 +96,17 @@ def test_stripped_comments(self):
         """Content field strips comments so ensure they aren't allowed"""
 
         self.assertFalse(
-            Nh3ContentModelForm(
+            Nh3CharFieldContentModelForm(
                 data={"content": "<!-- this is a comment -->"}
             ).is_valid()
         )
 
     def test_field_choices(self):
         """Content field strips comments so ensure they aren't allowed"""
-        test_data = dict(Nh3NullableContent.CHOICES)
+        test_data = dict(Nh3CharFieldNullableContent.CHOICES)
 
         for key, value in test_data.items():
-            form = Nh3NullableContentModelForm(data={"choice": key})
+            form = Nh3CharFieldNullableContentModelForm(data={"choice": key})
             self.assertTrue(form.is_valid())
             obj = form.save()
             self.assertEqual(obj.get_choice_display(), value)
diff --git a/tests/test_models_deprecated.py b/tests/test_models_deprecated.py
new file mode 100644
index 0000000..5842305
--- /dev/null
+++ b/tests/test_models_deprecated.py
@@ -0,0 +1,9 @@
+from django.test import TestCase
+
+
+class TestNh3FieldModelField(TestCase):
+    """Test deprecated model field"""
+
+    def test_init_raises_warning(self):
+        with self.assertWarns(DeprecationWarning):
+            from .deprecated_models import Nh3FieldContent  # noqa: F401
diff --git a/tests/test_models_textfield.py b/tests/test_models_textfield.py
new file mode 100644
index 0000000..fc525c8
--- /dev/null
+++ b/tests/test_models_textfield.py
@@ -0,0 +1,112 @@
+from django.test import TestCase
+from django.utils.safestring import SafeString
+
+from .forms import Nh3TextFieldContentModelForm, Nh3TextFieldNullableContentModelForm
+from .models import Nh3TextFieldContent, Nh3TextFieldNullableContent
+
+
+class TestNh3TextFieldModelField(TestCase):
+    """Test model field"""
+
+    def test_cleaning(self):
+        """Test values are sanitized"""
+        test_data = {
+            "html_data": "<h1>Heading</h1>",
+            "no_html": "Heading",
+            "html_comment": "<!-- this is a comment -->",
+        }
+        expected_values = {
+            "html_data": "Heading",
+            "no_html": "Heading",
+            "html_comment": "",
+        }
+
+        for key, value in test_data.items():
+            obj = Nh3TextFieldContent.objects.create(content=value)
+            self.assertEqual(obj.content, expected_values[key])
+
+    def test_retrieved_values_are_template_safe(self):
+        obj = Nh3TextFieldContent.objects.create(content="some content")
+        obj.refresh_from_db()
+        self.assertIsInstance(obj.content, SafeString)
+        obj = Nh3TextFieldContent.objects.create(content="")
+        obj.refresh_from_db()
+        self.assertIsInstance(obj.content, SafeString)
+
+    def test_saved_values_are_template_safe(self):
+        obj = Nh3TextFieldContent(content="some content")
+        obj.save()
+        self.assertIsInstance(obj.content, SafeString)
+        obj = Nh3TextFieldContent(content="")
+        obj.save()
+        self.assertIsInstance(obj.content, SafeString)
+
+    def test_saved_none_values_are_none(self):
+        obj = Nh3TextFieldContent(null_field=None)
+        obj.save()
+        self.assertIsNone(obj.null_field)
+
+
+class TestNh3TextFieldNullableModelField(TestCase):
+    """Test model field"""
+
+    def test_cleaning(self):
+        """Test values are sanitized"""
+        test_data = {
+            "none": None,
+            "empty": "",
+            "whitespaces": "   ",
+            "linebreak": "\n",
+        }
+        expected_values = {
+            "none": None,
+            "empty": "",
+            "whitespaces": "   ",
+            "linebreak": "\n",
+        }
+
+        for key, value in test_data.items():
+            obj = Nh3TextFieldNullableContent.objects.create(content=value)
+            self.assertEqual(obj.content, expected_values[key])
+
+
+class TestNh3TextFieldModelFormField(TestCase):
+    """Test model form field"""
+
+    def test_cleaning(self):
+        """Test values are sanitized"""
+        test_data = {
+            "html_data": "<h1>Heading</h1>",
+            "no_html": "Heading",
+            "spacing": " Heading ",
+        }
+        expected_values = {
+            "html_data": "Heading",
+            "no_html": "Heading",
+            "spacing": "Heading",
+        }
+
+        for key, value in test_data.items():
+            form = Nh3TextFieldContentModelForm(data={"content": value})
+            self.assertTrue(form.is_valid())
+            obj = form.save()
+            self.assertEqual(obj.content, expected_values[key])
+
+    def test_stripped_comments(self):
+        """Content field strips comments so ensure they aren't allowed"""
+
+        self.assertFalse(
+            Nh3TextFieldContentModelForm(
+                data={"content": "<!-- this is a comment -->"}
+            ).is_valid()
+        )
+
+    def test_field_choices(self):
+        """Content field strips comments so ensure they aren't allowed"""
+        test_data = dict(Nh3TextFieldNullableContent.CHOICES)
+
+        for key, value in test_data.items():
+            form = Nh3TextFieldNullableContentModelForm(data={"choice": key})
+            self.assertTrue(form.is_valid())
+            obj = form.save()
+            self.assertEqual(obj.get_choice_display(), value)