Heading formats (titlesec)

The look of a LaTeX heading is decided in a startlingly small place. Four lines beginning at line 302 of article.cls — a single call passing six arguments to a command named \@startsection — settle the typeface, the numbering and the space around every \section. What the titlesec package (by Javier Bezos) does is read those six arguments back out of the class and re-serve them as the human-readable \titleformat and \titlespacing. That is why merely loading it changes nothing on the page. This page opens the class’s \@startsection first to see how a heading really works, then covers writing \titleformat and \titlespacing, what the star actually means, and why the package collides with KOMA-Script and memoir — all measured.

\@startsection: the six arguments in which a class defines a heading

Print the definition of \section and out comes a single call with six arguments. Every heading in the standard classes except \part has this shape, and the arguments are, in order, the name of the heading, its level number, the indent, the space above, the space below, and the formatting. Lines 302 to 305 of article.cls are exactly that.

article.cls
% article.cls, lines 302-305 (TeX Live 2024)
\newcommand\section{\@startsection {section}{1}{\z@}%
                                   {-3.5ex \@plus -1ex \@minus -.2ex}%
                                   {2.3ex \@plus.2ex}%
                                   {\normalfont\Large\bfseries}}

% \paragraph, from the same file - note the NEGATIVE after-skip
\newcommand\paragraph{\@startsection{paragraph}{4}{\z@}%
                                    {3.25ex \@plus1ex \@minus.2ex}%
                                    {-1em}%
                                    {\normalfont\normalsize\bfseries}}

The interesting part is that the signs of the fourth and fifth arguments carry meaning. A negative fourth argument (the space above) means “do not indent the paragraph that follows this heading”; a negative fifth argument (the space below) means “do not break the line after the heading — let the text run on,” that is, a run-in heading. \section has -3.5ex above, so the paragraph after a section is not indented. \paragraph has -1em below, so \paragraph is a run-in heading. Switching behaviour by the sign of a length is a thoroughly 1980s piece of TeX design. The proof is in the implementation: the routine titlesec uses to read a class definition back out chooses the starred \titlespacing* when the fourth is negative and the runin shape when the fifth is, flipping the sign in each case.

Sectioning commandLevel in articleLevel in book / report
\part0−1
\chapter(does not exist)0
\section11
\subsection22
\subsubsection33
\paragraph4 (run-in by default)4
\subparagraph5 (likewise)5

So there are six levels in article (0 through 5) and seven in book and report (−1 through 5), the extra one being \chapter. How deep the numbering goes is held in \c@secnumdepth; measured, it is 3 in article and 2 in book and report. That is why article numbers down to \subsubsection while book stops at \subsection. Write \setcounter{secnumdepth}{4} and \paragraph gets a number too. How deep the table of contents goes is a separate count, kept in tocdepth.

What actually happens when you load titlesec

Nothing changes on the page. What changes is the definition of \section: the call to \@startsection is replaced by titlesec’s own machinery. Measured, \section before loading is \@startsection {section}{1}{\z@}{-3.5ex ...}{2.3ex ...}{\normalfont \Large \bfseries}, and after loading it is the single token \ttl@straightclass {section}. In the process the six arguments are moved into titlesec’s internal store, where the space above becomes the concrete length 15.0694pt plus 4.30554pt minus 0.86108pt and the space below 9.90276pt plus 0.86108pt. Those are simply 3.5ex and 2.3ex evaluated, since 1ex = 4.30554pt in 10pt Computer Modern (the plus and minus parts are the stretch and shrink LaTeX is allowed to use when levelling the bottom of a page). In short it is an equivalence transformation: not a scrap of meaning is lost.

latex
% Measured in a 10pt article. These two lines reproduce exactly what the
% class already does - they are what titlesec extracted on load.
\titleformat{\section}[hang]
  {\normalfont\Large\bfseries}{\thesection}{1em}{}
\titlespacing*{\section}{0pt}
  {15.0694pt plus 4.30554pt minus 0.86108pt}   % = 3.5ex plus 1ex minus .2ex
  {9.90276pt plus 0.86108pt}                   % = 2.3ex plus .2ex

Writing \titleformat: seven arguments and six shapes

\titleformat is the centre of the redesign. It takes a lot of arguments, but they map onto the six of \@startsection, so there is nothing to fear. If you only want a different typeface, the starred \titleformat* is the shortest route — two arguments, as in \titleformat*{\section}{\large\bfseries\sffamily} — and the numbering and spacing stay as the class set them. Shorter still, there is an easy setup driven by package options alone: \usepackage[sf,bf]{titlesec} makes every heading sans-serif bold. The options come in three families — rm sf tt md bf up it sl sc; big medium small tiny; raggedleft center raggedright — plus compact, which tightens the space above and below. To change only how the number prints, use \titlelabel, as in \titlelabel{\thetitle.\quad}, where \thetitle stands for that heading’s number.

latex
\titleformat{⟨command⟩}[⟨shape⟩]{⟨format⟩}{⟨label⟩}{⟨sep⟩}{⟨before-code⟩}[⟨after-code⟩]
  • ⟨command⟩ — the very sectioning command you are redesigning: one of \part, \chapter, \section, \subsection, \subsubsection, \paragraph, \subparagraph.
  • [⟨shape⟩] (optional) — the title’s “shape”: hang, block, display, runin, leftmargin, rightmargin, frame, drop, wrap. Defaults to hang.
  • {⟨format⟩} — formatting applied to both label and title. Set typeface and size together here, and alignment with \filcenter, \filright, \filleft, \fillast, or \filinner / \filouter for two-sided work.
  • {⟨label⟩} — how the number prints; put \thesection, or \@seccntformat{section} to keep the class’s own formatting. Leaving it empty suppresses the number in the table of contents too.
  • {⟨sep⟩} — the gap between label and title. It must be a length and may not be empty (horizontal in hang/block, vertical in display, the title-to-frame distance in frame).
  • {⟨before-code⟩} — code run just before the title. This is where decoration goes: \titlerule for a rule, \titleline to set arbitrary material on a line of its own.
  • [⟨after-code⟩] (optional) — code run just after the title (vertical mode in hang/block/display, horizontal mode in runin/leftmargin).
shapeEffect
hangThe default: a hanging label with the title aligned beside it (a standard \section)
blockSets label and title as a single block (paragraph); the right choice for centred headings
displayPuts the label on its own line above the title (a standard \chapter)
runinA run-in title: no break, the text continues on the same line (a standard \paragraph)
leftmarginPlaces the title inside the left margin; rightmargin is the right-margin version
frameLike display, but the title is framed
dropLets the text wrap around the title; wrap is the variant that fits the title box to its contents

\titlespacing and the meaning of the star: why you almost always want \titlespacing*

The star means suppress the indent of the paragraph that follows the heading. The easiest way to remember it is as a direct translation of the \@startsection convention from the previous section: a negative space-above means “no indent”. The standard \section has -3.5ex above, so to keep the class’s look you use the starred form — that is why the advice is “almost always \titlespacing*”. Its arguments are the extra left margin, the space above the heading, the separation between heading and text, and an optional extra right margin. In the leftmargin shapes the first argument is the width of the title; in runin it is the indent just before it. In the drop, wrap and runin shapes there is no indent to suppress in the first place, so the star has no effect there.

latex
\titlespacing*{⟨command⟩}{⟨left⟩}{⟨before-sep⟩}{⟨after-sep⟩}[⟨right⟩]

% All arguments must be LENGTHS. A value containing a command such as
% \stretch, or an empty {}, produces:
%   ! Missing number, treated as zero.
%   ! Illegal unit of measure (pt inserted).
%
% Shorthand: *4 means 4 units of \beforetitleunit (or \aftertitleunit)
\titlespacing*{\section}{0pt}{*4}{*2}

Every argument must be a length (a dimension). Pass a value containing a command, such as \stretch{1}, and you get ! Missing number, treated as zero. followed by ! Illegal unit of measure (pt inserted). An empty {} produces the same pair of errors. If writing full skip values is tiresome, abbreviate with * and a number, as in *4; the units for that shorthand live in \beforetitleunit and \aftertitleunit.

One behaviour worth measuring, because people fall into it. \titlespacing does nothing for \chapter or \part unless you also define their format with \titleformat. In the report class, writing only \titlespacing*{\chapter}{0pt}{0pt}{10pt} and then measuring where the body text after a chapter lands gives 305.1077pt — not one scaled point different from leaving it out. Add \titleformat{\chapter}[display]{...} to the same file and it moves up to 229.2701pt. No warning and no error is issued, so the awkward part is not noticing. \part is implemented without \@startsection in the first place, so even the easy setup leaves it alone. For both levels, start from \titleformat.

Building a ruled heading and a run-in heading

\titleformat and \titlespacing* are normally written as a pair. The preamble below turns \section into a heading with a thin rule above the title, and \subsection into a run-in heading that ends in a period and lets the text continue. The idiom for the rule is to put \titlerule in the {⟨before-code⟩} of the [display] shape. The [explicit] option stops the title text from appearing on its own, so that you must place it explicitly with #1 — the mechanism you need when you want decoration on both sides of the title.

document.tex
\documentclass{article}
\usepackage[explicit]{titlesec}

% A rule above the title, sans-serif bold below it
\titleformat{\section}[display]
  {\normalfont\Large\bfseries\sffamily}   % applies to label and title
  {\thesection}                           % the number
  {0pt}                                   % label-to-title gap (vertical here)
  {\titlerule\vspace{2pt}#1}              % before-code, then the title itself
\titlespacing*{\section}{0pt}
  {3.5ex plus 1ex minus .2ex}{2.3ex plus .2ex}

% A run-in subsection: "1.1 Beta. Text continues here."
\titleformat{\subsection}[runin]
  {\normalfont\bfseries}{\thesubsection}{.5em}{#1.}
\titlespacing*{\subsection}{0pt}{2ex plus .5ex}{.5em}

\begin{document}
\section{Alpha}
The paragraph after a starred \titlespacing is not indented.
\subsection{Beta}
This text runs on from the heading, on the same line.
\end{document}

Matching the contents list and the running head: titletoc and titleps

Once the headings are redesigned, you generally want the contents list and the running head to match. titlesec ships two companion packages in the same bundle, usable on their own: titletoc for the contents side and titleps for the page-style side. The core of titletoc is \titlecontents, which defines a contents entry completely, and \dottedcontents, a one-shot preset with dotted leaders. The only trap is that the leading ⟨section⟩ argument is the name without a backslash (section, chapter, figure, …). titleps defines a whole page style with \newpagestyle and fills its left, centre and right slots with \sethead and \setfoot. Which heading name reaches the running head is controlled by \settitlemarks, and \bottitlemarks, \toptitlemarks and \firsttitlemarks let you pick which heading on the page to use. Building headers and footers as such — including fancyhdr — belongs to its own page.

latex
\usepackage{titletoc}
% Dotted leaders for section entries:
%   indent 1.5em, no numbered-entry format, label width 2.3em, leader 1pc
\dottedcontents{section}[1.5em]{}{2.3em}{1pc}

\usepackage{titleps}
\newpagestyle{main}{\sethead{\thesection}{}{\thepage}}
\pagestyle{main}

Why you should not use titlesec with KOMA-Script or memoir

Both families build their headings with machinery of their own, so titlesec trying to intercept \@startsection on top of them does not mesh. The two fail in completely different ways, though. KOMA-Script speaks up. Load titlesec in scrartcl and you first get Class scrartcl Warning: Usage of package titlesec together with a KOMA-Script class is not recommended., followed by one message per heading: Package titlesec Warning: Non standard sectioning command \section detected. Using default spacing and no format. That second message is the substance of the problem: KOMA-Script’s \section calls \scr@startsection rather than \@startsection, so titlesec can read none of the class’s design out and re-sets the heading with its own defaults instead. Nor does it always stop at a warning. Combine it with a heading option on the class side, as in \documentclass[headings=big]{scrartcl}, and the measured result is ! Package titlesec Error: No format for this command. followed by ! Missing number, treated as zero. and ! Illegal unit of measure (pt inserted). — the typesetting simply collapses there. With KOMA-Script, restyle headings through the class’s own interface, \setkomafont and \RedeclareSectionCommand.

memoir, by contrast, says nothing at all. Tried on TeX Live 2024, the combination compiled with no warning and no error, and \titleformat worked as expected. That is because memoir’s \section has the shape \sechook \@startsection {section}{1}{\secindent}{\beforesecskip}{\aftersecskip}{\normalfont \secheadstyle}, which titlesec recognises as standard and can read back. Look at what it read back, though, and while \secheadstyle and \secindent are kept as names, the spaces above and below were frozen at the values in force at load time, 15.0694pt and 9.90276pt. In other words \setsecheadstyle survives but \setbeforesecskip and \setaftersecskip stop having any effect — the package breaks half of the interface, silently. memoir has a complete heading-design mechanism of its own, and using that one is the safer road.

Two more things around titlesec worth knowing. \titleclass re-assigns the class of a level, as in \titleclass{\subsection}{straight}; the valid class names are top, page and straight, and anything else raises Package titlesec Error: Unknown sectioning class. \assignpagestyle{\chapter}{plain} sets the page style for the page on which a given heading starts. And a note on the author: Javier Bezos is also the current maintainer of the multilingual package babel and the author of enumitem, which controls list layout — so if you spend a day in LaTeX adjusting headings, lists and languages, you meet the same person’s code three times.