The original code from the vertical navigation post scores 75% on accessibility, mostly not contrast related, I asked Opus 4 to do some optimization on that subject, important. Adjusted code scores 95% on accessibility, 5% left to fix for contrast remarks:
the code
# -*- coding: utf-8 -*-
"""
Created on Thu Nov 6 12:51:45 2025
@author: win11
"""
import dash
from dash import html, Output, Input, dcc, clientside_callback
import dash_bootstrap_components as dbc
app = dash.Dash(
__name__,
external_stylesheets=[dbc.themes.SOLAR],
# Add meta tags for language and viewport
meta_tags=[
{"name": "viewport", "content": "width=device-width, initial-scale=1"},
]
)
# Set the language attribute for the HTML document
app.index_string = '''
<!DOCTYPE html>
<html lang="en">
<head>
{%metas%}
<title>{%title%}</title>
{%favicon%}
{%css%}
</head>
<body>
{%app_entry%}
<footer>
{%config%}
{%scripts%}
{%renderer%}
</footer>
</body>
</html>
'''
app.title = "My Accessible Dash App"
app.layout = dbc.Container([
dcc.Location(id="url", refresh='callback-nav'),
html.Main([ # Use main element for primary content
html.Section(
html.H1("Home", className="section-title"), # Use heading for better structure
className="section",
id="home",
**{"data-label": "Home"}
),
html.Section(
html.H1("About Us", className="section-title"),
className="section",
id="about",
**{"data-label": "About Us"}
),
html.Section(
html.H1("Get In Touch", className="section-title"),
className="section",
id="contact",
**{"data-label": "Get In Touch"}
),
]),
html.Nav(
id="my-navigation",
className="nav",
**{"role": "navigation", "aria-label": "Main navigation"},
children=[
html.Div(
className="nav-item",
children=[
html.A(
"Home", # Add text content to the link
href="#home",
className="nav-link",
id="link1",
**{"aria-label": "Navigate to Home section"}
),
html.Span("Home", className="nav-label", **{"aria-hidden": "true"})
]
),
html.Div(
className="nav-item",
children=[
html.A(
"About Us", # Add text content to the link
href="#about",
className="nav-link",
id="link2",
**{"aria-label": "Navigate to About Us section"}
),
html.Span("About Us", className="nav-label", **{"aria-hidden": "true"})
]
),
html.Div(
className="nav-item",
children=[
html.A(
"Get In Touch", # Add text content to the link
href="#contact",
className="nav-link",
id="link3",
**{"aria-label": "Navigate to Get In Touch section"}
),
html.Span("Get In Touch", className="nav-label", **{"aria-hidden": "true"})
]
),
])
], fluid=True)
app.clientside_callback(
"""function (id) {
activateNavigation();
return window.dash_clientside.no_update
}""",
Output('my-navigation', 'children'), Input('my-navigation', 'children'))
if __name__ == '__main__':
app.run(debug=False)
the js to handle navigation (place somewhere in assets):
function activateNavigation() {
const sections = document.querySelectorAll(".section");
const navLinks = document.querySelectorAll(".nav-link");
const observer = new IntersectionObserver(
(entries) => {
navLinks.forEach((navLink) => {
navLink.classList.remove("nav-link-selected");
});
const visibleSection = entries.find((entry) => entry.isIntersecting);
if (visibleSection) {
const visibleSectionId = visibleSection.target.getAttribute("id");
const correspondingNavLink = document.querySelector(`.nav-link[href="#${visibleSectionId}"]`);
if (correspondingNavLink) {
correspondingNavLink.classList.add("nav-link-selected");
}
}
},
{ threshold: 0.5 }
);
sections.forEach((section) => observer.observe(section));
}
the css (place somewhere in assets):
html {
scroll-behavior: smooth;
}
body {
font-family: "Quicksand", sans-serif;
margin: 0;
color: #000;
overflow: hidden;
}
/* uncomment to hide the scrollbar but keep the scroll action*/
/*
body::-webkit-scrollbar{
display: none;
} */
.section {
height: 100vh;
}
#home {
background: #aff8db;
}
#about {
background: #ffabab;
}
#contact {
background: #fff5ba;
}
#section4 {
background: #2496b3;
}
#section5 {
background: #002B36;
}
#section6 {
background: #41eda8;
}
#section7 {
background: #df3838;
}
#section8 {
background: #c3f0fb;
}
.nav {
--nav-gap: 10px;
padding: var(--nav-gap);
position: fixed;
right: 10px;
top: 50%;
transform: translateY(-50%);
display: inline-block;
}
.nav-item {
align-items: center;
display: flex;
margin-bottom: var(--nav-gap);
flex-direction: row-reverse;
}
.nav-link {
height: 25px;
width: 25px;
background-color: rgba(0, 0, 0, 0.7);
border-radius: 50%;
display: inline-block;
padding: 10px;
margin: 5px;
transition: transform 0.1s;
/* Accessibility additions */
position: relative;
overflow: hidden;
font-size: 0;
}
.nav-link:hover ~ .nav-label {
opacity: 1;
}
.nav-link-selected {
background: #000000;
transform: scale(1.2);
}
.nav-label {
color: #000;
font-weight: bold;
opacity: 0;
transition: opacity 0.1s;
font-size: 1rem;
}
/* Visually hide link text but keep it accessible to screen readers */
.nav-link-text {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
overflow: hidden;
}