From 687dd807c3bdecc1839678e5742eb7b0c575700c Mon Sep 17 00:00:00 2001 From: Kyle McLaren Date: Mon, 27 Jul 2026 12:37:46 +0100 Subject: [PATCH 1/3] Make the docs nav usable on a phone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three changes to the nav accordion, all measured on fly.io/docs at 390x844: Collapsed sections were clipped with grid-rows/opacity only, so all 112 links in closed sections stayed focusable and stayed in the accessibility tree — a screen reader user swiped through 128 items to reach the 16 that were visible. Collapsed panels now carry `inert`. Section headers were two tap targets with no visible seam: on "Getting Started" the first 127px navigated to the section index and the remaining 221px toggled it. The header is now a single button that toggles, and the section's index page moves into the list as an "Overview" child. Sections whose links already include that page (8 of 15 in the main nav) are left alone rather than growing a duplicate. Triggers gained aria-controls pointing at the panel they own. The panels no longer claim role="region" — every closed section was announcing itself as a landmark, and a nav sublist is better left as a list. Breadcrumbs put `truncate` on every segment inside a flex row, so they all shrank together and a three-level path rendered as "D... > Fly Mac... > An introduction to Fly ..." on a phone. They now scroll sideways instead. Needs the matching CSS and JS in superfly/landing to land together. Co-Authored-By: Claude Opus 5 (1M context) --- partials/_accordion_nav.html.erb | 64 +++++++++++++++++++++----------- partials/_breadcrumbs.html.erb | 13 ++++--- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/partials/_accordion_nav.html.erb b/partials/_accordion_nav.html.erb index 8e405cb38e..d0128c4298 100644 --- a/partials/_accordion_nav.html.erb +++ b/partials/_accordion_nav.html.erb @@ -1,13 +1,29 @@ -<% +<% def section_open?(section) (!section[:path].nil? && current_page?(section[:path])) || (!section[:open].nil? && section[:open]) || (section[:links] && section[:links].any? { |page| (!page[:path].nil? && current_page?(page[:path])) || (page[:links] && section_open?(page)) }) end + # Ties a trigger to the panel it controls via aria-controls. + def accordion_id(section, prefix) + slug = section[:title].to_s.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-+|-+\z/, "") + "#{prefix}-#{slug.empty? ? "section" : slug}" + end + + # The section's own index page moves into the list as "Overview" so the header + # row has exactly one job instead of being half link, half toggle. Skipped + # when the links already include that page, which most sections do. + def overview_needed?(section) + return false if section[:path].nil? + target = section[:path].to_s.chomp("/") + !Array(section[:links]).any? { |page| page[:path].to_s.chomp("/") == target } + end + nav.each { |nav| nav[:open] = section_open?(nav) unless nav[:title].nil? } - nav.each do |nav| + nav.each_with_index do |nav, nav_index| + section_id = accordion_id(nav, "nav-#{nav_index}") %> <% if nav[:links].nil? %> <%= nav_link nav[:title], nav[:path], class: "title-link" %> @@ -29,36 +45,42 @@ <% else %>