diff --git a/app/but/jury_but_pv.py b/app/but/jury_but_pv.py
index 78d8d4dea4d4a2d83a9324c8f0d559c618a736b0..2940345edb6e6296a318da91e352456a14772707 100644
--- a/app/but/jury_but_pv.py
+++ b/app/but/jury_but_pv.py
@@ -47,7 +47,7 @@ def pvjury_table_but(formsemestre_id: int, format="html"):
     title = "Procès-verbal de jury BUT annuel"
 
     if format == "html":
-        line_sep = "<br/>"
+        line_sep = "<br>"
     else:
         line_sep = "\n"
     # remplace pour le BUT la fonction sco_pvjury.pvjury_table
diff --git a/app/pe/pe_avislatex.py b/app/pe/pe_avislatex.py
index fc64253ca0eb71d482d76c26d4117829cd1142c6..5a50773832159b234c0be8bfb2c5b66aa019e919 100644
--- a/app/pe/pe_avislatex.py
+++ b/app/pe/pe_avislatex.py
@@ -253,7 +253,7 @@ def get_annotation_PE(etudid, tag_annotation_pe):
             )  # Suppression du tag d'annotation PE
             annotationPE = annotationPE.replace("\r", "")  # Suppression des \r
             annotationPE = annotationPE.replace(
-                "<br/>", "\n\n"
+                "<br>", "\n\n"
             )  # Interprète les retours chariots html
             return annotationPE
     return ""  # pas d'annotations
diff --git a/app/pe/pe_view.py b/app/pe/pe_view.py
index ba129f7b6e1bd2b181b3a6d4014c0a456bddd666..06302cd8df0d39a0e66859c12d5a25bb56c3e31d 100644
--- a/app/pe/pe_view.py
+++ b/app/pe/pe_view.py
@@ -55,7 +55,7 @@ def _pe_view_sem_recap_form(formsemestre_id):
         <p class="help">
         Cette fonction génère un ensemble de fichiers permettant d'éditer des avis de
         poursuites d'études.
-        <br/>
+        <br>
         De nombreux aspects sont paramétrables:
         <a href="https://scodoc.org/AvisPoursuiteEtudes" target="_blank" rel="noopener">
         voir la documentation</a>.
@@ -65,7 +65,7 @@ def _pe_view_sem_recap_form(formsemestre_id):
         <div class="pe_template_up">
             Les templates sont généralement installés sur le serveur ou dans le
             paramétrage de ScoDoc.
-            <br/>
+            <br>
             Au besoin, vous pouvez spécifier ici votre propre fichier de template
             (<tt>un_avis.tex</tt>):
             <div class="pe_template_upb">Template:
diff --git a/app/scodoc/TrivialFormulator.py b/app/scodoc/TrivialFormulator.py
index 0920ab0e59c888c7f92bc5e365fa4787198e0be1..f3c768a2db40a2052cfd9440b8d2668134ad84ec 100644
--- a/app/scodoc/TrivialFormulator.py
+++ b/app/scodoc/TrivialFormulator.py
@@ -38,6 +38,9 @@ def TrivialFormulator(
     html_foot_markup="",
     readonly=False,
     is_submitted=False,
+    title="",
+    after_table="",
+    before_table="{title}",
 ):
     """
     form_url : URL for this form
@@ -74,7 +77,8 @@ def TrivialFormulator(
           HTML elements:
              input_type : 'text', 'textarea', 'password',
                           'radio', 'menu', 'checkbox',
-                          'hidden', 'separator', 'file', 'date', 'datedmy' (avec validation),
+                          'hidden', 'separator', 'table_separator',
+                          'file', 'date', 'datedmy' (avec validation),
                           'boolcheckbox', 'text_suggest',
                           'color'
                          (default text)
@@ -111,6 +115,9 @@ def TrivialFormulator(
         html_foot_markup=html_foot_markup,
         readonly=readonly,
         is_submitted=is_submitted,
+        title=title,
+        after_table=after_table,
+        before_table=before_table,
     )
     form = t.getform()
     if t.canceled():
@@ -144,6 +151,9 @@ class TF(object):
         html_foot_markup="",  # html snippet put at the end, just after the table
         readonly=False,
         is_submitted=False,
+        title="",
+        after_table="",
+        before_table="{title}",
     ):
         self.form_url = form_url
         self.values = values.copy()
@@ -165,6 +175,9 @@ class TF(object):
         self.top_buttons = top_buttons
         self.bottom_buttons = bottom_buttons
         self.html_foot_markup = html_foot_markup
+        self.title = title
+        self.after_table = after_table
+        self.before_table = before_table
         self.readonly = readonly
         self.result = None
         self.is_submitted = is_submitted
@@ -426,6 +439,7 @@ class TF(object):
         R.append('<input type="hidden" name="%s_submitted" value="1">' % self.formid)
         if self.top_buttons:
             R.append(buttons_markup + "<p></p>")
+        R.append(self.before_table.format(title=self.title))
         R.append('<table class="tf">')
         for field, descr in self.formdescription:
             if descr.get("readonly", False):
@@ -453,6 +467,16 @@ class TF(object):
                     etempl = separatortemplate
                     R.append(etempl % {"label": title, "item_dom_attr": item_dom_attr})
                     continue
+                elif input_type == "table_separator":
+                    etempl = ""
+                    # Table ouverte ?
+                    if len([p for p in R if "<table" in p]) > len(
+                        [p for p in R if "</table" in p]
+                    ):
+                        R.append(f"""</table>{self.after_table}""")
+                    R.append(
+                        f"""{self.before_table.format(title=descr.get("title", ""))}<table class="tf">"""
+                    )
                 else:
                     etempl = itemtemplate
             lab = []
@@ -610,7 +634,7 @@ class TF(object):
                         '<input type="hidden" name="%s" id="%s" value="%s" %s >'
                         % (field, wid, values[field], attribs)
                     )
-            elif input_type == "separator":
+            elif (input_type == "separator") or (input_type == "table_separator"):
                 pass
             elif input_type == "file":
                 lem.append(
@@ -641,13 +665,15 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
                 )
                 lem.append(('value="%(' + field + ')s" >') % values)
             else:
-                raise ValueError("unkown input_type for form (%s)!" % input_type)
+                raise ValueError(f"unkown input_type for form ({input_type})!")
             explanation = descr.get("explanation", "")
             if explanation:
-                lem.append('<span class="tf-explanation">%s</span>' % explanation)
+                lem.append(f"""<span class="tf-explanation">{explanation}</span>""")
             comment = descr.get("comment", "")
             if comment:
-                lem.append('<br/><span class="tf-comment">%s</span>' % comment)
+                if (input_type != "checkbox") and (input_type != "boolcheckbox"):
+                    lem.append("<br>")
+                lem.append(f"""<span class="tf-comment">{comment}</span>""")
             R.append(
                 etempl
                 % {
@@ -657,11 +683,11 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
                 }
             )
         R.append("</table>")
-
+        R.append(self.after_table)
         R.append(self.html_foot_markup)
 
         if self.bottom_buttons:
-            R.append("<br/>" + buttons_markup)
+            R.append("<br>" + buttons_markup)
 
         if add_no_enter_js:
             R.append(
@@ -753,7 +779,7 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
 
         if input_type == "separator":  # separator
             R.append('<td colspan="2">%s' % title)
-        else:
+        elif input_type != "table_separator":
             R.append('<td class="tf-ro-fieldlabel%s">' % klass)
             R.append("%s</td>" % title)
             R.append('<td class="tf-ro-field%s">' % klass)
@@ -786,7 +812,11 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
             R.append(
                 '<div class="tf-ro-textarea">%s</div>' % html.escape(self.values[field])
             )
-        elif input_type == "separator" or input_type == "hidden":
+        elif (
+            input_type == "separator"
+            or input_type == "hidden"
+            or input_type == "table_separator"
+        ):
             pass
         elif input_type == "file":
             R.append("'%s'" % self.values[field])
diff --git a/app/scodoc/html_sco_header.py b/app/scodoc/html_sco_header.py
index 0b2a3d5870198a23b4a3c47f8721a3afdc1c75ad..409e4d134303433fb045b7e349185485a1979d0a 100644
--- a/app/scodoc/html_sco_header.py
+++ b/app/scodoc/html_sco_header.py
@@ -284,8 +284,8 @@ def sco_header(
         if current_user.passwd_temp:
             H.append(
                 f"""<div class="passwd_warn">
-    Attention !<br/>
-    Vous avez reçu un mot de passe temporaire.<br/>
+    Attention !<br>
+    Vous avez reçu un mot de passe temporaire.<br>
     Vous devez le changer: <a href="{scu.UsersURL}/form_change_password?user_name={current_user.user_name}">cliquez ici</a>
     </div>"""
             )
diff --git a/app/scodoc/html_sidebar.py b/app/scodoc/html_sidebar.py
index 5b578d68c9e72ac84444b9a97e86bc677621c497..45497ff2b96966e19350a946d14833308ebda94b 100644
--- a/app/scodoc/html_sidebar.py
+++ b/app/scodoc/html_sidebar.py
@@ -48,26 +48,26 @@ def sidebar_common():
             url_for("users.user_info_page", 
             scodoc_dept=g.scodoc_dept, user_name=current_user.user_name)
             }">{current_user.user_name}</a>
-        <br/><a id="deconnectlink" href="{url_for("auth.logout")}">déconnexion</a>
+        <br><a id="deconnectlink" href="{url_for("auth.logout")}">déconnexion</a>
         </div>
         {sidebar_dept()}
         <h2 class="insidebar">Scolarité</h2>
-        <a href="{scu.ScoURL()}" class="sidebar">Semestres</a> <br/> 
-        <a href="{scu.NotesURL()}" class="sidebar">Programmes</a> <br/> 
-        <a href="{scu.AbsencesURL()}" class="sidebar">Absences</a> <br/>
+        <a href="{scu.ScoURL()}" class="sidebar">Semestres</a> <br>
+        <a href="{scu.NotesURL()}" class="sidebar">Programmes</a> <br>
+        <a href="{scu.AbsencesURL()}" class="sidebar">Absences</a> <br>
         """
     ]
     if current_user.has_permission(
         Permission.ScoUsersAdmin
     ) or current_user.has_permission(Permission.ScoUsersView):
         H.append(
-            f"""<a href="{scu.UsersURL()}" class="sidebar">Utilisateurs</a> <br/>"""
+            f"""<a href="{scu.UsersURL()}" class="sidebar">Utilisateurs</a> <br>"""
         )
 
     if current_user.has_permission(Permission.ScoChangePreferences):
         H.append(
             f"""<a href="{url_for("scolar.edit_preferences", scodoc_dept=g.scodoc_dept)}" 
-            class="sidebar">Paramétrage</a> <br/>"""
+            class="sidebar">Paramétrage</a> <br>"""
         )
 
     return "".join(H)
@@ -84,7 +84,7 @@ def sidebar(etudid: int = None):
     H = [
         f"""<div class="sidebar">
         { sidebar_common() }
-        <div class="box-chercheetud">Chercher étudiant:<br/>
+        <div class="box-chercheetud">Chercher étudiant:<br>
         <form method="get" id="form-chercheetud"
             action="{url_for('scolar.search_etud_in_dept', scodoc_dept=g.scodoc_dept) }">
         <div><input type="text" size="12" class="in-expnom" name="expnom" spellcheck="false"></input></div>
@@ -121,7 +121,7 @@ def sidebar(etudid: int = None):
             nbabsnj = nbabs - nbabsjust
             H.append(
                 f"""<span title="absences du { cur_sem["date_debut"] } au { cur_sem["date_fin"] }">(1/2 j.)
-                <br/>{ nbabsjust } J., { nbabsnj } N.J.</span>"""
+                <br>{ nbabsjust } J., { nbabsnj } N.J.</span>"""
             )
         H.append("<ul>")
         if current_user.has_permission(Permission.ScoAbsChange):
@@ -150,7 +150,7 @@ def sidebar(etudid: int = None):
     # Logo
     H.append(
         f"""<div class="logo-insidebar">
-        <div class="sidebar-bottom"><a href="{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }" class="sidebar">À propos</a><br/>
+        <div class="sidebar-bottom"><a href="{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }" class="sidebar">À propos</a><br>
         <a href="{ scu.SCO_USER_MANUAL }" target="_blank" class="sidebar">Aide</a>
         </div></div>
         <div class="logo-logo">
diff --git a/app/scodoc/safehtml.py b/app/scodoc/safehtml.py
index c7f084229a3ebafbb18ac9438be66ab8136d2fe8..2988a0f32c2d5d1aca673318bd4ab2f549fab16f 100644
--- a/app/scodoc/safehtml.py
+++ b/app/scodoc/safehtml.py
@@ -51,7 +51,7 @@ def convert_html_to_text(s):
 
 
 def newline_to_br(text):
-    return text.replace("\n", "<br/>")
+    return text.replace("\n", "<br>")
 
 
 class HTMLSanitizer(HTMLParser):
diff --git a/app/scodoc/sco_abs_views.py b/app/scodoc/sco_abs_views.py
index ec8eaf1f40b609f298d6ef513a72825f064938b3..98faeb51926e9e7ecbc1e9c4961f45f97e08133d 100644
--- a/app/scodoc/sco_abs_views.py
+++ b/app/scodoc/sco_abs_views.py
@@ -251,7 +251,7 @@ def SignaleAbsenceEtud():  # etudid implied
 <td><input type="text" name="datefin" size="10" class="datepicker"/> <em>j/m/a</em></td>
 </tr>
 </table>
-<br/>
+<br>
 <input type="radio" name="demijournee" value="2" checked>Journée(s)
 &nbsp;<input type="radio" name="demijournee" value="1">Matin(s)
 &nbsp;<input type="radio" name="demijournee" value="0">Après-midi
@@ -260,7 +260,7 @@ def SignaleAbsenceEtud():  # etudid implied
 
 <p>
 <input type="checkbox" name="estjust"/>Absence justifiée.
-<br/>
+<br>
 Raison: <input type="text" name="description" size="42"/> (optionnel)
 </p>
 
@@ -398,13 +398,13 @@ def JustifAbsenceEtud():  # etudid implied
 <td><input type="text" name="datefin" size="10" class="datepicker"/></td>
 </tr>
 </table>
-<br/>
+<br>
 
 <input type="radio" name="demijournee" value="2" checked>Journée(s)
 &nbsp;<input type="radio" name="demijournee" value="1">Matin(s)
 &nbsp;<input type="radio" name="demijournee" value="0">Apr&egrave;s midi
 
-<br/><br/>
+<br><br>
 Raison: <input type="text" name="description" size="42"/> (optionnel)
 
 <p>
@@ -752,9 +752,9 @@ def CalAbs(etudid, sco_year=None):
         ),
         """<table><tr><td><h2>Absences de %(nomprenom)s (%(inscription)s)</h2><p>"""
         % etud,
-        """<b><font color="#EE0000">A : absence NON justifiée</font><br/>
-             <font color="#F8B7B0">a : absence justifiée</font><br/>
-             <font color="#8EA2C6">X : justification sans absence</font><br/>
+        """<b><font color="#EE0000">A : absence NON justifiée</font><br>
+             <font color="#F8B7B0">a : absence justifiée</font><br>
+             <font color="#8EA2C6">X : justification sans absence</font><br>
              %d absences sur l'année, dont %d justifiées (soit %d non justifiées)</b> <em>(%d justificatifs inutilisés)</em>
           </p>
            """
diff --git a/app/scodoc/sco_apogee_csv.py b/app/scodoc/sco_apogee_csv.py
index 852cdf033ee9b69fb7dba8ab80e846e9b6adbad4..db0a64c288d4645f541bb59b01f8569d52f96f17 100644
--- a/app/scodoc/sco_apogee_csv.py
+++ b/app/scodoc/sco_apogee_csv.py
@@ -980,8 +980,8 @@ class ApoData(object):
             log("Colonnes declarees: %s" % declared)
             log("Colonnes presentes: %s" % present)
             raise ScoFormatError(
-                """Fichier Apogee invalide<br/>Colonnes declarees: <tt>%s</tt>
-            <br/>Colonnes presentes: <tt>%s</tt>"""
+                """Fichier Apogee invalide<br>Colonnes declarees: <tt>%s</tt>
+            <br>Colonnes presentes: <tt>%s</tt>"""
                 % (declared, present)
             )
         # l'ensemble de tous les codes des elements apo des semestres:
diff --git a/app/scodoc/sco_archives_etud.py b/app/scodoc/sco_archives_etud.py
index 9311fa457a24bd34e7ec475928a823edef696fe3..c0a40b14b22c3eb05ebac7bcfd8c57e867566767 100644
--- a/app/scodoc/sco_archives_etud.py
+++ b/app/scodoc/sco_archives_etud.py
@@ -295,7 +295,7 @@ def etudarchive_import_files_form(group_id):
         supprimer, via la fiche de chaque étudiant.</b>
          </p>
          <p class="help">Cette page permet de charger en une seule fois les fichiers
-        de plusieurs étudiants.<br/>
+        de plusieurs étudiants.<br>
           Il faut d'abord remplir une feuille excel donnant les noms
           des fichiers (un fichier par étudiant).
          </p>
diff --git a/app/scodoc/sco_bulletins_generator.py b/app/scodoc/sco_bulletins_generator.py
index c62eff057e051918560d2c23221301467617e2f7..4f40a53e16c94e8ae162383a09c7a9edc915d287 100644
--- a/app/scodoc/sco_bulletins_generator.py
+++ b/app/scodoc/sco_bulletins_generator.py
@@ -237,7 +237,7 @@ class BulletinGenerator:
             # compris: reportlab is not thread safe !
             #   see http://two.pairlist.net/pipermail/reportlab-users/2006-June/005037.html
             # (donc maintenant protégé dans ScoDoc par un Lock global)
-            self.diagnostic = "erreur lors de la génération du PDF<br/>"
+            self.diagnostic = "erreur lors de la génération du PDF<br>"
             self.diagnostic += "<pre>" + traceback.format_exc() + "</pre>"
             return []
         return Table(Pt, colWidths=colWidths, style=pdfTableStyle)
diff --git a/app/scodoc/sco_cost_formation.py b/app/scodoc/sco_cost_formation.py
index 0cc0d78b8ed3b843fdc4b85a99636ee7be97e118..167ff770d8520ef04cb5ce08627f7a09dd1cba08 100644
--- a/app/scodoc/sco_cost_formation.py
+++ b/app/scodoc/sco_cost_formation.py
@@ -132,9 +132,9 @@ def formsemestre_table_estim_cost(
         ],
         html_caption="""<div class="help">
                     Estimation du coût de formation basé sur le programme pédagogique
-    et les nombres de groupes.<br/>
-    Coût théorique en heures équivalent TD.<br/>
-    Attention: ne prend en compte que les modules utilisés dans ce semestre.<br/>
+    et les nombres de groupes.<br>
+    Coût théorique en heures équivalent TD.<br>
+    Attention: ne prend en compte que les modules utilisés dans ce semestre.<br>
     Attention: prend en compte <em>tous les modules</em> utilisés dans ce semestre, ce qui
     peut conduire à une sur-estimation du coût s'il y a des modules optionnels
     (dans ce cas, retoucher le tableau excel exporté).
@@ -173,10 +173,10 @@ def formsemestre_estim_cost(
     h = """
     <form name="f" method="get" action="%s">
     <input type="hidden" name="formsemestre_id" value="%s"></input>
-    Nombre de groupes de TD: <input type="text" name="n_group_td" value="%s" onchange="document.f.submit()"/><br/>
+    Nombre de groupes de TD: <input type="text" name="n_group_td" value="%s" onchange="document.f.submit()"/><br>
     Nombre de groupes de TP: <input type="text" name="n_group_tp" value="%s" onchange="document.f.submit()"/>
     &nbsp;Coefficient heures TP: <input type="text" name="coef_tp" value="%s" onchange="document.f.submit()"/>
-    <br/>
+    <br>
     </form>
     """ % (
         request.base_url,
diff --git a/app/scodoc/sco_dept.py b/app/scodoc/sco_dept.py
index 42f3db762bab1bb89d95d29e8885061a906a3805..e31752a048deeaa385b81dbd0a6d205f2fdc17bc 100644
--- a/app/scodoc/sco_dept.py
+++ b/app/scodoc/sco_dept.py
@@ -98,7 +98,7 @@ def index_html(showcodes=0, showsemtable=0):
         H.append(
             """<h2>Aucun utilisateur défini !</h2><p>Pour définir des utilisateurs
         <a href="Users">passez par la page Utilisateurs</a>.
-        <br/>
+        <br>
         Définissez au moins un utilisateur avec le rôle AdminXXX (le responsable du département XXX).
         </p>
         """
diff --git a/app/scodoc/sco_etape_bilan.py b/app/scodoc/sco_etape_bilan.py
index b3575a34e28472ee13a0982c4a5dac6cc367b542..a2dd56bbff9a59211eddb872af76641281a448d7 100644
--- a/app/scodoc/sco_etape_bilan.py
+++ b/app/scodoc/sco_etape_bilan.py
@@ -542,7 +542,7 @@ class EtapeBilan(object):
                 ind_col,
                 comptage,
                 "",
-                json.dumps(self.titres[ind_col].replace("<br/>", " / "))[1:-1],
+                json.dumps(self.titres[ind_col].replace("<br>", " / "))[1:-1],
             )
         elif ind_col == COL_CUMUL:
             javascript = "doFiltrage(%s, %s, '.%s', '*', '%s', '%s', '%s');" % (
@@ -561,7 +561,7 @@ class EtapeBilan(object):
                 ind_col,
                 comptage,
                 json.dumps(self.titres[ind_row])[1:-1],
-                json.dumps(self.titres[ind_col].replace("<br/>", " / "))[1:-1],
+                json.dumps(self.titres[ind_col].replace("<br>", " / "))[1:-1],
             )
         return '<a href="#synthese" onclick="%s">%d</a>' % (javascript, count)
 
@@ -590,9 +590,9 @@ class EtapeBilan(object):
         for key_etape in liste_etapes:
             col_id = self.indicatifs[key_etape]
             col_ids.append(col_id)
-            self.titres[col_id] = "%s<br/>%s" % key_to_values(key_etape)
+            self.titres[col_id] = "%s<br>%s" % key_to_values(key_etape)
         col_ids.append(COL_CUMUL)
-        self.titres[COL_CUMUL] = "Total<br/>semestre"
+        self.titres[COL_CUMUL] = "Total<br>semestre"
 
         rows = []
         for semestre in liste_semestres:
@@ -675,7 +675,7 @@ class EtapeBilan(object):
                 NIP_NON_UNIQUE,
             )
             H.append(
-                'Code(s) nip) partagé(s) par <a href="#synthèse" onclick="%s">%d</a> étudiants<br/>'
+                'Code(s) nip) partagé(s) par <a href="#synthèse" onclick="%s">%d</a> étudiants<br>'
                 % (javascript, self.tag_count[NIP_NON_UNIQUE])
             )
         return "\n".join(H)
@@ -728,11 +728,11 @@ class EtapeBilan(object):
                 prenom = data_etu.data_scodoc["prenom"]
                 link = self.link_etu(etudid, nom)
             tag = ", ".join([tag for tag in sorted(data_etu.tags)])
-            semestre = "<br/>".join(
+            semestre = "<br>".join(
                 [self.link_semestre(sem, True) for sem in data_etu.semestres]
             )
-            annees = "<br/>".join([etape[0] for etape in data_etu.etapes])
-            etapes = "<br/>".join([etape[1] for etape in data_etu.etapes])
+            annees = "<br>".join([etape[0] for etape in data_etu.etapes])
+            etapes = "<br>".join([etape[1] for etape in data_etu.etapes])
             classe = data_etu.ind_row + data_etu.ind_col
             if NIP_NON_UNIQUE in data_etu.tags:
                 classe += " " + NIP_NON_UNIQUE
diff --git a/app/scodoc/sco_etud.py b/app/scodoc/sco_etud.py
index bb869d5a9dbd50aa9ac57cf884180c5876b09913..632f5b72c64d394eb03bbe3a14873b6a05f217e1 100644
--- a/app/scodoc/sco_etud.py
+++ b/app/scodoc/sco_etud.py
@@ -907,7 +907,7 @@ def fill_etuds_info(etuds: list[dict], add_admission=True):
             etud["ilycee"] = "Lycée " + format_lycee(etud["nomlycee"])
             if etud["villelycee"]:
                 etud["ilycee"] += " (%s)" % etud.get("villelycee", "")
-            etud["ilycee"] += "<br/>"
+            etud["ilycee"] += "<br>"
         else:
             if etud.get("codelycee"):
                 etud["ilycee"] = format_lycee_from_code(etud["codelycee"])
diff --git a/app/scodoc/sco_evaluation_check_abs.py b/app/scodoc/sco_evaluation_check_abs.py
index b464cd673b1f8fa47e9d0009c7b14e32a602721b..21e10f533704d806f8145eb6f71919f2e8e17c93 100644
--- a/app/scodoc/sco_evaluation_check_abs.py
+++ b/app/scodoc/sco_evaluation_check_abs.py
@@ -224,7 +224,7 @@ def formsemestre_check_absences_html(formsemestre_id):
             "Vérification absences aux évaluations de ce semestre",
         ),
         """<p class="help">Vérification de la cohérence entre les notes saisies et les absences signalées.
-          Sont listés tous les modules avec des évaluations.<br/>Aucune action n'est effectuée:
+          Sont listés tous les modules avec des évaluations.<br>Aucune action n'est effectuée:
           il vous appartient de corriger les erreurs détectées si vous le jugez nécessaire.
           </p>""",
     ]
diff --git a/app/scodoc/sco_find_etud.py b/app/scodoc/sco_find_etud.py
index 403716431b23f9cddc2331101387e4fc5aa86fc0..163daf747825fe0912e309bb29c3f9d341724f96 100644
--- a/app/scodoc/sco_find_etud.py
+++ b/app/scodoc/sco_find_etud.py
@@ -58,7 +58,7 @@ def form_search_etud(
     <b>{title}</b>
     <input type="text" name="expnom" class="in-expnom" width="12" spellcheck="false" value="">
     <input type="submit" value="Chercher">
-    <br/>(entrer une partie du nom)
+    <br>(entrer une partie du nom)
     """
     )
     if dest_url:
diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py
index 1195c575be5fc86e07bacb52b24c3903d24835cb..f3afb4d792c17785bd3a8df4195ab16289b3d12d 100644
--- a/app/scodoc/sco_formsemestre_edit.py
+++ b/app/scodoc/sco_formsemestre_edit.py
@@ -1659,7 +1659,7 @@ def formsemestre_change_publication_bul(
             "<h2>Confirmer la %s publication des bulletins ?</h2>" % msg,
             helpmsg="""Il est parfois utile de désactiver la diffusion des bulletins,
             par exemple pendant la tenue d'un jury ou avant harmonisation des notes.
-            <br/>
+            <br>
             Ce réglage n'a d'effet que si votre établissement a interfacé ScoDoc et un portail étudiant.
             """,
             dest_url="",
diff --git a/app/scodoc/sco_formsemestre_inscriptions.py b/app/scodoc/sco_formsemestre_inscriptions.py
index 540282e9c02fdde0c6bc05ab3a21ab87c059c6a8..2c4fd681a38e573e6d096aad95da851fc16e6bc3 100644
--- a/app/scodoc/sco_formsemestre_inscriptions.py
+++ b/app/scodoc/sco_formsemestre_inscriptions.py
@@ -893,7 +893,7 @@ def formsemestre_inscrits_ailleurs(formsemestre_id):
         H.append("</ul>")
         H.append("<p>Total: %d étudiants concernés.</p>" % len(etudlist))
         H.append(
-            """<p class="help">Ces étudiants sont inscrits dans le semestre sélectionné et aussi dans d'autres semestres qui se déroulent en même temps ! <br/>Sauf exception, cette situation est anormale:</p>
+            """<p class="help">Ces étudiants sont inscrits dans le semestre sélectionné et aussi dans d'autres semestres qui se déroulent en même temps ! <br>Sauf exception, cette situation est anormale:</p>
         <ul>
         <li>vérifier que les dates des semestres se suivent sans se chevaucher</li>
         <li>ou si besoin désinscrire le(s) étudiant(s) de l'un des semestres (via leurs fiches individuelles).</li>
diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py
index f129a36a21c5d2c0e9b2f6c2fc9ceda0d6bcb209..635b96c5fce783b659e6f16a67a12477442e3e7b 100644
--- a/app/scodoc/sco_formsemestre_validation.py
+++ b/app/scodoc/sco_formsemestre_validation.py
@@ -349,7 +349,7 @@ def formsemestre_validation_etud_form(
 
         H.append("</table>")
         H.append(
-            '<p><br/></p><input type="submit" value="Valider ce choix" disabled="1" id="subut"/>'
+            '<p><br></p><input type="submit" value="Valider ce choix" disabled="1" id="subut"/>'
         )
         H.append("</form>")
 
@@ -1299,7 +1299,7 @@ def check_formation_ues(formation_id):
     H = [
         """<div class="ue_warning"><span>Attention:</span> les UE suivantes de cette formation
         sont utilisées dans des
-        semestres de rangs différents (eg S1 et S3). <br/>Cela peut engendrer des problèmes pour
+        semestres de rangs différents (eg S1 et S3). <br>Cela peut engendrer des problèmes pour
         la capitalisation des UE. Il serait préférable d'essayer de rectifier cette situation:
         soit modifier le programme de la formation (définir des UE dans chaque semestre),
         soit veiller à saisir le bon indice de semestre dans le menu lors de la validation d'une
diff --git a/app/scodoc/sco_import_etuds.py b/app/scodoc/sco_import_etuds.py
index 89338e8bcb5a0a5a0c26f1a2a78d70af59671c1d..d8b5c3e011bf99d9ebbfc4b56e01ba21cac600e0 100644
--- a/app/scodoc/sco_import_etuds.py
+++ b/app/scodoc/sco_import_etuds.py
@@ -302,7 +302,7 @@ def scolars_import_excel_file(
             else:
                 unknown.append(f)
         raise ScoValueError(
-            """Nombre de colonnes incorrect (devrait être %d, et non %d)<br/>
+            """Nombre de colonnes incorrect (devrait être %d, et non %d)<br>
             (colonnes manquantes: %s, colonnes invalides: %s)"""
             % (len(titles), len(fs), list(missing.keys()), unknown)
         )
diff --git a/app/scodoc/sco_import_users.py b/app/scodoc/sco_import_users.py
index e5a3e1e0eaa9dd9b8e89f638ade10127cdaf7b6c..8683c00801efb8908cbcffd2f200d044d47c7cf8 100644
--- a/app/scodoc/sco_import_users.py
+++ b/app/scodoc/sco_import_users.py
@@ -122,7 +122,7 @@ def import_excel_file(datafile, force=""):
             del cols[tit]
     if cols or unknown:
         raise ScoValueError(
-            """colonnes incorrectes (on attend %d, et non %d) <br/>
+            """colonnes incorrectes (on attend %d, et non %d) <br>
             (colonnes manquantes: %s, colonnes invalides: %s)"""
             % (len(TITLES), len(fs), list(cols.keys()), unknown)
         )
diff --git a/app/scodoc/sco_liste_notes.py b/app/scodoc/sco_liste_notes.py
index b3aeba7ade1132bf8c983bec1ea2c0d65d7bb41f..402073b511172e5113ee04dad9468dc1b470f700 100644
--- a/app/scodoc/sco_liste_notes.py
+++ b/app/scodoc/sco_liste_notes.py
@@ -517,18 +517,18 @@ def _make_table_notes(
                 hh += "s"
             hh += ", %d en attente." % (nb_att)
 
-            pdf_title = "<br/> BORDEREAU DE SIGNATURES"
-            pdf_title += "<br/><br/>%(titre)s" % sem
-            pdf_title += "<br/>(%(mois_debut)s - %(mois_fin)s)" % sem
+            pdf_title = "<br> BORDEREAU DE SIGNATURES"
+            pdf_title += "<br><br>%(titre)s" % sem
+            pdf_title += "<br>(%(mois_debut)s - %(mois_fin)s)" % sem
             pdf_title += " semestre %s %s" % (
                 sem["semestre_id"],
                 sem.get("modalite", ""),
             )
-            pdf_title += f"<br/>Notes du module {module.code} - {module.titre}"
-            pdf_title += "<br/>Evaluation : %(description)s " % e
+            pdf_title += f"<br>Notes du module {module.code} - {module.titre}"
+            pdf_title += "<br>Evaluation : %(description)s " % e
             if len(e["jour"]) > 0:
                 pdf_title += " (%(jour)s)" % e
-            pdf_title += "(noté sur %(note_max)s )<br/><br/>" % e
+            pdf_title += "(noté sur %(note_max)s )<br><br>" % e
         else:
             hh = " %s, %s (%d étudiants)" % (
                 E["description"],
@@ -623,11 +623,11 @@ def _make_table_notes(
         commentkeys.sort(key=lambda x: int(x[1]))
         for (comment, key) in commentkeys:
             C.append(
-                '<span class="colcomment">(%s)</span> <em>%s</em><br/>' % (key, comment)
+                '<span class="colcomment">(%s)</span> <em>%s</em><br>' % (key, comment)
             )
         if commentkeys:
             C.append(
-                '<span><a class=stdlink" href="evaluation_list_operations?evaluation_id=%s">Gérer les opérations</a></span><br/>'
+                '<span><a class=stdlink" href="evaluation_list_operations?evaluation_id=%s">Gérer les opérations</a></span><br>'
                 % E["evaluation_id"]
             )
         eval_info = "xxx"
diff --git a/app/scodoc/sco_lycee.py b/app/scodoc/sco_lycee.py
index 483eaa3ae54ab3682f964a5190fd4dc0ecca060b..1aba7b89afce4b978053eb71a903d1859d7436dc 100644
--- a/app/scodoc/sco_lycee.py
+++ b/app/scodoc/sco_lycee.py
@@ -229,7 +229,7 @@ def js_coords_lycees(etuds_by_lycee):
             lyc = etuds_by_lycee[codelycee][0]
             if not lyc.get("positionlycee", False):
                 continue
-            listeetuds = "<br/>%d étudiants: " % len(
+            listeetuds = "<br>%d étudiants: " % len(
                 etuds_by_lycee[codelycee]
             ) + ", ".join(
                 [
diff --git a/app/scodoc/sco_moduleimpl_inscriptions.py b/app/scodoc/sco_moduleimpl_inscriptions.py
index 44978ba7dc4da22213dbf17de6952753fb3b4792..d15eb229a1ba9a7289ee12980578a70056ada0d6 100644
--- a/app/scodoc/sco_moduleimpl_inscriptions.py
+++ b/app/scodoc/sco_moduleimpl_inscriptions.py
@@ -152,7 +152,7 @@ def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
         { _make_menu(partitions, "Ajouter", "true") }
         { _make_menu(partitions, "Enlever", "false")}
         </tr></table>
-        <p><br/></p>
+        <p><br></p>
         <table class="sortable" id="mi_table">
         <tr>
             <th>Nom</th>
diff --git a/app/scodoc/sco_page_etud.py b/app/scodoc/sco_page_etud.py
index 12828e870b9ec9e29015edda390615c3c7db5f7e..220e941620b64aa8eb0c476c129d6de63e354935 100644
--- a/app/scodoc/sco_page_etud.py
+++ b/app/scodoc/sco_page_etud.py
@@ -189,7 +189,7 @@ def ficheEtud(etudid=None):
         else:
             info["paysdomicile"] = ""
     if info["telephone"] or info["telephonemobile"]:
-        info["telephones"] = "<br/>%s &nbsp;&nbsp; %s" % (
+        info["telephones"] = "<br>%s &nbsp;&nbsp; %s" % (
             info["telephonestr"],
             info["telephonemobilestr"],
         )
@@ -506,9 +506,9 @@ def ficheEtud(etudid=None):
 <b>Ajouter une annotation sur %(nomprenom)s: </b>
 <table><tr>
 <tr><td><textarea name="comment" rows="4" cols="50" value=""></textarea>
-<br/><font size=-1>
+<br><font size=-1>
 <i>Ces annotations sont lisibles par tous les enseignants et le secrétariat.</i>
-<br/>
+<br>
 <i>L'annotation commençant par "PE:" est un avis de poursuite d'études.</i>
 </font>
 </td></tr>
diff --git a/app/scodoc/sco_placement.py b/app/scodoc/sco_placement.py
index 7682d1ea47d998290f6715a1189fb3b0d9bdbd0d..898bd31b93b300a686f9acdfa4cd6ffbcd01147a 100644
--- a/app/scodoc/sco_placement.py
+++ b/app/scodoc/sco_placement.py
@@ -338,7 +338,7 @@ class PlacementRunner:
         return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
 
     def _production_pdf(self):
-        pdf_title = "<br/>".join(self.desceval)
+        pdf_title = "<br>".join(self.desceval)
         pdf_title += (
             "\nDate : %(jour)s - Horaire : %(heure_debut)s à %(heure_fin)s"
             % self.eval_data
diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py
index f6315583646efeb83dc2e0b15aea703c4d685c72..3ceba6085d31d3e8bd52544120afaed94fa83798 100644
--- a/app/scodoc/sco_preferences.py
+++ b/app/scodoc/sco_preferences.py
@@ -43,7 +43,7 @@ Au niveau du code interface, on défini pour chaque préférence:
  - initvalue : valeur initiale
  - explanation: explication en français
  - size: longueur du chap texte
- - input_type: textarea,separator,... type de widget TrivialFormulator a utiliser
+ - input_type: textarea, separator, ... type de widget TrivialFormulator a utiliser
  - rows, rols: geometrie des textareas
  - category: misc ou bul ou page_bulletins ou abs ou general ou portal 
              ou pdf ou pvpdf ou ...
@@ -202,7 +202,7 @@ _INSTALLED_FONTS = ", ".join(sco_pdf.get_available_font_names())
 
 PREF_CATEGORIES = (
     # sur page "Paramètres"
-    ("general", {}),
+    ("general", {"title": ""}),  # voir paramètre titlr de TrivialFormulator
     ("misc", {"title": "Divers"}),
     ("apc", {"title": "BUT et Approches par Compétences"}),
     ("abs", {"title": "Suivi des absences", "related": ("bul",)}),
@@ -1008,7 +1008,7 @@ class BasePreferences(object):
             (
                 "PV_LETTER_DIPLOMA_SIGNATURE",
                 {
-                    "initvalue": """Le %(DirectorTitle)s, <br/>%(DirectorName)s""",
+                    "initvalue": """Le %(DirectorTitle)s, <br>%(DirectorName)s""",
                     "title": """Signature des lettres individuelles de diplôme""",
                     "explanation": """%(DirectorName)s et %(DirectorTitle)s remplacés""",
                     "input_type": "textarea",
@@ -1020,8 +1020,8 @@ class BasePreferences(object):
             (
                 "PV_LETTER_PASSAGE_SIGNATURE",
                 {
-                    "initvalue": """Pour le Directeur de l'IUT<br/>
-                        et par délégation<br/>
+                    "initvalue": """Pour le Directeur de l'IUT<br>
+                        et par délégation<br>
                         Le Chef du département""",
                     "title": """Signature des lettres individuelles de passage d'un semestre à l'autre""",
                     "explanation": """%(DirectorName)s et %(DirectorTitle)s remplacés""",
@@ -1056,7 +1056,7 @@ class BasePreferences(object):
                         <para leftindent="%(pv_htab1)s">%(codepostaldomicile)s %(villedomicile)s</para>
 
                         <para spaceBefore="25mm" fontSize="14" alignment="center">
-                        <b>Jury de %(type_jury)s  <br/> %(titre_formation)s</b>
+                        <b>Jury de %(type_jury)s  <br> %(titre_formation)s</b>
                         </para>
 
                         <para spaceBefore="10mm" fontSize="14" leftindent="0">
@@ -1499,7 +1499,7 @@ class BasePreferences(object):
                 "bul_pdf_sig_left",
                 {
                     "initvalue": """<para>La direction des études
-                        <br/>
+                        <br>
                         %(responsable)s
                         </para>
                         """,
@@ -1515,7 +1515,7 @@ class BasePreferences(object):
                 "bul_pdf_sig_right",
                 {
                     "initvalue": """<para>Le chef de département
-                        <br/>
+                        <br>
                         %(ChiefDeptName)s
                         </para>
                         """,
@@ -1891,7 +1891,7 @@ class BasePreferences(object):
                     "explanation": """si cette  adresse est indiquée, TOUS les mails
                     envoyés par ScoDoc de ce département vont aller vers elle
                     AU LIEU DE LEUR DESTINATION NORMALE !""",
-                    "size": 30,
+                    "size": 60,
                     "category": "debug",
                     "only_global": True,
                 },
@@ -1935,7 +1935,7 @@ class BasePreferences(object):
                 value = _get_pref_default_value_from_config(name, pref[1])
                 self.default[name] = value
                 self.prefs[None][name] = value
-                log("creating missing preference for %s=%s" % (name, value))
+                log(f"creating missing preference for {name}={value}")
                 # add to db table
                 self._editor.create(
                     cnx, {"dept_id": self.dept_id, "name": name, "value": value}
@@ -1999,7 +1999,7 @@ class BasePreferences(object):
 
             if not pdb:
                 # crée préférence
-                log("create pref sem=%s %s=%s" % (formsemestre_id, name, value))
+                log(f"create pref sem={formsemestre_id} {name}={value}")
                 self._editor.create(
                     cnx,
                     {
@@ -2036,7 +2036,7 @@ class BasePreferences(object):
 
     def set(self, formsemestre_id, name, value):
         if not name or name[0] == "_" or name not in self.prefs_name:
-            raise ValueError("invalid preference name: %s" % name)
+            raise ValueError(f"invalid preference name: {name}")
         if formsemestre_id and name in self.prefs_only_global:
             raise ValueError("pref %s is always defined globaly")
         if not formsemestre_id in self.prefs:
@@ -2055,7 +2055,7 @@ class BasePreferences(object):
             cnx, args={"formsemestre_id": formsemestre_id, "name": name}
         )
         if pdb:
-            log("deleting pref sem=%s %s" % (formsemestre_id, name))
+            log(f"deleting pref sem={formsemestre_id} {name}")
             assert pdb[0]["dept_id"] == self.dept_id
             self._editor.delete(cnx, pdb[0]["pref_id"])
             sco_cache.invalidate_formsemestre()  # > modif preferences
@@ -2067,14 +2067,18 @@ class BasePreferences(object):
         self.load()
         H = [
             html_sco_header.sco_header(page_title="Préférences"),
-            "<h2>Préférences globales pour %s</h2>" % scu.ScoURL(),
+            f"<h2>Préférences globales pour {scu.ScoURL()}</h2>",
             # f"""<p><a href="{url_for("scodoc.configure_logos", scodoc_dept=g.scodoc_dept)
             # }">modification des logos du département (pour documents pdf)</a></p>"""
             # if current_user.is_administrator()
             # else "",
-            """<p class="help">Ces paramètres s'appliquent par défaut à tous les semestres, sauf si ceux-ci définissent des valeurs spécifiques.</p>
-              <p class="msg">Attention: cliquez sur "Enregistrer les modifications" en bas de page pour appliquer vos changements !</p>
-              """,
+            """<p class="help">Ces paramètres s'appliquent par défaut à tous les semestres,
+                sauf si ceux-ci définissent des valeurs spécifiques.
+            </p>
+            <p class="msg">Attention: cliquez sur "Enregistrer les modifications"
+                en bas de page pour appliquer vos changements !
+            </p>
+            """,
         ]
         form = self.build_tf_form()
         tf = TrivialFormulator(
@@ -2083,6 +2087,9 @@ class BasePreferences(object):
             form,
             initvalues=self.prefs[None],
             submitlabel="Enregistrer les modifications",
+            title="Département et institution",
+            before_table="<details><summary>{title}</summary>",
+            after_table="</details>",
         )
         if tf[0] == 0:
             return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
@@ -2094,7 +2101,7 @@ class BasePreferences(object):
             self.save()
             return flask.redirect(scu.ScoURL() + "?head_message=Préférences modifiées")
 
-    def build_tf_form(self, categories=[], formsemestre_id=None):
+    def build_tf_form(self, categories: list[str] = None, formsemestre_id: int = None):
         """Build list of elements for TrivialFormulator.
         If formsemestre_id is not specified, edit global prefs.
         """
@@ -2119,7 +2126,7 @@ class BasePreferences(object):
                             onclick="set_global_pref(this, '{pref_name}');"
                             >utiliser paramètre global</span>"""
                     if formsemestre_id and self.is_global(formsemestre_id, pref_name):
-                        # valeur actuelle globale (ou vient d'etre supprimee localement):
+                        # valeur actuelle globale (ou vient d'etre supprimée localement):
                         # montre la valeur et menus pour la rendre locale
                         descr["readonly"] = True
                         menu_global = f"""<select class="tf-selglobal"
@@ -2138,8 +2145,11 @@ class BasePreferences(object):
                 if title:
                     form.append(
                         (
-                            f"sep_{cat}",
-                            {"input_type": "separator", "title": f"<h3>{title}</h3>"},
+                            f"table_{cat}",
+                            {
+                                "input_type": "table_separator",
+                                "title": f"{title}",
+                            },
                         )
                     )
                 subtitle = cat_descr.get("subtitle", None)
@@ -2246,6 +2256,9 @@ function set_global_pref(el, pref_name) {
             initvalues=self,
             cssclass="sco_pref",
             submitlabel="Enregistrer les modifications",
+            title="Département et institution",
+            before_table="<details><summary>{title}</summary>",
+            after_table="</details>",
         )
         dest_url = (
             scu.NotesURL()
diff --git a/app/scodoc/sco_pvpdf.py b/app/scodoc/sco_pvpdf.py
index 39056469c4849414a0f6acc2475e23005c9b571b..f1dae2536913c8a85f34c14874eb9adfe19623e7 100644
--- a/app/scodoc/sco_pvpdf.py
+++ b/app/scodoc/sco_pvpdf.py
@@ -340,11 +340,11 @@ class PVTemplate(CourrierIndividuelTemplate):
 def _simulate_br(paragraph_txt: str, para="<para>") -> str:
     """Reportlab bug turnaround (could be removed in a future version).
     p is a string with Reportlab intra-paragraph XML tags.
-    Replaces <br/> (currently ignored by Reportlab) by </para><para>
-    Also replaces <br> by <br/>
+    Replaces <br> (currently ignored by Reportlab) by </para><para>
+    Also replaces <br> by <br>
     """
     return ("</para>" + para).join(
-        re.split(r"<.*?br.*?/>", paragraph_txt.replace("<br>", "<br/>"))
+        re.split(r"<.*?br.*?/>", paragraph_txt.replace("<br>", "<br>"))
     )
 
 
@@ -515,7 +515,7 @@ def pdf_lettre_individuelle(sem, decision, etud: Identite, params, signature=Non
     params.update(decision["identite"])
     # fix domicile
     if params["domicile"]:
-        params["domicile"] = params["domicile"].replace("\\n", "<br/>")
+        params["domicile"] = params["domicile"].replace("\\n", "<br>")
 
     # UE capitalisées:
     if decision["decisions_ue"] and decision["decisions_ue_descr"]:
@@ -649,8 +649,8 @@ def add_apc_infos(formsemestre: FormSemestre, params: dict, decision: dict):
         params["decision_sem_descr"] = decision_annee.get("code") or ""
         params[
             "decision_ue_txt"
-        ] = f"""{params["decision_ue_txt"]}<br/>
-            <b>Niveaux de compétences:</b><br/> {decision.get("descr_decisions_rcue") or ""}
+        ] = f"""{params["decision_ue_txt"]}<br>
+            <b>Niveaux de compétences:</b><br> {decision.get("descr_decisions_rcue") or ""}
         """
 
 
diff --git a/app/scodoc/sco_report.py b/app/scodoc/sco_report.py
index 31e9d90ef80f0fcbcec85f7ced215d9ad7af6c70..725337ae0f96f55f13f1a77a448b97615466c9db 100644
--- a/app/scodoc/sco_report.py
+++ b/app/scodoc/sco_report.py
@@ -384,7 +384,7 @@ def formsemestre_report_counts(
         else:
             checked = ""
         F.append(
-            '<br/><input type="checkbox" name="only_primo" onchange="document.f.submit()" %s>Restreindre aux primo-entrants</input>'
+            '<br><input type="checkbox" name="only_primo" onchange="document.f.submit()" %s>Restreindre aux primo-entrants</input>'
             % checked
         )
         F.append(
@@ -928,7 +928,7 @@ def _gen_form_selectetuds(
     else:
         checked = ""
     F.append(
-        '<br/><input type="checkbox" name="only_primo" onchange="javascript: submit(this);" %s/>Restreindre aux primo-entrants'
+        '<br><input type="checkbox" name="only_primo" onchange="javascript: submit(this);" %s/>Restreindre aux primo-entrants'
         % checked
     )
     F.append(
diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py
index 1dbf0ba3a17c981903679bbc4662282223e67710..d110f3ba3ecf6db3e74c83a6d9c292ae4127cb4a 100644
--- a/app/scodoc/sco_saisie_notes.py
+++ b/app/scodoc/sco_saisie_notes.py
@@ -237,7 +237,7 @@ def do_evaluation_upload_xls():
                 ni += 1
         except:
             diag.append(
-                'Erreur: Ligne invalide ! (erreur ligne %d)<br/>"%s"'
+                'Erreur: Ligne invalide ! (erreur ligne %d)<br>"%s"'
                 % (ni, str(lines[ni]))
             )
             raise InvalidNoteValue()
diff --git a/app/scodoc/sco_semset.py b/app/scodoc/sco_semset.py
index 37b39fa839ff02b369459740ea930f0248527e7f..b32ee32c05fff1f771b5e74e7b5db45a92abc433 100644
--- a/app/scodoc/sco_semset.py
+++ b/app/scodoc/sco_semset.py
@@ -139,7 +139,7 @@ class SemSet(dict):
         # Construction du ou des lien(s) vers le semestre
         pattern = '<a class="stdlink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titreannee)s</a>'
         self["semlinks"] = [(pattern % sem) for sem in self.sems]
-        self["semtitles_str"] = "<br/>".join(self["semlinks"])
+        self["semtitles_str"] = "<br>".join(self["semlinks"])
 
     def fill_formsemestres(self):
         for sem in self.sems:
@@ -290,16 +290,15 @@ class SemSet(dict):
                 % (self["semset_id"], sem["formsemestre_id"])
             )
             H.append(
-                "<br/>Etapes: <tt>%(etapes_apo_str)s</tt>, %(nbinscrits)s inscrits"
-                % sem
+                "<br>Etapes: <tt>%(etapes_apo_str)s</tt>, %(nbinscrits)s inscrits" % sem
             )
-            H.append("<br/>Elément Apogée année: ")
+            H.append("<br>Elément Apogée année: ")
             if sem["elt_annee_apo"]:
                 H.append("<tt>%(elt_annee_apo)s</tt>" % sem)
             else:
                 H.append('<span style="color: red;">manquant</span>')
 
-            H.append("<br/>Elément Apogée semestre: ")
+            H.append("<br>Elément Apogée semestre: ")
             if sem["elt_sem_apo"]:
                 H.append("<tt>%(elt_sem_apo)s</tt>" % sem)
             else:
diff --git a/app/scodoc/sco_synchro_etuds.py b/app/scodoc/sco_synchro_etuds.py
index bfcd0f81f6909e856f6d1abc212290fd74534f5d..a4b99b5844768f521bf7dd771da280e2277d85cd 100644
--- a/app/scodoc/sco_synchro_etuds.py
+++ b/app/scodoc/sco_synchro_etuds.py
@@ -463,7 +463,7 @@ def list_synch(sem, anneeapogee=None):
                 "id": "etuds_noninscrits",
                 "title": "Étudiants non inscrits dans ce semestre",
                 "help": """Ces étudiants sont déjà connus par ScoDoc, sont inscrits dans cette étape Apogée mais ne sont pas inscrits à ce semestre ScoDoc. Cochez les étudiants à inscrire.""",
-                "comment": """ dans ScoDoc et Apogée, <br/>mais pas inscrits
+                "comment": """ dans ScoDoc et Apogée, <br>mais pas inscrits
                       dans ce semestre""",
                 "title_target": "",
                 "with_checkbox": True,
diff --git a/app/scodoc/sco_trombino.py b/app/scodoc/sco_trombino.py
index 15f0e0c0b0f8a423980fc2e2947552fcc5d42c3d..a5239c78e81e181ecbb310616375b443e43035fd 100644
--- a/app/scodoc/sco_trombino.py
+++ b/app/scodoc/sco_trombino.py
@@ -506,7 +506,7 @@ def photos_import_files_form(group_ids=()):
          de chaque étudiant (menu "Étudiant" / "Changer la photo").</b>
          </p>
          <p class="help">Cette page permet de charger en une seule fois les photos
-         de plusieurs étudiants.<br/>
+         de plusieurs étudiants.<br>
           Il faut d'abord remplir une feuille excel donnant les noms
           des fichiers images (une image par étudiant).
          </p>
diff --git a/app/scodoc/sco_ue_external.py b/app/scodoc/sco_ue_external.py
index f75d4f811ce9fda7020ced9853b6ead97d333a83..ca1bdba7b95dcde4751c2a7b3b285fa2896e4d54 100644
--- a/app/scodoc/sco_ue_external.py
+++ b/app/scodoc/sco_ue_external.py
@@ -238,7 +238,7 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
             javascripts=["js/sco_ue_external.js"],
         ),
         """<p class="help">Cette page permet d'indiquer que l'étudiant a suivi une UE 
-    dans un autre établissement et qu'elle doit être intégrée dans le semestre courant.<br/>
+    dans un autre établissement et qu'elle doit être intégrée dans le semestre courant.<br>
     La note (/20) obtenue par l'étudiant doit toujours être spécifiée.</br>
     On peut choisir une UE externe existante (dans le menu), ou bien en créer une, qui sera 
     alors ajoutée à la formation.
diff --git a/app/scodoc/sco_users.py b/app/scodoc/sco_users.py
index bbb25f80df5ef1747d5785011164a4440a0950ba..a0f08c15c84b072a7400e1910d5a4bb437dfd438 100644
--- a/app/scodoc/sco_users.py
+++ b/app/scodoc/sco_users.py
@@ -290,7 +290,7 @@ def check_modif_user(
             (si ok est faux, l'utilisateur peut quand même forcer la creation)
         - msg: message warning à presenter à l'utilisateur
     """
-    MSG_OPT = """<br/>Attention: (vous pouvez forcer l'opération en cochant "<em>Ignorer les avertissements</em>" en bas de page)"""
+    MSG_OPT = """<br>Attention: (vous pouvez forcer l'opération en cochant "<em>Ignorer les avertissements</em>" en bas de page)"""
     # ce login existe ?
     user = _user_list(user_name)
     if edit and not user:  # safety net, le user_name ne devrait pas changer
diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css
index ed16cbb632dd72d8c36d1be600887845e5662a62..b5cdee32cac294a6bac0e1c98581dfa2eaef3025 100644
--- a/app/static/css/scodoc.css
+++ b/app/static/css/scodoc.css
@@ -2863,10 +2863,13 @@ select.tf-selglobal {
 }
 
 td.tf-fieldlabel {
-  /* font-weight: bold; */
   vertical-align: top;
 }
 
+td.tf-field {
+  max-width: 800px;
+}
+
 .tf-comment {
   font-size: 80%;
   font-style: italic;
@@ -2876,6 +2879,12 @@ td.tf-fieldlabel {
   font-style: italic;
 }
 
+#tf details summary {
+  font-size: 130%;
+  margin-top: 6px;
+  margin-bottom: 6px;
+}
+
 .radio_green {
   background-color: green;
 }
diff --git a/app/views/absences.py b/app/views/absences.py
index 3deb173f92cfeb2e9e92767a2df07af8509d8053..d076fe78b3b5f44947c9d9a3955b1450878acca6 100644
--- a/app/views/absences.py
+++ b/app/views/absences.py
@@ -699,7 +699,7 @@ def _gen_form_saisie_groupe(
     }
     </script>
     <div id="AjaxDiv"></div>
-    <br/>
+    <br>
     <table rules="cols" frame="box" class="abs_form_table">
     <tr><th class="formabs_contetud">%d étudiants</th>
     """
@@ -990,7 +990,7 @@ def EtatAbsencesGr(
         ),
         html_title=html_sco_header.html_sem_header(title, with_page_header=False)
         + form_date,
-        # "<p>Période du %s au %s (nombre de <b>demi-journées</b>)<br/>" % (debut, fin),
+        # "<p>Période du %s au %s (nombre de <b>demi-journées</b>)<br>" % (debut, fin),
         base_url="%s&formsemestre_id=%s&debut=%s&fin=%s"
         % (groups_infos.base_url, formsemestre_id, debut, fin),
         filename="etat_abs_"
@@ -1003,7 +1003,7 @@ def EtatAbsencesGr(
 Justifs non utilisés: nombre de demi-journées avec justificatif mais sans absences relevées.
 </p>
 <p class="help">
-Cliquez sur un nom pour afficher le calendrier des absences<br/>
+Cliquez sur un nom pour afficher le calendrier des absences<br>
 ou entrez une date pour visualiser les absents un jour donné&nbsp;:
 </p>
 <div style="margin-bottom: 10px;">
@@ -1422,7 +1422,7 @@ def process_billet_absence_form(billet_id):
         H.append(tab.html())
         if billet.justified:
             H.append(
-                """<p>L'étudiant pense pouvoir justifier cette absence.<br/>
+                """<p>L'étudiant pense pouvoir justifier cette absence.<br>
                 <em>Vérifiez le justificatif avant d'enregistrer.</em></p>"""
             )
         F = f"""<p><a class="stdlink" href="{
@@ -1437,7 +1437,7 @@ def process_billet_absence_form(billet_id):
             </p>
             """
 
-        return "\n".join(H) + "<br/>" + tf[1] + F + html_sco_header.sco_footer()
+        return "\n".join(H) + "<br>" + tf[1] + F + html_sco_header.sco_footer()
     elif tf[0] == -1:
         return flask.redirect(scu.ScoURL())
     else:
diff --git a/app/views/notes.py b/app/views/notes.py
index 30b10b383db95f41062dffddf0fc31e4d3e10326..ae42f4879fb1c19bfc804bc3048e72f0d9a42743 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -852,7 +852,7 @@ def formsemestre_change_lock(formsemestre_id, dialog_confirmed=False):
             helpmsg="""Les notes d'un semestre verrouillé ne peuvent plus être modifiées.
             Un semestre verrouillé peut cependant être déverrouillé facilement à tout moment
             (par son responsable ou un administrateur).
-            <br/>
+            <br>
             Le programme d'une formation qui a un semestre verrouillé ne peut plus être modifié.
             """,
             dest_url="",
@@ -3045,12 +3045,12 @@ def check_sem_integrity(formsemestre_id, fix=False):
     if bad_ue:
         H += [
             "<h2>Modules d'une autre formation que leur UE:</h2>",
-            "<br/>".join([str(x) for x in bad_ue]),
+            "<br>".join([str(x) for x in bad_ue]),
         ]
     if bad_sem:
         H += [
             "<h2>Module du semestre dans une autre formation:</h2>",
-            "<br/>".join([str(x) for x in bad_sem]),
+            "<br>".join([str(x) for x in bad_sem]),
         ]
     if not bad_ue and not bad_sem:
         H.append("<p>Aucun problème à signaler !</p>")
@@ -3106,7 +3106,7 @@ def check_form_integrity(formation_id, fix=False):
                 if mod["formation_id"] != formation_id:
                     bad.append(mod)
     if bad:
-        txth = "<br/>".join([str(x) for x in bad])
+        txth = "<br>".join([str(x) for x in bad])
         txt = "\n".join([str(x) for x in bad])
         log("check_form_integrity: formation_id=%s\ninconsistencies:" % formation_id)
         log(txt)
@@ -3162,7 +3162,7 @@ def check_formsemestre_integrity(formsemestre_id):
         diag = ["OK"]
         log("ok")
     return (
-        html_sco_header.sco_header() + "<br/>".join(diag) + html_sco_header.sco_footer()
+        html_sco_header.sco_header() + "<br>".join(diag) + html_sco_header.sco_footer()
     )
 
 
diff --git a/app/views/scolar.py b/app/views/scolar.py
index 58211d7a396403f2c5bbcd6cd596c10b39970624..7b1079e927251a2f5dc2dec27a80ee32aff6e2a5 100644
--- a/app/views/scolar.py
+++ b/app/views/scolar.py
@@ -2141,9 +2141,9 @@ def form_students_import_infos_admissions(formsemestre_id=None):
             Seuls les étudiants actuellement inscrits dans ce semestre ScoDoc seront affectés, 
             les autres lignes de la feuille seront ignorées. Et seules les colonnes intéressant ScoDoc 
             seront importées: il est inutile d'éliminer les autres.
-            <br/>
+            <br>
             <em>Seules les données "admission" seront modifiées (et pas l'identité de l'étudiant).</em>
-            <br/>
+            <br>
             <em>Les colonnes "nom" et "prenom" sont requises, ou bien une colonne "etudid".</em>
             </p>
             <p>
@@ -2240,7 +2240,7 @@ def formsemestre_import_etud_admission(formsemestre_id, import_email=True):
         H.append("<h3>Adresses mails modifiées:</h3>")
         for (info, new_mail) in changed_mails:
             H.append(
-                "%s: <tt>%s</tt> devient <tt>%s</tt><br/>"
+                "%s: <tt>%s</tt> devient <tt>%s</tt><br>"
                 % (info["nom"], info["email"], new_mail)
             )
     return "\n".join(H) + html_sco_header.sco_footer()
diff --git a/app/views/users.py b/app/views/users.py
index 7b8d98813e032f4ca4c935e2a766781847816641..8c726f1a36e3b3ffe341803350e40ac7c9c8f4d7 100644
--- a/app/views/users.py
+++ b/app/views/users.py
@@ -936,7 +936,7 @@ def change_password(user_name, password, password2):
         if not is_valid_password(password):
             H.append(
                 f"""<p><b>ce mot de passe n'est pas assez compliqué !</b>
-                <br/>(oui, il faut un mot de passe vraiment compliqué !)
+                <br>(oui, il faut un mot de passe vraiment compliqué !)
                 </p>
                 <p><a href="{dest_url}" class="stdlink">Recommencer</a></p>
                 """