惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
The Exploit Database - CXSecurity.com
F
Fortinet All Blogs
U
Unit 42
F
Full Disclosure
雷峰网
雷峰网
博客园 - 司徒正美
云风的 BLOG
云风的 BLOG
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tailwind CSS Blog
The Cloudflare Blog
Last Week in AI
Last Week in AI
罗磊的独立博客
D
DataBreaches.Net
C
Check Point Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
O
OpenAI News
C
CXSECURITY Database RSS Feed - CXSecurity.com
aimingoo的专栏
aimingoo的专栏
S
Security @ Cisco Blogs
大猫的无限游戏
大猫的无限游戏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
S
SegmentFault 最新的问题
NISL@THU
NISL@THU
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Hacker News
The Hacker News
Webroot Blog
Webroot Blog
Security Latest
Security Latest
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Google DeepMind News
Google DeepMind News
酷 壳 – CoolShell
酷 壳 – CoolShell
N
News | PayPal Newsroom
P
Proofpoint News Feed
B
Blog RSS Feed
MongoDB | Blog
MongoDB | Blog
C
Cybersecurity and Infrastructure Security Agency CISA
N
News and Events Feed by Topic
Google Online Security Blog
Google Online Security Blog
H
Help Net Security
Spread Privacy
Spread Privacy
T
Threat Research - Cisco Blogs
GbyAI
GbyAI
I
Intezer
Application and Cybersecurity Blog
Application and Cybersecurity Blog
M
MIT News - Artificial intelligence
Vercel News
Vercel News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
IT之家
IT之家
MyScale Blog
MyScale Blog
腾讯CDC

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Focus Issues and Refinement Support
ShaynaProductions · 2026-06-16 · via DEV Community

Prologue

A while ago, I decided to develop a fully accessible main navigation component in React and write a series of articles documenting the steps it took to create a non-trivial accessible component.

My last development article completed the base requirements for keyboard functionality within the component; attention now shifts to adding some of the last functionality required, closing sublists when the lists holding them now close and determining what happens when a closed component is entered via the keyboard through the Tab and Shift+Tab keys.


Note: This article is one of a series demonstrating building a React navigational component from scratch while considering accessibility through the process. The articles are accompanied by a GitHub repository with releases tied to one or more articles; each builds on the previous one until a fully implemented navigation component is complete.

Each release and its associated tag contain fully runnable code for the article. The code discussed in this article is available in the release. and may be downloaded at release 0.8.0. Links in the article will take you to the proper file in the tagged GitHub Repository.

Because the code for this release is scattered, line numbers are added to make it easier to locate in the linked GitHub file. Line numbers are also provided for those who would like to follow along with a downloaded copy.

While code examples are written in JavaScript for brevity, all actual code is written in Typescript and targets React 19.x, all while using vanilla CSS. Examples use Next.js v16.x, which is not required to run the navigation component.

You can view the requirements for the Focus and Refinement Support Release along with previous requirements.


Content Links

  • Introduction
  • Acceptance Criteria
  • Entering

  • Closings

    • Setting Up For Success

Introduction

The implementation of keyboard handling left one obvious keyboard issue to fix: an apparent keyboard trap that occurs when focus shifts into the component via a Shift+Tab key combination.

In addition to the keyboard trap, other issues arise that, while not errors, can be refined to meet user expectations. Closing sublists when a parent is closed, or closing open lists when navigating through the top row, will give the component a finished aspect.

This release, while fixing one nagging issue, once again focuses on the internal, foundational aspects necessary to support those requirements and sets up the next release for success, enhancing the component to close cleanly, beginning with additions to the NavigationProvider.

Acceptance Criteria

Focus and Closing Foundation Release

  • AC 1 - When Shift+Tab is used to enter the component and the last child on the top row is a button, focus should land on that button

A navigation component is not modal; it does not block interaction with other elements on a page, and exiting can be done using the Tab and Shift+Tab keys on the first and last component elements.

As a reminder, the entire component is always in the document object model, meaning that when Shift+Tab is executed to shift focus into the component, there's a good chance the focus will initially land on a link in an unexpanded sublist. In that case, the focus should shift to its parent on the last row.

For the last component element, if it is contained in an unexpanded sublist, focus should shift to its parent on the last row.

Entering

AC 1 fixes the last remaining keyboard-related issue, which occurs when Shift+Tab moves focus from outside the component to the last focusable element in the navigation component. If the top-row parent is a button and the sublist containing the last focusable element is closed, focus should shift to the top-row parent.

Any disappearance of the focus outline is regarded as an error by a screen-perceiving, keyboard-operating user.

The solution is to shift the focus to the top-row parent when focus is placed on the last element of the component within a closed sublist. This requires another event listener, onFocus.

Navigation Item

const handleFocus = () => {
  const linkEl = linkRef.current;
  const focusableEl = handleLinkFocus(linkEl);
  if (!!focusableEl && focusableEl !== linkEl) {
    shiftFocus(focusableEl);
  }
};

...
const linkProps = {
  ...,
  onFocus: handleFocus,
  onKeyDown: handleKeyDown,
  ref: linkRef,
  ...rest,
};

GitHub (release 0.8.0) - NavigationItem.tsx - Line 79

The handleFocus function calls the useNavigation hook's handleLinkFocus function, which can return a new element to shift focus. The function may also return the current link element. The focus should shift only when the returned element is both defined and different.

UseNavigation

HandleLinkFocus


const handleLinkFocus = ( linkEl) => {
  if (_isLastElementInComponent(linkEl)) {
    return _handleLastChildFocus(linkEl);
  } 
};

GitHub (release 0.8.0) - useNavigation.tsx - HandleLinkFocus Line 369

The last element of a component is always a link, so a check is implemented to determine whether the link holding focus is the last component element; if so, the handler for the last child is called, which will either return itself or its topmost ancestor on the top row.

_isLastElementInComponent
const _isLastElementInComponent = (focusedEl) => {
    return focusedEl === _getLastElementbyComponent();
  };

GitHub (release 0.8.0) - useNavigation.tsx - _isLastElementInComponent Line 153

The question of whether the currently focused element is the last component requires the system to know which element is the last in the component.

_getLastElementInComponent

const _getLastElementInComponent = () => {
    let lastEl = lastComponentElement;
    if (!lastEl) {
      const lastElement = _getLastElementInIndexedList(0);
      if (lastElement.type === "button") {
        lastEl = _getLastElementByParent( lastElement  );
      } else {
        lastEl = lastElement;
      }
      setLastComponentElement(lastEl);
    }
    return lastEl ;
  };

GitHub (release 0.8.0) - useNavigation.tsx - _getLastElementByComponent Line 95

Determining the last element of the component involves finding the last focusable element associated with a particular parent, which, in this case, would be the last element in the top row. The _getLastElementByParent function triggers a recursive call, which can be resource-intensive. Since the entire nested list resides in the DOM, the last element will not change, so it makes sense to store it in state on the first call.

If the link is the last element within the component, a specific handler for that condition is called.

_handleLastChildFocus

const _handleLastChildFocus = (focusedEl) => {
    const { isSubListOpen } =  _getNavigationObjectByListElement(focusedEl);
    if (!isSubListOpen) {
      return _getLastElementInTopRow(focusedEl);
    } else {
      return focusedEl;
    }
  };

GitHub (release 0.8.0) - useNavigation.tsx - _handleLastChildFocus Line 203

The only way focus can land on an item in a sublist that has not been expanded is through the Shift+Tab action, at which point the last element in the top row is returned for the focus shift. The currently focused element is returned when the sublist is opened.

_getLastElementInTopRow

const _getLastElementInTopRow = (focusedEl) => {
    const lastTopEl = _getLastElementInIndexedList(0);
    if (lastTopEl.type === "button") {
      const lastEl = _getLastElementByParent(
        lastTopEl as ControllingElementType,
      );
      /* istanbul ignore else */
      if (focusedEl === lastEl) {
        return lastTopEl;
      }
    }
  };

GitHub (release 0.8.0) - useNavigation.tsx - _getLastElementInRow Line 116

If the last element on the top row is a button, the recursive function is called to request the last component element associated with the last element in the top row; if the link matches, the last element on the top row is returned, and the issue is fixed.

Retrieving the last element in the top row first requires fetching the last element in that row.

_getLastElementInIndexedList

const _getLastElementInIndexedList = useCallback(
    (index) => {
      const { storedList } = getNavigationArray()[index];
      return storedList[storedList.length - 1];
    },
    [getNavigationArray],
  );

GitHub (release 0.8.0) - useNavigation.tsx - _getLastElementInIndexedList Line 74

And now, focus shifts to the end button when the component is entered through Shift+Tab.

With the last keyboard issue resolved, attention can be turned to closing scenarios.

Closings

What should happen when an open list containing other open sublists is closed? In the current iteration, nothing happens, meaning that when a list is reopened, any previously opened sublists remain visible.

When thinking about navigation, it can be helpful to look at the requirements for menus, whether they be operating systems or browser applications. In either case, when a list is closed, any open lists under it are also closed.

The question is: how can this be accomplished within the current architecture? While a button in the SubNavigation component can close the list it is associated with, how can it reach into another open subnavigation component in its downline and trigger the close?

Storing the closeSubNavigation function in the associated navigation object should do the trick. Each function can be held and passed by reference, and executing the passed function will call the appropriate functions.

Setting up for Success

Functions shouldn't be stored in state; triggering them there can cause re-rendering, which could be detrimental to data stability. Instead, a map object is created within a reference object, which holds the functions associated with the parent element. This map object can inject the appropriate functions into the navigation array objects when requested.

Since all the data used to manage the navigation component is stored in the navigation context provider, updates to that data are required.

_dispatchSubListCloseByParent

const _dispatchSubListCloseByParent =
  useRef(new Map());

GitHub (release 0.8.0) - NavigationProvider.tsx - _dispatchSubListCloseByParent Line 46

Unlike updates to state, updates to a ref held within the context provider don't trigger a re-render when the map object changes.

getNavigationArray

const getNavigationArray =  useCallback(() => {
    return state.navigationArray.map((obj) => ({
      ...obj,
      dispatchSubListClose: _dispatchSubListCloseByParent.current.get(
        obj.storedParentEl,
      ),
    }));
  }, [state.navigationArray]);

GitHub (release 0.8.0) - NavigationProvider.tsx - getNavigationArray Line 49

The function returning the navigation array is updated to inject the subListClose function as dispatchSubListClose into the array when it is returned. The function isn't part of the state holding the array of navigation objects; it's injected each time the function is called.

registerButtonAsParent

const registerButtonAsParent = (isListOpen, parentEl, dispatchSubListClose) => {
    dispatch({ type: "SET_PARENT", parentEl, isListOpen });
    _dispatchSubListCloseByParent.current.set(parentEl, dispatchSubListClose);
  };

GitHub (release 0.8.0) - NavigationProvider.tsx - registerButtonAsParent
Line 59

RegisterButtonAsParent is also modified, setting the map directly and tying the close function to the parent element.

All that's left to do is to add the close function in the correct useEffect.

SubNavigation

useEffect(() => {
  if (buttonRef.current !== null) {
    registerItemInCurrentList(buttonRef.current as FocusableElementType);
    registerButtonAsParent(
      isSubListOpen,
      buttonRef.current,
      closeSubNavigation,
    );
  }
}, [buttonRef, closeSubNavigation, isSubListOpen, registerButtonAsParent, registerItemInCurrentList]);

GitHub (release 0.8.0) - SubNavigation.tsx - Line 83

With registration complete, work on actually closing the sublists can begin and will be detailed in the next article.

Summary

With keyboard navigation (finally) completed, attention now turns to refinements; fixing the last entry issue by sending focus to the last button in the top row when the last element in the component receives focus in an unopened list and preparing architecture to support each button closing itself and any open sublists maintained by its children.