diff --git a/voluptuous/schema_builder.py b/voluptuous/schema_builder.py
index 429ab32..2c430a5 100644
--- a/voluptuous/schema_builder.py
+++ b/voluptuous/schema_builder.py
@@ -875,10 +875,11 @@ class VirtualPathComponent(str):
 class Marker(object):
     """Mark nodes for special treatment."""
 
-    def __init__(self, schema_, msg=None):
+    def __init__(self, schema_, msg=None, description=None):
         self.schema = schema_
         self._schema = Schema(schema_)
         self.msg = msg
+        self.description = description
 
     def __call__(self, v):
         try:
@@ -930,8 +931,9 @@ class Optional(Marker):
     {'key2': 'value'}
     """
 
-    def __init__(self, schema, msg=None, default=UNDEFINED):
-        super(Optional, self).__init__(schema, msg=msg)
+    def __init__(self, schema, msg=None, default=UNDEFINED, description=None):
+        super(Optional, self).__init__(schema, msg=msg,
+                                       description=description)
         self.default = default_factory(default)
 
 
@@ -971,8 +973,9 @@ class Exclusive(Optional):
     ...             'social': {'social_network': 'barfoo', 'token': 'tEMp'}})
     """
 
-    def __init__(self, schema, group_of_exclusion, msg=None):
-        super(Exclusive, self).__init__(schema, msg=msg)
+    def __init__(self, schema, group_of_exclusion, msg=None, description=None):
+        super(Exclusive, self).__init__(schema, msg=msg,
+                                        description=description)
         self.group_of_exclusion = group_of_exclusion
 
 
@@ -1038,8 +1041,9 @@ class Required(Marker):
     {'key': []}
     """
 
-    def __init__(self, schema, msg=None, default=UNDEFINED):
-        super(Required, self).__init__(schema, msg=msg)
+    def __init__(self, schema, msg=None, default=UNDEFINED, description=None):
+        super(Required, self).__init__(schema, msg=msg,
+                                       description=description)
         self.default = default_factory(default)
 
 

RFC: Add description attribute to Required/Optional
So I'm not sure if this should be even a thing in voluptuous, but since more users of this lib might be interested I thought I'll bring it up.

We use voluptuous in [Home Assistant](https://github.com/home-assistant/home-assistant) and it's awesome. For some of the things that we have voluptuous validate, we would like to generate a schema of what is allowed.

To this end I started an experiment a while ago called [voluptuous form](https://github.com/balloob/voluptuous_form). I'm looking into reviving this effort but there is one missing piece of the puzzle: description of the fields.

Would description be something that would make sense to add to Voluptuous?

Potential syntax:

```python
import voluptuous as vol

SCHEMA = vol.Schema({
  vol.Required('name', description='The name of the user.'): str,
  vol.Required('age', description='The age of the user.'): int,
})
```
diff --git a/voluptuous/tests/tests.py b/voluptuous/tests/tests.py
index 8b82f98..48cbfd0 100644
--- a/voluptuous/tests/tests.py
+++ b/voluptuous/tests/tests.py
@@ -6,7 +6,7 @@ import sys
 from nose.tools import assert_equal, assert_raises, assert_true
 
 from voluptuous import (
-    Schema, Required, Optional, Extra, Invalid, In, Remove, Literal,
+    Schema, Required, Exclusive, Optional, Extra, Invalid, In, Remove, Literal,
     Url, MultipleInvalid, LiteralInvalid, TypeInvalid, NotIn, Match, Email,
     Replace, Range, Coerce, All, Any, Length, FqdnUrl, ALLOW_EXTRA, PREVENT_EXTRA,
     validate, ExactSequence, Equal, Unordered, Number, Maybe, Datetime, Date,
@@ -915,3 +915,17 @@ def test_PathExists():
     schema = Schema(PathExists())
     assert_raises(MultipleInvalid, schema, 3)
     schema(os.path.abspath(__file__))
+
+
+def test_description():
+    marker = Marker(Schema(str), description='Hello')
+    assert marker.description == 'Hello'
+
+    optional = Optional('key', description='Hello')
+    assert optional.description == 'Hello'
+
+    exclusive = Exclusive('alpha', 'angles', description='Hello')
+    assert exclusive.description == 'Hello'
+
+    required = Required('key', description='Hello')
+    assert required.description == 'Hello'

pip install -e .
parse_log_pytest
pytest
nose pytest
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///opt/conda/conda-bld/attrs_1642510447205/work
certifi==2021.5.30
importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1631916693255/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
more-itertools @ file:///tmp/build/80754af9/more-itertools_1637733554872/work
nose==1.3.7
packaging @ file:///tmp/build/80754af9/packaging_1637314298585/work
pluggy @ file:///tmp/build/80754af9/pluggy_1615976315926/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pyparsing @ file:///tmp/build/80754af9/pyparsing_1635766073266/work
pytest==6.2.4
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
typing_extensions @ file:///opt/conda/conda-bld/typing_extensions_1647553014482/work
-e git+https://github.com/alecthomas/voluptuous.git@48d1ea8f0f07478e50da38935d7e452f80d8501b#egg=voluptuous
zipp @ file:///tmp/build/80754af9/zipp_1633618647012/work

name: voluptuous
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=21.4.0=pyhd3eb1b0_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - importlib-metadata=4.8.1=py36h06a4308_0
  - importlib_metadata=4.8.1=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - more-itertools=8.12.0=pyhd3eb1b0_0
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=21.3=pyhd3eb1b0_0
  - pip=21.2.2=py36h06a4308_0
  - pluggy=0.13.1=py36h06a4308_0
  - py=1.11.0=pyhd3eb1b0_0
  - pyparsing=3.0.4=pyhd3eb1b0_0
  - pytest=6.2.4=py36h06a4308_2
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - toml=0.10.2=pyhd3eb1b0_0
  - typing_extensions=4.1.1=pyh06a4308_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.6.0=pyhd3eb1b0_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - nose==1.3.7
prefix: /opt/conda/envs/voluptuous

voluptuous/tests/tests.py::test_description
voluptuous/tests/tests.py::test_exact_sequence voluptuous/tests/tests.py::test_required voluptuous/tests/tests.py::test_extra_with_required voluptuous/tests/tests.py::test_iterate_candidates voluptuous/tests/tests.py::test_in voluptuous/tests/tests.py::test_not_in voluptuous/tests/tests.py::test_contains voluptuous/tests/tests.py::test_remove voluptuous/tests/tests.py::test_extra_empty_errors voluptuous/tests/tests.py::test_literal voluptuous/tests/tests.py::test_class voluptuous/tests/tests.py::test_email_validation voluptuous/tests/tests.py::test_email_validation_with_none voluptuous/tests/tests.py::test_email_validation_with_empty_string voluptuous/tests/tests.py::test_email_validation_without_host voluptuous/tests/tests.py::test_fqdn_url_validation voluptuous/tests/tests.py::test_fqdn_url_without_domain_name voluptuous/tests/tests.py::test_fqdnurl_validation_with_none voluptuous/tests/tests.py::test_fqdnurl_validation_with_empty_string voluptuous/tests/tests.py::test_fqdnurl_validation_without_host voluptuous/tests/tests.py::test_url_validation voluptuous/tests/tests.py::test_url_validation_with_none voluptuous/tests/tests.py::test_url_validation_with_empty_string voluptuous/tests/tests.py::test_url_validation_without_host voluptuous/tests/tests.py::test_copy_dict_undefined voluptuous/tests/tests.py::test_sorting voluptuous/tests/tests.py::test_schema_extend voluptuous/tests/tests.py::test_schema_extend_overrides voluptuous/tests/tests.py::test_schema_extend_key_swap voluptuous/tests/tests.py::test_subschema_extension voluptuous/tests/tests.py::test_repr voluptuous/tests/tests.py::test_list_validation_messages voluptuous/tests/tests.py::test_nested_multiple_validation_errors voluptuous/tests/tests.py::test_humanize_error voluptuous/tests/tests.py::test_fix_157 voluptuous/tests/tests.py::test_range_exlcudes_nan voluptuous/tests/tests.py::test_equal voluptuous/tests/tests.py::test_unordered voluptuous/tests/tests.py::test_maybe voluptuous/tests/tests.py::test_empty_list_as_exact voluptuous/tests/tests.py::test_empty_dict_as_exact voluptuous/tests/tests.py::test_schema_decorator_match_with_args voluptuous/tests/tests.py::test_schema_decorator_unmatch_with_args voluptuous/tests/tests.py::test_schema_decorator_match_with_kwargs voluptuous/tests/tests.py::test_schema_decorator_unmatch_with_kwargs voluptuous/tests/tests.py::test_schema_decorator_match_return_with_args voluptuous/tests/tests.py::test_schema_decorator_unmatch_return_with_args voluptuous/tests/tests.py::test_schema_decorator_match_return_with_kwargs voluptuous/tests/tests.py::test_schema_decorator_unmatch_return_with_kwargs voluptuous/tests/tests.py::test_schema_decorator_return_only_match voluptuous/tests/tests.py::test_schema_decorator_return_only_unmatch voluptuous/tests/tests.py::test_schema_decorator_partial_match_called_with_args voluptuous/tests/tests.py::test_schema_decorator_partial_unmatch_called_with_args voluptuous/tests/tests.py::test_schema_decorator_partial_match_called_with_kwargs voluptuous/tests/tests.py::test_schema_decorator_partial_unmatch_called_with_kwargs voluptuous/tests/tests.py::test_unicode_as_key voluptuous/tests/tests.py::test_number_validation_with_string voluptuous/tests/tests.py::test_number_validation_with_invalid_precision_invalid_scale voluptuous/tests/tests.py::test_number_validation_with_valid_precision_scale_yield_decimal_true voluptuous/tests/tests.py::test_number_when_precision_scale_none_yield_decimal_true voluptuous/tests/tests.py::test_number_when_precision_none_n_valid_scale_case1_yield_decimal_true voluptuous/tests/tests.py::test_number_when_precision_none_n_valid_scale_case2_yield_decimal_true voluptuous/tests/tests.py::test_number_when_precision_none_n_invalid_scale_yield_decimal_true voluptuous/tests/tests.py::test_number_when_valid_precision_n_scale_none_yield_decimal_true voluptuous/tests/tests.py::test_number_when_invalid_precision_n_scale_none_yield_decimal_true voluptuous/tests/tests.py::test_number_validation_with_valid_precision_scale_yield_decimal_false voluptuous/tests/tests.py::test_named_tuples_validate_as_tuples voluptuous/tests/tests.py::test_datetime voluptuous/tests/tests.py::test_date voluptuous/tests/tests.py::test_ordered_dict voluptuous/tests/tests.py::test_marker_hashable voluptuous/tests/tests.py::test_validation_performance voluptuous/tests/tests.py::test_IsDir voluptuous/tests/tests.py::test_IsFile voluptuous/tests/tests.py::test_PathExists
voluptuous/schema_builder.py
voluptuous/schema_builder.py