Figure Friday 2025 - week 44

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;
}
2 Likes