logo

LaTeX Tagging Project

Tagging Heading Structures

Generic headings

Packages

KOMA

Memoir

Current work on the LaTeX Tagging project is centered around providing a framework for declaring accessible section heading structures via LaTeX Templates. This will form the basis for providing updates to existing document classes, either directly or via emulations of existing packages such as titlesec.

This page allows selected LaTeX tagging examples to be processed with LaTeX to produce PDF which may then be viewed or validated to the PDF/UA-2 accessibility standard.

Validation is provided by the open source veraPDF validator hosted at texlive.net and by the LaTeX project show-pdf-tags validation hosted on the same server. As a convenience, a link is provided to open the generated PDF at ngPDF. ngPDF is provided free of charge by its authors, although is not open source, it is not a necessary component of the validation services shown on this page.

The tagging status page lists many test files showing the tagging status of over a thousand LaTeX packages. A small sample are highlighted here, corresponding to current work items.

The examples are presented in an online editor to allow you to experiment and change the examples. The resulting TeX files are not stored and all generated PDF are deleted from the server after an hour. The current public release of lualatex-dev from TeXLive is used to process all examples. Development code from the LaTeX git repositories is not used here.


We are grateful that a part of this project is funded through NGI0 Commons Fund, a fund established by NLnet with financial support from the European Commission’s Next Generation Internet program. Learn more at the NLnet project page.

NLnet foundation logo NGI Zero Logo

1. Generic Heading Template Implementation

The standard article class will already use the heading templates as shown in the example below. This is included here to demonstrate this test infrastructure, before the emulation of the heading packages has been undertaken.

%
\DocumentMetadata{tagging=on,pdfstandard=ua-2}
\documentclass{article}

\EditInstance{heading}{section}{
 number-format=\fbox{\theheading},
 before-vspace=2cm
}
\EditInstance{heading}{subsection}
{number-format=\fbox{\theheading}}

\title{Heading templates example}
\author{L. Team}
\begin{document}
\maketitle
\section{Introduction}
some text

\subsection{subsec a}
some text
\subsection{subsec b}
some text

\section{Main section}
more text
\end{document}
%

1a. Implement generic order key handling

In layouts for headings, captions, etc. the placement of individual data items (such as fixed strings like “Chapter”, a generated number, the title, an optional sub-title, some punctuation, etc.) needs a flexible solution in templates. This avoids the need for different templates just because the order of title and number got swapped or because of similar minor differences.

To allow for such variations within a single template, the concept of “order” keys has been developed. The template support code was augmented to offer a \template_process_order_clist:nnn command that takes an order key defined by the template and processes its comma list to typeset the items (and generate approriate tagging for them). You may find the documentation in latex-lab on CTAN or on your LaTeX installation with texdoc latex-lab-template.

The test file below defines a small template (that is not doing much) but lets you experiment with the mechanism and its results.

%
\DocumentMetadata
  { lang=en-US, pdfversion=2.0, pdfstandard=ua-2, tagging=on }

% not doing math so set this to use less run resources
  
\tagpdfsetup {math/mathml/luamml/load=false}

  
\documentclass{article}

\title{Simple template with order key}

%
%  Define and implement a simple template that takes 3 arguments:
%   #1  key/value list for overwriting instance parameter settings
%   #2  a title
%   #3  a note
%
%  Internally it generates a number (or rather it has one hardwired in as this is
%  only for testing purposes.


% ---------------------------- define a template for testing ---------------------

\ExplSyntaxOn

\NewTemplateType{threeargs}{3}

\DeclareTemplateInterface{threeargs}{trial}{3}{
%
%  In the order key it supports ordering of prefix,number,title,punct,note and
%  3 customizable separators. The default makes use of all of them, but as prefix
%  is \NoValue and separator-a is empty nothing is typeset for them
%
  ,order       : commalist = {prefix,separator-a,number,separator-b,title,
                              punct,separator-c,note}
  ,prefix      : tokenlist = \NoValue
  ,number      : tokenlist = 4711
  ,punct       : tokenlist = .
  ,separator-a : tokenlist = 
  ,separator-b : tokenlist = \
  ,separator-c : tokenlist = \quad
%
% title and note have no keys because they are part of the arguments.
%
% Declarations (like font setting) that can be used:
%
  ,heading-decls: tokenlist = 
%
  ,prefix-decls : tokenlist = 
  ,number-decls : tokenlist = 
  ,punct-decls  : tokenlist =
  ,note-decls   : tokenlist = 
  ,title-decls  : tokenlist =
%
%
% Alternatively, or in addition, formatting commands can be used.
% The punct-format ensures that a punctuation is only added if there isn't one
% already at this point.
% The one for note adds (...) by default.
%
  ,prefix-format : function{1} = #1
  ,number-format : function{1} = #1
  ,punct-format  : function{1} = \AddPunct{#1}
  ,note-format   : function{1} = (#1)
  ,title-format  : function{1} = #1
%  
%  This is for surounding the number with a Lbl structure for tagging (see documentation).
%  
  ,number-tag  : tokenlist = Lbl
}

\DeclareTemplateCode{threeargs}{trial}{3}{
%  
%  Key bindings have to have standard names to work with order process, the conventions
%  are straight forward (see documentation).
%  
  ,order       = \l__trial_order_clist
  ,prefix      = \l__trial_prefix_tl
  ,number      = \l__trial_number_tl
  ,punct       = \l__trial_punct_tl
  ,separator-a = name {l__trial_separator-a_tl} % special because of 1 and - in the name
  ,separator-b = name {l__trial_separator-b_tl}
  ,separator-c = name {l__trial_separator-c_tl}
%
  ,heading-decls = \l__trial_heading_decls_tl
%
  ,prefix-decls  = \l__trial_prefix_decls_tl
  ,number-decls  = \l__trial_number_decls_tl
  ,punct-decls   = \l__trial_punct_decls_tl
  ,title-decls   = \l__trial_title_decls_tl
  ,note-decls    = \l__trial_note_decls_tl
%
  ,prefix-format = \__trial_prefix_format:n
  ,number-format = \__trial_number_format:n
  ,punct-format  = \__trial_punct_format:n
  ,title-format  = \__trial_title_format:n
  ,note-format   = \__trial_note_format:n
%
  ,number-tag  = \l__trial_number_tag_tl
}
{
%  This now implementes the template.
%
%  Process the key/value list is any and overwrite settings:
%  
  \SetKnownTemplateKeys{threeargs}{trial}{#1}
%  
% Set the missing variables for title and note from the mandatory arguments
% of the template: 
%  
  \tl_set:Nn \l__trial_title_tl {#2}  % save title arg
  \tl_set:Nn \l__trial_note_tl  {#3}  % save note arg
%  
%  Then typeset the whole thing as a simple paragraph, with order
%  key processing in the middle.
%  
  \par \noindent
  %
  \group_begin:
    \l__trial_heading_decls_tl
    \template_process_order_clist:nnn
     { trial }{ order }
     { note, number, prefix, punct, separator-a, separator-b, separator-c, title }
  %
    \par
  \group_end:
  \bigskip
  \hrule
  \bigskip
}


% ---------------------------- define a few instances  ---------------------



\DeclareInstance{threeargs}{default}{trial}{
  % no parameter settings, just use what the template has default values
}


\DeclareInstance{threeargs}{section}{trial}{
    prefix        = §
   ,separator-a   =       % no space
   ,heading-decls = \large\bfseries
   ,title-decls   = \scshape
   ,note-decls    = \mdseries\normalshape
}

\DeclareInstance{threeargs}{chapter}{trial}{
    prefix        = Chapter
   ,separator-a   = \ \rule[-10pt]{2pt}{50pt}\
   ,separator-b   = \\[10pt]
   ,order         = {number,separator-a,prefix,separator-b,title,punct} % no note
   ,heading-decls = \Large\bfseries
   ,title-decls   = \Huge\mdseries\scshape
}


% -------- define a simple command to use one of the instances  ---------------------

%
% Arguments are
%   #1  instance name
%   #1  key/value list with parameters (optional)
%   #3  title
%   #4  note (optional


\NewDocumentCommand\head{m O{} m o }
{ \UseInstance{threeargs}
              {#1}            % instance name
              {#2}{#3}{#4}    % template arguments
}


\newcommand\mynotes[1]{\par\noindent\texttt{\itshape #1}\par\medskip}

\ExplSyntaxOff



\begin{document}

\title{Testing order keys}
\author{Frank Mittelbach, \LaTeX{} Project}
\maketitle

% A bit of tracing (you find the output in the .log file):

\DebugTemplatesOn

\head{default}
     {Title}[default result] 

\head{default}
     {Title ending in punctuation!}[no period added] 


\mynotes{Use the optional argument for parameter changes:}
     
\head{default}
     [prefix=Chapter,separator-a=\ ,title-decls=\itshape]
     {Title}[prefix added]


\mynotes{Use section template instead:}
     
\head{section}
     {Title}[default result] 

\head{section}
     {Title ending in punctuation!}[no period added] 


\head{section}
     [prefix=Chapter,separator-a=\ ,title-decls=\itshape]
     {Title}[prefix added]


\head{default}
    [order={title,separator-b,number,separator-c,note}]
    {Title and number swapped}[and punctuation always dropped] 


\mynotes{Use (strange) chapter template instead:}
     
\head{chapter}
     {Title}[default result] 

\head{chapter}
     {Title ending in punctuation!}[no period added] 


\end{document}
%

The new mechanism has also been applied in the templates used to declare theorem-like environments. In the next example various changes are made to the plain instance of type theoremstyle to exhibit some of the features:

%
\DocumentMetadata
  {
    lang=en-US,
    pdfversion=2.0,
    pdfstandard=ua-2,
    tagging=on,
  }

\documentclass{article}

\title{Theorem examples with order key}

\newtheorem{thm}{Theorem}[section]

\begin{document}

\section{A section}

\DebugTemplatesOn  % to see the processing in the log

\begin{thm}[It has a name]
  A theorem with default layout.
\end{thm}

\EditInstance{thmstyle}{plain}{
    order         =  {title,separator-a,number,punct,separator-b,note}
  , caption-decls = \sffamily\bfseries
  , note-decls    = \mdseries
  , after-hspace  = 20pt plus 2pt
}  

\begin{thm}[It has a name]
  A theorem with period after number and not after the note and a wide
  space separating it from th body text.
\end{thm}

\EditInstance{thmstyle}{plain}{
  , order       =  {number,separator-a,title,separator-b,note,separator}
  , separator-b = \\
  , separator   = \\[5pt]
  , after-hspace = 0pt
  , note-format = #1
  , note-decls = \footnotesize
}  

\begin{thm}[It has a name too]
  A theorem with number and title swapped and the note on a line by
  itself (if present).  No claim that this looks good :-)
\end{thm}

\begin{thm}
  As before, but without a note. Therefore \texttt{separator-b} in
  front of the non-existent note is dropped but the final
  \texttt{separator} remains.
\end{thm}

\end{document}

%

We have also replaced the initial templates for heading commands to make full use of the order key functionality. This is explored in the next example:

%
\DocumentMetadata
  { lang=en-US, pdfversion=2.0, pdfstandard=ua-2, tagging=on }

\documentclass[twocolumn]{report}

\usepackage{kantlipsum}

\title{Heading examples with order key}

\begin{document}


\chapter{Default headings}            \kant[1]
\section{A section title}             \kant[2]
\subsection{A subsection title}       \kant[3]
\subsubsection{A subsubsection title} \kant[4]
\paragraph{A paragraph title}         \kant[5]
\subparagraph{A subparagraph title}   \kant[6]


% Various spacing changes in the new \chapter
%  + different fonts
%  + title boxed

\EditInstance{heading}{chapter}
  { placement = normal,
    column-spanning = false,
    before-vspace   = 0pt,
    penalty         = -300,
    after-penalty-vspace = 20pt,
    after-vspace    = 10pt,
    heading-decls   = \raggedright \parindent 0pt \sffamily \bfseries \huge ,
    title-format    = \parbox[t]{.7\linewidth}{\raggedleft #1}
  }

%  title and number flipped with some rules inbetween:

\EditInstance{headformat}{chapter}
 { heading-indent = 0em,
   order = {title, separator-a, number},
   separator-a = \ \rule[-20pt]{2pt}{60pt}\rule[-5pt]{3pt}{30pt}\ ,
 }

% 

\EditInstance{heading}{section}
 { prefix = Section,
   heading-decls = \normalfont \sffamily \large \bfseries \centering ,
   title-decls = \Large
 }
\DeclareInstance{headformat}{section}{display}
 { heading-indent = 0pt,
   order = { separator-a, prefix, separator-b, number, separator-c, title},
   separator-a = \hspace*{1em}\hrulefill\ ,
   separator-b = \nobreakspace,
   separator-c = \ \hrulefill \hspace*{1em}\linebreak
 }

\EditInstance{heading}{subsection}
 { prefix = \S,
   punct = \AddPunct{.} ,
   heading-decls = \sffamily \bfseries
 }

\EditInstance{headformat}{subsection}
 { separator-a = \, ,
   separator-b = \
 }
 
\EditInstance{heading}{subsubsection}
 {  heading-decls = \sffamily \bfseries \itshape
 }

\chapter{Alternate}          \kant[1]
\section{A section title}             \kant[2]
\subsection{A subsection title}       \kant[3]
\subsubsection{A subsubsection title} \kant[4]


% To look at the current instance values one can use:
%
%   \ShowInstanceValues{heading}{chapter}
%   \ShowInstanceValues{headformat}{chapter}
%
% However, This can't be used on the website installation of LaTeX as
% it generates a non-zero return code! For this you need to run the
% document locally.


\end{document}

%

1e. Extend key binding setup

In some templates the variable names in the key binds need to contain characters normally not directly usable, e.g., a number or a hyphen. To support this the template declaration syntax was extended with the keyword name so that one can write

 separator-a = name {l__trial_separator-a_tl}

which generates \__trial_separator-a_tl a macro name that can’t be written directly because of the hyphen, which is normally not allowed in macro names (the same would be the case if you need a digit as part of the name). This is, for example, needed in the order processing implementation where such variables are constructed from key names that may contain such characters.

As an example testing the implementation for the new keyword look at the test in section 1a which makes use of the functionality in 3 places.

2. Package Reimplementations/adjustments

2a-2e. titlesec

The titlesec package is currently incompatible with the LaTeX tagging code, and this example demonstrates that no PDF is generated and just the error log is shown.

It is included here as it is expected that an accessible version of this package functionality will be developed soon as part of a wider effort to implement LaTeX section headings via a new template interface producing accessible PDF outputs.

%
\DocumentMetadata
  { lang=en-US, pdfversion=2.0, pdfstandard=ua-2, tagging=on }

\documentclass{report}
\usepackage{titlesec}

\titleformat{\section}
  {\LARGE\sffamily\slshape}
  {\thesection}{1em}{}
\titlespacing{\section}
  {-6pc}{3.5ex plus .1ex minus .2ex}{1.5ex minus .1ex}

\begin{document}

\chapter{Some chapter}
\section{Some section}
\subsection{Some subsection}
Normal text

\end{document}
%

2f. The fncychap package

The fncychap package implements a number of chapter layouts and offers customization possibilities to adjusts these layouts. Originally written about 20 years ago, its implementation directly alters several LaTeX’s internals to provide the customization possibilities and as a result its code is incompatible with LaTeX’s new heading code that enables tagging.

We have now provided adjustments to the package code that are automatically loaded when the package is used in a document. With these adjustements all chapter layouts offered by the package become tagging-aware and the package can thus be used when producing accessible documents.

The following example document loads the package and then shows the the results of all chapter layouts one after another (the somewhat lengthy code at the top of the example is only there to allow more than one chapter layout to be used in a single document. A normal usage of the package would just have a line like \usepackage[Lenny][fncychap] in the preamble).

%
\DocumentMetadata
  { lang=en-US, pdfversion=2.0, pdfstandard=ua-2, tagging=on }

\documentclass{book}

% For testing we alter LaTeX's option handling so that the macros containing the
% option code are not emptied at the end of the package. This enables us to use
% \ds@Lenny, \ds@Sonny, etc. in the test code showing the results of the different
% package option in one document.

\makeatletter
% save original and restore it after fncychap got loaded.
\let\@process@pti@ns@saved\@process@pti@ns
% then change:
\def\@process@pti@ns{%
  \@for\CurrentOption:=\@curroptions\do{%
    \@ifundefined{ds@\detokenize\expandafter{\CurrentOption}}%
      {\@use@ption
       \default@ds}%
      \@use@ption}%
% don't do the next two lines so that the \ds@... commands aren't removed:
%  
%  \@for\CurrentOption:=\@declaredoptions\do{%
%    \expandafter\let\csname ds@\CurrentOption\endcsname\relax}%
%
  \let\CurrentOption\@empty
  \let\@fileswith@pti@ns\@@fileswith@pti@ns
  \AtEndOfPackage{\expandafter\let
                     \csname unprocessedoptions-\@currname.\@currext\endcsname
                     \relax}}
\makeatother

% Now load the package without option so that we get LaTeX headings by
% default.

\usepackage{fncychap}

\usepackage{color}     % necessary for one of the chapter styles



\makeatletter

% Restore the option handler to its standard definition

\let\@process@pti@ns\@process@pti@ns@saved


% Define a command to test chapters in front, main, backmatter and for the appendix.
% The argument contains a chapter style name, i.e., one that would normally be used
% as an option to the package.

\newcommand\testdata[1]{
\typeout{================== #1 =============}
% 
% Load the code of one option:
% 
  \csname ds@#1\endcsname
% 
% Generate a chapter heading in front/main/appendix/backmatter each:
% 
  \frontmatter  \chapter{Frontmatter -- #1}  Some text.   \section*{A section} More text.
  \mainmatter   \chapter{Main -- #1}         Some text.   \section{A section} More text.
  \mainmatter   \chapter{More Main -- #1}    Some text.   \section{A section} More text.
  \appendix     \chapter{Appendix -- #1}     Some text. %  \section{A section} More text.
  \backmatter   \chapter{Backmatter -- #1}   Some text. %  \section{A section} More text.
%
% Undo the \appendix settings for next trial  
%
  \def\@chapapp{\chaptername}%
  \protected\def\thechapter{\thespecialchapter\arabic}%
  \setcounter{chapter}{0}%
}
\makeatother

\DebugHeadingsOn
\DebugTemplatesOn

\begin{document}

\title{Testing fncychap headings}
\author{Frank Mittelbach, \LaTeX{} Project}
\date{2026-09-17}
\maketitle

% Test each chapter style in turn, starting with LaTeX's default layout

\testdata{LaTeX default layout}
\testdata{Lenny}
\testdata{Sonny}
\testdata{PetersLenny}
\testdata{Glenn}
\testdata{Conny}
\testdata{Rejne}
\testdata{Bjarne}
\testdata{Bjornstrup}

\end{document}

%

In this second example a customized design is exhibited showing that freeform customizations as offered by the package work as well and result in tagged and accessible chapter headings.

%
\DocumentMetadata
  { lang=en-US, pdfversion=2.0, pdfstandard=ua-2, tagging=on }

\documentclass{report}

\usepackage{fncychap}

% A manually customized chapter heading (taken from the fncychap manual):

\makeatletter % for \@chapapp

\ChNameVar{\Large\rm}            % sets the style for name
\ChNumVar{\Huge}                 % sets the style for digit
\ChTitleVar{\Large\rm\centering} % sets the style for title
\ChRuleWidth{4pt}
\ChNameUpperCase % Make name uppercase
\renewcommand{\DOCH}{%
    \setlength{\fboxrule}{\RW}   % Let fbox lines be
                                 % controlled by \ChRuleWidth
    \fbox{\CNV\FmN{\@chapapp}\space \CNoV\thechapter}%
    \par\nobreak
    \vspace{40pt}}
\makeatother

\title{Testing custom fncychap headings}

\begin{document}

\chapter{Special chapter}  
Some text.

\end{document}

%

2g. The quotechappackage

2h. The tocloft package

The tocloft package was incompatible with the LaTeX tagging code and this example demonstrated one of the issues with it: the TOC data is incomplete and not correctly tagged. This is no longer the case: the package was updated and is now compatible with LaTeX’s tagging functionality.

%
\DocumentMetadata { lang=en-US, pdfstandard=ua-2, tagging=on } % comment out to see untagged result

\documentclass{article}

\usepackage{hyperref}
\usepackage{tocloft}

\begin{document}

\title{Test for tocloft with hyperref} \maketitle

\tableofcontents

\section{Introduction}
Check whether in the TOC the reference to this section is hyperlinked.
\end{document}
%

3. KOMA-Script Classes

4. memoir Class