diff --git a/django/forms/widgets.py b/django/forms/widgets.py
index e4f8957189..eed1fa5c3b 100644
--- a/django/forms/widgets.py
+++ b/django/forms/widgets.py
@@ -696,27 +696,35 @@ class NullBooleanSelect(Select):
     """
     def __init__(self, attrs=None):
         choices = (
-            ('1', _('Unknown')),
-            ('2', _('Yes')),
-            ('3', _('No')),
+            ('unknown', _('Unknown')),
+            ('true', _('Yes')),
+            ('false', _('No')),
         )
         super().__init__(attrs, choices)
 
     def format_value(self, value):
         try:
-            return {True: '2', False: '3', '2': '2', '3': '3'}[value]
+            return {
+                True: 'true', False: 'false',
+                'true': 'true', 'false': 'false',
+                # For backwards compatibility with Django < 2.2.
+                '2': 'true', '3': 'false',
+            }[value]
         except KeyError:
-            return '1'
+            return 'unknown'
 
     def value_from_datadict(self, data, files, name):
         value = data.get(name)
         return {
-            '2': True,
             True: True,
             'True': True,
-            '3': False,
             'False': False,
             False: False,
+            'true': True,
+            'false': False,
+            # For backwards compatibility with Django < 2.2.
+            '2': True,
+            '3': False,
         }.get(value)
 
 
diff --git a/docs/releases/2.2.txt b/docs/releases/2.2.txt
index debc60c5f6..a8bd9ab488 100644
--- a/docs/releases/2.2.txt
+++ b/docs/releases/2.2.txt
@@ -380,6 +380,11 @@ Miscellaneous
 
 * Support for ``cx_Oracle`` < 6.0 is removed.
 
+* In an attempt to provide more semantic query data, ``NullBooleanSelect`` now
+  renders ``<option>`` values of ``unknown``, ``true``, and ``false`` instead
+  of ``1``, ``2``, and ``3``. For backwards compatibility, the old values are
+  still accepted as data.
+
 .. _deprecated-features-2.2:
 
 Features deprecated in 2.2
diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py
index ba233e72ff..bad24b5230 100644
--- a/tests/forms_tests/tests/test_forms.py
+++ b/tests/forms_tests/tests/test_forms.py
@@ -2356,39 +2356,57 @@ Password: <input type="password" name="password" required>
 
         p = Person({'name': 'Joe'}, auto_id=False)
         self.assertHTMLEqual(str(p['is_cool']), """<select name="is_cool">
-<option value="1" selected>Unknown</option>
-<option value="2">Yes</option>
-<option value="3">No</option>
+<option value="unknown" selected>Unknown</option>
+<option value="true">Yes</option>
+<option value="false">No</option>
 </select>""")
         p = Person({'name': 'Joe', 'is_cool': '1'}, auto_id=False)
         self.assertHTMLEqual(str(p['is_cool']), """<select name="is_cool">
-<option value="1" selected>Unknown</option>
-<option value="2">Yes</option>
-<option value="3">No</option>
+<option value="unknown" selected>Unknown</option>
+<option value="true">Yes</option>
+<option value="false">No</option>
 </select>""")
         p = Person({'name': 'Joe', 'is_cool': '2'}, auto_id=False)
         self.assertHTMLEqual(str(p['is_cool']), """<select name="is_cool">
-<option value="1">Unknown</option>
-<option value="2" selected>Yes</option>
-<option value="3">No</option>
+<option value="unknown">Unknown</option>
+<option value="true" selected>Yes</option>
+<option value="false">No</option>
 </select>""")
         p = Person({'name': 'Joe', 'is_cool': '3'}, auto_id=False)
         self.assertHTMLEqual(str(p['is_cool']), """<select name="is_cool">
-<option value="1">Unknown</option>
-<option value="2">Yes</option>
-<option value="3" selected>No</option>
+<option value="unknown">Unknown</option>
+<option value="true">Yes</option>
+<option value="false" selected>No</option>
 </select>""")
         p = Person({'name': 'Joe', 'is_cool': True}, auto_id=False)
         self.assertHTMLEqual(str(p['is_cool']), """<select name="is_cool">
-<option value="1">Unknown</option>
-<option value="2" selected>Yes</option>
-<option value="3">No</option>
+<option value="unknown">Unknown</option>
+<option value="true" selected>Yes</option>
+<option value="false">No</option>
 </select>""")
         p = Person({'name': 'Joe', 'is_cool': False}, auto_id=False)
         self.assertHTMLEqual(str(p['is_cool']), """<select name="is_cool">
-<option value="1">Unknown</option>
-<option value="2">Yes</option>
-<option value="3" selected>No</option>
+<option value="unknown">Unknown</option>
+<option value="true">Yes</option>
+<option value="false" selected>No</option>
+</select>""")
+        p = Person({'name': 'Joe', 'is_cool': 'unknown'}, auto_id=False)
+        self.assertHTMLEqual(str(p['is_cool']), """<select name="is_cool">
+<option value="unknown" selected>Unknown</option>
+<option value="true">Yes</option>
+<option value="false">No</option>
+</select>""")
+        p = Person({'name': 'Joe', 'is_cool': 'true'}, auto_id=False)
+        self.assertHTMLEqual(str(p['is_cool']), """<select name="is_cool">
+<option value="unknown">Unknown</option>
+<option value="true" selected>Yes</option>
+<option value="false">No</option>
+</select>""")
+        p = Person({'name': 'Joe', 'is_cool': 'false'}, auto_id=False)
+        self.assertHTMLEqual(str(p['is_cool']), """<select name="is_cool">
+<option value="unknown">Unknown</option>
+<option value="true">Yes</option>
+<option value="false" selected>No</option>
 </select>""")
 
     def test_forms_with_file_fields(self):
@@ -2758,9 +2776,9 @@ Good luck picking a username that doesn&#39;t already exist.</p>
 <label class="required" for="id_name">Name:</label> <input type="text" name="name" id="id_name" required></li>
 <li class="required"><label class="required" for="id_is_cool">Is cool:</label>
 <select name="is_cool" id="id_is_cool">
-<option value="1" selected>Unknown</option>
-<option value="2">Yes</option>
-<option value="3">No</option>
+<option value="unknown" selected>Unknown</option>
+<option value="true">Yes</option>
+<option value="false">No</option>
 </select></li>
 <li><label for="id_email">Email:</label> <input type="email" name="email" id="id_email"></li>
 <li class="required error"><ul class="errorlist"><li>This field is required.</li></ul>
@@ -2774,9 +2792,9 @@ Good luck picking a username that doesn&#39;t already exist.</p>
 <input type="text" name="name" id="id_name" required></p>
 <p class="required"><label class="required" for="id_is_cool">Is cool:</label>
 <select name="is_cool" id="id_is_cool">
-<option value="1" selected>Unknown</option>
-<option value="2">Yes</option>
-<option value="3">No</option>
+<option value="unknown" selected>Unknown</option>
+<option value="true">Yes</option>
+<option value="false">No</option>
 </select></p>
 <p><label for="id_email">Email:</label> <input type="email" name="email" id="id_email"></p>
 <ul class="errorlist"><li>This field is required.</li></ul>
@@ -2792,9 +2810,9 @@ Good luck picking a username that doesn&#39;t already exist.</p>
 <input type="text" name="name" id="id_name" required></td></tr>
 <tr class="required"><th><label class="required" for="id_is_cool">Is cool:</label></th>
 <td><select name="is_cool" id="id_is_cool">
-<option value="1" selected>Unknown</option>
-<option value="2">Yes</option>
-<option value="3">No</option>
+<option value="unknown" selected>Unknown</option>
+<option value="true">Yes</option>
+<option value="false">No</option>
 </select></td></tr>
 <tr><th><label for="id_email">Email:</label></th><td>
 <input type="email" name="email" id="id_email"></td></tr>
diff --git a/tests/forms_tests/widget_tests/test_nullbooleanselect.py b/tests/forms_tests/widget_tests/test_nullbooleanselect.py
index 779a88893f..a732e86da4 100644
--- a/tests/forms_tests/widget_tests/test_nullbooleanselect.py
+++ b/tests/forms_tests/widget_tests/test_nullbooleanselect.py
@@ -11,36 +11,81 @@ class NullBooleanSelectTest(WidgetTest):
     def test_render_true(self):
         self.check_html(self.widget, 'is_cool', True, html=(
             """<select name="is_cool">
-            <option value="1">Unknown</option>
-            <option value="2" selected>Yes</option>
-            <option value="3">No</option>
+            <option value="unknown">Unknown</option>
+            <option value="true" selected>Yes</option>
+            <option value="false">No</option>
             </select>"""
         ))
 
     def test_render_false(self):
         self.check_html(self.widget, 'is_cool', False, html=(
             """<select name="is_cool">
-            <option value="1">Unknown</option>
-            <option value="2">Yes</option>
-            <option value="3" selected>No</option>
+            <option value="unknown">Unknown</option>
+            <option value="true">Yes</option>
+            <option value="false" selected>No</option>
             </select>"""
         ))
 
     def test_render_none(self):
         self.check_html(self.widget, 'is_cool', None, html=(
             """<select name="is_cool">
-            <option value="1" selected>Unknown</option>
-            <option value="2">Yes</option>
-            <option value="3">No</option>
+            <option value="unknown" selected>Unknown</option>
+            <option value="true">Yes</option>
+            <option value="false">No</option>
             </select>"""
         ))
 
-    def test_render_value(self):
+    def test_render_value_unknown(self):
+        self.check_html(self.widget, 'is_cool', 'unknown', html=(
+            """<select name="is_cool">
+            <option value="unknown" selected>Unknown</option>
+            <option value="true">Yes</option>
+            <option value="false">No</option>
+            </select>"""
+        ))
+
+    def test_render_value_true(self):
+        self.check_html(self.widget, 'is_cool', 'true', html=(
+            """<select name="is_cool">
+            <option value="unknown">Unknown</option>
+            <option value="true" selected>Yes</option>
+            <option value="false">No</option>
+            </select>"""
+        ))
+
+    def test_render_value_false(self):
+        self.check_html(self.widget, 'is_cool', 'false', html=(
+            """<select name="is_cool">
+            <option value="unknown">Unknown</option>
+            <option value="true">Yes</option>
+            <option value="false" selected>No</option>
+            </select>"""
+        ))
+
+    def test_render_value_1(self):
+        self.check_html(self.widget, 'is_cool', '1', html=(
+            """<select name="is_cool">
+            <option value="unknown" selected>Unknown</option>
+            <option value="true">Yes</option>
+            <option value="false">No</option>
+            </select>"""
+        ))
+
+    def test_render_value_2(self):
         self.check_html(self.widget, 'is_cool', '2', html=(
             """<select name="is_cool">
-            <option value="1">Unknown</option>
-            <option value="2" selected>Yes</option>
-            <option value="3">No</option>
+            <option value="unknown">Unknown</option>
+            <option value="true" selected>Yes</option>
+            <option value="false">No</option>
+            </select>"""
+        ))
+
+    def test_render_value_3(self):
+        self.check_html(self.widget, 'is_cool', '3', html=(
+            """<select name="is_cool">
+            <option value="unknown">Unknown</option>
+            <option value="true">Yes</option>
+            <option value="false" selected>No</option>
             </select>"""
         ))
 
@@ -55,9 +100,9 @@ class NullBooleanSelectTest(WidgetTest):
             self.check_html(widget, 'id_bool', True, html=(
                 """
                 <select name="id_bool">
-                    <option value="1">Unbekannt</option>
-                    <option value="2" selected>Ja</option>
-                    <option value="3">Nein</option>
+                    <option value="unknown">Unbekannt</option>
+                    <option value="true" selected>Ja</option>
+                    <option value="false">Nein</option>
                 </select>
                 """
             ))