diff --git a/.gitignore b/.gitignore
index 96407f9..325ced7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,6 @@ develop-eggs/
 eggs/
 parts/
 coverage/
+.coverage
+htmlcov/
+docs/_build/
diff --git a/CHANGES.rst b/CHANGES.rst
index d50a970..6b42179 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,14 +1,15 @@
-Changes
-=======
+=========
+ Changes
+=========
 
-4.5 (unreleased)
-================
+4.5.0 (unreleased)
+==================
 
-- Nothing changed yet.
+- Move documentation to https://zopeformlib.readthedocs.io
 
 
-4.4 (2017-08-15)
-================
+4.4.0 (2017-08-15)
+==================
 
 - Add support for Python 3.5, and 3.6.
 
diff --git a/MANIFEST.in b/MANIFEST.in
index 45019b2..470140c 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -6,6 +6,10 @@ include tox.ini
 include .travis.yml
 include .coveragerc
 
+recursive-include docs *.py
+recursive-include docs *.rst
+recursive-include docs Makefile
+
 recursive-include src *
 
 global-exclude *.pyc
diff --git a/README.rst b/README.rst
index 8a4ff99..8f052d2 100644
--- a/README.rst
+++ b/README.rst
@@ -1,9 +1,17 @@
-``zope.formlib``
-================
+==============
+ zope.formlib
+==============
 
 .. image:: https://travis-ci.org/zopefoundation/zope.formlib.svg?branch=master
         :target: https://travis-ci.org/zopefoundation/zope.formlib
 
+.. image:: https://readthedocs.org/projects/zopeformlib/badge/?version=latest
+        :target: https://zopeformlib.readthedocs.io/en/latest/
+        :alt: Documentation Status
+
+
 Forms are web components that use widgets to display and input data.
 Typically a template displays the widgets by accessing an attribute or
 method on an underlying class.
+
+Documentation is hosted at https://zopeformlib.readthedocs.io/en/latest/
diff --git a/docs/api/index.rst b/docs/api/index.rst
new file mode 100644
index 0000000..d746395
--- /dev/null
+++ b/docs/api/index.rst
@@ -0,0 +1,78 @@
+===============
+ API Reference
+===============
+
+zope.formlib.interfaces
+=======================
+
+.. automodule:: zope.formlib.interfaces
+
+zope.formlib.boolwidgets
+========================
+
+.. automodule:: zope.formlib.boolwidgets
+
+zope.formlib.errors
+===================
+
+.. automodule:: zope.formlib.errors
+
+zope.formlib.exception
+======================
+
+.. automodule:: zope.formlib.exception
+
+zope.formlib.form
+=================
+
+.. automodule:: zope.formlib.form
+
+zope.formlib.i18n
+=================
+
+.. automodule:: zope.formlib.i18n
+
+zope.formlib.itemswidgets
+=========================
+
+.. automodule:: zope.formlib.itemswidgets
+
+zope.formlib.namedtemplate
+==========================
+
+.. automodule:: zope.formlib.namedtemplate
+
+zope.formlib.objectwidget
+=========================
+
+.. automodule:: zope.formlib.objectwidget
+
+zope.formlib.sequencewidget
+===========================
+
+.. automodule:: zope.formlib.sequencewidget
+
+zope.formlib.source
+===================
+
+.. automodule:: zope.formlib.source
+
+zope.formlib.textwidgets
+========================
+
+.. automodule:: zope.formlib.textwidgets
+
+zope.formlib.utility
+====================
+
+.. automodule:: zope.formlib.utility
+
+zope.formlib.widget
+===================
+
+.. automodule:: zope.formlib.widget
+
+zope.formlib.widgets
+====================
+
+.. automodule:: zope.formlib.widgets
diff --git a/docs/changelog.rst b/docs/changelog.rst
new file mode 100644
index 0000000..d9e113e
--- /dev/null
+++ b/docs/changelog.rst
@@ -0,0 +1,1 @@
+.. include:: ../CHANGES.rst
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..e1ba4b5
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,200 @@
+# -*- coding: utf-8 -*-
+#
+# zope.formlib documentation build configuration file, created by
+# sphinx-quickstart on Thu Jun  8 07:02:25 2017.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+import os
+import sys
+import pkg_resources
+# Use the python versions instead of the cython compiled versions
+# for better documentation extraction and ease of tweaking docs.
+os.environ['PURE_PYTHON'] = '1'
+
+sys.path.append(os.path.abspath('../src'))
+rqmt = pkg_resources.require('zope.formlib')[0]
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    'sphinx.ext.autodoc',
+    'sphinx.ext.intersphinx',
+    'sphinx.ext.viewcode',
+    'repoze.sphinx.autointerface',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'zope.formlib'
+copyright = u'2017, Zope Foundation contributors'
+author = u'Zope Foundation contributors'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '%s.%s' % tuple(map(int, rqmt.version.split('.')[:2]))
+# The full version, including alpha/beta/rc tags.
+release = rqmt.version
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This patterns also effect to html_static_path and html_extra_path
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+default_role = 'obj'
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+add_module_names = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'perldoc'
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'sphinx_rtd_theme'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#
+# html_theme_options = {}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+
+# -- Options for HTMLHelp output ------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'zopeformlibdoc'
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+    # The paper size ('letterpaper' or 'a4paper').
+    #
+    # 'papersize': 'letterpaper',
+
+    # The font size ('10pt', '11pt' or '12pt').
+    #
+    # 'pointsize': '10pt',
+
+    # Additional stuff for the LaTeX preamble.
+    #
+    # 'preamble': '',
+
+    # Latex figure (float) alignment
+    #
+    # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+#  author, documentclass [howto, manual, or own class]).
+latex_documents = [
+    (master_doc, 'zopeformlib.tex', u'zope.formlib Documentation',
+     u'Zopefoundation', 'manual'),
+]
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    (master_doc, 'zopeformlib', u'zope.formlib Documentation',
+     [author], 1)
+]
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+    (master_doc, 'zopeformlib', u'zope.formlib Documentation',
+     author, 'zopeformlib', 'One line description of project.',
+     'Miscellaneous'),
+]
+
+
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {
+    'https://docs.python.org/': None,
+    'https://zopebrowser.readthedocs.io/en/latest': None,
+    'https://zopecomponent.readthedocs.io/en/latest': None,
+    'https://zopeinterface.readthedocs.io/en/latest': None,
+    'https://zopepublisher.readthedocs.io/en/latest': None,
+    'https://zopeschema.readthedocs.io/en/latest': None,
+}
+
+extlinks = {
+    'issue': ('https://github.com/zopefoundation/zope.formlib/issues/%s',
+              'issue #'),
+    'pr': ('https://github.com/zopefoundation/zope.formlib/pull/%s',
+           'pull request #')}
+
+autodoc_default_flags = ['members', 'show-inheritance']
+autoclass_content = 'both'
+# This causes the order in __all__ to be ignored :(
+# autodoc_member_order = 'bysource'
diff --git a/docs/errors.rst b/docs/errors.rst
new file mode 100644
index 0000000..f14f462
--- /dev/null
+++ b/docs/errors.rst
@@ -0,0 +1,1 @@
+.. include:: ../src/zope/formlib/errors.rst
diff --git a/docs/form.rst b/docs/form.rst
new file mode 100644
index 0000000..4006578
--- /dev/null
+++ b/docs/form.rst
@@ -0,0 +1,1 @@
+.. include:: ../src/zope/formlib/form.rst
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..5f42944
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,34 @@
+
+.. include:: ../README.rst
+
+
+Introduction and Basics
+=======================
+
+.. toctree::
+   :maxdepth: 2
+
+   form
+   widgets
+   errors
+   objectwidget
+   source
+   changelog
+
+
+API Details
+===========
+
+.. toctree::
+   :maxdepth: 2
+
+   api/index
+
+
+====================
+ Indices and tables
+====================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/docs/objectwidget.rst b/docs/objectwidget.rst
new file mode 100644
index 0000000..4cbbb77
--- /dev/null
+++ b/docs/objectwidget.rst
@@ -0,0 +1,1 @@
+.. include:: ../src/zope/formlib/objectwidget.rst
diff --git a/docs/source.rst b/docs/source.rst
new file mode 100644
index 0000000..f8ccceb
--- /dev/null
+++ b/docs/source.rst
@@ -0,0 +1,1 @@
+.. include:: ../src/zope/formlib/source.rst
diff --git a/docs/widgets.rst b/docs/widgets.rst
new file mode 100644
index 0000000..b6c2a51
--- /dev/null
+++ b/docs/widgets.rst
@@ -0,0 +1,1 @@
+.. include:: ../src/zope/formlib/widgets.rst
diff --git a/rtd-requirements.txt b/rtd-requirements.txt
new file mode 100644
index 0000000..e9704b8
--- /dev/null
+++ b/rtd-requirements.txt
@@ -0,0 +1,1 @@
+.[docs]
diff --git a/setup.py b/setup.py
index 4a86845..d0ae4de 100644
--- a/setup.py
+++ b/setup.py
@@ -45,16 +45,11 @@ setup(name='zope.formlib',
       author='Zope Foundation and Contributors',
       author_email='zope-dev@zope.org',
       description='Form generation and validation library for Zope',
-      long_description=(read('README.rst')
-                        + '\n\n' +
-                        read('src', 'zope', 'formlib', 'form.txt')
-                        + '\n\n' +
-                        read('src', 'zope', 'formlib', 'widgets.txt')
-                        + '\n\n' +
-                        read('src', 'zope', 'formlib', 'errors.txt')
-                        + '\n\n' +
-                        read('CHANGES.rst')
-                        ),
+      long_description=(
+          read('README.rst')
+          + '\n\n' +
+          read('CHANGES.rst')
+      ),
       license='ZPL 2.1',
       keywords="zope3 form widget",
       classifiers=[
@@ -75,40 +70,46 @@ setup(name='zope.formlib',
           'Operating System :: OS Independent',
           'Topic :: Internet :: WWW/HTTP',
           'Framework :: Zope3',
-          ],
+      ],
       url='https://github.com/zopefoundation/zope.formlib',
       packages=find_packages('src'),
-      package_dir = {'': 'src'},
+      package_dir={'': 'src'},
       namespace_packages=['zope',],
-      extras_require=dict(
-          test=['zope.configuration',
-                'zope.testing',
-                'zope.testrunner',
-               ]
-          ),
+      extras_require={
+          'test': [
+              'zope.configuration',
+              'zope.testing',
+              'zope.testrunner',
+          ],
+          'docs': [
+              'Sphinx',
+              'sphinx_rtd_theme',
+              'repoze.sphinx.autointerface',
+          ],
+      },
       install_requires=[
-        'setuptools',
-        'pytz',
-        'zope.browser>=1.1',
-        'zope.browserpage>=3.11.0',
-        'zope.component',
-        'zope.event',
-        'zope.i18n',
-        'zope.i18nmessageid',
-        'zope.interface',
-        'zope.lifecycleevent',
-        'zope.publisher',
-        'zope.schema>=3.5.1',
-        'zope.security',
-        'zope.traversing',
-        'zope.datetime',
-        ],
-      tests_require = [
+          'setuptools',
+          'pytz',
+          'zope.browser>=1.1',
+          'zope.browserpage>=3.11.0',
+          'zope.component',
+          'zope.event',
+          'zope.i18n',
+          'zope.i18nmessageid',
+          'zope.interface',
+          'zope.lifecycleevent',
+          'zope.publisher',
+          'zope.schema>=3.5.1',
+          'zope.security',
+          'zope.traversing',
+          'zope.datetime',
+      ],
+      tests_require=[
           'zope.configuration',
           'zope.testing',
           'zope.testrunner',
-          ],
-      test_suite = '__main__.alltests',
-      include_package_data = True,
-      zip_safe = False,
-      )
+      ],
+      test_suite='__main__.alltests',
+      include_package_data=True,
+      zip_safe=False,
+)
diff --git a/src/zope/formlib/TODO.txt b/src/zope/formlib/TODO.rst
similarity index 100%
rename from src/zope/formlib/TODO.txt
rename to src/zope/formlib/TODO.rst
diff --git a/src/zope/formlib/bugs.txt b/src/zope/formlib/bugs.rst
similarity index 100%
rename from src/zope/formlib/bugs.txt
rename to src/zope/formlib/bugs.rst
diff --git a/src/zope/formlib/errors.txt b/src/zope/formlib/errors.rst
similarity index 87%
rename from src/zope/formlib/errors.txt
rename to src/zope/formlib/errors.rst
index 6f60521..b313855 100644
--- a/src/zope/formlib/errors.txt
+++ b/src/zope/formlib/errors.rst
@@ -1,14 +1,17 @@
-Error handling
-==============
+================
+ Error handling
+================
+
+.. currentmodule:: zope.formlib.interfaces
 
 These are a couple of functional tests that were written on-the-go ... In the
 future this might become more extensive ...
 
 Displaying invalidation errors
-------------------------------
+==============================
 
 Validation errors, e.g. cause by invariants, are converted into readable text
-by adapting them to IWidgetInputErrorView:
+by adapting them to `IWidgetInputErrorView`:
 
     >>> from zope.publisher.browser import TestRequest
     >>> from zope.interface.exceptions import Invalid
@@ -20,7 +23,7 @@ by adapting them to IWidgetInputErrorView:
     >>> message
     u'<span class="error">You are wrong!</span>'
 
-Interface invariant methods raise zope.interface.Invalid exception. Test if
+Interface invariant methods raise `zope.interface.Invalid` exception. Test if
 this exception gets handled by the error_views.
 
     >>> myError = Invalid('My error message')
@@ -54,9 +57,9 @@ And yes, we can even handle an i18n message in an Invalid exception:
     u'<span class="error">[[my.domain][My i18n error message]]</span>'
 
 Displaying widget input errors
-------------------------------
+==============================
 
-WidgetInputError exceptions also work with i18n messages:
+`WidgetInputError` exceptions also work with i18n messages:
 
     >>> from zope.formlib.interfaces import WidgetInputError
     >>> myError = WidgetInputError(
diff --git a/src/zope/formlib/form.py b/src/zope/formlib/form.py
index 0d30b80..b6196ef 100644
--- a/src/zope/formlib/form.py
+++ b/src/zope/formlib/form.py
@@ -10,7 +10,9 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-"""Forms
+"""Forms.
+
+This module provides the `zope.formlib.interfaces.IFormAPI` interface.
 """
 import binascii
 import datetime
@@ -67,7 +69,8 @@ def expandPrefix(prefix):
     return prefix
 
 @interface.implementer(interfaces.IFormField)
-class FormField:
+class FormField(object):
+    """Implementation of `zope.formlib.interfaces.IFormField`. """
 
     def __init__(self, field, name=None, prefix='',
                  for_display=None, for_input=None, custom_widget=None,
@@ -97,6 +100,7 @@ def _initkw(keep_readonly=(), omit_readonly=False, **defaults):
 
 @interface.implementer(interfaces.IFormFields)
 class FormFields(object):
+    """Implementation of `zope.formlib.interfaces.IFormFields`."""
 
     def __init__(self, *args, **kw):
         keep_readonly, omit_readonly, defaults = _initkw(**kw)
@@ -186,6 +190,7 @@ def fields(*args, **kw):
 
 @interface.implementer(interfaces.IWidgets)
 class Widgets(object):
+    """Implementation of `zope.formlib.interfaces.IWidgets`."""
 
     def __init__(self, widgets, prefix_length=None, prefix=None):
         self.__Widgets_widgets_items__ = widgets
@@ -239,7 +244,7 @@ def canWrite(context, field):
 def setUpWidgets(form_fields,
                  form_prefix=None, context=None, request=None, form=None,
                  data=(), adapters=None, ignore_request=False):
-
+    """Sets up widgets."""
     if request is None:
         request = form.request
     if context is None and form is not None:
@@ -340,6 +345,7 @@ def _createWidget(form_field, field, request, iface):
 
 
 def getWidgetsData(widgets, form_prefix, data):
+    """See `zope.formlib.interfaces.IFormAPI.getWidgetsData`"""
     errors = []
     form_prefix = expandPrefix(form_prefix)
 
@@ -508,6 +514,7 @@ class FormData:
 
 
 def checkInvariants(form_fields, form_data, context):
+    """See `zope.formlib.interfaces.IFormAPI.checkInvariants`"""
 
     # First, collect the data for the various schemas
     schema_data = {}
@@ -561,6 +568,7 @@ def applyData(context, form_fields, data, adapters=None):
     return descriptions
 
 def applyChanges(context, form_fields, data, adapters=None):
+    """See `zope.formlib.interfaces.IFormAPI.applyChanges`"""
     return bool(applyData(context, form_fields, data, adapters))
 
 
@@ -575,6 +583,7 @@ def _callify(meth):
 
 @interface.implementer(interfaces.IAction)
 class Action(object):
+    """See `zope.formlib.interfaces.IAction`"""
     _identifier = re.compile('[A-Za-z][a-zA-Z0-9_]*$')
 
     def __init__(self, label, success=None, failure=None,
@@ -663,7 +672,8 @@ def render_submit_button(self):
             (self.__name__, self.__name__, escape(label, quote=True))
             )
 
-class action:
+class action(object):
+    """See `zope.formlib.interfaces.IFormAPI.action`"""
     def __init__(self, label, actions=None, **options):
         caller_locals = sys._getframe(1).f_locals
         if actions is None:
@@ -716,7 +726,7 @@ class Actions(object):
         return self.__class__(*[a.__get__(inst) for a in self.actions])
 
 def handleSubmit(actions, data, default_validate=None):
-
+    """Handle a submit."""
     for action in actions:
         if action.submitted():
             errors = action.validate(data)
diff --git a/src/zope/formlib/form.txt b/src/zope/formlib/form.rst
similarity index 96%
rename from src/zope/formlib/form.txt
rename to src/zope/formlib/form.rst
index dce6721..fd2e764 100644
--- a/src/zope/formlib/form.txt
+++ b/src/zope/formlib/form.rst
@@ -1,5 +1,8 @@
-Forms
-=====
+=======
+ Forms
+=======
+
+.. currentmodule:: zope.formlib.form
 
 Forms are web components that use widgets to display and input data.
 Typically a template displays the widgets by accessing an attribute or
@@ -35,7 +38,7 @@ schema to the `Fields` constructor:
     >>> class MyForm:
     ...     form_fields = form.Fields(IOrder)
 
-This sets up a set of form fields from the interface, IOrder.
+This sets up a set of form fields from the interface, ``IOrder``.
 
     >>> len(MyForm.form_fields)
     6
@@ -58,7 +61,7 @@ or by omitting fields:
     >>> [w.__name__ for w in MyForm.form_fields.omit('now', 'identifier')]
     ['name', 'min_size', 'max_size', 'color']
 
-We can omit read-only fields using the omit_readonly option when
+We can omit read-only fields using the *omit_readonly* option when
 setting up the fields:
 
     >>> class MyForm:
@@ -68,7 +71,7 @@ setting up the fields:
 
 
 Getting HTML
-------------
+============
 
 Having defined form fields, we can use them to generate HTML
 forms. Typically, this is done at run time by form class
@@ -86,8 +89,8 @@ instances. Let's look at an example that displays some input widgets:
     ...             ignore_request=ignore_request)
     ...         return '\n'.join([w() for w in widgets])
 
-Here we used ``form.setUpWidgets`` to create widget instances from our
-form-field specifications.  The second argument to ``setUpWidgets`` is a
+Here we used `setUpWidgets` to create widget instances from our
+form-field specifications.  The second argument to `setUpWidgets` is a
 form prefix.  All of the widgets on this form are given the same
 prefix.  This allows multiple forms to be used within a single form
 tag, assuming that each form uses a different form prefix.
@@ -124,8 +127,8 @@ output:
 
 Sometimes we don't want this behavior: we want to ignore the request values,
 particularly after a form has been processed and before it is drawn again.
-This can be accomplished with the 'ignore_request' argument in
-setUpWidgets.
+This can be accomplished with the *ignore_request* argument in
+`setUpWidgets`.
 
     >>> print(MyForm(None, request)(ignore_request=True))
     ... # doctest: +NORMALIZE_WHITESPACE
@@ -140,10 +143,10 @@ setUpWidgets.
 
 
 Reading data
-------------
+============
 
 Of course, we don't just want to display inputs.  We want to get the
-input data.  We can use getWidgetsData for that:
+input data.  We can use `getWidgetsData` for that:
 
     >>> from pprint import pprint
     >>> class MyForm:
@@ -250,7 +253,7 @@ If we provide valid data, we'll get the data back:
 It's up to the form to decide what to do with the information.
 
 Invariants
-----------
+==========
 
 The `getWidgetsData` function checks individual field constraints.
 Interfaces can also provide invariants that we may also want to check.
@@ -271,7 +274,7 @@ greater than or equal to the minimum:
     ...         if order.max_size < order.min_size:
     ...             raise interface.Invalid("Maximum is less than Minimum")
 
-We can update our form to check the invariant using 'checkInvariants':
+We can update our form to check the invariant using `checkInvariants`:
 
     >>> class MyForm:
     ...     form_fields = form.Fields(IOrder, omit_readonly=True)
@@ -383,7 +386,7 @@ invariants are ignored:
 
 
 Edit Forms
-----------
+==========
 
 A common application of forms is edit forms.  Edit forms are special
 in 2 ways:
@@ -395,9 +398,9 @@ in 2 ways:
   object being edited.
 
 The form package provides some functions to assist with creating edit
-forms.  When we set up our form_fields, we use the `render_context`
-option, which uses data from the context passed to setUpWidgets.
-Let's create a content class that provides `IOrder` and a simple form
+forms.  When we set up our form_fields, we use the *render_context*
+option, which uses data from the context passed to `setUpWidgets`.
+Let's create a content class that provides ``IOrder`` and a simple form
 that uses it:
 
     >>> import datetime
@@ -438,7 +441,7 @@ that uses it:
 
 Note that, in this case, we got the values from the request, because
 we used an old request.  If we want to redraw the form after processing a
-request, it is safest to pass ignore_request = True to setUpWidgets so that
+request, it is safest to pass ``ignore_request = True`` to `setUpWidgets` so that
 the form is redrawn with the values as found in the object, not on the request.
 
     >>> print(MyForm(order, request)(ignore_request=True))
@@ -582,11 +585,11 @@ changes were applied:
 
 because the new and old values are the same.
 
-The code we included in `MyForm` above is generic: it applies to any
+The code we included in ``MyForm`` above is generic: it applies to any
 edit form.
 
 Actions
--------
+=======
 
 Our commit logic is a little complicated.  It would be far more
 complicated if there were multiple submit buttons.
@@ -752,9 +755,9 @@ Lets walk through the `__call__` method.
 
 - We set up our widgets as before.
 
-- We use `form.handleSubmit` to validate our data.  We pass the form,
-  actions, prefix, and `validate` method.  For each action,
-  `form.handleSubmit` checks to see if the action was submitted.  If the
+- We use `handleSubmit` to validate our data.  We pass the form,
+  actions, prefix, and ``validate`` method.  For each action,
+  `handleSubmit` checks to see if the action was submitted.  If the
   action was submitted, it checks to see if it has a validator.  If
   the action has a validator, the action's validator is called,
   otherwise the validator passed is called.  The validator result (a
@@ -869,13 +872,13 @@ Ah, much better.  And our order has been updated:
     1.0
 
 Helpful base classes
---------------------
+====================
 
 Our form has a lot of repetitive code. A number of helpful base
 classes provide standard form implementation.
 
 Form
-~~~~
+----
 
 The `Form` base class provides a number of common attribute definitions.
 It provides:
@@ -953,16 +956,16 @@ We inherited most of our behavior from the base class.
 
 We also used the `action` decorator.  The action decorator:
 
-- creates an `actions` variable if one isn't already created,
+- creates an ``actions`` variable if one isn't already created,
 
 - defines an action with the given label and any other arguments, and
 
-- appends the action to the `actions` list.
+- appends the action to the ``actions`` list.
 
 The `action` decorator accepts the same arguments as the `Action`
-class with the exception of the `success` option.
+class with the exception of the *success* option.
 
-The creation of the `actions` is a bit magic, but provides
+The creation of the ``actions`` is a bit magic, but provides
 simplification in common cases.
 
 Now we can try out our form:
@@ -1013,7 +1016,7 @@ Now we can try out our form:
     20.0
 
 EditForm
-~~~~~~~~
+--------
 
 Our `handle_edit_action` action is common to edit forms.  An
 `EditForm` base class captures this commonality.  It also sets up
@@ -1076,11 +1079,11 @@ Note that `EditForm` shows the date and time when content are
 modified.
 
 Multiple Schemas and Adapters
------------------------------
+=============================
 
 Forms can use fields from multiple schemas.  This can be done in a
 number of ways.  For example, multiple schemas can be passed to
-`form.Fields`:
+`Fields`:
 
     >>> class IDescriptive(interface.Interface):
     ...     title = schema.TextLine(title=u"Title")
@@ -1202,11 +1205,11 @@ name:
 
 If you aren't using `EditForm`, you can get a dictionary populated in
 the same way by `setUpWidgets` by passing the dictionary as an
-`adapters` keyword argument.
+*adapters* keyword argument.
 
 
 Named Widget Access
--------------------
+===================
 
 The value returned from `setUpWidgets` supports named-based lookup as well as
 iteration:
@@ -1224,18 +1227,18 @@ iteration:
     'form.title'
 
 Form-field manipulations
-------------------------
+========================
 
 The form-field constructor is very flexible.  We've already seen that
 we can supply multiple schemas.  Here are some other things you can
 do.
 
 Specifying individual fields
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+----------------------------
 
 You can specify individual fields for a form.  Here, we'll create a
-form that collects just the name from `IOrder` and the title from
-`IDescriptive`:
+form that collects just the name from ``IOrder`` and the title from
+``IDescriptive``:
 
     >>> class MyForm(form.EditForm):
     ...     form_fields = form.Fields(IOrder['name'],
@@ -1266,7 +1269,7 @@ You can also use stand-alone fields:
 But make sure the fields have a '__name__', as was done above.
 
 Concatenating field collections
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-------------------------------
 
 It is sometimes convenient to combine multiple field collections.
 Field collections support concatenation. For example, we may want to
@@ -1290,11 +1293,11 @@ combine field definitions:
            size="20" type="text" value=""  />
 
 Using fields for display
-~~~~~~~~~~~~~~~~~~~~~~~~
+------------------------
 
 Normally, any writable fields get input widgets.  We may want to
 indicate that some fields should be used for display only. We can do
-this using the `for_display` option when setting up form_fields:
+this using the *for_display* option when setting up form_fields:
 
     >>> class MyForm(form.EditForm):
     ...     form_fields = (
@@ -1329,7 +1332,7 @@ by `EditForm` has a condition to prevent it's use when there are no
 input widgets. Check it out for an example of using action conditions.
 
 Using fields for input
-~~~~~~~~~~~~~~~~~~~~~~
+----------------------
 
 We may want to indicate that some fields should be used for input even
 if the underlying schema field is read-only. We can do this using the
@@ -1354,7 +1357,7 @@ if the underlying schema field is read-only. We can do this using the
            size="10" type="text" value="20.0"  />
 
 Displaying or editing raw data
-------------------------------
+==============================
 
 Sometimes, you want to display or edit data that doesn't come from an
 object.  One way to do this is to pass the data to setUpWidgets.
@@ -1431,7 +1434,7 @@ our form fields:
 Note that we didn't get data from the request because we are using all
 display widgets.
 
-Passing `ignore_request=True` to the `setUpWidgets` function ignores
+Passing ``ignore_request=True`` to the `setUpWidgets` function ignores
 the request for all values passed in the data dictionary, in order to
 help with redrawing a form after a successful action handler.  We'll
 fake that quickly by forcing ignore_request to be `True`.
@@ -1461,7 +1464,7 @@ fake that quickly by forcing ignore_request to be `True`.
 
 
 Specifying Custom Widgets
--------------------------
+=========================
 
 It is possible to use custom widgets for specific fields.  This can be
 done for a variety of reasons, but the provided mechanism should work
@@ -1482,7 +1485,7 @@ Let's create a simple custom widget to use in our demonstration::
     ...         return '<span class="iso-datetime">2005-05-04</span>'
 
 To set the custom widget factory for a field, assign to the
-`custom_widget` attribute of the form field object::
+``custom_widget`` attribute of the form field object::
 
     >>> class MyForm(form.Form):
     ...     actions = ()
@@ -1497,7 +1500,7 @@ To set the custom widget factory for a field, assign to the
     <span class="iso-datetime">2005-05-04</span>
 
 Specifying Fields individually
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+------------------------------
 
 All of the previous examples set up fields as collections.  We can
 also set up forms individually and pass them to the Fields
@@ -1516,13 +1519,13 @@ written more simply as:
     <span class="iso-datetime">2005-05-04</span>
 
 Computing default values
-~~~~~~~~~~~~~~~~~~~~~~~~
+------------------------
 
 We saw earlier that we could provide initial widget data by passing a
 dictionary to setUpWidgets.  We can also supply a function or method
 name when we set up form fields.
 
-We might like to include the `now` field in our forms.  We can provide
+We might like to include the ``now`` field in our forms.  We can provide
 a function for getting the needed initial value:
 
     >>> import datetime
@@ -1585,12 +1588,12 @@ Note that the function passed must take a form as an argument.  The
 form base classes always pass the form to `setUpWidgets`.
 
 Advanced Usage Hints
---------------------
+====================
 
 This section documents patterns for advanced usage of the formlib package.
 
 Multiple button groups
-~~~~~~~~~~~~~~~~~~~~~~
+----------------------
 
 Multiple button groups can be accomplished many ways, but the way we've found
 that reuses the most code is the following:
@@ -1614,22 +1617,24 @@ that reuses the most code is the following:
     ...
 
 The template then can render the button groups separately--something like the
-following, for instance:
+following, for instance::
 
     <input tal:repeat="action view/primary_actions"
        tal:replace="structure action/render"
        />
 
-and
+and::
 
     <input tal:repeat="action view/secondary_actions"
        tal:replace="structure action/render"
        />
 
-But the form machinery can still find the correct button. # TODO: demo
+But the form machinery can still find the correct button.
+
+.. # TODO: demo the above
 
 Dividing display of widget errors and invariant errors
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+------------------------------------------------------
 
 Even though the form machinery only has a single errors attribute, if designers
 wish to render widget errors differently than invariant errors, they can be
@@ -1638,11 +1643,11 @@ all widget errors should implement zope.formlib.interfaces.IWidgetInputError,
 and invariant errors shouldn't, because they don't come from a widget.
 Therefore, a simple division such as the following should suffice.
 
-# TODO
+.. # TODO
 
 
 Omitting the form prefix
-~~~~~~~~~~~~~~~~~~~~~~~~
+------------------------
 
 For certain use cases (e.g. forms that post data to a different server whose
 software you do not control) it is important to be able to generate forms
@@ -1741,11 +1746,11 @@ It is also possible to keep the form prefix and just suppress the 'actions' pref
            value="Button" class="button" />
 
 Additional Cases
-----------------
+================
 
 
 Automatic Context Adaptation
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+----------------------------
 
 As you may know already, the formlib will automatically adapt the context to
 find a widget and data for a particular field. In an early version of
@@ -1808,7 +1813,7 @@ Here are some more places where the behavior was incorrect:
 
 
 Event descriptions
-~~~~~~~~~~~~~~~~~~
+------------------
 
 The ObjectModifiedEvent can be annotated with descriptions about the involved
 schemas and fields. The formlib provides these annotations with the help of the
@@ -1841,7 +1846,7 @@ Cleanup:
     >>> zope.event.subscribers.remove(eventLog)
 
 Actions that cause a redirect
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-----------------------------
 
 When an action causes a redirect, the following `render` phase is omitted as
 the result will not be displayed anyway. This is both a performance
@@ -1876,7 +1881,7 @@ information.
     render was called
 
 Prevent form submit for GET requests
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+------------------------------------
 
 It can be useful to only accept form submits over POST requests. This, for
 example, prevents replaying data-modifying actions when reloading a page in a
@@ -1975,14 +1980,15 @@ methods are accepted::
     Action: handle {'title': 'Submitted Title'}
 
 Prevent Cross-site Request Forgery (CSRF) attacks
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-See also: http://en.wikipedia.org/wiki/Cross-site_request_forgery.
+-------------------------------------------------
 
-The CSRF protection in zope.formlib assumes the attacker cannot get hold of
-information stored in a cookie that is send to the domain handling the form
-submit. zope.formlib verifies that the token as sent with the cookie is
-identical to the value as sent with the form (as a hidden input field).
+The `cross-site request forgery
+<https://en.wikipedia.org/wiki/Cross-site_request_forgery.>`_
+protection in zope.formlib assumes the attacker cannot get hold of
+information stored in a cookie that is send to the domain handling the
+form submit. zope.formlib verifies that the token as sent with the
+cookie is identical to the value as sent with the form (as a hidden
+input field).
 
 zope.formlib will set a random token in the cookie when first accessing the
 form. Any subsequent form rendering and submit handling will use the token
@@ -2008,8 +2014,8 @@ Issues to research:
 * Tests for applications that use form components with CSRF protection
   enabled, is cumbersome. Can we help that somehow?
 
-* Is using os.urandom() for generating a token sufficient *and*
-  available cross-platform? Could uuid.uuid4() be an alternative?
+* Is using `os.urandom` for generating a token sufficient *and*
+  available cross-platform? Could `uuid.uuid4` be an alternative?
 
 When first visting a form, a CSRF token will be set in the cookie::
 
@@ -2180,7 +2186,7 @@ We can indeed submit data to the forms::
     >>> _ = multi()
     Action: handle in Form Two
 
-There is a view for the InvalidCSRFTokenError::
+There is a view for the `.InvalidCSRFTokenError`::
 
     >>> from zope.component import getMultiAdapter
     >>> from zope.formlib.interfaces import InvalidCSRFTokenError
diff --git a/src/zope/formlib/interfaces.py b/src/zope/formlib/interfaces.py
index 2d829ed..78d40ab 100644
--- a/src/zope/formlib/interfaces.py
+++ b/src/zope/formlib/interfaces.py
@@ -382,7 +382,7 @@ class ISubPage(Interface):
 
 
 class IFormAPI(Interface):
-    """API to facilitate creating forms, provided by zope.formlib.form
+    """API to facilitate creating forms, provided by `zope.formlib.form`
     """
 
     def Field(schema_field, **options):
@@ -420,7 +420,7 @@ class IFormAPI(Interface):
 
 
     def Fields(*arguments, **options):
-        """Create form-fields collection (IFormFields)
+        """Create form-fields collection (`IFormFields`)
 
         Creates a form-field collection from a collection of:
 
@@ -651,13 +651,11 @@ class IFormAPI(Interface):
         that the options don't include the success option.
 
         The function is designed to be used as a decorator (Python 2.4
-        and later), as in:
-
-        @action("Edit")
-        def handle_edit(self, action, data):
-            ...
-
+        and later), as in::
 
+            @action("Edit")
+            def handle_edit(self, action, data):
+                ...
         """
 
     def validate(form, actions, form_prefix, data, default_validate=None):
@@ -779,7 +777,7 @@ class IFormBaseCustomization(ISubPage, IBrowserPage):
 
 
 class IFormFields(Interface):
-    """A colection of form fields (IFormField objects)
+    """A colection of form fields (`IFormField` objects)
     """
 
     def __len__():
@@ -811,7 +809,7 @@ class IFormFields(Interface):
         """
 
     def __add__(form_fields):
-        """Add two form fields collections (IFormFields)
+        """Add two form fields collections (`IFormFields`)
 
         Return a new IFormFields that is the concatination of the two
         IFormFields.
@@ -820,8 +818,8 @@ class IFormFields(Interface):
     def select(*names):
         """Select fields with given names in order
 
-        Return a new IFormFields that is a selection from the original
-        IFormFields that has the named fields in the specified order.
+        Return a new `IFormFields` that is a selection from the original
+        `IFormFields` that has the named fields in the specified order.
         """
 
     def omit(*names):
diff --git a/src/zope/formlib/objectwidget.py b/src/zope/formlib/objectwidget.py
index b49b0df..98c9f27 100644
--- a/src/zope/formlib/objectwidget.py
+++ b/src/zope/formlib/objectwidget.py
@@ -147,6 +147,7 @@ class ObjectWidget(BrowserWidget, InputWidget):
 
 
     def applyChanges(self, content):
+        """See `zope.formlib.interfaces.IInputWidget.applyChanges`"""
         field = self.context
 
         # create our new object value
diff --git a/src/zope/formlib/objectwidget.txt b/src/zope/formlib/objectwidget.rst
similarity index 80%
rename from src/zope/formlib/objectwidget.txt
rename to src/zope/formlib/objectwidget.rst
index 510d400..0b0cc8e 100644
--- a/src/zope/formlib/objectwidget.txt
+++ b/src/zope/formlib/objectwidget.rst
@@ -2,7 +2,7 @@
 Object Widget
 =============
 
-The following example shows a Family with Mother and Father.
+The following example shows a ``Family`` with ``Mother`` and ``Father``.
 First define the interface for a person:
 
   >>> from zope.interface import Interface, implementer
@@ -35,8 +35,9 @@ Let's define the interface family:
   ...                     required=False,
   ...                     schema=IPerson)
 
-Let's define the class family with FieldProperty's mother and father
-FieldProperty validate the values if they get added:
+Let's define the class ``Family`` using
+`zope.schema.fieldproperty.FieldProperty` for ``mother`` and ``father``.
+``FieldProperty`` instances validate the values if they get added:
 
   >>> from zope.schema.fieldproperty import FieldProperty
 
@@ -50,7 +51,7 @@ FieldProperty validate the values if they get added:
   ...         self.mother = mother
   ...         self.father = father
 
-Let's make an instance of Family with None attributes:
+Let's make an instance of Family with `None` attributes:
 
   >>> family = Family()
   >>> bool(family.mother == None)
@@ -77,7 +78,7 @@ Let's define a dummy class which doesn't implements IPerson:
   ...     def __init__(self, name=''):
   ...         self.name = name
 
-Raise a SchemaNotProvided exception if we add a Dummy instance to a Family
+Raise a `zope.schema.interfaces.SchemaNotProvided` exception if we add a Dummy instance to a Family
 object:
 
   >>> foo = Dummy('foo')
@@ -97,7 +98,7 @@ Now let's setup a enviroment for use the widget like in a real application:
   >>> from zope.formlib.widgets import ObjectWidget
   >>> from zope.formlib.interfaces import IInputWidget
 
-Register the TextLine widget used in the IPerson interface for the field 'name'.
+Register the `zope.schema.TextLine` widget used in the IPerson interface for the field 'name'.
 
   >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
   >>> from zope.component import provideAdapter
@@ -116,22 +117,21 @@ instance on the family object:
   >>> family.mother.name
   u'Margrith'
 
-Now let's initialize a ObjectWidget with the right attributes:
+Now let's initialize a `.ObjectWidget` with the right attributes:
 
   >>> mother_field = IFamily['mother']
   >>> factory = Person
   >>> widget = ObjectWidget(mother_field, request, factory)
 
-Now comes the magic. Apply changes means we force the ObjectWidget to read
-the request, extract the value and save it on the content. The ObjectWidget
+Now comes the magic. Apply changes means we force the `.ObjectWidget` to read
+the request, extract the value and save it on the content. The `.ObjectWidget`
 instance uses a real Person class (factory) for add the value. The value is
-temporary stored in this factory class. The ObjectWidget reads the value from
+temporary stored in this factory class. The `.ObjectWidget` reads the value from
 this factory and set it to the attribute 'name' of the instance mother
 (The object mother is already there). If we don't have an instance mother
 already stored in the family object, the factory instance will be stored
 directly to the family attribute mother. For more information see the method
-'applyChanges()' in the interface
-zope.formlib.objectwidget.ObjectWidget.
+`zope.formlib.objectwidget.ObjectWidget.applyChanges`.
 
   >>> widget.applyChanges(family)
   True
@@ -145,4 +145,4 @@ Test the updated mother's name value on the object family:
   True
 
 So, now you know my mothers and fathers name. I hope it's also clear how to
-use the Object field and the ObjectWidget.
+use the `zope.schema.Object` field and the `.ObjectWidget`.
diff --git a/src/zope/formlib/source.txt b/src/zope/formlib/source.rst
similarity index 95%
rename from src/zope/formlib/source.txt
rename to src/zope/formlib/source.rst
index 012a646..4b25e10 100644
--- a/src/zope/formlib/source.txt
+++ b/src/zope/formlib/source.rst
@@ -3,7 +3,7 @@ Source Widgets
 ==============
 
 Sources are objects that represent sets of values from which one might choose
-and are used with Choice schema fields. Source widgets currently fall into two
+and are used with `zope.schema.Choice` schema fields. Source widgets currently fall into two
 categories:
 
 - widgets for iterable sources
@@ -23,7 +23,7 @@ All of our examples will be using the component architecture::
   >>> import zope.component
   >>> import zope.schema
 
-This `ITerms` implementation can be used for the sources involved in
+This ``ITerms`` implementation can be used for the sources involved in
 our tests::
 
   >>> import base64
@@ -67,10 +67,11 @@ We'll also need request objects::
 Iterable Source Widgets
 =======================
 
-Iterable sources are expected to be simpler than queriable sources, so they
-represent a good place to start.  The most important aspect of iterable sources
-for widgets is that it's actually possible to enumerate all the values from the
-source.  This allows each possible value to be listed in a  <select> form field.
+Iterable sources are expected to be simpler than queriable sources, so
+they represent a good place to start. The most important aspect of
+iterable sources for widgets is that it's actually possible to
+enumerate all the values from the source. This allows each possible
+value to be listed in a ``<select>`` form field.
 
 Let's start with a simple example.  We have a very trivial source,
 which is basically a list::
@@ -79,7 +80,7 @@ which is basically a list::
   ... class SourceList(list):
   ...     pass
 
-We need to register our `ITerms` view::
+We need to register our ``ITerms`` view::
 
   >>> zope.component.provideAdapter(
   ...     ListTerms,
@@ -166,7 +167,7 @@ selected::
   <input name="field.dog-empty-marker" type="hidden" value="1" />
   </div>
 
-Dropdown widgets are achieved with SourceDropdownWidget, which simply
+Dropdown widgets are achieved with `.SourceDropdownWidget`, which simply
 generates a selection list of size 1::
 
   >>> request = TestRequest()
@@ -178,8 +179,8 @@ generates a selection list of size 1::
   <select id="field.dog" name="field.dog" size="1" >
   <option selected="selected" value="">(nothing selected)</option>...
 
-An alternative to SourceSelectWidget for small numbers of items is
-SourceRadioWidget that provides a radio button group for the items::
+An alternative to `.SourceSelectWidget` for small numbers of items is
+`.SourceRadioWidget` that provides a radio button group for the items::
 
   >>> request = TestRequest()
   >>> widget = zope.formlib.source.SourceRadioWidget(
@@ -227,13 +228,13 @@ We'll select an item by setting the appropriate fields in the request::
   </div>
 
 For list-valued fields with items chosen from iterable sources, there are the
-SourceMultiSelectWidget and SourceOrderedMultiSelectWidget widgets. The latter
+`.SourceMultiSelectWidget` and `.SourceOrderedMultiSelectWidget` widgets. The latter
 widget includes support for re-ording the list items.
-SourceOrderedMultiSelectWidget is configured as the default widget for lists of
+`.SourceOrderedMultiSelectWidget` is configured as the default widget for lists of
 choices.
 
 If you don't need ordering support through the web UI, then you can use
-the simpler SourceMultiSelectWidget::
+the simpler `.SourceMultiSelectWidget`::
 
   >>> dogSource = SourceList([
   ...     u'spot', u'bowser', u'prince', u'duchess', u'lassie'])
@@ -302,7 +303,7 @@ Finally, what does the widget look like now::
   </div>
 
 
-An alternative for small numbers of items is to use SourceMultiCheckBoxWidget::
+An alternative for small numbers of items is to use `.SourceMultiCheckBoxWidget`::
 
   >>> request = TestRequest()
   >>> widget = zope.formlib.source.SourceMultiCheckBoxWidget(
@@ -377,7 +378,7 @@ Finally, what does the widget look like now::
   </div>
 
 
-For list ordering support, use SourceOrderedMultiSelectWidget::
+For list ordering support, use `.SourceOrderedMultiSelectWidget`::
 
   >>> request = TestRequest()
   >>> widget = zope.formlib.source.SourceOrderedMultiSelectWidget(
@@ -427,7 +428,7 @@ Select two items::
   ['spot', 'lassie']
 
 
-For set-valued fields, use SourceMultiSelectSetWidget::
+For set-valued fields, use `.SourceMultiSelectSetWidget`::
 
   >>> dogSet = zope.schema.Set(
   ...     __name__ = 'dogSet',
@@ -509,7 +510,7 @@ The default widgets for selecting values from sources use the
 following approach:
 
 - One or more query objects are obtained from the source by adapting the source
-  to `zope.schema.ISourceQueriables`. If no adapter is obtained, then the
+  to `zope.schema.interfaces.ISourceQueriables`. If no adapter is obtained, then the
   source itself is assumed to be queriable.
 
 - For each queriable found, a
@@ -524,13 +525,13 @@ which is basically a list:
   ... class SourceList(list):
   ...     pass
 
-We need to register our `ITerms` view::
+We need to register our ``ITerms`` view::
 
   >>> zope.component.provideAdapter(
   ...     ListTerms,
   ...     (SourceList, zope.publisher.interfaces.browser.IBrowserRequest))
 
-We aren't going to provide an adapter to `ISourceQueriables`, so the source
+We aren't going to provide an adapter to ``ISourceQueriables``, so the source
 itself will be used as it's own queriable.  We need to provide a query view
 for the source::
 
@@ -611,8 +612,10 @@ in the form and by "selecting" the submit button::
   >>> request.form['field.dog.query.string'] = u'o'
   >>> request.form['field.dog.query'] = u'Search'
 
-Because the field is required, a non-selection is not valid. Thus, while the
-widget still hasInput, it will raise an error when you getInputValue::
+Because the field is required, a non-selection is not valid. Thus,
+while the widget still
+`~zope.formlib.interfaces.IInputWidget.hasInput`, it will raise an
+error when you `~zope.formlib.interfaces.IInputWidget.getInputValue`::
 
   >>> widget.hasInput()
   True
@@ -727,7 +730,7 @@ combines multiple sources::
   ...     def getQueriables(self):
   ...         return self.sources
 
-This multi-source implements `ISourceQueriables`. It assumes that the sources
+This multi-source implements ``ISourceQueriables``. It assumes that the sources
 it's given are queriable and just returns the sources as the queryable objects.
 
 We can reuse our terms view::
@@ -1059,7 +1062,7 @@ Using vocabulary-dependent widgets with sources
 
 if you have a widget that uses old-style vocabularies but don't have the time
 to rewrite it for sources, all is not lost! The wrapper
-IterableSourceVocabulary can be used to make sources and ITerms look like a
+`.IterableSourceVocabulary` can be used to make sources and ``ITerms`` look like a
 vocabulary. This allows us to use vocabulary-based widgets with sources
 instead of vocabularies.
 
diff --git a/src/zope/formlib/widgets.txt b/src/zope/formlib/widgets.rst
similarity index 74%
rename from src/zope/formlib/widgets.txt
rename to src/zope/formlib/widgets.rst
index 34cec56..486ebfb 100644
--- a/src/zope/formlib/widgets.txt
+++ b/src/zope/formlib/widgets.rst
@@ -1,11 +1,16 @@
-Browser Widgets
-===============
+=================
+ Browser Widgets
+=================
+
+.. currentmodule:: zope.formlib.interfaces
 
 Formlib defines widgets: views on bound schema fields. Many of these
-are straightforward.  For instance, see the `TextWidget` in
-textwidgets.py, which is a subclass of BrowserWidget in widget.py.  It
-is registered as an `IBrowserRequest` view of an `ITextLine` schema
-field, providing the `IInputWidget` interface::
+are straightforward. For instance, see the `.TextWidget` in
+textwidgets.py, which is a subclass of `.BrowserWidget` in widget.py.
+It is registered as an
+`zope.publisher.interfaces.browser.IBrowserRequest` view of an
+`zope.schema.interfaces.ITextLine` schema field, providing the
+`IInputWidget` interface::
 
   <view
       type="zope.publisher.interfaces.browser.IBrowserRequest"
@@ -16,66 +21,68 @@ field, providing the `IInputWidget` interface::
       />
 
 The widget then receives the field and the request as arguments to the factory
-(i.e., the `TextWidget` class).
+(i.e., the `.TextWidget` class).
 
 Some widgets in formlib extend this pattern. The widget registration
-is extended for `Choice` fields and for the `collection` fields.
+is extended for `zope.schema.Choice` fields and for the ``collection`` fields.
 
 Default Choice Field Widget Registration and Lookup
----------------------------------------------------
+===================================================
 
 All field widgets are obtained by looking up a browser `IInputWidget`
-or `IDisplayWidget` view for the field object.  For `Choice` fields,
+or `IDisplayWidget` view for the field object.  For `zope.schema.Choice` fields,
 the default registered widget defers all of its behavior to the result
 of another lookup: a browser widget view for the field *and* the
 Choice field's vocabulary.
 
-This allows registration of Choice widgets that differ on the basis of the
-vocabulary type.  For example, a widget for a vocabulary of images might have
-a significantly different user interface than a widget for a vocabulary of
-words.  A dynamic vocabulary might implement `IIterableVocabulary` if its
-contents are below a certain length, but not implement the marker "iterable"
-interface if the number of possible values is above the threshhold.
+This allows registration of Choice widgets that differ on the basis of
+the vocabulary type. For example, a widget for a vocabulary of images
+might have a significantly different user interface than a widget for
+a vocabulary of words. A dynamic vocabulary might implement
+`zope.schema.interfaces.IIterableVocabulary` if its contents are below
+a certain length, but not implement the marker "iterable" interface if
+the number of possible values is above the threshhold.
 
 This also means that choice widget factories are called with with an additional
 argument.  Rather than being called with the field and the request as
 arguments, choice widgets receive the field, vocabulary, and request as
 arguments.
 
-Some `Choice` widgets may also need to provide a source interface,
+Some `zope.schema.Choice` widgets may also need to provide a source interface,
 particularly if the vocabulary is too big to iterate over.
 
 Default Collection Field Widget Registration and Lookup
--------------------------------------------------------
+=======================================================
 
 The default configured lookup for collection fields -- List, Tuple, and Set, for
 instance -- begins with the usual lookup for a browser widget view for the
 field object.  This widget defers its display to the result of another lookup:
-a browser widget view registered for the field and the field's `value_type`
+a browser widget view registered for the field and the field's ``value_type``
 (the type of the contained values).  This allows registrations for collection
 widgets that differ on the basis of the members -- a widget for entering a list
 of text strings might differ significantly from a widget for entering a list of
 dates...or even a list of choices, as discussed below.
 
-This registration pattern has three implications that should be highlighted. 
+This registration pattern has three implications that should be highlighted.
 
-* First, collection fields that do not specify a `value_type` probably cannot
+* First, collection fields that do not specify a ``value_type`` probably cannot
   have a reasonable widget.
 
 * Second, collection widgets that wish to be the default widget for a
-  collection with any `value_type` should be registered for the collection
-  field and a generic value_type: the `IField` interface.  Do  not register the
-  generic widget for the collection field only or you will break the lookup
-  behavior as described here.
+  collection with any ``value_type`` should be registered for the
+  collection field and a generic value_type: the
+  `zope.schema.interfaces.IField` interface. Do not register the
+  generic widget for the collection field only or you will break the
+  lookup behavior as described here.
 
 * Third, like choice widget factories, sequence widget factories (classes or
   functions) take three arguments.  Typical sequence widgets receive the
-  field, the `value_type`, and the request as arguments.
+  field, the ``value_type``, and the request as arguments.
 
 Collections of Choices
-~~~~~~~~~~~~~~~~~~~~~~
+----------------------
 
-If a collection field's `value_type` is a `Choice` field, the second widget
+If a collection field's ``value_type`` is a `zope.schema.Choice` field, the second widget
 again defers its behavior, this time to a third lookup based on the collection
 field and the choice's vocabulary.  This means that a widget for a list of
 large image choices can be different than a widget for a list of small image
@@ -88,13 +95,13 @@ a "unique" or "lenient" token depending on the field's value, but this is not
 enabled in the default Zope 3 configuration.
 
 Registering Widgets for a New Collection Field Type
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+---------------------------------------------------
 
 Because of this lookup pattern, basic widget registrations for new field types
 must follow a recipe.  For example, a developer may introduce a new Bag field
 type for simple shopping cart functionality and wishes to add widgets for it
 within the default Zope 3 collection widget registration.  The bag widgets
-should be registered something like this. 
+should be registered something like this.
 
 The only hard requirement is that the developer must register the bag + choice
 widget: the widget is just the factory for the third dispatch as described
@@ -120,7 +127,7 @@ above, so the developer can use the already implemented widgets listed below::
 
 Beyond this, the developer may also have a generic bag widget she wishes to
 register.  This might look something like this, assuming there's a
-`BagSequenceWidget` available in this package::
+``BagSequenceWidget`` available in this package::
 
   <view
       type="zope.publisher.interfaces.browser.IBrowserRequest"
@@ -132,8 +139,8 @@ register.  This might look something like this, assuming there's a
       />
 
 Then any widgets for the bag and a vocabulary would be registered according to
-this general pattern, in which `IIterableVocabulary` would be the interface of
-any appropriate vocabulary and `BagWidget` is some appropriate widget::
+this general pattern, in which `zope.schema.interfaces.IIterableVocabulary` would be the interface of
+any appropriate vocabulary and ``BagWidget`` is some appropriate widget::
 
   <view
       type="zope.publisher.interfaces.browser.IBrowserRequest"
@@ -146,7 +153,7 @@ any appropriate vocabulary and `BagWidget` is some appropriate widget::
 
 
 Choice widgets and the missing value
-------------------------------------
+====================================
 
 Choice widgets for a non-required field include a "no value" item to allow for
 not selecting any value at all. This value used to be omitted for required
@@ -162,7 +169,7 @@ Starting with version zope.app.form 3.6.0, dropdown widgets for
 required fields display a "no value" item even for required fields if
 an arbitrary value would have to be selected by the widget otherwise.
 
-To switch the old behaviour back on for backwards compatibility, do
+To switch the old behaviour back on for backwards compatibility, do::
 
   zope.formlib.itemswidgets.EXPLICIT_EMPTY_SELECTION = False
 
diff --git a/tox.ini b/tox.ini
index d405848..ee51bae 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,9 +1,15 @@
 [tox]
 envlist =
-    py27,py34,py35,py36,pypy,pypy3
+    py27,py34,py35,py36,pypy,pypy3,docs
 
 [testenv]
 commands =
     zope-testrunner --test-path=src
 deps =
     .[test]
+
+[testenv:docs]
+commands =
+    sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html
+deps =
+    .[docs]

Move docs to RTD
I was trying to cross-reference this project and discovered I couldn't.
diff --git a/src/zope/formlib/tests/test_formlib.py b/src/zope/formlib/tests/test_formlib.py
index ef4017c..3b1b029 100644
--- a/src/zope/formlib/tests/test_formlib.py
+++ b/src/zope/formlib/tests/test_formlib.py
@@ -759,7 +759,7 @@ def test_suite():
     ])
     return unittest.TestSuite((
         doctest.DocFileSuite(
-            '../errors.txt',
+            '../errors.rst',
             setUp=formSetUp, tearDown=tearDown, checker=checker,
             optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
             ),
@@ -770,7 +770,7 @@ def test_suite():
         #     optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
         #     ),
         doctest.DocFileSuite(
-            '../form.txt',
+            '../form.rst',
             setUp=formSetUp, tearDown=tearDown,
             optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
             checker=checker
diff --git a/src/zope/formlib/tests/test_objectwidget.py b/src/zope/formlib/tests/test_objectwidget.py
index f3031c8..70ef285 100644
--- a/src/zope/formlib/tests/test_objectwidget.py
+++ b/src/zope/formlib/tests/test_objectwidget.py
@@ -145,7 +145,7 @@ def test_suite():
     return unittest.TestSuite((
         unittest.makeSuite(ObjectWidgetTest),
         doctest.DocFileSuite(
-                '../objectwidget.txt',
+                '../objectwidget.rst',
                 setUp=testing.setUp, tearDown=testing.tearDown,
                 checker=checker),
         doctest.DocTestSuite(),
diff --git a/src/zope/formlib/tests/test_source.py b/src/zope/formlib/tests/test_source.py
index 5a9bdec..05634e9 100644
--- a/src/zope/formlib/tests/test_source.py
+++ b/src/zope/formlib/tests/test_source.py
@@ -19,10 +19,9 @@ from .support import checker
 def test_suite():
     import doctest
     return doctest.DocFileSuite(
-        '../source.txt',
+        '../source.rst',
         setUp=testing.setUp, tearDown=testing.tearDown, checker=checker)
 
 if __name__ == '__main__':
     import unittest
     unittest.main(defaultTest='test_suite')
-

pip install -e .[test]
parse_log_pytest
pytest
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
multipart==1.1.0
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
python-gettext==4.1
pytz==2025.2
six==1.17.0
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
transaction==3.1.0
typing_extensions @ file:///opt/conda/conda-bld/typing_extensions_1647553014482/work
zipp @ file:///tmp/build/80754af9/zipp_1633618647012/work
zope.browser==2.4
zope.browserpage==4.4.0
zope.component==5.1.0
zope.configuration==4.4.1
zope.contenttype==4.6
zope.datetime==4.3.0
zope.deprecation==4.4.0
zope.event==4.6
zope.exceptions==4.6
-e git+https://github.com/zopefoundation/zope.formlib.git@0813545c8c2153cff0577679fec6bb8f00332a8d#egg=zope.formlib
zope.hookable==5.4
zope.i18n==4.9.0
zope.i18nmessageid==5.1.1
zope.interface==5.5.2
zope.lifecycleevent==4.4
zope.location==4.3
zope.pagetemplate==4.6.0
zope.proxy==4.6.1
zope.publisher==6.1.0
zope.schema==6.2.1
zope.security==5.8
zope.tal==4.5
zope.tales==5.2
zope.testing==5.0.1
zope.testrunner==5.6
zope.traversing==4.4.1

name: zope.formlib
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:
      - multipart==1.1.0
      - python-gettext==4.1
      - pytz==2025.2
      - six==1.17.0
      - transaction==3.1.0
      - zope-browser==2.4
      - zope-browserpage==4.4.0
      - zope-component==5.1.0
      - zope-configuration==4.4.1
      - zope-contenttype==4.6
      - zope-datetime==4.3.0
      - zope-deprecation==4.4.0
      - zope-event==4.6
      - zope-exceptions==4.6
      - zope-hookable==5.4
      - zope-i18n==4.9.0
      - zope-i18nmessageid==5.1.1
      - zope-interface==5.5.2
      - zope-lifecycleevent==4.4
      - zope-location==4.3
      - zope-pagetemplate==4.6.0
      - zope-proxy==4.6.1
      - zope-publisher==6.1.0
      - zope-schema==6.2.1
      - zope-security==5.8
      - zope-tal==4.5
      - zope-tales==5.2
      - zope-testing==5.0.1
      - zope-testrunner==5.6
      - zope-traversing==4.4.1
prefix: /opt/conda/envs/zope.formlib

src/zope/formlib/tests/test_formlib.py::test_suite src/zope/formlib/tests/test_objectwidget.py::test_suite src/zope/formlib/tests/test_source.py::test_suite
src/zope/formlib/tests/test_formlib.py::test_error_handling src/zope/formlib/tests/test_formlib.py::test_form_template_i18n src/zope/formlib/tests/test_formlib.py::test_setUpWidgets_prefix src/zope/formlib/tests/test_objectwidget.py::ObjectWidgetTest::test_applyChanges src/zope/formlib/tests/test_objectwidget.py::ObjectWidgetTest::test_applyChangesNoChange src/zope/formlib/tests/test_objectwidget.py::ObjectWidgetTest::test_error
docs/errors.rst MANIFEST.in docs/objectwidget.rst src/zope/formlib/bugs.txt .gitignore docs/api/index.rst docs/index.rst docs/conf.py src/zope/formlib/form.py src/zope/formlib/form.txt src/zope/formlib/source.txt tox.ini docs/source.rst src/zope/formlib/errors.txt README.rst src/zope/formlib/widgets.txt setup.py src/zope/formlib/objectwidget.txt CHANGES.rst src/zope/formlib/TODO.txt src/zope/formlib/interfaces.py docs/widgets.rst rtd-requirements.txt docs/form.rst docs/changelog.rst src/zope/formlib/objectwidget.py
docs/errors.rst src/zope/formlib/widgets.rst MANIFEST.in docs/objectwidget.rst .gitignore docs/api/index.rst docs/index.rst docs/conf.py src/zope/formlib/TODO.rst src/zope/formlib/objectwidget.rst src/zope/formlib/form.py src/zope/formlib/source.rst tox.ini docs/source.rst README.rst src/zope/formlib/errors.rst setup.py CHANGES.rst src/zope/formlib/interfaces.py docs/widgets.rst src/zope/formlib/bugs.rst rtd-requirements.txt docs/form.rst docs/changelog.rst src/zope/formlib/form.rst src/zope/formlib/objectwidget.py