Options de classe et création

\documentclass[unknownoption]{article} compile sans broncher. Une option de classe mal orthographiée n’est pas une erreur : LaTeX enfouit LaTeX Warning: Unused global option(s): dans le journal et vous rend quand même un PDF, si bien qu’on ne le remarque presque jamais. Écrivez plutôt \usepackage[unknownoption]{color} et la compilation s’arrête net. Cette asymétrie n’est pas un caprice : elle découle d’une conception délibérée où une option non reconnue n’a pas la même destination par défaut dans une classe et dans un package. À partir de ce mécanisme, cette page construit \DeclareOption et \ProcessOptions, le plus récent \DeclareKeys, puis \LoadClass, qui permet d’asseoir sa propre classe sur une classe existante.

Pourquoi une option de classe mal orthographiée n’arrête pas la compilation

La réponse est écrite noir sur blanc dans le clsguide officiel. Si un fichier de classe ne contient aucun \DeclareOption*, toutes les options qu’il n’a pas déclarées sont silencieusement transmises à tous les packages. Si un fichier de package ne contient aucun \DeclareOption*, chaque option non déclarée provoque une erreur. Une option de classe circule donc au cas où quelqu’un la réclamerait ; ce n’est que si personne ne l’a prise au moment de \begin{document} que LaTeX signale LaTeX Warning: Unused global option(s): suivi des noms restants entre crochets. Une option de package, elle, n’a nulle part où aller : un nom inconnu devient aussitôt ! LaTeX Error: Unknown option 'unknownoption' for package 'color'.

La conception se défend : les options que la classe elle-même ignore mais qu’un package chargé plus tard réclamera — les options globales, comme dans \documentclass[dvipsnames]{article} — servent tous les jours. Le prix à payer : les fautes de frappe restent muettes. Deux habitudes sont payantes. D’abord, chercher Unused global option dans le journal après chaque compilation. Ensuite, placer \listfiles en tête du préambule pour que la fin du journal liste tous les fichiers chargés avec leur version. Notez au passage qu’appeler \OptionNotUsed dans le code d’une option l’envoie volontairement sur cette même liste d’options inutilisées.

La différence entre une classe (.cls) et un package (.sty)

Le clsguide énonce le critère en une ligne : si les commandes peuvent servir avec n’importe quelle classe de document, faites-en un package ; sinon, faites-en une classe. Une classe définit le type même du document et se charge exactement une fois, avec \documentclass. Un package se charge avec \usepackage, autant qu’on veut, et ajoute des fonctions indépendantes du type de document. L’exemple du guide est parlant : la classe qu’une entreprise écrit pour composer ses lettres sur son propre papier à en-tête repose sur letter mais ne sert avec aucune autre classe — d’où ownlet.cls ; le package graphics, qui insère des images, fonctionne avec toutes les classes — d’où graphics.sty.

Il existe aussi deux sortes de classes : les autonomes, comme article, report et letter, et celles qui étendent ou varient une autre classe — le clsguide cite proc, bâtie sur article. Une classe que vous écrivez vous-même relève presque à coup sûr de la seconde catégorie, car composer une maquette à partir de rien est rarement rentable. Les conventions d’écriture d’un .cls et d’un .sty sont quasi identiques ; les commandes existent simplement par paires Class et Package (\ProvidesClass\ProvidesPackage, \LoadClass\RequirePackage, \PassOptionsToClass\PassOptionsToPackage).

Les options standard que votre classe devrait accepter

Les utilisateurs passeront des options à votre classe exactement comme à une classe standard : il faut donc au minimum la panoplie habituelle — 10pt / 11pt / 12pt pour la taille de base, a4paper / letterpaper pour le papier, onecolumn / twocolumn pour les colonnes, oneside / twoside pour le recto seul ou le recto verso, et draft, qui marque les lignes débordantes d’une barre noire (son opposé est final). Vous n’avez cependant rien à implémenter vous-même : comme on va le voir, l’usage est de les transmettre à la classe de base.

OptionSignificationDéfaut
10pt / 11pt / 12pttaille de base du corps10pt
a4paper / letterpaperformat du papier (aussi b5paper, legalpaper, …)letterpaper
onecolumn / twocolumnune colonne / deux colonnesonecolumn
oneside / twosidemise en page recto seul / recto versooneside (mais twoside pour book)
draft / finalsi les lignes débordantes reçoivent une barre noirefinal

Si vous voulez que la classe décide elle-même de ce qui s’applique lorsque l’utilisateur ne précise rien, écrivez \ExecuteOptions{a4paper,11pt} avant \ProcessOptions. Cela déclare « exécute d’abord le code de ces options », et le clsguide présente précisément cette forme pour donner à une classe sa conception par défaut. L’utilisation de ces options côté \documentclass[...], et les options propres à book comme openright, relèvent de la page sur la classe de document et le préambule. À partir d’ici, nous nous concentrons sur l’écriture de la classe qui les reçoit.

Identifier le fichier dès le début : \NeedsTeXFormat et \ProvidesClass

Les deux premières lignes d’un fichier de classe (myclass.cls) sont presque du boilerplate. \NeedsTeXFormat{LaTeX2e} déclare que le fichier vise LaTeX2e. Ensuite, \ProvidesClass{myclass}[2026/01/01 v1.0 My example class] annonce le nom de classe, la date de publication, la version et une brève description. Cette ligne paie surtout dans le journal : après compilation, on y lit Document Class: myclass 2026/01/01 v1.0 My example class. Quand un coauteur dit que le document ne compile pas, lui demander cette seule ligne révèle aussitôt s’il détient un vieux .cls.

La partie entre crochets est facultative, mais l’indiquer permet aux utilisateurs d’exiger une version minimale via la date au format YYYY/MM/DD, comme \documentclass{myclass}[2026/01/01]. Pour un package, la commande correspondante est \ProvidesPackage{mypackage}[2026/01/01 v1.0 ...] ; \NeedsTeXFormat est commun aux deux. Règle ferme : le nom dans \ProvidesClass doit correspondre au nom réel du fichier — dans myclass.cls, écrivez toujours \ProvidesClass{myclass}.

latex
\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{myclass}[2026/01/01 v1.0 My example class]

Déclarer les options : \DeclareOption et \CurrentOption

Chaque option acceptée par votre classe se déclare avec \DeclareOption{option}{code}. Quand l’utilisateur la spécifie, son code s’exécute au moment où \ProcessOptions est atteint (voir plus bas). Ce code peut être n’importe quelle construction LaTeX valide, mais en pratique c’est le plus souvent une seule ligne qui active un booléen créé avec \newif ; faire le gros du travail plus tard, en consultant le drapeau, évite les accidents d’ordre.

Le réceptacle des options non déclarées est la forme étoilée \DeclareOption*{code}, à l’intérieur de laquelle \CurrentOption se développe en nom de l’option en cours de traitement. La ligne la plus fréquente d’une classe personnalisée s’en sert pour transmettre telle quelle toute option inconnue à la classe de base. Grâce à elle, les utilisateurs peuvent passer 10pt ou a4paper tout naturellement bien que vous ne les ayez jamais déclarées : vous avez redirigé vers une destination choisie le comportement par défaut vu en introduction, « une classe transmet en silence ».

latex
% pass anything we do not handle ourselves on to article
\DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}}

Traiter les options puis charger la classe de base : \ProcessOptions et \LoadClass

Déclarer ne suffit pas : le code des options retenues ne s’exécute qu’à l’appel de \ProcessOptions. En pratique, on écrit presque toujours \ProcessOptions\relax. Comme il existe aussi un \ProcessOptions* étoilé, le \relax final sélectionne sûrement la forme non étoilée et évite une lecture anticipée inutile ainsi que des messages d’erreur trompeurs — le clsguide le recommande explicitement. La forme non étoilée traite les options dans l’ordre de déclaration ; la forme étoilée, dans l’ordre où l’appelant les a listées.

Composer une maquette à partir de rien est rarement rentable : la plupart des classes personnalisées s’appuient sur une classe existante, grâce à \LoadClass[options]{article}, qui charge toutes les commandes et le style de article.cls. Cette commande ne s’utilise que dans un fichier de classe, et au plus une fois par fichier. L’ordre compte : pour que les options données par l’utilisateur dans \documentclass[...] atteignent la classe de base, placez \LoadClass après le traitement des options (\ProcessOptions) — déclarez la transmission, laissez \ProcessOptions répartir, puis chargez la base. Pour transmettre exactement les options reçues, \LoadClassWithOptions{article} est le raccourci ; dans un package, \RequirePackage remplace \LoadClass, et \RequirePackageWithOptions couvre le cas « tout transmettre ».

Tout ce qui suit \LoadClass est l’endroit où s’exprime enfin le caractère de votre classe : redéfinir les titres avec \renewcommand, ajuster les marges avec \setlength, définir de nouvelles commandes et environnements avec \newcommand / \newenvironment. Les packages supplémentaires nécessaires se chargent ici avec \RequirePackage. Autrement dit : retenez que seuls la déclaration et le traitement des options précèdent \LoadClass, et les questions d’ordre cessent de se poser.

Exemple complet : une classe minimale qui étend article

En réunissant tout cela, on obtient ce .cls minimal. Il s’appuie sur article, ajoute sa propre option draft, transmet les options inconnues à article, fournit a4paper par défaut et règle enfin les marges et la numérotation des sections à son goût. Enregistrez-le sous myclass.cls à côté du manuscrit et utilisez-le avec \documentclass[11pt,a4paper,draft]{myclass}.

latex
\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{myclass}[2026/01/01 v1.0 My example class]

% --- declare options ---
\newif\if@my@draft \@my@draftfalse
\DeclareOption{draft}{\@my@drafttrue}
% forward everything else to article
\DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}}

% --- defaults, then execute, then load the base class ---
\ExecuteOptions{a4paper}
\ProcessOptions\relax
\LoadClass{article}

% --- this class's own character ---
\RequirePackage[margin=25mm]{geometry}
\setlength{\parindent}{0pt}
\renewcommand{\thesection}{\Alph{section}}
\if@my@draft
  \AtBeginDocument{\typeout{myclass: DRAFT MODE}}
\fi

\endinput

Le \endinput final indique à LaTeX que le fichier s’arrête là ; on l’ajoute par convention, et les notes ou exemples écrits après ne sont jamais lus. Pour en faire un package, remplacez \ProvidesClass par \ProvidesPackage et \LoadClass par \RequirePackage : le même squelette devient un .sty.

La méthode moderne : \DeclareKeys et \ProcessKeyOptions

\DeclareOption reste parfaitement valable, mais il est conçu pour des interrupteurs présent/absent ; une option porteuse de valeur comme logo=acme.pdf vous laisserait l’analyser vous-même. Le noyau LaTeX fournit donc désormais sa propre interface clé-valeur : déclarer les clés avec \DeclareKeys et les traiter avec \ProcessKeyOptions. Chaque nom de clé porte une « propriété » qui décide de son comportement ; les propriétés de base sont .code (exécuter du code arbitraire), .if / .ifnot (activer un booléen TeX), .store (enregistrer la valeur dans une macro) et .usage (option admissible seulement au chargement, partout dans le préambule, ou sans restriction). Les clés inconnues passent par \DeclareUnknownKeyHandler, et dès lors qu’on appelle \ProcessKeyOptions, il n’est pas nécessaire d’appeler aussi \ProcessOptions.

latex
\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{keyclass}[2026/01/01 v1.0 Key-value demo class]

\DeclareKeys[keyclass]{
  draft.if   = @keyclass@draft,
  logo.store = \@keyclass@logo,
  logo.usage = load
}
% anything that is not one of our keys goes to article
\DeclareUnknownKeyHandler[keyclass]{%
  \PassOptionsToClass{\CurrentOption}{article}}
\ProcessKeyOptions[keyclass]   % no \ProcessOptions needed
\LoadClass{article}

\endinput

Ce mécanisme venait à l’origine du package l3keys2e, dont le cœur a depuis été intégré au noyau LaTeX2ε (le noyau livré avec TeX Live 2024 est LaTeX2e 2023-11-01 et fournit \DeclareKeys, \ProcessKeyOptions et \SetKeys). Des packages existants chargent encore l3keys2e : jlreq.cls, par exemple, exécute \RequirePackage{l3keys2e} près de son début. Le choix est simple : si ne serait-ce qu’une option prend une valeur, utilisez \DeclareKeys ; si ce ne sont que des interrupteurs, \DeclareOption suffit. Pour modifier des réglages après chargement, employez \SetKeys.

Le test minimal à lancer avant diffusion

Une classe agit sur tout le document dès son chargement ; stabilisez donc son comportement avec un tout petit document de test avant d’écrire du contenu réel. Deux points seulement sont à vérifier : que les options standard comme 11pt ou twocolumn atteignent bien la classe de base, et que seules vos options propres sont traitées par votre code. Si le comportement diffère de vos attentes, la cause est presque à coup sûr la position de \ProcessOptions, la transmission dans \DeclareOption* ou l’ordre de \LoadClass.

latex
\listfiles                     % log every file and version that is loaded
\documentclass[11pt,a4paper,draft]{myclass}
\begin{document}
\section{Smoke test}
Check the body size, the paper, the draft switch,
the heading style and the margins.
\end{document}
  • Le journal identifie-t-il la classe ? Vérifiez que la ligne Document Class: myclass ..., avec la date et la version écrites dans \ProvidesClass, figure dans le .log. Un écart entre nom de fichier et nom de classe finira par égarer quelqu’un.
  • Les options standard sont-elles préservées ? Si 11pt ou twocolumn est ignoré, réexaminez la transmission dans \DeclareOption* ou la position de \LoadClass.
  • Faites exprès une faute de frappe. Compilez \documentclass[nosuchoption]{myclass} et vérifiez que Unused global option(s) apparaît dans le journal. Sinon, c’est qu’un package destinataire l’avale en silence.
  • Ne laissez rien après \endinput. Des notes ou exemples en fin de fichier seront lus comme entrée dès que ce marqueur disparaît.

Penser aussi à la forme de diffusion

Une classe personnalisée n’est vraiment mise à l’épreuve ni au moment où elle fonctionne chez vous, mais quand quelqu’un d’autre la charge dans un autre environnement. Au minimum, gardez dans un même répertoire le .cls, un court document d’exemple, un README et un historique, et vérifiez que l’exemple compile tel quel. Dans le README, distinguez les options transmises à la classe de base de celles que votre classe traite elle-même, afin que l’on puisse suivre où 11pt agit. Quand le projet grossit, passez aux outils doc et docstrip de LaTeX : source et commentaire réunis dans un .dtx, .cls engendré depuis un .ins — diffusion et documentation suivent alors la même voie.

terminal
myclass/
  myclass.cls
  sample.tex
  README.md
  CHANGELOG.md

Enfin, mettez \listfiles dans l’exemple. La fin du journal énumère alors tous les fichiers chargés avec leur version : on distingue d’un coup d’œil si un utilisateur traîne un vieux myclass.cls local ou si les packages attendus sont bien chargés. Une classe est le socle de tout le document : à long terme, un ordre de chargement, un traitement des options et des informations de journal soignés rapportent bien plus sûrement qu’une macro de corps supplémentaire.