krz/orgo

Lightning fast org-mode static site generator.

clone: git clone https://gitbay.org/krz/orgo.git

2ff694787076f19a71fb0378186033fa937fb278

verified · cmc

author: Christian Cleberg <hello@cleberg.net> · 2026-08-11T21:12:36Z

0.19.1: name the footnote back-links

`<a class="footnote-back" href="#fnr-1">↩</a>` has that glyph as its entire
accessible name, so a screen reader announces "left arrow with hook" once per
note and the reader cannot tell which reference any of them returns to. Each one
now carries `aria-label="Back to reference N"`, and the section carries
`aria-label="Footnotes"` — a `<hr>` is a picture of a section break rather than
a section, and the landmark was otherwise announced as an unnamed "section".

No visual change, so no stylesheet has to move.

Also documents template partials in the guide: a snippet that is not really
layout — an invitation to reply, a licence line — is better as its own file, and
the render key follows `{% include %}` so editing one re-renders exactly its
users.
 CHANGELOG.md                               |  7 +++++++
 Cargo.lock                                 |  2 +-
 Cargo.toml                                 |  2 +-
 docs/guide/04-templates.org                | 18 ++++++++++++++++++
 src/render.rs                              | 13 +++++++++++--
 tests/constructs.rs                        | 17 +++++++++++++++++
 tests/snapshots/site__footnote_render.snap |  8 ++++----
 tests/snapshots/site__site_guide_html.snap |  4 ++--
 8 files changed, 61 insertions(+), 10 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6f3a002..5f9ef86 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,13 @@ Two conventions worth knowing before reading:
 Versions follow the compatibility promise in the README: config keys, template variables,
 CLI flags and URLs are the stable surface.
 
+## 0.19.1
+
+- Footnote back-links carry `aria-label="Back to reference N"`, and the notes section is
+  labelled. A link whose only visible content is `↩` has that glyph as its whole
+  accessible name, so a screen reader announced "left arrow with hook" once per note with
+  no way to tell them apart.
+
 ## 0.19.0
 
 - **Full-content collections.** `include_content = true` gives a listing template each
diff --git a/Cargo.lock b/Cargo.lock
index 889e229..5c1f0df 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -675,7 +675,7 @@ dependencies = [
 
 [[package]]
 name = "org-ssg"
-version = "0.19.0"
+version = "0.19.1"
 dependencies = [
  "anyhow",
  "blake3",
diff --git a/Cargo.toml b/Cargo.toml
index 3ea7be8..9a5c39e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "org-ssg"
-version = "0.19.0"
+version = "0.19.1"
 edition = "2021"
 description = "Org-mode static site generator that renders the org element tree straight to HTML"
 license = "MIT"
diff --git a/docs/guide/04-templates.org b/docs/guide/04-templates.org
index afb9197..9d43d33 100644
--- a/docs/guide/04-templates.org
+++ b/docs/guide/04-templates.org
@@ -77,6 +77,24 @@ page.
 
 Subdirectories work, so ={% include "partials/header.html" %}= does what you expect.
 
+** Partials keep a snippet out of a layout
+
+A block of content that is not really layout — an invitation to reply, a licence line, a
+donation ask — is better as its own file than as a line inside =base.html=:
+
+#+BEGIN_SRC html
+{% extends "base.html" %}
+{% block content %}
+{{ body | safe }}
+{% include "reply.html" %}
+{% endblock %}
+#+END_SRC
+
+Emptying =reply.html= removes it everywhere; editing it re-renders exactly the pages that
+include it, because the render key follows includes. And because the *page* chooses its
+layout, an individual page opts in with =#+TEMPLATE: post.html= or out with
+=#+TEMPLATE: base.html=, without a rule deciding for a whole directory.
+
 * Variables
 
 ** body
diff --git a/src/render.rs b/src/render.rs
index 205db67..65b697c 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -708,7 +708,12 @@ impl Renderer<'_> {
         let order = self.order.clone();
         let inline_defs = self.inline_defs.clone();
         let block_defs = self.block_defs.clone();
-        out.push_str("<section class=\"footnotes\">\n<hr>\n<ol>\n");
+        // Named, because a `<hr>` is a picture of a section break rather than a section:
+        // without the label this landmark is announced as "section" and the reader has to
+        // guess what they have reached.
+        out.push_str(
+            "<section class=\"footnotes\" aria-label=\"Footnotes\">\n<hr>\n<ol>\n",
+        );
         for (idx, label) in order.iter().enumerate() {
             let n = idx + 1;
             out.push_str(&format!("<li id=\"fn-{n}\">"));
@@ -719,8 +724,12 @@ impl Renderer<'_> {
                     self.render_element(el, out);
                 }
             }
+            // The glyph is the whole visible link, so it is also the whole accessible
+            // name: a screen reader would otherwise announce "left arrow with hook",
+            // identically, once per note.
             out.push_str(&format!(
-                " <a class=\"footnote-back\" href=\"#fnr-{n}\">&#8617;</a></li>\n"
+                " <a class=\"footnote-back\" href=\"#fnr-{n}\" \
+                 aria-label=\"Back to reference {n}\">&#8617;</a></li>\n"
             ));
         }
         out.push_str("</ol>\n</section>\n");
diff --git a/tests/constructs.rs b/tests/constructs.rs
index a54db7f..fa1df2f 100644
--- a/tests/constructs.rs
+++ b/tests/constructs.rs
@@ -752,3 +752,20 @@ fn an_unexpanded_include_reports_itself() {
         doc.diagnostics[0]
     );
 }
+
+/// A back-link whose whole visible content is `↩` has that glyph as its whole accessible
+/// name, so a screen reader announces "left arrow with hook" once per note and the reader
+/// cannot tell which reference each one returns to.
+#[test]
+fn footnote_links_and_section_are_labelled() {
+    let html = html_of("Text[fn:1] and more[fn:2].\n\n[fn:1] First.\n\n[fn:2] Second.\n");
+    assert!(
+        html.contains("<section class=\"footnotes\" aria-label=\"Footnotes\">"),
+        "the landmark is named:\n{html}"
+    );
+    assert!(
+        html.contains("aria-label=\"Back to reference 1\"")
+            && html.contains("aria-label=\"Back to reference 2\""),
+        "each back-link says where it goes:\n{html}"
+    );
+}
diff --git a/tests/snapshots/site__footnote_render.snap b/tests/snapshots/site__footnote_render.snap
index 5ad0725..829cd40 100644
--- a/tests/snapshots/site__footnote_render.snap
+++ b/tests/snapshots/site__footnote_render.snap
@@ -4,13 +4,13 @@ expression: "render_fragment(\"footnote.org\")"
 ---
 <p>Text with a reference.<sup class="footnote-ref"><a id="fnr-1" href="#fn-1">1</a></sup> And a second one.<sup class="footnote-ref"><a id="fnr-2" href="#fn-2">2</a></sup></p>
 <p>An inline footnote.<sup class="footnote-ref"><a id="fnr-3" href="#fn-3">3</a></sup></p>
-<section class="footnotes">
+<section class="footnotes" aria-label="Footnotes">
 <hr>
 <ol>
 <li id="fn-1"><p>The first definition.</p>
- <a class="footnote-back" href="#fnr-1">&#8617;</a></li>
+ <a class="footnote-back" href="#fnr-1" aria-label="Back to reference 1">&#8617;</a></li>
 <li id="fn-2"><p>The second definition, with <em>emphasis</em>.</p>
- <a class="footnote-back" href="#fnr-2">&#8617;</a></li>
-<li id="fn-3">defined right here <a class="footnote-back" href="#fnr-3">&#8617;</a></li>
+ <a class="footnote-back" href="#fnr-2" aria-label="Back to reference 2">&#8617;</a></li>
+<li id="fn-3">defined right here <a class="footnote-back" href="#fnr-3" aria-label="Back to reference 3">&#8617;</a></li>
 </ol>
 </section>
diff --git a/tests/snapshots/site__site_guide_html.snap b/tests/snapshots/site__site_guide_html.snap
index d152eae..a517c5e 100644
--- a/tests/snapshots/site__site_guide_html.snap
+++ b/tests/snapshots/site__site_guide_html.snap
@@ -40,11 +40,11 @@ expression: "page(&pages, \"guide.org\").html"
 <tr><td>beta</td><td>20</td></tr>
 </tbody>
 </table>
-<section class="footnotes">
+<section class="footnotes" aria-label="Footnotes">
 <hr>
 <ol>
 <li id="fn-1"><p>Read the manual before you begin.</p>
- <a class="footnote-back" href="#fnr-1">&#8617;</a></li>
+ <a class="footnote-back" href="#fnr-1" aria-label="Back to reference 1">&#8617;</a></li>
 </ol>
 </section>
 </main>