summaryrefslogtreecommitdiffstats
path: root/doc/development/theming.rst
blob: 538dcafa6e577bef7b091d7188c6381a9faf85a1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
HTML theme development
======================

.. versionadded:: 0.6

.. note::

   This document provides information about creating your own theme. If you
   simply wish to use a pre-existing HTML themes, refer to
   :doc:`/usage/theming`.

Sphinx supports changing the appearance of its HTML output via *themes*.  A
theme is a collection of HTML templates, stylesheet(s) and other static files.
Additionally, it has a configuration file which specifies from which theme to
inherit, which highlighting style to use, and what options exist for customizing
the theme's look and feel.

Themes are meant to be project-unaware, so they can be used for different
projects without change.

.. note::

   See :ref:`dev-extensions` for more information that may
   be helpful in developing themes.


Creating themes
---------------

Themes take the form of either a directory or a zipfile (whose name is the
theme name), containing the following:

* A :file:`theme.conf` file.
* HTML templates, if needed.
* A ``static/`` directory containing any static files that will be copied to the
  output static directory on build.  These can be images, styles, script files.

The :file:`theme.conf` file is in INI format [1]_ (readable by the standard
Python :mod:`configparser` module) and has the following structure:

.. sourcecode:: ini

    [theme]
    inherit = base theme
    stylesheet = main CSS name
    pygments_style = stylename
    sidebars = localtoc.html, relations.html, sourcelink.html, searchbox.html

    [options]
    variable = default value

* The **inherit** setting gives the name of a "base theme", or ``none``.  The
  base theme will be used to locate missing templates (most themes will not have
  to supply most templates if they use ``basic`` as the base theme), its options
  will be inherited, and all of its static files will be used as well. If you
  want to also inherit the stylesheet, include it via CSS' ``@import`` in your
  own.

* The **stylesheet** setting gives a list of CSS filenames separated commas which
  will be referenced in the HTML header.  You can also use CSS' ``@import``
  technique to include one from the other, or use a custom HTML template that
  adds ``<link rel="stylesheet">`` tags as necessary.  Setting the
  :confval:`html_style` config value will override this setting.

* The **pygments_style** setting gives the name of a Pygments style to use for
  highlighting.  This can be overridden by the user in the
  :confval:`pygments_style` config value.

* The **pygments_dark_style** setting gives the name of a Pygments style to use
  for highlighting when the CSS media query ``(prefers-color-scheme: dark)``
  evaluates to true. It is injected into the page using
  :meth:`~sphinx.application.Sphinx.add_css_file()`.

* The **sidebars** setting gives the comma separated list of sidebar templates
  for constructing sidebars.  This can be overridden by the user in the
  :confval:`html_sidebars` config value.

* The **options** section contains pairs of variable names and default values.
  These options can be overridden by the user in :confval:`html_theme_options`
  and are accessible from all templates as ``theme_<name>``.

.. versionadded:: 1.7
   sidebar settings

.. versionchanged:: 5.1

   The stylesheet setting accepts multiple CSS filenames

.. _distribute-your-theme:

Distribute your theme as a Python package
-----------------------------------------

As a way to distribute your theme, you can use a Python package.  This makes it
easier for users to set up your theme.

To distribute your theme as a Python package, please define an entry point
called ``sphinx.html_themes`` in your ``setup.py`` file, and write a ``setup()``
function to register your themes using ``add_html_theme()`` API in it::

    # 'setup.py'
    setup(
        ...
        entry_points = {
            'sphinx.html_themes': [
                'name_of_theme = your_package',
            ]
        },
        ...
    )

    # 'your_package.py'
    from os import path

    def setup(app):
        app.add_html_theme('name_of_theme', path.abspath(path.dirname(__file__)))

If your theme package contains two or more themes, please call
``add_html_theme()`` twice or more.

.. versionadded:: 1.2
   'sphinx_themes' entry_points feature.

.. deprecated:: 1.6
   ``sphinx_themes`` entry_points has been deprecated.

.. versionadded:: 1.6
   ``sphinx.html_themes`` entry_points feature.


Templating
----------

The :doc:`guide to templating <templating>` is helpful if you want to write your
own templates.  What is important to keep in mind is the order in which Sphinx
searches for templates:

* First, in the user's ``templates_path`` directories.
* Then, in the selected theme.
* Then, in its base theme, its base's base theme, etc.

When extending a template in the base theme with the same name, use the theme
name as an explicit directory: ``{% extends "basic/layout.html" %}``.  From a
user ``templates_path`` template, you can still use the "exclamation mark"
syntax as described in the templating document.


.. _theming-static-templates:

Static templates
~~~~~~~~~~~~~~~~

Since theme options are meant for the user to configure a theme more easily,
without having to write a custom stylesheet, it is necessary to be able to
template static files as well as HTML files.  Therefore, Sphinx supports
so-called "static templates", like this:

If the name of a file in the ``static/`` directory of a theme (or in the user's
static path, for that matter) ends with ``_t``, it will be processed by the
template engine.  The ``_t`` will be left from the final file name.  For
example, the *classic* theme has a file ``static/classic.css_t`` which uses
templating to put the color options into the stylesheet.  When a documentation
project is built with the classic theme, the output directory will contain a
``_static/classic.css`` file where all template tags have been processed.


Use custom page metadata in HTML templates
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Any key / value pairs in :doc:`field lists </usage/restructuredtext/field-lists>`
that are placed *before* the page's title will be available to the Jinja
template when building the page within the :data:`meta` attribute. For example,
if a page had the following text before its first title:

.. code-block:: rst

    :mykey: My value

    My first title
    --------------

Then it could be accessed within a Jinja template like so:

.. code-block:: jinja

    {%- if meta is mapping %}
        {{ meta.get("mykey") }}
    {%- endif %}

Note the check that ``meta`` is a dictionary ("mapping" in Jinja
terminology) to ensure that using it in this way is valid.


Defining custom template functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Sometimes it is useful to define your own function in Python that you wish to
then use in a template. For example, if you'd like to insert a template value
with logic that depends on the user's configuration in the project, or if you'd
like to include non-trivial checks and provide friendly error messages for
incorrect configuration in the template.

To define your own template function, you'll need to define two functions
inside your module:

* A **page context event handler** (or **registration**) function. This is
  connected to the :class:`.Sphinx` application via an event callback.
* A **template function** that you will use in your Jinja template.

First, define the registration function, which accepts the arguments for
:event:`html-page-context`.

Within the registration function, define the template function that you'd like to
use within Jinja. The template function should return a string or Python objects
(lists, dictionaries) with strings inside that Jinja uses in the templating process

.. note::

    The template function will have access to all of the variables that
    are passed to the registration function.

At the end of the registration function, add the template function to the
Sphinx application's context with ``context['template_func'] = template_func``.

Finally, in your extension's ``setup()`` function, add your registration
function as a callback for :event:`html-page-context`.

.. code-block:: python

   # The registration function
    def setup_my_func(app, pagename, templatename, context, doctree):
        # The template function
        def my_func(mystring):
            return "Your string is %s" % mystring
        # Add it to the page's context
        context['my_func'] = my_func

    # Your extension's setup function
    def setup(app):
        app.connect("html-page-context", setup_my_func)

Now, you will have access to this function in jinja like so:

.. code-block:: jinja

   <div>
   {{ my_func("some string") }}
   </div>


Add your own static files to the build assets
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

By default, Sphinx copies static files on the ``static/`` directory of the template
directory.  However, if your package needs to place static files outside of the
``static/`` directory for some reasons, you need to copy them to the ``_static/``
directory of HTML outputs manually at the build via an event hook.  Here is an
example of code to accomplish this:

.. code-block:: python

   from os import path
   from sphinx.util.fileutil import copy_asset_file

   def copy_custom_files(app, exc):
       if app.builder.format == 'html' and not exc:
           staticdir = path.join(app.builder.outdir, '_static')
           copy_asset_file('path/to/myextension/_static/myjsfile.js', staticdir)

   def setup(app):
       app.connect('build-finished', copy_custom_files)


Inject JavaScript based on user configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If your extension makes use of JavaScript, it can be useful to allow users
to control its behavior using their Sphinx configuration. However, this can
be difficult to do if your JavaScript comes in the form of a static library
(which will not be built with Jinja).

There are two ways to inject variables into the JavaScript space based on user
configuration.

First, you may append ``_t`` to the end of any static files included with your
extension. This will cause Sphinx to process these files with the templating
engine, allowing you to embed variables and control behavior.

For example, the following JavaScript structure:

.. code-block:: none

   mymodule/
   ├── _static
   │   └── myjsfile.js_t
   └── mymodule.py

Will result in the following static file placed in your HTML's build output:

.. code-block:: none

   _build/
   └── html
       └── _static
           └── myjsfile.js

See :ref:`theming-static-templates` for more information.

Second, you may use the :meth:`.Sphinx.add_js_file` method without pointing it
to a file. Normally, this method is used to insert a new JavaScript file
into your site. However, if you do *not* pass a file path, but instead pass
a string to the "body" argument, then this text will be inserted as JavaScript
into your site's head. This allows you to insert variables into your project's
JavaScript from Python.

For example, the following code will read in a user-configured value and then
insert this value as a JavaScript variable, which your extension's JavaScript
code may use:

.. code-block:: python

    # This function reads in a variable and inserts it into JavaScript
    def add_js_variable(app):
        # This is a configuration that you've specified for users in `conf.py`
        js_variable = app.config['my_javascript_variable']
        js_text = "var my_variable = '%s';" % js_variable
        app.add_js_file(None, body=js_text)
    # We connect this function to the step after the builder is initialized
    def setup(app):
        # Tell Sphinx about this configuration variable
        app.add_config_value('my_javascript_variable', 0, 'html')
        # Run the function after the builder is initialized
        app.connect('builder-inited', add_js_variable)

As a result, in your theme you can use code that depends on the presence of
this variable. Users can control the variable's value by defining it in their
:file:`conf.py` file.


.. [1] It is not an executable Python file, as opposed to :file:`conf.py`,
       because that would pose an unnecessary security risk if themes are
       shared.