From 965c3ac72a09475b19137085f69f3f760f58cb0e Mon Sep 17 00:00:00 2001 From: Brian Dominick Date: Sat, 11 Jul 2026 10:58:03 -0400 Subject: [PATCH 1/4] Wrap footnote reference links in A bare a.footnote-ref link is not announced as a footnote reference by most screen readers and renders inline with body text instead of as a superscript. Wrapping it in (with .footnote-ref class) restores both the visual and semantic cue without touching the link's own attributes/role. This is a BREAKING CHANGE for applications that expect a.footnote-ref, which is nolonger generated by the backend. --- data/templates/inline_footnote.html.slim | 16 +++++++++------- test/examples/html5/inline_footnote.html | 7 +++---- test/examples/html5/regressions.html | 23 ++++++++--------------- 3 files changed, 20 insertions(+), 26 deletions(-) diff --git a/data/templates/inline_footnote.html.slim b/data/templates/inline_footnote.html.slim index e7bdaff..ff7cf19 100644 --- a/data/templates/inline_footnote.html.slim +++ b/data/templates/inline_footnote.html.slim @@ -1,9 +1,11 @@ - if (index = local_attr :index) - a.footnote-ref [ - id=(footnoteref_id unless type == :xref) - href="##{footnote_id}" - title="View footnote #{index}" - role='doc-noteref' ] - | [#{index}] + sup.footnote-ref + a [ + id=(footnoteref_id unless type == :xref) + href="##{footnote_id}" + title="View footnote #{index}" + role='doc-noteref' ] + | [#{index}] - else - a.footnote-ref.broken title="Unresolved footnote reference." [#{text}] + sup.footnote-ref.broken + a title="Unresolved footnote reference." [#{text}] diff --git a/test/examples/html5/inline_footnote.html b/test/examples/html5/inline_footnote.html index 2970317..34812e0 100644 --- a/test/examples/html5/inline_footnote.html +++ b/test/examples/html5/inline_footnote.html @@ -1,13 +1,12 @@ -Apocalyptic party.[1] +Apocalyptic party.[1] -A bold statement.[1] -Another outrageous statement.[1] +A bold statement.[1] Another outrageous statement.[1] -A bold statement.[foobar] +A bold statement.[foobar] diff --git a/test/examples/html5/regressions.html b/test/examples/html5/regressions.html index 10722ca..a88dbf1 100644 --- a/test/examples/html5/regressions.html +++ b/test/examples/html5/regressions.html @@ -25,33 +25,26 @@

Heading

- - - + - -
- cell [1] + cell [1]
- cell [1] + cell [1]
-

paragraph [2]

-

another paragraph [2]

-
-
+

paragraph [2]

+

another paragraph [2]

+

    -
  1. - first +
  2. first
  3. -
  4. - second +
  5. second
From 24136a1312e4bbe12795e731d42fecfb50ee0999 Mon Sep 17 00:00:00 2001 From: Brian Dominick Date: Sat, 11 Jul 2026 11:32:27 -0400 Subject: [PATCH 2/4] Replace fixed-level
block titles with

The block_with_title method rendered every block's optional title as an

, regardless of where the block actually sits in the document's real section hierarchy. That's misleading to screen reader users navigating by heading level, and wrong whenever a titled block (e.g., a sidebar, admonition) ends up nested in an h2 or an h4. Now renders the title as a

instead, linked to its containing block via aria-labelledby/id (generate_title_id). Adds role="heading" without aria-level to keeps it discoverable as a heading landmark without asserting a level that isn't real. Applies to every block type that goes through block_with_title (dlist, olist, ulist, quote, verse, literal, open-block) plus paragraph and sidebar, which built their title markup by hand. This is a BREAKING CHANGE for titled block elements. Admonitions are unaffected but are similarly modified in a subsequent change. --- data/templates/helpers.rb | 35 +++++++++++++++++++++++++----- data/templates/paragraph.html.slim | 5 +++-- data/templates/sidebar.html.slim | 4 +--- test/examples/html5/dlist.html | 12 +++++----- test/examples/html5/literal.html | 4 ++-- test/examples/html5/olist.html | 4 ++-- test/examples/html5/open.html | 4 ++-- test/examples/html5/paragraph.html | 4 ++-- test/examples/html5/quote.html | 4 ++-- test/examples/html5/sidebar.html | 8 +++---- test/examples/html5/ulist.html | 4 ++-- test/examples/html5/verse.html | 4 ++-- 12 files changed, 56 insertions(+), 36 deletions(-) diff --git a/data/templates/helpers.rb b/data/templates/helpers.rb index d0bd4f3..2a8a411 100644 --- a/data/templates/helpers.rb +++ b/data/templates/helpers.rb @@ -168,8 +168,8 @@ def html_tag_if(condition, name, attributes = {}, content = nil, &block) # = block_with_title :class=>['quote-block', 'center'] # blockquote =content # - #

- #
Block Title
+ #
+ #

Block Title

#
Lorem ipsum
#
# @@ -189,13 +189,22 @@ def html_tag_if(condition, name, attributes = {}, content = nil, &block) #
Lorem ipsum
# # + # The title, when present, is rendered as a non-heading +p.block-title+ + # referenced via +aria-labelledby+ on the enclosing tag, instead of a + # fixed-level heading -- a heading tag pinned to a fixed level would clash + # with the document's real section hierarchy. + # # @param attrs [Hash, String] the tag's attributes as Hash), - # or the tag's class if it's not a Hash. + # or the tag's class if it's not a Hash. May include +:tag+ to + # override the enclosing element (default: +:section+, falling back + # to +:div+ when untitled; a non-+:section+ +:tag+ is kept as-is even + # when untitled, e.g. sidebar's +:aside+). # @param title [String, nil] the title. # @yield The block of Slim/HTML code within the tag (optional). # @return [String] a rendered HTML fragment. # def block_with_title(attrs = {}, title = @title, &block) + tag = attrs.delete(:tag) || :section if (klass = attrs[:class]).is_a? String klass = klass.split(' ') end @@ -206,14 +215,28 @@ def block_with_title(attrs = {}, title = @title, &block) # XXX quick hack nested = is_a?(::Asciidoctor::List) && (parent.is_a?(::Asciidoctor::ListItem) || parent.is_a?(::Asciidoctor::List)) - html_tag_if !nested, :div, attrs, yield + html_tag_if !nested, (tag == :section ? :div : tag), attrs, yield else - html_tag :section, attrs do - [html_tag(:h6, {class: 'block-title'}, title), yield].join("\n") + title_id = generate_title_id + attrs[:'aria-labelledby'] = title_id + html_tag tag, attrs do + [html_tag(:p, {class: 'block-title', id: title_id, role: 'heading'}, title), yield].join("\n") end end end + ## + # Generates a unique, namespaced id for a non-heading block title (see + # {#block_with_title}), so it can be referenced via +aria-labelledby+ + # instead of relying on a fixed-level heading tag. + # + # @param prefix [#to_s] a namespace for the id (default: the node's context). + # @return [String] a unique id, e.g. "sidebar-title-1". + # + def generate_title_id(prefix = context) + "#{prefix}-title-#{document.counter("html5s-#{prefix}-title")}" + end + def block_with_caption(position = :bottom, attrs = {}, &block) if (klass = attrs[:class]).is_a? String klass = klass.split(' ') diff --git a/data/templates/paragraph.html.slim b/data/templates/paragraph.html.slim index b4a1700..f781fb2 100644 --- a/data/templates/paragraph.html.slim +++ b/data/templates/paragraph.html.slim @@ -1,6 +1,7 @@ - if title? - section.paragraph id=id - h6.block-title =title + - title_id = generate_title_id + section.paragraph id=id aria-labelledby=title_id + p.block-title id=title_id role='heading' =title p class=role =content - else p id=id class=role =content diff --git a/data/templates/sidebar.html.slim b/data/templates/sidebar.html.slim index 3aa6769..50abaa6 100644 --- a/data/templates/sidebar.html.slim +++ b/data/templates/sidebar.html.slim @@ -1,4 +1,2 @@ -aside.sidebar id=id class=role - - if title? - h6.block-title =title += block_with_title :class=>'sidebar', :tag=>:aside =content diff --git a/test/examples/html5/dlist.html b/test/examples/html5/dlist.html index e087b18..d84a6fe 100644 --- a/test/examples/html5/dlist.html +++ b/test/examples/html5/dlist.html @@ -36,8 +36,8 @@ -
-
Asciidoctor
+
+

Asciidoctor

License
MIT
@@ -88,8 +88,8 @@
Asciidoctor
-
-
The most important questions
+
+

The most important questions

What is the answer to the Ultimate Question?
42
@@ -125,8 +125,8 @@
The most important questions
-
-
Computer terminology for noobs
+
+

Computer terminology for noobs

Hard drive
Permanent storage for operating system and/or user files.
diff --git a/test/examples/html5/literal.html b/test/examples/html5/literal.html index 4ce32c7..b17cfcf 100644 --- a/test/examples/html5/literal.html +++ b/test/examples/html5/literal.html @@ -6,8 +6,8 @@ -
-
Die again?
+
+

Die again?

error: The requested operation returned error: 1954 Forbidden search for defensive operations manual
 absolutely fatal: operation initiation lost in the dodecahedron of doom
 would you like to die again? y/n
diff --git a/test/examples/html5/olist.html b/test/examples/html5/olist.html index c899e53..c74371c 100644 --- a/test/examples/html5/olist.html +++ b/test/examples/html5/olist.html @@ -49,8 +49,8 @@ -
-
Steps
+
+

Steps

  1. Step 1
  2. Step 2
  3. diff --git a/test/examples/html5/open.html b/test/examples/html5/open.html index d0a5b33..30c0063 100644 --- a/test/examples/html5/open.html +++ b/test/examples/html5/open.html @@ -6,8 +6,8 @@ -
    -
    An open block
    +
    +

    An open block

    An open block can be an anonymous container, or it can masquerade as any other block.

    diff --git a/test/examples/html5/paragraph.html b/test/examples/html5/paragraph.html index 73a0a6b..b2f1d42 100644 --- a/test/examples/html5/paragraph.html +++ b/test/examples/html5/paragraph.html @@ -4,8 +4,8 @@

    To begin a new paragraph, separate it by at least one blank line.

    -
    -
    Paragraphs
    +
    +

    Paragraphs

    Paragraphs don’t require any special markup in AsciiDoc. A paragraph is just one or more lines of consecutive text.

    To begin a new paragraph, separate it by at least one blank line.

    diff --git a/test/examples/html5/quote.html b/test/examples/html5/quote.html index a19c75a..1e534fb 100644 --- a/test/examples/html5/quote.html +++ b/test/examples/html5/quote.html @@ -23,8 +23,8 @@ -
    -
    After landing the cloaked Klingon bird of prey in Golden Gate park:
    +
    +

    After landing the cloaked Klingon bird of prey in Golden Gate park:

    Everybody remember where we parked.

    diff --git a/test/examples/html5/sidebar.html b/test/examples/html5/sidebar.html index a8e3dce..9f0d365 100644 --- a/test/examples/html5/sidebar.html +++ b/test/examples/html5/sidebar.html @@ -6,11 +6,9 @@ -