From d78a017ba694d9109a87710e17a727095712e4c3 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Mon, 3 Aug 2026 13:57:34 +0200 Subject: [PATCH 1/5] feat(a11y): forward a heading level to section titles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every Bookshop wrapper rendered its section title as a plain div, so a page assembled from content blocks had no headings at all — not even an h1. Hinode's `assets/section-title.html` can now render a real heading, but only when a caller asks for a level. Work out which block opens the page and hand it the level the page asked for; every block after it becomes a subsection. A block that sets `heading_level` itself always wins. When the page passes no level, nothing is injected and the blocks keep rendering divs, so a caller that has not been updated is unaffected. The opening block is found by scanning the block list for the first entry with a heading title, rather than by watching which block renders first. Hugo chooses when a page's content is rendered, so render order is not a reliable signal. Forward the level through the twelve wrappers that render a section title, plus the hero and preview partials, and declare it in each component's schema. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 (1M context) --- .../components/approach/approach.hugo.html | 18 +++++++------- .../components/approach/approach.yml | 9 +++++++ .../components/articles/articles.hugo.html | 9 +++---- .../components/articles/articles.yml | 10 ++++++++ .../components/cards/cards.hugo.html | 9 +++---- component-library/components/cards/cards.yml | 10 ++++++++ .../contact-form/contact-form.hugo.html | 9 +++---- .../components/contact-form/contact-form.yml | 9 +++++++ .../components/faq/faq.hugo.html | 9 +++---- component-library/components/faq/faq.yml | 10 ++++++++ .../components/heading/heading.hugo.html | 7 +++--- .../components/heading/heading.yml | 10 ++++++++ .../components/hero/hero.hugo.html | 23 +++++++++--------- component-library/components/hero/hero.yml | 9 +++++++ .../components/list/list.hugo.html | 9 +++---- component-library/components/list/list.yml | 10 ++++++++ .../components/menu/menu.hugo.html | 9 +++---- component-library/components/menu/menu.yml | 9 +++++++ .../components/panels/panels.hugo.html | 9 +++---- .../components/panels/panels.yml | 10 ++++++++ .../components/preview/preview.hugo.html | 1 + .../components/preview/preview.yml | 9 +++++++ .../components/team/team.hugo.html | 9 +++---- component-library/components/team/team.yml | 10 ++++++++ .../testimonials/testimonials.hugo.html | 9 +++---- .../components/testimonials/testimonials.yml | 9 +++++++ .../video-message/video-message.hugo.html | 9 +++---- .../video-message/video-message.yml | 10 ++++++++ data/structures/hero.yml | 7 ++++++ data/structures/preview.yml | 7 ++++++ layouts/partials/assets/hero.html | 1 + layouts/partials/assets/preview.html | 5 ++-- layouts/partials/page/blocks.html | 24 +++++++++++++++++-- 33 files changed, 251 insertions(+), 66 deletions(-) diff --git a/component-library/components/approach/approach.hugo.html b/component-library/components/approach/approach.hugo.html index bdfd868..f2b1e23 100644 --- a/component-library/components/approach/approach.hugo.html +++ b/component-library/components/approach/approach.hugo.html @@ -71,10 +71,11 @@ {{ if $split }} {{/* Split layout: heading left, elements stacked right (50-50 on md+). */}} {{ $title := partial "assets/section-title.html" (dict - "page" $page - "heading" (merge (dict "size" 5) (or .heading dict)) - "justify" .justify - "class" "text-start mb-0" + "page" $page + "heading-level" (or .heading_level (index . "heading-level")) + "heading" (merge (dict "size" 5) (or .heading dict)) + "justify" .justify + "class" "text-start mb-0" ) }} {{ $items := "" }} @@ -140,10 +141,11 @@ {{ end}} {{- $raw = printf "%s%s" $raw (partial "assets/section-title.html" (dict - "page" $page - "heading" .heading - "justify" .justify - "class" (printf "pb-%d" $padding.y))) + "page" $page + "heading-level" (or .heading_level (index . "heading-level")) + "heading" .heading + "justify" .justify + "class" (printf "pb-%d" $padding.y))) -}} {{/* Add featured illustration */}} diff --git a/component-library/components/approach/approach.yml b/component-library/components/approach/approach.yml index 9da6cae..aca8454 100644 --- a/component-library/components/approach/approach.yml +++ b/component-library/components/approach/approach.yml @@ -10,6 +10,15 @@ comment: >- element icon such as '1-circle'. Both arguments default to the existing card grid. arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a + div instead of a heading. Defaults to the level the page assigns: the first + titled block of a page without a page header becomes its h1, the rest become + h2. numbered: type: bool optional: true diff --git a/component-library/components/articles/articles.hugo.html b/component-library/components/articles/articles.hugo.html index 76d2368..848c893 100644 --- a/component-library/components/articles/articles.hugo.html +++ b/component-library/components/articles/articles.hugo.html @@ -103,10 +103,11 @@ {{/* Main code */}} {{ if or (gt (len $pages) 0) (not .hide_empty) }} {{- $raw = printf "%s%s" $raw (partial "assets/section-title.html" (dict - "page" $page - "heading" .heading - "justify" .justify - "class" (printf "pb-%d" $padding.y))) + "page" $page + "heading-level" (or .heading_level (index . "heading-level")) + "heading" .heading + "justify" .justify + "class" (printf "pb-%d" $padding.y))) -}} {{ $partial := "assets/card-group.html" }} diff --git a/component-library/components/articles/articles.yml b/component-library/components/articles/articles.yml index 7c2cd69..fc4a134 100644 --- a/component-library/components/articles/articles.yml +++ b/component-library/components/articles/articles.yml @@ -1,5 +1,15 @@ comment: >- Renders a grid of articles from a Hugo section with optional filtering and pagination. +arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a + div instead of a heading. Defaults to the level the page assigns: the first + titled block of a page without a page header becomes its h1, the rest become + h2. example: | heading: title: Latest Articles diff --git a/component-library/components/cards/cards.hugo.html b/component-library/components/cards/cards.hugo.html index 75244c1..cd243da 100644 --- a/component-library/components/cards/cards.hugo.html +++ b/component-library/components/cards/cards.hugo.html @@ -53,10 +53,11 @@ {{/* Main code */}} {{- $raw = printf "%s%s" $raw (partial "assets/section-title.html" (dict - "page" $page - "heading" .heading - "justify" .justify - "class" (printf "pb-%d" $padding.y))) + "page" $page + "heading-level" (or .heading_level (index . "heading-level")) + "heading" .heading + "justify" .justify + "class" (printf "pb-%d" $padding.y))) -}} {{/* Init base arguments for card group */}} diff --git a/component-library/components/cards/cards.yml b/component-library/components/cards/cards.yml index e70deb7..00b34b2 100644 --- a/component-library/components/cards/cards.yml +++ b/component-library/components/cards/cards.yml @@ -1,5 +1,15 @@ comment: >- Renders a grid of cards with icons, titles, and descriptions. +arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a + div instead of a heading. Defaults to the level the page assigns: the first + titled block of a page without a page header becomes its h1, the rest become + h2. example: | heading: title: Our Services diff --git a/component-library/components/contact-form/contact-form.hugo.html b/component-library/components/contact-form/contact-form.hugo.html index dbc37ab..28230c9 100644 --- a/component-library/components/contact-form/contact-form.hugo.html +++ b/component-library/components/contact-form/contact-form.hugo.html @@ -75,10 +75,11 @@ }} {{ else }} {{- $raw = partial "assets/section-title.html" (dict - "page" $page - "heading" .heading - "justify" .justify - "class" (printf "pb-%d" $padding.y)) + "page" $page + "heading-level" (or .heading_level (index . "heading-level")) + "heading" .heading + "justify" .justify + "class" (printf "pb-%d" $padding.y)) -}} {{ $raw = printf "%s%s" $raw $form }} {{ end }} diff --git a/component-library/components/contact-form/contact-form.yml b/component-library/components/contact-form/contact-form.yml index 76dc0cc..b83db12 100644 --- a/component-library/components/contact-form/contact-form.yml +++ b/component-library/components/contact-form/contact-form.yml @@ -1,6 +1,15 @@ comment: >- Renders a contact form with an optional heading and custom form handler. arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a + div instead of a heading. Defaults to the level the page assigns: the first + titled block of a page without a page header becomes its h1, the rest become + h2. hook: type: string optional: false diff --git a/component-library/components/faq/faq.hugo.html b/component-library/components/faq/faq.hugo.html index 4bb48ba..79535e6 100644 --- a/component-library/components/faq/faq.hugo.html +++ b/component-library/components/faq/faq.hugo.html @@ -38,10 +38,11 @@ {{ $color := "" }} {{ if .contrast }}{{ $color = "white" }}{{ end }} {{- $raw := partial "assets/section-title.html" (dict - "page" $page - "heading" .heading - "justify" .justify - "class" (printf "pb-%d" $padding.y)) + "page" $page + "heading-level" (or .heading_level (index . "heading-level")) + "heading" .heading + "justify" .justify + "class" (printf "pb-%d" $padding.y)) -}} {{ $class := "" }} diff --git a/component-library/components/faq/faq.yml b/component-library/components/faq/faq.yml index 88a6281..6ca4cd0 100644 --- a/component-library/components/faq/faq.yml +++ b/component-library/components/faq/faq.yml @@ -1,5 +1,15 @@ comment: >- Renders an FAQ accordion with heading and a list of question/answer items. +arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a + div instead of a heading. Defaults to the level the page assigns: the first + titled block of a page without a page header becomes its h1, the rest become + h2. example: | heading: title: Frequently Asked Questions diff --git a/component-library/components/heading/heading.hugo.html b/component-library/components/heading/heading.hugo.html index 2f5b8b1..c6c090d 100644 --- a/component-library/components/heading/heading.hugo.html +++ b/component-library/components/heading/heading.hugo.html @@ -43,9 +43,10 @@ {{/* Main code */}} {{- $raw := partial "assets/section-title.html" (dict - "page" $page - "heading" .heading - "justify" .justify) + "page" $page + "heading-level" (or .heading_level (index . "heading-level")) + "heading" .heading + "justify" .justify) -}} {{ if $raw }} diff --git a/component-library/components/heading/heading.yml b/component-library/components/heading/heading.yml index 9a6d993..54562c2 100644 --- a/component-library/components/heading/heading.yml +++ b/component-library/components/heading/heading.yml @@ -1,5 +1,15 @@ comment: >- Renders a section heading with optional regular content. +arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a + div instead of a heading. Defaults to the level the page assigns: the first + titled block of a page without a page header becomes its h1, the rest become + h2. example: | heading: preheading: Preheading diff --git a/component-library/components/hero/hero.hugo.html b/component-library/components/hero/hero.hugo.html index c1bb4ed..3b951b1 100644 --- a/component-library/components/hero/hero.hugo.html +++ b/component-library/components/hero/hero.hugo.html @@ -32,17 +32,18 @@ {{ if not $error }} {{ $raw := partial "assets/hero.html" (dict - "page" $page - "breadcrumb" .breadcrumb - "heading" .heading - "illustration" .illustration - "align" .align - "order" .order - "link-type" (or .link_type (index . "link-type")) - "links" .links - "orientation" .orientation - "width" .width - "cover" .cover + "page" $page + "heading-level" (or .heading_level (index . "heading-level")) + "breadcrumb" .breadcrumb + "heading" .heading + "illustration" .illustration + "align" .align + "order" .order + "link-type" (or .link_type (index . "link-type")) + "links" .links + "orientation" .orientation + "width" .width + "cover" .cover ) }} diff --git a/component-library/components/hero/hero.yml b/component-library/components/hero/hero.yml index 70e8d32..de11b90 100644 --- a/component-library/components/hero/hero.yml +++ b/component-library/components/hero/hero.yml @@ -1,6 +1,15 @@ comment: >- Renders a hero banner with heading, optional background, and action links. arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a + div instead of a heading. Defaults to the level the page assigns: the first + titled block of a page without a page header becomes its h1, the rest become + h2. heading: optional: false example: | diff --git a/component-library/components/list/list.hugo.html b/component-library/components/list/list.hugo.html index 39f973a..8031e61 100644 --- a/component-library/components/list/list.hugo.html +++ b/component-library/components/list/list.hugo.html @@ -86,10 +86,11 @@ {{ $hideEmpty := or .hide_empty (index . "hide-empty") }} {{ if or (gt (len $pages) 0) (not $hideEmpty) }} {{- $raw := partial "assets/section-title.html" (dict - "page" $page - "heading" .heading - "justify" .justify - "class" (printf "pb-%d" $padding.y)) + "page" $page + "heading-level" (or .heading_level (index . "heading-level")) + "heading" .heading + "justify" .justify + "class" (printf "pb-%d" $padding.y)) -}} {{ if gt (len $pages) 0 }} {{ $content := "" }} diff --git a/component-library/components/list/list.yml b/component-library/components/list/list.yml index 2fa2c04..fbf6bcb 100644 --- a/component-library/components/list/list.yml +++ b/component-library/components/list/list.yml @@ -1,5 +1,15 @@ comment: >- Renders a sortable, searchable list of articles from a Hugo section. +arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a + div instead of a heading. Defaults to the level the page assigns: the first + titled block of a page without a page header becomes its h1, the rest become + h2. example: | heading: title: Articles diff --git a/component-library/components/menu/menu.hugo.html b/component-library/components/menu/menu.hugo.html index d8554f7..640d8af 100644 --- a/component-library/components/menu/menu.hugo.html +++ b/component-library/components/menu/menu.hugo.html @@ -36,10 +36,11 @@ {{/* Main code */}} {{- $raw := partial "assets/section-title.html" (dict - "page" $page - "heading" .heading - "justify" .justify - "class" (printf "pb-%d" $padding.y)) + "page" $page + "heading-level" (or .heading_level (index . "heading-level")) + "heading" .heading + "justify" .justify + "class" (printf "pb-%d" $padding.y)) -}} {{- $raw = printf "%s%s" $raw (partial "assets/menu.html" (dict diff --git a/component-library/components/menu/menu.yml b/component-library/components/menu/menu.yml index bac2de3..e07a8e6 100644 --- a/component-library/components/menu/menu.yml +++ b/component-library/components/menu/menu.yml @@ -1,6 +1,15 @@ comment: >- Renders a navigation menu in masonry or grid layout. arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a + div instead of a heading. Defaults to the level the page assigns: the first + titled block of a page without a page header becomes its h1, the rest become + h2. menu: optional: false example: | diff --git a/component-library/components/panels/panels.hugo.html b/component-library/components/panels/panels.hugo.html index bb422ca..89e5a52 100644 --- a/component-library/components/panels/panels.hugo.html +++ b/component-library/components/panels/panels.hugo.html @@ -79,10 +79,11 @@ {{/* Main code */}} {{- $raw := partial "assets/section-title.html" (dict - "page" $page - "heading" .heading - "justify" .justify - "class" (printf "pb-%d" $padding.y)) + "page" $page + "heading-level" (or .heading_level (index . "heading-level")) + "heading" .heading + "justify" .justify + "class" (printf "pb-%d" $padding.y)) -}} {{- $raw = printf "%s%s" $raw (partial "assets/nav.html" (dict diff --git a/component-library/components/panels/panels.yml b/component-library/components/panels/panels.yml index cdb977d..4598349 100644 --- a/component-library/components/panels/panels.yml +++ b/component-library/components/panels/panels.yml @@ -1,5 +1,15 @@ comment: >- Renders a set of expandable panels with icons and content. +arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a + div instead of a heading. Defaults to the level the page assigns: the first + titled block of a page without a page header becomes its h1, the rest become + h2. example: | heading: title: Features diff --git a/component-library/components/preview/preview.hugo.html b/component-library/components/preview/preview.hugo.html index a2b6656..0fb1c03 100644 --- a/component-library/components/preview/preview.hugo.html +++ b/component-library/components/preview/preview.hugo.html @@ -49,6 +49,7 @@ {{/* Main code */}} {{ $raw := partial "assets/preview.html" (dict "page" $page + "heading-level" (or .heading_level (index . "heading-level")) "url" .url "device" .device "controls-placement" (or .controls_placement (index . "controls-placement")) diff --git a/component-library/components/preview/preview.yml b/component-library/components/preview/preview.yml index 3ef1b7c..58a2c70 100644 --- a/component-library/components/preview/preview.yml +++ b/component-library/components/preview/preview.yml @@ -1,6 +1,15 @@ comment: >- Renders a live URL preview with switchable device views. arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a + div instead of a heading. Defaults to the level the page assigns: the first + titled block of a page without a page header becomes its h1, the rest become + h2. url: optional: false example: | diff --git a/component-library/components/team/team.hugo.html b/component-library/components/team/team.hugo.html index 45e64ab..2a46052 100644 --- a/component-library/components/team/team.hugo.html +++ b/component-library/components/team/team.hugo.html @@ -83,10 +83,11 @@ {{ $hideEmpty := or .hide_empty (index . "hide-empty") }} {{ if or (gt (len $pages) 0) (not $hideEmpty) }} {{- $raw := partial "assets/section-title.html" (dict - "page" $page - "heading" .heading - "justify" .justify - "class" (printf "pb-%d" $padding.y)) + "page" $page + "heading-level" (or .heading_level (index . "heading-level")) + "heading" .heading + "justify" .justify + "class" (printf "pb-%d" $padding.y)) -}} {{ $partial := "assets/card-group.html" }} diff --git a/component-library/components/team/team.yml b/component-library/components/team/team.yml index 7d431e1..733a48d 100644 --- a/component-library/components/team/team.yml +++ b/component-library/components/team/team.yml @@ -1,5 +1,15 @@ comment: >- Renders a team member grid sourced from a Hugo section. +arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a + div instead of a heading. Defaults to the level the page assigns: the first + titled block of a page without a page header becomes its h1, the rest become + h2. example: | heading: title: Meet the Team diff --git a/component-library/components/testimonials/testimonials.hugo.html b/component-library/components/testimonials/testimonials.hugo.html index 74c5654..328c521 100644 --- a/component-library/components/testimonials/testimonials.hugo.html +++ b/component-library/components/testimonials/testimonials.hugo.html @@ -36,10 +36,11 @@ {{/* Main code */}} {{- $raw := partial "assets/section-title.html" (dict - "page" $page - "heading" .heading - "justify" .justify - "class" (printf "pb-%d" $padding.y)) + "page" $page + "heading-level" (or .heading_level (index . "heading-level")) + "heading" .heading + "justify" .justify + "class" (printf "pb-%d" $padding.y)) -}} {{ $partial := partial "assets/testimonial-carousel.html" (dict diff --git a/component-library/components/testimonials/testimonials.yml b/component-library/components/testimonials/testimonials.yml index d4a5372..235f465 100644 --- a/component-library/components/testimonials/testimonials.yml +++ b/component-library/components/testimonials/testimonials.yml @@ -1,6 +1,15 @@ comment: >- Renders a testimonials carousel with client quotes. arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a + div instead of a heading. Defaults to the level the page assigns: the first + titled block of a page without a page header becomes its h1, the rest become + h2. testimonials: optional: false example: | diff --git a/component-library/components/video-message/video-message.hugo.html b/component-library/components/video-message/video-message.hugo.html index c4d574d..f940494 100644 --- a/component-library/components/video-message/video-message.hugo.html +++ b/component-library/components/video-message/video-message.hugo.html @@ -155,10 +155,11 @@ {{ end}} {{- $raw := partial "assets/section-title.html" (dict - "page" $page - "heading" .heading - "justify" .justify - "class" (printf "pb-%d" $padding.y)) + "page" $page + "heading-level" (or .heading_level (index . "heading-level")) + "heading" .heading + "justify" .justify + "class" (printf "pb-%d" $padding.y)) -}} {{ $video := "" }} diff --git a/component-library/components/video-message/video-message.yml b/component-library/components/video-message/video-message.yml index 1eb707b..f54d9ac 100644 --- a/component-library/components/video-message/video-message.yml +++ b/component-library/components/video-message/video-message.yml @@ -1,5 +1,15 @@ comment: >- Renders a video with side messages highlighting key features. +arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a + div instead of a heading. Defaults to the level the page assigns: the first + titled block of a page without a page header becomes its h1, the rest become + h2. example: | heading: title: Watch Our Overview diff --git a/data/structures/hero.yml b/data/structures/hero.yml index ee3a2c5..2d8bf0e 100644 --- a/data/structures/hero.yml +++ b/data/structures/hero.yml @@ -3,6 +3,13 @@ comment: >- supports a heading, featured illustration, and navigation links. You can also add a background image with an overlay to improve contrast. arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the hero title, from 1 (h1) to 6 (h6). Set 0 to render a + div instead of a heading. page: breadcrumb: heading: diff --git a/data/structures/preview.yml b/data/structures/preview.yml index 77e4e68..cf5cda1 100644 --- a/data/structures/preview.yml +++ b/data/structures/preview.yml @@ -1,6 +1,13 @@ comment: >- Renders a live URL preview with switchable device views (desktop, tablet, mobile). arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render + a div instead of a heading. page: optional: true comment: >- diff --git a/layouts/partials/assets/hero.html b/layouts/partials/assets/hero.html index 43b9360..b94e4cd 100644 --- a/layouts/partials/assets/hero.html +++ b/layouts/partials/assets/hero.html @@ -123,6 +123,7 @@ {{ $title := partial "assets/section-title.html" (dict "page" $page "heading" $heading + "heading-level" $args.headingLevel "use-title" $args.useTitle "links" $args.links "link-type" $args.linkType diff --git a/layouts/partials/assets/preview.html b/layouts/partials/assets/preview.html index 4f7a728..3df7cb2 100644 --- a/layouts/partials/assets/preview.html +++ b/layouts/partials/assets/preview.html @@ -309,8 +309,9 @@ {{/* Optional section heading */}} {{ if $args.heading.title }} {{ partial "assets/section-title.html" (dict - "page" $page - "heading" $args.heading + "page" $page + "heading" $args.heading + "heading-level" $args.headingLevel ) }} {{ end }} diff --git a/layouts/partials/page/blocks.html b/layouts/partials/page/blocks.html index 70118f4..3903e69 100644 --- a/layouts/partials/page/blocks.html +++ b/layouts/partials/page/blocks.html @@ -1,24 +1,44 @@ {{/* Renders the content blocks of a page. Callers either pass the page itself (legacy) or a dict - with the arguments "page" (owning page) and "embed" (optional flag to indicate the blocks are - embedded within another element). + with the arguments "page" (owning page), "embed" (optional flag to indicate the blocks are + embedded within another element) and "heading-level" (optional level for the first block that + carries a title). */}} {{/* Initialize arguments */}} {{ $none := dict }} {{ $page := . }} {{ $embed := $none.missing }} +{{ $headingLevel := 0 }} {{ if reflect.IsMap . }} {{ $page = .page }} {{ $embed = .embed }} + {{ $headingLevel = or (index . "heading-level") 0 }} {{ end }} {{ with $page.Params.content_blocks }} + {{/* Locate the block that opens the page. Blocks stand in for the page header when the layout + renders none, so the first one carrying a title takes the level the caller asked for and + every block after it becomes a subsection. Deriving this from the block list rather than + from the order in which blocks render keeps the outline stable, because Hugo decides for + itself when a page's content is rendered. */}} + {{ $lead := -1 }} + {{ if gt $headingLevel 0 }} + {{ range $k, $v := . }} + {{ if and (lt $lead 0) (index (index $v "heading" | default dict) "title") }} + {{ $lead = $k }} + {{ end }} + {{ end }} + {{ end }} + {{/* Inject an ordinal number and the owning page for each block */}} {{ $blocks := slice }} {{ range $k, $v := . }} {{ $b := merge $v (dict "_ordinal" $k "_page" $page) }} {{ if ne $embed nil }}{{ $b = merge $b (dict "_embed" $embed) }}{{ end }} + {{ if and (gt $headingLevel 0) (not (or (isset $v "heading_level") (isset $v "heading-level"))) }} + {{ $b = merge $b (dict "heading_level" (cond (eq $k $lead) $headingLevel (cond (lt $headingLevel 2) 2 $headingLevel))) }} + {{ end }} {{ $blocks = $blocks | append $b }} {{ end }} From a8b1e3ec17502fd9fd93f2a61cf6dc399063fa11 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Tue, 4 Aug 2026 13:09:04 +0200 Subject: [PATCH 2/5] fix(a11y): harden lead selection and wire remaining hero-based blocks The lead scan and level injection consult the component schema, so blocks whose component cannot render a leveled title (separator, releases) are passed over instead of failing validation. Blocks that hide themselves when their section is empty or that suppress their own heading no longer take the opening slot. The about, cta and featured wrappers forward the heading level through their hero and contact partials, the contact-form illustration branch does the same, and every wrapper reads an explicit level of 0 by key presence instead of relying on 0 being falsy. The example site builds against hinode v3.19.1, which understands section title heading levels. Co-Authored-By: Claude Fable 5 --- .../components/about/about.hugo.html | 3 +- component-library/components/about/about.yml | 10 +++++++ .../components/approach/approach.hugo.html | 4 +-- .../components/articles/articles.hugo.html | 2 +- .../components/cards/cards.hugo.html | 2 +- .../contact-form/contact-form.hugo.html | 5 ++-- .../components/cta/cta.hugo.html | 3 +- component-library/components/cta/cta.yml | 10 +++++++ .../components/faq/faq.hugo.html | 2 +- .../components/featured/featured.hugo.html | 3 +- .../components/featured/featured.yml | 10 +++++++ .../components/heading/heading.hugo.html | 2 +- .../components/hero/hero.hugo.html | 2 +- .../components/list/list.hugo.html | 2 +- .../components/menu/menu.hugo.html | 2 +- .../components/panels/panels.hugo.html | 2 +- .../components/preview/preview.hugo.html | 2 +- .../components/team/team.hugo.html | 2 +- .../testimonials/testimonials.hugo.html | 2 +- .../video-message/video-message.hugo.html | 2 +- data/structures/contact.yml | 7 +++++ exampleSite/go.mod | 2 +- exampleSite/go.sum | 2 ++ layouts/partials/assets/contact.html | 1 + layouts/partials/page/blocks.html | 29 +++++++++++++++---- 25 files changed, 88 insertions(+), 25 deletions(-) diff --git a/component-library/components/about/about.hugo.html b/component-library/components/about/about.hugo.html index d6c5976..50f7be2 100644 --- a/component-library/components/about/about.hugo.html +++ b/component-library/components/about/about.hugo.html @@ -39,8 +39,9 @@ {{ $ratio := index $settings "ratio" | default "auto" }} {{/* Generate the hero */}} - {{ $raw := partial "assets/hero.html" (dict + {{ $raw := partial "assets/hero.html" (dict "page" $page + "heading-level" (cond (isset . "heading_level") .heading_level (index . "heading-level")) "heading" (merge (dict "arrangement" "above" "width" 8) .heading) "illustration" (merge (dict "ratio" $ratio) .illustration) "order" .order diff --git a/component-library/components/about/about.yml b/component-library/components/about/about.yml index 69843b8..b8d7987 100644 --- a/component-library/components/about/about.yml +++ b/component-library/components/about/about.yml @@ -1,5 +1,15 @@ comment: >- Renders an About section with heading, illustration, and optional links. +arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a + div instead of a heading. Defaults to the level the page assigns: the first + titled block of a page without a page header becomes its h1, the rest become + h2. example: | heading: title: About Us diff --git a/component-library/components/approach/approach.hugo.html b/component-library/components/approach/approach.hugo.html index f2b1e23..8fc5c1a 100644 --- a/component-library/components/approach/approach.hugo.html +++ b/component-library/components/approach/approach.hugo.html @@ -72,7 +72,7 @@ {{/* Split layout: heading left, elements stacked right (50-50 on md+). */}} {{ $title := partial "assets/section-title.html" (dict "page" $page - "heading-level" (or .heading_level (index . "heading-level")) + "heading-level" (cond (isset . "heading_level") .heading_level (index . "heading-level")) "heading" (merge (dict "size" 5) (or .heading dict)) "justify" .justify "class" "text-start mb-0" @@ -142,7 +142,7 @@ {{- $raw = printf "%s%s" $raw (partial "assets/section-title.html" (dict "page" $page - "heading-level" (or .heading_level (index . "heading-level")) + "heading-level" (cond (isset . "heading_level") .heading_level (index . "heading-level")) "heading" .heading "justify" .justify "class" (printf "pb-%d" $padding.y))) diff --git a/component-library/components/articles/articles.hugo.html b/component-library/components/articles/articles.hugo.html index 848c893..1e242a3 100644 --- a/component-library/components/articles/articles.hugo.html +++ b/component-library/components/articles/articles.hugo.html @@ -104,7 +104,7 @@ {{ if or (gt (len $pages) 0) (not .hide_empty) }} {{- $raw = printf "%s%s" $raw (partial "assets/section-title.html" (dict "page" $page - "heading-level" (or .heading_level (index . "heading-level")) + "heading-level" (cond (isset . "heading_level") .heading_level (index . "heading-level")) "heading" .heading "justify" .justify "class" (printf "pb-%d" $padding.y))) diff --git a/component-library/components/cards/cards.hugo.html b/component-library/components/cards/cards.hugo.html index cd243da..7c33b4a 100644 --- a/component-library/components/cards/cards.hugo.html +++ b/component-library/components/cards/cards.hugo.html @@ -54,7 +54,7 @@ {{/* Main code */}} {{- $raw = printf "%s%s" $raw (partial "assets/section-title.html" (dict "page" $page - "heading-level" (or .heading_level (index . "heading-level")) + "heading-level" (cond (isset . "heading_level") .heading_level (index . "heading-level")) "heading" .heading "justify" .justify "class" (printf "pb-%d" $padding.y))) diff --git a/component-library/components/contact-form/contact-form.hugo.html b/component-library/components/contact-form/contact-form.hugo.html index 28230c9..4d1adec 100644 --- a/component-library/components/contact-form/contact-form.hugo.html +++ b/component-library/components/contact-form/contact-form.hugo.html @@ -61,9 +61,10 @@ {{ if .illustration }} {{/* Embed the form in a horizontal hero with an illustration */}} {{ $width := sub 12 (.illustration.width | default 6) }} - {{ $raw = partial "assets/hero.html" (dict + {{ $raw = partial "assets/hero.html" (dict "page" $page "raw" (printf `
%s` $padding.y $form) + "heading-level" (cond (isset . "heading_level") .heading_level (index . "heading-level")) "heading" (merge .heading (dict "width" $width)) "illustration" (merge .illustration (dict "width" 12)) "order" .order @@ -76,7 +77,7 @@ {{ else }} {{- $raw = partial "assets/section-title.html" (dict "page" $page - "heading-level" (or .heading_level (index . "heading-level")) + "heading-level" (cond (isset . "heading_level") .heading_level (index . "heading-level")) "heading" .heading "justify" .justify "class" (printf "pb-%d" $padding.y)) diff --git a/component-library/components/cta/cta.hugo.html b/component-library/components/cta/cta.hugo.html index b2602bb..2fc9e80 100644 --- a/component-library/components/cta/cta.hugo.html +++ b/component-library/components/cta/cta.hugo.html @@ -42,9 +42,10 @@ {{ end }} {{ end }} - {{ $raw := partial "assets/contact.html" (dict + {{ $raw := partial "assets/contact.html" (dict "page" $page "orientation" "horizontal" + "heading-level" (cond (isset . "heading_level") .heading_level (index . "heading-level")) "heading" .heading "contact" .contact "caption-url" (or .caption_url (index . "caption-url")) diff --git a/component-library/components/cta/cta.yml b/component-library/components/cta/cta.yml index 1f6dc09..217f3b8 100644 --- a/component-library/components/cta/cta.yml +++ b/component-library/components/cta/cta.yml @@ -1,5 +1,15 @@ comment: >- Renders a call-to-action section with heading and optional action links. +arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a + div instead of a heading. Defaults to the level the page assigns: the first + titled block of a page without a page header becomes its h1, the rest become + h2. example: | heading: title: Ready to get started? diff --git a/component-library/components/faq/faq.hugo.html b/component-library/components/faq/faq.hugo.html index 79535e6..9925b9d 100644 --- a/component-library/components/faq/faq.hugo.html +++ b/component-library/components/faq/faq.hugo.html @@ -39,7 +39,7 @@ {{ if .contrast }}{{ $color = "white" }}{{ end }} {{- $raw := partial "assets/section-title.html" (dict "page" $page - "heading-level" (or .heading_level (index . "heading-level")) + "heading-level" (cond (isset . "heading_level") .heading_level (index . "heading-level")) "heading" .heading "justify" .justify "class" (printf "pb-%d" $padding.y)) diff --git a/component-library/components/featured/featured.hugo.html b/component-library/components/featured/featured.hugo.html index 1c7be27..62569a5 100644 --- a/component-library/components/featured/featured.hugo.html +++ b/component-library/components/featured/featured.hugo.html @@ -31,8 +31,9 @@ {{ $page := or ._page page }} {{ if not $error }} - {{ $raw := partial "assets/hero.html" (dict + {{ $raw := partial "assets/hero.html" (dict "page" $page + "heading-level" (cond (isset . "heading_level") .heading_level (index . "heading-level")) "heading" (merge (dict "width" 8) (or .heading dict)) "illustration" (merge (dict "ratio" "auto") (or .illustration dict)) "order" .order diff --git a/component-library/components/featured/featured.yml b/component-library/components/featured/featured.yml index 8097172..3c60b86 100644 --- a/component-library/components/featured/featured.yml +++ b/component-library/components/featured/featured.yml @@ -1,5 +1,15 @@ comment: >- Renders a featured article with illustration, heading, and optional links. +arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a + div instead of a heading. Defaults to the level the page assigns: the first + titled block of a page without a page header becomes its h1, the rest become + h2. example: | heading: title: Featured Article diff --git a/component-library/components/heading/heading.hugo.html b/component-library/components/heading/heading.hugo.html index c6c090d..5618b90 100644 --- a/component-library/components/heading/heading.hugo.html +++ b/component-library/components/heading/heading.hugo.html @@ -44,7 +44,7 @@ {{/* Main code */}} {{- $raw := partial "assets/section-title.html" (dict "page" $page - "heading-level" (or .heading_level (index . "heading-level")) + "heading-level" (cond (isset . "heading_level") .heading_level (index . "heading-level")) "heading" .heading "justify" .justify) -}} diff --git a/component-library/components/hero/hero.hugo.html b/component-library/components/hero/hero.hugo.html index 3b951b1..a8a199f 100644 --- a/component-library/components/hero/hero.hugo.html +++ b/component-library/components/hero/hero.hugo.html @@ -33,7 +33,7 @@ {{ if not $error }} {{ $raw := partial "assets/hero.html" (dict "page" $page - "heading-level" (or .heading_level (index . "heading-level")) + "heading-level" (cond (isset . "heading_level") .heading_level (index . "heading-level")) "breadcrumb" .breadcrumb "heading" .heading "illustration" .illustration diff --git a/component-library/components/list/list.hugo.html b/component-library/components/list/list.hugo.html index 8031e61..757bc01 100644 --- a/component-library/components/list/list.hugo.html +++ b/component-library/components/list/list.hugo.html @@ -87,7 +87,7 @@ {{ if or (gt (len $pages) 0) (not $hideEmpty) }} {{- $raw := partial "assets/section-title.html" (dict "page" $page - "heading-level" (or .heading_level (index . "heading-level")) + "heading-level" (cond (isset . "heading_level") .heading_level (index . "heading-level")) "heading" .heading "justify" .justify "class" (printf "pb-%d" $padding.y)) diff --git a/component-library/components/menu/menu.hugo.html b/component-library/components/menu/menu.hugo.html index 640d8af..1bd89d8 100644 --- a/component-library/components/menu/menu.hugo.html +++ b/component-library/components/menu/menu.hugo.html @@ -37,7 +37,7 @@ {{/* Main code */}} {{- $raw := partial "assets/section-title.html" (dict "page" $page - "heading-level" (or .heading_level (index . "heading-level")) + "heading-level" (cond (isset . "heading_level") .heading_level (index . "heading-level")) "heading" .heading "justify" .justify "class" (printf "pb-%d" $padding.y)) diff --git a/component-library/components/panels/panels.hugo.html b/component-library/components/panels/panels.hugo.html index 89e5a52..1537b63 100644 --- a/component-library/components/panels/panels.hugo.html +++ b/component-library/components/panels/panels.hugo.html @@ -80,7 +80,7 @@ {{/* Main code */}} {{- $raw := partial "assets/section-title.html" (dict "page" $page - "heading-level" (or .heading_level (index . "heading-level")) + "heading-level" (cond (isset . "heading_level") .heading_level (index . "heading-level")) "heading" .heading "justify" .justify "class" (printf "pb-%d" $padding.y)) diff --git a/component-library/components/preview/preview.hugo.html b/component-library/components/preview/preview.hugo.html index 0fb1c03..2227e4e 100644 --- a/component-library/components/preview/preview.hugo.html +++ b/component-library/components/preview/preview.hugo.html @@ -49,7 +49,7 @@ {{/* Main code */}} {{ $raw := partial "assets/preview.html" (dict "page" $page - "heading-level" (or .heading_level (index . "heading-level")) + "heading-level" (cond (isset . "heading_level") .heading_level (index . "heading-level")) "url" .url "device" .device "controls-placement" (or .controls_placement (index . "controls-placement")) diff --git a/component-library/components/team/team.hugo.html b/component-library/components/team/team.hugo.html index 2a46052..70d35a1 100644 --- a/component-library/components/team/team.hugo.html +++ b/component-library/components/team/team.hugo.html @@ -84,7 +84,7 @@ {{ if or (gt (len $pages) 0) (not $hideEmpty) }} {{- $raw := partial "assets/section-title.html" (dict "page" $page - "heading-level" (or .heading_level (index . "heading-level")) + "heading-level" (cond (isset . "heading_level") .heading_level (index . "heading-level")) "heading" .heading "justify" .justify "class" (printf "pb-%d" $padding.y)) diff --git a/component-library/components/testimonials/testimonials.hugo.html b/component-library/components/testimonials/testimonials.hugo.html index 328c521..e89ffc1 100644 --- a/component-library/components/testimonials/testimonials.hugo.html +++ b/component-library/components/testimonials/testimonials.hugo.html @@ -37,7 +37,7 @@ {{/* Main code */}} {{- $raw := partial "assets/section-title.html" (dict "page" $page - "heading-level" (or .heading_level (index . "heading-level")) + "heading-level" (cond (isset . "heading_level") .heading_level (index . "heading-level")) "heading" .heading "justify" .justify "class" (printf "pb-%d" $padding.y)) diff --git a/component-library/components/video-message/video-message.hugo.html b/component-library/components/video-message/video-message.hugo.html index f940494..f4fe201 100644 --- a/component-library/components/video-message/video-message.hugo.html +++ b/component-library/components/video-message/video-message.hugo.html @@ -156,7 +156,7 @@ {{- $raw := partial "assets/section-title.html" (dict "page" $page - "heading-level" (or .heading_level (index . "heading-level")) + "heading-level" (cond (isset . "heading_level") .heading_level (index . "heading-level")) "heading" .heading "justify" .justify "class" (printf "pb-%d" $padding.y)) diff --git a/data/structures/contact.yml b/data/structures/contact.yml index b862bc3..dab6a59 100644 --- a/data/structures/contact.yml +++ b/data/structures/contact.yml @@ -2,6 +2,13 @@ comment: >- Displays a contact banner, typically placed at the bottom or right-hand side of the page. group: partial arguments: + heading-level: + type: int + optional: true + release: v2.4.0 + comment: >- + Heading level of the contact title, from 1 (h1) to 6 (h6). Set 0 to render + a div instead of a heading. page: data: optional: true diff --git a/exampleSite/go.mod b/exampleSite/go.mod index 6725e54..20459aa 100644 --- a/exampleSite/go.mod +++ b/exampleSite/go.mod @@ -6,7 +6,7 @@ require ( github.com/FortAwesome/Font-Awesome v0.0.0-20260715180930-14c65a3747d0 // indirect github.com/airbnb/lottie-web v5.13.0+incompatible // indirect github.com/cloudcannon/bookshop/hugo/v3 v3.19.0 // indirect - github.com/gethinode/hinode/v3 v3.18.0 // indirect + github.com/gethinode/hinode/v3 v3.19.1 // indirect github.com/gethinode/mod-blocks/v2 v2.3.6 // indirect github.com/gethinode/mod-bootstrap v1.5.0 // indirect github.com/gethinode/mod-csp v1.0.14 // indirect diff --git a/exampleSite/go.sum b/exampleSite/go.sum index c6abcf8..0e3f555 100644 --- a/exampleSite/go.sum +++ b/exampleSite/go.sum @@ -34,6 +34,8 @@ github.com/gethinode/hinode/v3 v3.16.3 h1:Zy2rG8RppxLM7k7hnUC6Do2tWHlrxRCW7Q2QYH github.com/gethinode/hinode/v3 v3.16.3/go.mod h1:PqmewfbVBnwMVs6C4ZAKxjt9pYTAsOz+YF9gznJKHsk= github.com/gethinode/hinode/v3 v3.18.0 h1:9ITPE1I0JlyGKwirYiZM759kPYd5YRD+mFtvPOOhb0g= github.com/gethinode/hinode/v3 v3.18.0/go.mod h1:PqmewfbVBnwMVs6C4ZAKxjt9pYTAsOz+YF9gznJKHsk= +github.com/gethinode/hinode/v3 v3.19.1 h1:NJO3sVyo5k3qlui6EuYMV7KCG0MT+8sLx03yWYJ1Yk8= +github.com/gethinode/hinode/v3 v3.19.1/go.mod h1:AAIoKEtwZGLcUQA0l+G/RKs9XPLX1TU61GfTYxZ+Yrs= github.com/gethinode/mod-blocks/v2 v2.2.5 h1:8glKSX7OENSgbRAp+Bc1JL68Ix3DSWcHWh5dlsNrNsk= github.com/gethinode/mod-blocks/v2 v2.2.5/go.mod h1:1a4A2RrNSEoqgUjJoNPc2T+JtfPe0GvjHNrxjXauWCA= github.com/gethinode/mod-blocks/v2 v2.2.6 h1:gTrTTyUcFLjcjuqvr5Ex923F2E777vOzkbFJDzNaSz0= diff --git a/layouts/partials/assets/contact.html b/layouts/partials/assets/contact.html index 280730b..ab6f019 100644 --- a/layouts/partials/assets/contact.html +++ b/layouts/partials/assets/contact.html @@ -66,6 +66,7 @@ {{ partial "assets/hero.html" (dict "page" $page + "heading-level" $args.headingLevel "heading" (merge $heading (dict "size" 4 "width" 10)) "background" $args.background "illustration" $illustration diff --git a/layouts/partials/page/blocks.html b/layouts/partials/page/blocks.html index 3903e69..cad6871 100644 --- a/layouts/partials/page/blocks.html +++ b/layouts/partials/page/blocks.html @@ -21,13 +21,28 @@ renders none, so the first one carrying a title takes the level the caller asked for and every block after it becomes a subsection. Deriving this from the block list rather than from the order in which blocks render keeps the outline stable, because Hugo decides for - itself when a page's content is rendered. */}} + itself when a page's content is rendered. A block that hides itself when its section is + empty (hide_empty), that suppresses its own heading (an explicit level of 0), or whose + component does not declare a heading-level argument in its schema is passed over. */}} {{ $lead := -1 }} {{ if gt $headingLevel 0 }} {{ range $k, $v := . }} - {{ if and (lt $lead 0) (index (index $v "heading" | default dict) "title") }} - {{ $lead = $k }} - {{ end }} + {{ if lt $lead 0 }} + {{- $name := index $v "_bookshop_name" | default "" }} + {{- $sidecar := index (index (site.Data.structures.components | default dict) $name | default dict) $name | default dict }} + {{- $leveled := isset ($sidecar.arguments | default dict) "heading-level" }} + {{- $titled := index (index $v "heading" | default dict) "title" }} + {{- $hidden := or (index $v "hide_empty") (index $v "hide-empty") }} + {{- $suppressed := false }} + {{- if isset $v "heading_level" }} + {{- $suppressed = eq (index $v "heading_level") 0 }} + {{- else if isset $v "heading-level" }} + {{- $suppressed = eq (index $v "heading-level") 0 }} + {{- end }} + {{- if and $leveled $titled (not $hidden) (not $suppressed) }} + {{- $lead = $k }} + {{- end }} + {{- end }} {{ end }} {{ end }} @@ -37,7 +52,11 @@ {{ $b := merge $v (dict "_ordinal" $k "_page" $page) }} {{ if ne $embed nil }}{{ $b = merge $b (dict "_embed" $embed) }}{{ end }} {{ if and (gt $headingLevel 0) (not (or (isset $v "heading_level") (isset $v "heading-level"))) }} - {{ $b = merge $b (dict "heading_level" (cond (eq $k $lead) $headingLevel (cond (lt $headingLevel 2) 2 $headingLevel))) }} + {{- $name := index $v "_bookshop_name" | default "" }} + {{- $sidecar := index (index (site.Data.structures.components | default dict) $name | default dict) $name | default dict }} + {{- if isset ($sidecar.arguments | default dict) "heading-level" }} + {{- $b = merge $b (dict "heading_level" (cond (eq $k $lead) $headingLevel (cond (lt $headingLevel 2) 2 $headingLevel))) }} + {{- end }} {{ end }} {{ $blocks = $blocks | append $b }} {{ end }} From e6d605874d6f124527b06c49e581847ee5703dac Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Tue, 4 Aug 2026 16:58:19 +0200 Subject: [PATCH 3/5] fix(a11y): rank hide_empty blocks last in lead selection Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 3 ++- layouts/partials/page/blocks.html | 25 ++++++++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 6669853..4821ef8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -178,7 +178,8 @@ When `.hugo.html` components call Hugo partials, they pass a dict with kebab-cas ## Key Constraints - **Bookshop live editing**: Components must access arguments directly (not through helper partials) -- **Dual parameter names**: Support both snake_case (CloudCannon) and kebab-case (Hugo) using `(or .snake_case (index . "kebab-case"))` +- **Dual parameter names**: Support both snake_case (CloudCannon) and kebab-case (Hugo) using `(or .snake_case (index . "kebab-case"))`. For arguments where `0` or `false` is a meaningful value (such as `heading_level`), test key presence instead: `(cond (isset . "snake_case") .snake_case (index . "kebab-case"))` +- **Heading injection contract**: `page/blocks.html` injects a heading level only into blocks whose component sidecar (`component-library/components//.yml`) declares a `heading-level` argument. A component that renders a section title but omits that entry silently keeps rendering `
` titles instead of failing validation - **Hugo version**: Requires Hugo Extended 0.147.6+ - **Conventional Commits**: All commits must follow the specification (enforced by commitlint + husky) - **Semantic versioning**: Releases are automated via semantic-release on the `main` branch diff --git a/layouts/partials/page/blocks.html b/layouts/partials/page/blocks.html index cad6871..ce3b81f 100644 --- a/layouts/partials/page/blocks.html +++ b/layouts/partials/page/blocks.html @@ -21,15 +21,19 @@ renders none, so the first one carrying a title takes the level the caller asked for and every block after it becomes a subsection. Deriving this from the block list rather than from the order in which blocks render keeps the outline stable, because Hugo decides for - itself when a page's content is rendered. A block that hides itself when its section is - empty (hide_empty), that suppresses its own heading (an explicit level of 0), or whose - component does not declare a heading-level argument in its schema is passed over. */}} + itself when a page's content is rendered. A block that suppresses its own heading (an + explicit level of 0) or whose component does not declare a heading-level argument in its + schema is passed over. A block that hides itself when its section is empty (hide_empty) + ranks last: a later titled block wins over it, and the first such block leads only when + no other candidate exists. */}} + {{ $components := site.Data.structures.components | default dict }} {{ $lead := -1 }} {{ if gt $headingLevel 0 }} + {{ $fallback := -1 }} {{ range $k, $v := . }} {{ if lt $lead 0 }} {{- $name := index $v "_bookshop_name" | default "" }} - {{- $sidecar := index (index (site.Data.structures.components | default dict) $name | default dict) $name | default dict }} + {{- $sidecar := index (index $components $name | default dict) $name | default dict }} {{- $leveled := isset ($sidecar.arguments | default dict) "heading-level" }} {{- $titled := index (index $v "heading" | default dict) "title" }} {{- $hidden := or (index $v "hide_empty") (index $v "hide-empty") }} @@ -39,11 +43,18 @@ {{- else if isset $v "heading-level" }} {{- $suppressed = eq (index $v "heading-level") 0 }} {{- end }} - {{- if and $leveled $titled (not $hidden) (not $suppressed) }} - {{- $lead = $k }} + {{- if and $leveled $titled (not $suppressed) }} + {{- if not $hidden }} + {{- $lead = $k }} + {{- else if lt $fallback 0 }} + {{- $fallback = $k }} + {{- end }} {{- end }} {{- end }} {{ end }} + {{ if lt $lead 0 }} + {{ $lead = $fallback }} + {{ end }} {{ end }} {{/* Inject an ordinal number and the owning page for each block */}} @@ -53,7 +64,7 @@ {{ if ne $embed nil }}{{ $b = merge $b (dict "_embed" $embed) }}{{ end }} {{ if and (gt $headingLevel 0) (not (or (isset $v "heading_level") (isset $v "heading-level"))) }} {{- $name := index $v "_bookshop_name" | default "" }} - {{- $sidecar := index (index (site.Data.structures.components | default dict) $name | default dict) $name | default dict }} + {{- $sidecar := index (index $components $name | default dict) $name | default dict }} {{- if isset ($sidecar.arguments | default dict) "heading-level" }} {{- $b = merge $b (dict "heading_level" (cond (eq $k $lead) $headingLevel (cond (lt $headingLevel 2) 2 $headingLevel))) }} {{- end }} From 39dac8933627c0379bdbfd294a89a82eec9938b2 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Wed, 5 Aug 2026 12:27:21 +0200 Subject: [PATCH 4/5] docs: note the hide_empty fallback limit in the lead scan Co-Authored-By: Claude Fable 5 --- layouts/partials/page/blocks.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/layouts/partials/page/blocks.html b/layouts/partials/page/blocks.html index ce3b81f..3ad405b 100644 --- a/layouts/partials/page/blocks.html +++ b/layouts/partials/page/blocks.html @@ -25,7 +25,9 @@ explicit level of 0) or whose component does not declare a heading-level argument in its schema is passed over. A block that hides itself when its section is empty (hide_empty) ranks last: a later titled block wins over it, and the first such block leads only when - no other candidate exists. */}} + no other candidate exists. Whether such a block will render is unknown here; when every + candidate hides itself and the first turns out empty while a later one renders, the + page has no h1. */}} {{ $components := site.Data.structures.components | default dict }} {{ $lead := -1 }} {{ if gt $headingLevel 0 }} From 8661960477e402b105b44d4dfd333da3e4516b93 Mon Sep 17 00:00:00 2001 From: Mark Dumay <61946753+markdumay@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:26:54 +0200 Subject: [PATCH 5/5] refactor(a11y): declare heading-level through the shared argument system heading-level was defined inline in seventeen component sidecars and three partial structures. Common arguments belong in mod-utils _arguments.yml, where use-title -- the boolean it replaces -- has always lived; consumers reference them by bare key. - sidecars drop the definition; several return to comment + example, which is all they carried before this branch - the three partial structures keep a release-only override, the shape 98 arguments across hinode's structures already use - each consuming component declares heading_level in its blueprint, which is what states a component accepts an argument - the lead scan and the injection read the blueprint instead of the sidecar, so rendering no longer depends on the docs layer mod-llm reads - bump mod-utils to v6.9.0 for the definitions, hinode to v3.19.3 Co-Authored-By: Claude Opus 5 (1M context) --- .../components/about/about.bookshop.yml | 1 + component-library/components/about/about.yml | 10 ---------- .../components/approach/approach.bookshop.yml | 1 + component-library/components/approach/approach.yml | 9 --------- .../components/articles/articles.bookshop.yml | 1 + component-library/components/articles/articles.yml | 10 ---------- .../components/cards/cards.bookshop.yml | 1 + component-library/components/cards/cards.yml | 10 ---------- .../contact-form/contact-form.bookshop.yml | 1 + .../components/contact-form/contact-form.yml | 9 --------- component-library/components/cta/cta.bookshop.yml | 1 + component-library/components/cta/cta.yml | 10 ---------- component-library/components/faq/faq.bookshop.yml | 1 + component-library/components/faq/faq.yml | 10 ---------- .../components/featured/featured.bookshop.yml | 1 + component-library/components/featured/featured.yml | 10 ---------- .../components/heading/heading.bookshop.yml | 1 + component-library/components/heading/heading.yml | 10 ---------- component-library/components/hero/hero.bookshop.yml | 1 + component-library/components/hero/hero.yml | 9 --------- component-library/components/list/list.bookshop.yml | 1 + component-library/components/list/list.yml | 10 ---------- component-library/components/menu/menu.bookshop.yml | 1 + component-library/components/menu/menu.yml | 9 --------- .../components/panels/panels.bookshop.yml | 1 + component-library/components/panels/panels.yml | 10 ---------- .../components/preview/preview.bookshop.yml | 1 + component-library/components/preview/preview.yml | 9 --------- component-library/components/team/team.bookshop.yml | 1 + component-library/components/team/team.yml | 10 ---------- .../testimonials/testimonials.bookshop.yml | 1 + .../components/testimonials/testimonials.yml | 9 --------- .../video-message/video-message.bookshop.yml | 1 + .../components/video-message/video-message.yml | 10 ---------- data/structures/contact.yml | 5 ----- data/structures/hero.yml | 5 ----- data/structures/preview.yml | 5 ----- exampleSite/assets/jsconfig.json | 11 +++++++++++ exampleSite/go.mod | 4 ++-- exampleSite/go.sum | 4 ++++ go.mod | 2 +- go.sum | 2 ++ layouts/partials/page/blocks.html | 12 ++++++------ 43 files changed, 43 insertions(+), 188 deletions(-) create mode 100644 exampleSite/assets/jsconfig.json diff --git a/component-library/components/about/about.bookshop.yml b/component-library/components/about/about.bookshop.yml index affa772..3d99dbb 100644 --- a/component-library/components/about/about.bookshop.yml +++ b/component-library/components/about/about.bookshop.yml @@ -17,6 +17,7 @@ blueprint: arrangement: width: size: + heading_level: background: backdrop: color: diff --git a/component-library/components/about/about.yml b/component-library/components/about/about.yml index b8d7987..69843b8 100644 --- a/component-library/components/about/about.yml +++ b/component-library/components/about/about.yml @@ -1,15 +1,5 @@ comment: >- Renders an About section with heading, illustration, and optional links. -arguments: - heading-level: - type: int - optional: true - release: v2.4.0 - comment: >- - Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a - div instead of a heading. Defaults to the level the page assigns: the first - titled block of a page without a page header becomes its h1, the rest become - h2. example: | heading: title: About Us diff --git a/component-library/components/approach/approach.bookshop.yml b/component-library/components/approach/approach.bookshop.yml index 2574641..af01c40 100644 --- a/component-library/components/approach/approach.bookshop.yml +++ b/component-library/components/approach/approach.bookshop.yml @@ -17,6 +17,7 @@ blueprint: arrangement: width: size: + heading_level: background: backdrop: color: diff --git a/component-library/components/approach/approach.yml b/component-library/components/approach/approach.yml index aca8454..9da6cae 100644 --- a/component-library/components/approach/approach.yml +++ b/component-library/components/approach/approach.yml @@ -10,15 +10,6 @@ comment: >- element icon such as '1-circle'. Both arguments default to the existing card grid. arguments: - heading-level: - type: int - optional: true - release: v2.4.0 - comment: >- - Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a - div instead of a heading. Defaults to the level the page assigns: the first - titled block of a page without a page header becomes its h1, the rest become - h2. numbered: type: bool optional: true diff --git a/component-library/components/articles/articles.bookshop.yml b/component-library/components/articles/articles.bookshop.yml index a2f2069..addfa98 100644 --- a/component-library/components/articles/articles.bookshop.yml +++ b/component-library/components/articles/articles.bookshop.yml @@ -17,6 +17,7 @@ blueprint: arrangement: width: size: + heading_level: background: backdrop: color: diff --git a/component-library/components/articles/articles.yml b/component-library/components/articles/articles.yml index fc4a134..7c2cd69 100644 --- a/component-library/components/articles/articles.yml +++ b/component-library/components/articles/articles.yml @@ -1,15 +1,5 @@ comment: >- Renders a grid of articles from a Hugo section with optional filtering and pagination. -arguments: - heading-level: - type: int - optional: true - release: v2.4.0 - comment: >- - Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a - div instead of a heading. Defaults to the level the page assigns: the first - titled block of a page without a page header becomes its h1, the rest become - h2. example: | heading: title: Latest Articles diff --git a/component-library/components/cards/cards.bookshop.yml b/component-library/components/cards/cards.bookshop.yml index ae477a5..ba6dc16 100644 --- a/component-library/components/cards/cards.bookshop.yml +++ b/component-library/components/cards/cards.bookshop.yml @@ -17,6 +17,7 @@ blueprint: arrangement: width: size: + heading_level: background: backdrop: color: diff --git a/component-library/components/cards/cards.yml b/component-library/components/cards/cards.yml index 00b34b2..e70deb7 100644 --- a/component-library/components/cards/cards.yml +++ b/component-library/components/cards/cards.yml @@ -1,15 +1,5 @@ comment: >- Renders a grid of cards with icons, titles, and descriptions. -arguments: - heading-level: - type: int - optional: true - release: v2.4.0 - comment: >- - Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a - div instead of a heading. Defaults to the level the page assigns: the first - titled block of a page without a page header becomes its h1, the rest become - h2. example: | heading: title: Our Services diff --git a/component-library/components/contact-form/contact-form.bookshop.yml b/component-library/components/contact-form/contact-form.bookshop.yml index c932ce6..ba89f1b 100644 --- a/component-library/components/contact-form/contact-form.bookshop.yml +++ b/component-library/components/contact-form/contact-form.bookshop.yml @@ -16,6 +16,7 @@ blueprint: content: align: width: + heading_level: background: backdrop: color: diff --git a/component-library/components/contact-form/contact-form.yml b/component-library/components/contact-form/contact-form.yml index b83db12..76dc0cc 100644 --- a/component-library/components/contact-form/contact-form.yml +++ b/component-library/components/contact-form/contact-form.yml @@ -1,15 +1,6 @@ comment: >- Renders a contact form with an optional heading and custom form handler. arguments: - heading-level: - type: int - optional: true - release: v2.4.0 - comment: >- - Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a - div instead of a heading. Defaults to the level the page assigns: the first - titled block of a page without a page header becomes its h1, the rest become - h2. hook: type: string optional: false diff --git a/component-library/components/cta/cta.bookshop.yml b/component-library/components/cta/cta.bookshop.yml index 37f05d4..25389aa 100644 --- a/component-library/components/cta/cta.bookshop.yml +++ b/component-library/components/cta/cta.bookshop.yml @@ -17,6 +17,7 @@ blueprint: arrangement: width: size: + heading_level: background: backdrop: color: diff --git a/component-library/components/cta/cta.yml b/component-library/components/cta/cta.yml index 217f3b8..1f6dc09 100644 --- a/component-library/components/cta/cta.yml +++ b/component-library/components/cta/cta.yml @@ -1,15 +1,5 @@ comment: >- Renders a call-to-action section with heading and optional action links. -arguments: - heading-level: - type: int - optional: true - release: v2.4.0 - comment: >- - Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a - div instead of a heading. Defaults to the level the page assigns: the first - titled block of a page without a page header becomes its h1, the rest become - h2. example: | heading: title: Ready to get started? diff --git a/component-library/components/faq/faq.bookshop.yml b/component-library/components/faq/faq.bookshop.yml index 4cc5f22..ca16138 100644 --- a/component-library/components/faq/faq.bookshop.yml +++ b/component-library/components/faq/faq.bookshop.yml @@ -17,6 +17,7 @@ blueprint: arrangement: width: size: + heading_level: background: backdrop: color: diff --git a/component-library/components/faq/faq.yml b/component-library/components/faq/faq.yml index 6ca4cd0..88a6281 100644 --- a/component-library/components/faq/faq.yml +++ b/component-library/components/faq/faq.yml @@ -1,15 +1,5 @@ comment: >- Renders an FAQ accordion with heading and a list of question/answer items. -arguments: - heading-level: - type: int - optional: true - release: v2.4.0 - comment: >- - Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a - div instead of a heading. Defaults to the level the page assigns: the first - titled block of a page without a page header becomes its h1, the rest become - h2. example: | heading: title: Frequently Asked Questions diff --git a/component-library/components/featured/featured.bookshop.yml b/component-library/components/featured/featured.bookshop.yml index 9ff83c2..638376f 100644 --- a/component-library/components/featured/featured.bookshop.yml +++ b/component-library/components/featured/featured.bookshop.yml @@ -17,6 +17,7 @@ blueprint: arrangement: width: size: + heading_level: background: backdrop: color: diff --git a/component-library/components/featured/featured.yml b/component-library/components/featured/featured.yml index 3c60b86..8097172 100644 --- a/component-library/components/featured/featured.yml +++ b/component-library/components/featured/featured.yml @@ -1,15 +1,5 @@ comment: >- Renders a featured article with illustration, heading, and optional links. -arguments: - heading-level: - type: int - optional: true - release: v2.4.0 - comment: >- - Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a - div instead of a heading. Defaults to the level the page assigns: the first - titled block of a page without a page header becomes its h1, the rest become - h2. example: | heading: title: Featured Article diff --git a/component-library/components/heading/heading.bookshop.yml b/component-library/components/heading/heading.bookshop.yml index 19f1d89..adde3d0 100644 --- a/component-library/components/heading/heading.bookshop.yml +++ b/component-library/components/heading/heading.bookshop.yml @@ -17,6 +17,7 @@ blueprint: arrangement: width: size: + heading_level: background: backdrop: color: diff --git a/component-library/components/heading/heading.yml b/component-library/components/heading/heading.yml index 54562c2..9a6d993 100644 --- a/component-library/components/heading/heading.yml +++ b/component-library/components/heading/heading.yml @@ -1,15 +1,5 @@ comment: >- Renders a section heading with optional regular content. -arguments: - heading-level: - type: int - optional: true - release: v2.4.0 - comment: >- - Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a - div instead of a heading. Defaults to the level the page assigns: the first - titled block of a page without a page header becomes its h1, the rest become - h2. example: | heading: preheading: Preheading diff --git a/component-library/components/hero/hero.bookshop.yml b/component-library/components/hero/hero.bookshop.yml index 9c970d4..1a64202 100644 --- a/component-library/components/hero/hero.bookshop.yml +++ b/component-library/components/hero/hero.bookshop.yml @@ -18,6 +18,7 @@ blueprint: arrangement: width: 8 size: + heading_level: background: backdrop: color: diff --git a/component-library/components/hero/hero.yml b/component-library/components/hero/hero.yml index de11b90..70e8d32 100644 --- a/component-library/components/hero/hero.yml +++ b/component-library/components/hero/hero.yml @@ -1,15 +1,6 @@ comment: >- Renders a hero banner with heading, optional background, and action links. arguments: - heading-level: - type: int - optional: true - release: v2.4.0 - comment: >- - Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a - div instead of a heading. Defaults to the level the page assigns: the first - titled block of a page without a page header becomes its h1, the rest become - h2. heading: optional: false example: | diff --git a/component-library/components/list/list.bookshop.yml b/component-library/components/list/list.bookshop.yml index b840ffe..ba84809 100644 --- a/component-library/components/list/list.bookshop.yml +++ b/component-library/components/list/list.bookshop.yml @@ -17,6 +17,7 @@ blueprint: arrangement: width: size: + heading_level: background: backdrop: color: diff --git a/component-library/components/list/list.yml b/component-library/components/list/list.yml index fbf6bcb..2fa2c04 100644 --- a/component-library/components/list/list.yml +++ b/component-library/components/list/list.yml @@ -1,15 +1,5 @@ comment: >- Renders a sortable, searchable list of articles from a Hugo section. -arguments: - heading-level: - type: int - optional: true - release: v2.4.0 - comment: >- - Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a - div instead of a heading. Defaults to the level the page assigns: the first - titled block of a page without a page header becomes its h1, the rest become - h2. example: | heading: title: Articles diff --git a/component-library/components/menu/menu.bookshop.yml b/component-library/components/menu/menu.bookshop.yml index ab43df0..b67dd94 100644 --- a/component-library/components/menu/menu.bookshop.yml +++ b/component-library/components/menu/menu.bookshop.yml @@ -17,6 +17,7 @@ blueprint: arrangement: width: size: + heading_level: background: backdrop: color: diff --git a/component-library/components/menu/menu.yml b/component-library/components/menu/menu.yml index e07a8e6..bac2de3 100644 --- a/component-library/components/menu/menu.yml +++ b/component-library/components/menu/menu.yml @@ -1,15 +1,6 @@ comment: >- Renders a navigation menu in masonry or grid layout. arguments: - heading-level: - type: int - optional: true - release: v2.4.0 - comment: >- - Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a - div instead of a heading. Defaults to the level the page assigns: the first - titled block of a page without a page header becomes its h1, the rest become - h2. menu: optional: false example: | diff --git a/component-library/components/panels/panels.bookshop.yml b/component-library/components/panels/panels.bookshop.yml index 45dfaf0..dc925cb 100644 --- a/component-library/components/panels/panels.bookshop.yml +++ b/component-library/components/panels/panels.bookshop.yml @@ -17,6 +17,7 @@ blueprint: arrangement: width: size: + heading_level: background: backdrop: color: diff --git a/component-library/components/panels/panels.yml b/component-library/components/panels/panels.yml index 4598349..cdb977d 100644 --- a/component-library/components/panels/panels.yml +++ b/component-library/components/panels/panels.yml @@ -1,15 +1,5 @@ comment: >- Renders a set of expandable panels with icons and content. -arguments: - heading-level: - type: int - optional: true - release: v2.4.0 - comment: >- - Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a - div instead of a heading. Defaults to the level the page assigns: the first - titled block of a page without a page header becomes its h1, the rest become - h2. example: | heading: title: Features diff --git a/component-library/components/preview/preview.bookshop.yml b/component-library/components/preview/preview.bookshop.yml index 39d85d1..69b4b87 100644 --- a/component-library/components/preview/preview.bookshop.yml +++ b/component-library/components/preview/preview.bookshop.yml @@ -17,6 +17,7 @@ blueprint: arrangement: width: size: + heading_level: background: backdrop: color: diff --git a/component-library/components/preview/preview.yml b/component-library/components/preview/preview.yml index 58a2c70..3ef1b7c 100644 --- a/component-library/components/preview/preview.yml +++ b/component-library/components/preview/preview.yml @@ -1,15 +1,6 @@ comment: >- Renders a live URL preview with switchable device views. arguments: - heading-level: - type: int - optional: true - release: v2.4.0 - comment: >- - Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a - div instead of a heading. Defaults to the level the page assigns: the first - titled block of a page without a page header becomes its h1, the rest become - h2. url: optional: false example: | diff --git a/component-library/components/team/team.bookshop.yml b/component-library/components/team/team.bookshop.yml index 3a675a0..b008dcf 100644 --- a/component-library/components/team/team.bookshop.yml +++ b/component-library/components/team/team.bookshop.yml @@ -17,6 +17,7 @@ blueprint: arrangement: width: size: + heading_level: background: backdrop: color: diff --git a/component-library/components/team/team.yml b/component-library/components/team/team.yml index 733a48d..7d431e1 100644 --- a/component-library/components/team/team.yml +++ b/component-library/components/team/team.yml @@ -1,15 +1,5 @@ comment: >- Renders a team member grid sourced from a Hugo section. -arguments: - heading-level: - type: int - optional: true - release: v2.4.0 - comment: >- - Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a - div instead of a heading. Defaults to the level the page assigns: the first - titled block of a page without a page header becomes its h1, the rest become - h2. example: | heading: title: Meet the Team diff --git a/component-library/components/testimonials/testimonials.bookshop.yml b/component-library/components/testimonials/testimonials.bookshop.yml index 3009e89..7e7c5c0 100644 --- a/component-library/components/testimonials/testimonials.bookshop.yml +++ b/component-library/components/testimonials/testimonials.bookshop.yml @@ -17,6 +17,7 @@ blueprint: arrangement: width: size: + heading_level: background: backdrop: color: diff --git a/component-library/components/testimonials/testimonials.yml b/component-library/components/testimonials/testimonials.yml index 235f465..d4a5372 100644 --- a/component-library/components/testimonials/testimonials.yml +++ b/component-library/components/testimonials/testimonials.yml @@ -1,15 +1,6 @@ comment: >- Renders a testimonials carousel with client quotes. arguments: - heading-level: - type: int - optional: true - release: v2.4.0 - comment: >- - Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a - div instead of a heading. Defaults to the level the page assigns: the first - titled block of a page without a page header becomes its h1, the rest become - h2. testimonials: optional: false example: | diff --git a/component-library/components/video-message/video-message.bookshop.yml b/component-library/components/video-message/video-message.bookshop.yml index f531f37..05ec411 100644 --- a/component-library/components/video-message/video-message.bookshop.yml +++ b/component-library/components/video-message/video-message.bookshop.yml @@ -17,6 +17,7 @@ blueprint: arrangement: width: size: + heading_level: background: backdrop: color: diff --git a/component-library/components/video-message/video-message.yml b/component-library/components/video-message/video-message.yml index f54d9ac..1eb707b 100644 --- a/component-library/components/video-message/video-message.yml +++ b/component-library/components/video-message/video-message.yml @@ -1,15 +1,5 @@ comment: >- Renders a video with side messages highlighting key features. -arguments: - heading-level: - type: int - optional: true - release: v2.4.0 - comment: >- - Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render a - div instead of a heading. Defaults to the level the page assigns: the first - titled block of a page without a page header becomes its h1, the rest become - h2. example: | heading: title: Watch Our Overview diff --git a/data/structures/contact.yml b/data/structures/contact.yml index dab6a59..1507b09 100644 --- a/data/structures/contact.yml +++ b/data/structures/contact.yml @@ -3,12 +3,7 @@ comment: >- group: partial arguments: heading-level: - type: int - optional: true release: v2.4.0 - comment: >- - Heading level of the contact title, from 1 (h1) to 6 (h6). Set 0 to render - a div instead of a heading. page: data: optional: true diff --git a/data/structures/hero.yml b/data/structures/hero.yml index 2d8bf0e..a15e3d5 100644 --- a/data/structures/hero.yml +++ b/data/structures/hero.yml @@ -4,12 +4,7 @@ comment: >- also add a background image with an overlay to improve contrast. arguments: heading-level: - type: int - optional: true release: v2.4.0 - comment: >- - Heading level of the hero title, from 1 (h1) to 6 (h6). Set 0 to render a - div instead of a heading. page: breadcrumb: heading: diff --git a/data/structures/preview.yml b/data/structures/preview.yml index cf5cda1..4f0fd83 100644 --- a/data/structures/preview.yml +++ b/data/structures/preview.yml @@ -2,12 +2,7 @@ comment: >- Renders a live URL preview with switchable device views (desktop, tablet, mobile). arguments: heading-level: - type: int - optional: true release: v2.4.0 - comment: >- - Heading level of the section title, from 1 (h1) to 6 (h6). Set 0 to render - a div instead of a heading. page: optional: true comment: >- diff --git a/exampleSite/assets/jsconfig.json b/exampleSite/assets/jsconfig.json new file mode 100644 index 0000000..89453fc --- /dev/null +++ b/exampleSite/assets/jsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "paths": { + "*": [ + "../../../../../../go/pkg/mod/github.com/gethinode/hinode/v3@v3.19.3/assets/*", + "../../../../../../go/pkg/mod/github.com/nextapps-de/flexsearch@v0.0.0-20260529083235-f7ed963096a0/dist/*", + "../../../../../../go/pkg/mod/github.com/twbs/bootstrap@v5.3.8+incompatible/dist/js/*" + ] + } + } +} \ No newline at end of file diff --git a/exampleSite/go.mod b/exampleSite/go.mod index f56ccdd..82e0cc5 100644 --- a/exampleSite/go.mod +++ b/exampleSite/go.mod @@ -6,7 +6,7 @@ require ( github.com/FortAwesome/Font-Awesome v0.0.0-20260715180930-14c65a3747d0 // indirect github.com/airbnb/lottie-web v5.13.0+incompatible // indirect github.com/cloudcannon/bookshop/hugo/v3 v3.19.0 // indirect - github.com/gethinode/hinode/v3 v3.19.2 // indirect + github.com/gethinode/hinode/v3 v3.19.3 // indirect github.com/gethinode/mod-blocks/v2 v2.3.6 // indirect github.com/gethinode/mod-bootstrap v1.5.0 // indirect github.com/gethinode/mod-csp v1.0.14 // indirect @@ -18,7 +18,7 @@ require ( github.com/gethinode/mod-lottie/v3 v3.0.4 // indirect github.com/gethinode/mod-mermaid/v5 v5.0.4 // indirect github.com/gethinode/mod-simple-datatables/v4 v4.1.1 // indirect - github.com/gethinode/mod-utils/v6 v6.8.5 // indirect + github.com/gethinode/mod-utils/v6 v6.9.0 // indirect github.com/nextapps-de/flexsearch v0.0.0-20260529083235-f7ed963096a0 // indirect github.com/twbs/bootstrap v5.3.8+incompatible // indirect ) diff --git a/exampleSite/go.sum b/exampleSite/go.sum index a13db42..670639d 100644 --- a/exampleSite/go.sum +++ b/exampleSite/go.sum @@ -36,6 +36,8 @@ github.com/gethinode/hinode/v3 v3.18.0 h1:9ITPE1I0JlyGKwirYiZM759kPYd5YRD+mFtvPO github.com/gethinode/hinode/v3 v3.18.0/go.mod h1:PqmewfbVBnwMVs6C4ZAKxjt9pYTAsOz+YF9gznJKHsk= github.com/gethinode/hinode/v3 v3.19.2 h1:7WmBNZyS3R+hIi2BBMkZIh31MQix830wpCWwl65u+UA= github.com/gethinode/hinode/v3 v3.19.2/go.mod h1:AAIoKEtwZGLcUQA0l+G/RKs9XPLX1TU61GfTYxZ+Yrs= +github.com/gethinode/hinode/v3 v3.19.3 h1:gykPH9leoa1i69ATzGLRRSyeKUNtpEequxwvsnB/eDA= +github.com/gethinode/hinode/v3 v3.19.3/go.mod h1:9yBDgansb+wj1ingsqt2odSS8NMY+iRglICyLLgkIjo= github.com/gethinode/mod-blocks/v2 v2.2.5 h1:8glKSX7OENSgbRAp+Bc1JL68Ix3DSWcHWh5dlsNrNsk= github.com/gethinode/mod-blocks/v2 v2.2.5/go.mod h1:1a4A2RrNSEoqgUjJoNPc2T+JtfPe0GvjHNrxjXauWCA= github.com/gethinode/mod-blocks/v2 v2.2.6 h1:gTrTTyUcFLjcjuqvr5Ex923F2E777vOzkbFJDzNaSz0= @@ -120,6 +122,8 @@ github.com/gethinode/mod-utils/v6 v6.8.4 h1:L8x3IcWEn2kms7WWuYwtbi/x7lIR18z+eJhU github.com/gethinode/mod-utils/v6 v6.8.4/go.mod h1:E5tO9w3VKaidJpu1nI8zAKmh0bddFHOIIQnudAaXQTs= github.com/gethinode/mod-utils/v6 v6.8.5 h1:hzJgNjw6NIORMvxeJ3Q6+qPaPzLc4CQtNTWRXFVjFPM= github.com/gethinode/mod-utils/v6 v6.8.5/go.mod h1:E5tO9w3VKaidJpu1nI8zAKmh0bddFHOIIQnudAaXQTs= +github.com/gethinode/mod-utils/v6 v6.9.0 h1:+7RsNAfYCaM3rAzwrkTC6Ui3vcpGYWj1Ek1CmUVcaq0= +github.com/gethinode/mod-utils/v6 v6.9.0/go.mod h1:E5tO9w3VKaidJpu1nI8zAKmh0bddFHOIIQnudAaXQTs= github.com/nextapps-de/flexsearch v0.0.0-20260529083235-f7ed963096a0 h1:QDKcU3q39lFGzdVwM6kgCGnW3ibbMfYIi0Rwl62iJpo= github.com/nextapps-de/flexsearch v0.0.0-20260529083235-f7ed963096a0/go.mod h1:5GdMfPAXzbA2gXBqTjC6l27kioSYzHlqDMh0+wyx7sU= github.com/twbs/bootstrap v5.3.8+incompatible h1:eK1fsXP7R/FWFt+sSNmmvUH9usPocf240nWVw7Dh02o= diff --git a/go.mod b/go.mod index 312918d..044ca96 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,5 @@ go 1.19 require ( github.com/cloudcannon/bookshop/hugo/v3 v3.19.0 // indirect github.com/gethinode/mod-fontawesome/v6 v6.1.1 // indirect - github.com/gethinode/mod-utils/v6 v6.8.5 // indirect + github.com/gethinode/mod-utils/v6 v6.9.0 // indirect ) diff --git a/go.sum b/go.sum index 925be6d..a9ef8a6 100644 --- a/go.sum +++ b/go.sum @@ -24,3 +24,5 @@ github.com/gethinode/mod-utils/v6 v6.8.4 h1:L8x3IcWEn2kms7WWuYwtbi/x7lIR18z+eJhU github.com/gethinode/mod-utils/v6 v6.8.4/go.mod h1:E5tO9w3VKaidJpu1nI8zAKmh0bddFHOIIQnudAaXQTs= github.com/gethinode/mod-utils/v6 v6.8.5 h1:hzJgNjw6NIORMvxeJ3Q6+qPaPzLc4CQtNTWRXFVjFPM= github.com/gethinode/mod-utils/v6 v6.8.5/go.mod h1:E5tO9w3VKaidJpu1nI8zAKmh0bddFHOIIQnudAaXQTs= +github.com/gethinode/mod-utils/v6 v6.9.0 h1:+7RsNAfYCaM3rAzwrkTC6Ui3vcpGYWj1Ek1CmUVcaq0= +github.com/gethinode/mod-utils/v6 v6.9.0/go.mod h1:E5tO9w3VKaidJpu1nI8zAKmh0bddFHOIIQnudAaXQTs= diff --git a/layouts/partials/page/blocks.html b/layouts/partials/page/blocks.html index 3ad405b..d2944fb 100644 --- a/layouts/partials/page/blocks.html +++ b/layouts/partials/page/blocks.html @@ -22,8 +22,8 @@ every block after it becomes a subsection. Deriving this from the block list rather than from the order in which blocks render keeps the outline stable, because Hugo decides for itself when a page's content is rendered. A block that suppresses its own heading (an - explicit level of 0) or whose component does not declare a heading-level argument in its - schema is passed over. A block that hides itself when its section is empty (hide_empty) + explicit level of 0) or whose component does not declare heading_level in its blueprint + is passed over. A block that hides itself when its section is empty (hide_empty) ranks last: a later titled block wins over it, and the first such block leads only when no other candidate exists. Whether such a block will render is unknown here; when every candidate hides itself and the first turns out empty while a later one renders, the @@ -35,8 +35,8 @@ {{ range $k, $v := . }} {{ if lt $lead 0 }} {{- $name := index $v "_bookshop_name" | default "" }} - {{- $sidecar := index (index $components $name | default dict) $name | default dict }} - {{- $leveled := isset ($sidecar.arguments | default dict) "heading-level" }} + {{- $blueprint := (index (index $components $name | default dict) (printf "%s.bookshop" $name) | default dict).blueprint | default dict }} + {{- $leveled := isset $blueprint "heading_level" }} {{- $titled := index (index $v "heading" | default dict) "title" }} {{- $hidden := or (index $v "hide_empty") (index $v "hide-empty") }} {{- $suppressed := false }} @@ -66,8 +66,8 @@ {{ if ne $embed nil }}{{ $b = merge $b (dict "_embed" $embed) }}{{ end }} {{ if and (gt $headingLevel 0) (not (or (isset $v "heading_level") (isset $v "heading-level"))) }} {{- $name := index $v "_bookshop_name" | default "" }} - {{- $sidecar := index (index $components $name | default dict) $name | default dict }} - {{- if isset ($sidecar.arguments | default dict) "heading-level" }} + {{- $blueprint := (index (index $components $name | default dict) (printf "%s.bookshop" $name) | default dict).blueprint | default dict }} + {{- if isset $blueprint "heading_level" }} {{- $b = merge $b (dict "heading_level" (cond (eq $k $lead) $headingLevel (cond (lt $headingLevel 2) 2 $headingLevel))) }} {{- end }} {{ end }}