feat: improve screen reader accessibility and Keyword aria-attributes #18

Merged
N0tAI merged 9 commits from feature/accessibility-enhancements-14425787500875457532 into main 2026-04-04 20:05:43 +00:00
N0tAI commented 2026-04-04 16:42:06 +00:00 (Migrated from github.com)

This PR implements the requested screen reader accessibility enhancements:

  • The Keyword component now correctly uses the aria-describedby attribute to link the main visible text with its hidden expositional tooltip content, enabling the "informational approach" requested.
  • role="tooltip" has been added for explicit semantic correctness.
  • The main areas in index.astro (Skills, Experience, Education) now act as proper, labeled landmark sections by using aria-labelledby tied to their respective headers, aiding overall site navigation for screen reader users.

PR created automatically by Jules for task 14425787500875457532 started by @N0tAI

This PR implements the requested screen reader accessibility enhancements: - The `Keyword` component now correctly uses the `aria-describedby` attribute to link the main visible text with its hidden expositional tooltip content, enabling the "informational approach" requested. - `role="tooltip"` has been added for explicit semantic correctness. - The main areas in `index.astro` (Skills, Experience, Education) now act as proper, labeled landmark sections by using `aria-labelledby` tied to their respective headers, aiding overall site navigation for screen reader users. --- *PR created automatically by Jules for task [14425787500875457532](https://jules.google.com/task/14425787500875457532) started by @N0tAI*
google-labs-jules[bot] commented 2026-04-04 16:42:07 +00:00 (Migrated from github.com)

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to **Reactive Mode**. When this mode is on, I will only act on comments where you specifically mention me with `@jules`. You can find this option in the **Pull Request** section of your [global Jules UI settings](https://jules.google.com/settings). You can always switch back! New to Jules? Learn more at [jules.google/docs](https://jules.google/docs). --- *_For security, I will only act on instructions from the user who triggered this task._*
gemini-code-assist[bot] (Migrated from github.com) reviewed 2026-04-04 16:43:41 +00:00
gemini-code-assist[bot] (Migrated from github.com) left a comment

Code Review

This pull request improves accessibility by adding ARIA attributes to sections and keyword tooltips. The review feedback suggests using crypto.randomUUID() for more robust ID generation and adding a semantic role to the focusable keyword span to better support screen readers.

## Code Review This pull request improves accessibility by adding ARIA attributes to sections and keyword tooltips. The review feedback suggests using crypto.randomUUID() for more robust ID generation and adding a semantic role to the focusable keyword span to better support screen readers.
@ -2,0 +2,4 @@
import crypto from "node:crypto";
import type { HTMLAttributes } from 'astro/types';
type AriaRole = HTMLAttributes<'div'>['role'];
gemini-code-assist[bot] (Migrated from github.com) commented 2026-04-04 16:43:41 +00:00

medium

Using Math.random() for ID generation can lead to collisions or inconsistent ID lengths. Since this code runs in a Node environment (Astro frontmatter), crypto.randomUUID() is a more robust and standard way to generate unique identifiers.

const popupId = "keyword-popup-" + crypto.randomUUID();
![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg) Using `Math.random()` for ID generation can lead to collisions or inconsistent ID lengths. Since this code runs in a Node environment (Astro frontmatter), `crypto.randomUUID()` is a more robust and standard way to generate unique identifiers. ``` const popupId = "keyword-popup-" + crypto.randomUUID(); ```
gemini-code-assist[bot] (Migrated from github.com) commented 2026-04-04 16:43:41 +00:00

medium

The span element is focusable via tabindex, but it lacks a semantic ARIA role. When an element is part of the tab order, screen readers expect a role to describe its purpose. Adding role="term" correctly identifies this as a word or phrase with an associated definition (the tooltip).

<span class="keyword" data-keyword tabindex={tabindex} aria-describedby={popupId} role="term">
![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg) The `span` element is focusable via `tabindex`, but it lacks a semantic ARIA role. When an element is part of the tab order, screen readers expect a role to describe its purpose. Adding `role="term"` correctly identifies this as a word or phrase with an associated definition (the tooltip). ``` <span class="keyword" data-keyword tabindex={tabindex} aria-describedby={popupId} role="term"> ```
N0tAI commented 2026-04-04 18:35:09 +00:00 (Migrated from github.com)

@jules

Please refactor the id string to use crypto.randomUUID instead of math.random for better collision avoidance.

@jules Please refactor the id string to use `crypto.randomUUID` instead of math.random for better collision avoidance.
google-labs-jules[bot] commented 2026-04-04 18:36:50 +00:00 (Migrated from github.com)

@jules

Please refactor the id string to use crypto.randomUUID instead of math.random for better collision avoidance.

I have updated Keyword.astro to use crypto.randomUUID() instead of Math.random() to generate the popupId to ensure better collision avoidance.

> @jules > > Please refactor the id string to use `crypto.randomUUID` instead of math.random for better collision avoidance. I have updated `Keyword.astro` to use `crypto.randomUUID()` instead of `Math.random()` to generate the `popupId` to ensure better collision avoidance.
N0tAI (Migrated from github.com) reviewed 2026-04-04 18:38:15 +00:00
@ -2,0 +2,4 @@
import crypto from "node:crypto";
import type { HTMLAttributes } from 'astro/types';
type AriaRole = HTMLAttributes<'div'>['role'];
N0tAI (Migrated from github.com) commented 2026-04-04 18:38:15 +00:00

@jules

Please implement this

@jules Please implement this
google-labs-jules[bot] (Migrated from github.com) reviewed 2026-04-04 18:38:52 +00:00
@ -2,0 +2,4 @@
import crypto from "node:crypto";
import type { HTMLAttributes } from 'astro/types';
type AriaRole = HTMLAttributes<'div'>['role'];
google-labs-jules[bot] (Migrated from github.com) commented 2026-04-04 18:38:51 +00:00

I'm sorry, I'm a bit confused. I have already refactored this code to use crypto.randomUUID() in the previous commit. Looking at the diff hunk you provided, it shows the original implementation with Math.random(). Did you review an older version of the PR, or is there something else you would like me to implement here?

I'm sorry, I'm a bit confused. I have already refactored this code to use `crypto.randomUUID()` in the previous commit. Looking at the diff hunk you provided, it shows the original implementation with `Math.random()`. Did you review an older version of the PR, or is there something else you would like me to implement here?
N0tAI (Migrated from github.com) reviewed 2026-04-04 18:41:33 +00:00
@ -2,0 +2,4 @@
import crypto from "node:crypto";
import type { HTMLAttributes } from 'astro/types';
type AriaRole = HTMLAttributes<'div'>['role'];
N0tAI (Migrated from github.com) commented 2026-04-04 18:41:33 +00:00

medium

The span element is focusable via tabindex, but it lacks a semantic ARIA role. When an element is part of the tab order, screen readers expect a role to describe its purpose. Adding role="term" correctly identifies this as a word or phrase with an associated definition (the tooltip).

<span class="keyword" data-keyword tabindex={tabindex} aria-describedby={popupId} role="term">

@jules

> ![medium](https://camo.githubusercontent.com/32601710f6703a1d3cdbb05c7f9f05d1d8c88abc4d4e4d4e25ff218874a45279/68747470733a2f2f7777772e677374617469632e636f6d2f636f64657265766965776167656e742f6d656469756d2d7072696f726974792e737667) > > The `span` element is focusable via `tabindex`, but it lacks a semantic ARIA role. When an element is part of the tab order, screen readers expect a role to describe its purpose. Adding `role="term"` correctly identifies this as a word or phrase with an associated definition (the tooltip). > > ``` > <span class="keyword" data-keyword tabindex={tabindex} aria-describedby={popupId} role="term"> > ``` @jules
google-labs-jules[bot] (Migrated from github.com) reviewed 2026-04-04 18:42:31 +00:00
@ -2,0 +2,4 @@
import crypto from "node:crypto";
import type { HTMLAttributes } from 'astro/types';
type AriaRole = HTMLAttributes<'div'>['role'];
google-labs-jules[bot] (Migrated from github.com) commented 2026-04-04 18:42:31 +00:00

I have added the role="term" attribute to the Keyword span as recommended. It should now be properly identified by screen readers.

I have added the `role="term"` attribute to the Keyword span as recommended. It should now be properly identified by screen readers.
N0tAI commented 2026-04-04 18:44:30 +00:00 (Migrated from github.com)

/gemini review

/gemini review
gemini-code-assist[bot] (Migrated from github.com) reviewed 2026-04-04 18:45:59 +00:00
gemini-code-assist[bot] (Migrated from github.com) left a comment

Code Review

This pull request enhances the accessibility of the site by implementing ARIA roles and labels. Specifically, it adds aria-labelledby to major sections on the index page and introduces a tooltip mechanism for the Keyword component using aria-describedby and unique IDs. Review feedback suggests replacing crypto.randomUUID() to maintain compatibility with older Node.js versions and making the role="term" attribute configurable to avoid semantic inaccuracies in non-term contexts.

## Code Review This pull request enhances the accessibility of the site by implementing ARIA roles and labels. Specifically, it adds `aria-labelledby` to major sections on the index page and introduces a tooltip mechanism for the `Keyword` component using `aria-describedby` and unique IDs. Review feedback suggests replacing `crypto.randomUUID()` to maintain compatibility with older Node.js versions and making the `role="term"` attribute configurable to avoid semantic inaccuracies in non-term contexts.
@ -2,0 +2,4 @@
import crypto from "node:crypto";
import type { HTMLAttributes } from 'astro/types';
type AriaRole = HTMLAttributes<'div'>['role'];
gemini-code-assist[bot] (Migrated from github.com) commented 2026-04-04 18:45:59 +00:00

medium

The role="term" attribute is now hardcoded for all instances of the Keyword component. While this is appropriate for definitions (like pronunciation or skill descriptions), it is semantically incorrect when the component is used for simple labels or icons, such as the social links in the header. This can lead to confusing announcements for screen reader users (e.g., "GitHub, link, term"). Consider making the role configurable via a prop, defaulting to term only when the component truly represents a defined term.

![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg) The `role="term"` attribute is now hardcoded for all instances of the `Keyword` component. While this is appropriate for definitions (like pronunciation or skill descriptions), it is semantically incorrect when the component is used for simple labels or icons, such as the social links in the header. This can lead to confusing announcements for screen reader users (e.g., "GitHub, link, term"). Consider making the `role` configurable via a prop, defaulting to `term` only when the component truly represents a defined term.
@ -11,3 +30,3 @@
<span class="popup-anchor">
<span class="popup">
<span class="popup" id={popupId} role={hasRole ? "tooltip" : undefined}>
(<slot />)
gemini-code-assist[bot] (Migrated from github.com) commented 2026-04-04 18:45:59 +00:00

high

The use of crypto.randomUUID() will cause build failures in environments using Node.js versions earlier than 19 (such as Node 18, which is the current LTS and supported by Astro), as the crypto object is not available in the global scope by default in those versions. To ensure compatibility across all supported Node.js environments, consider using a more widely available method for generating unique IDs or explicitly importing randomUUID from node:crypto.

const popupId = "keyword-popup-" + Math.random().toString(36).slice(2, 11);
![high](https://www.gstatic.com/codereviewagent/high-priority.svg) The use of `crypto.randomUUID()` will cause build failures in environments using Node.js versions earlier than 19 (such as Node 18, which is the current LTS and supported by Astro), as the `crypto` object is not available in the global scope by default in those versions. To ensure compatibility across all supported Node.js environments, consider using a more widely available method for generating unique IDs or explicitly importing `randomUUID` from `node:crypto`. ``` const popupId = "keyword-popup-" + Math.random().toString(36).slice(2, 11); ```
N0tAI (Migrated from github.com) reviewed 2026-04-04 19:19:48 +00:00
@ -2,0 +2,4 @@
import crypto from "node:crypto";
import type { HTMLAttributes } from 'astro/types';
type AriaRole = HTMLAttributes<'div'>['role'];
N0tAI (Migrated from github.com) commented 2026-04-04 19:19:48 +00:00

medium

The role="term" attribute is now hardcoded for all instances of the Keyword component. While this is appropriate for definitions (like pronunciation or skill descriptions), it is semantically incorrect when the component is used for simple labels or icons, such as the social links in the header. This can lead to confusing announcements for screen reader users (e.g., "GitHub, link, term"). Consider making the role configurable via a prop, defaulting to term only when the component truly represents a defined term.

@jules

Can you please implement this fix?

> ![medium](https://camo.githubusercontent.com/32601710f6703a1d3cdbb05c7f9f05d1d8c88abc4d4e4d4e25ff218874a45279/68747470733a2f2f7777772e677374617469632e636f6d2f636f64657265766965776167656e742f6d656469756d2d7072696f726974792e737667) > > The `role="term"` attribute is now hardcoded for all instances of the `Keyword` component. While this is appropriate for definitions (like pronunciation or skill descriptions), it is semantically incorrect when the component is used for simple labels or icons, such as the social links in the header. This can lead to confusing announcements for screen reader users (e.g., "GitHub, link, term"). Consider making the `role` configurable via a prop, defaulting to `term` only when the component truly represents a defined term. @jules Can you please implement this fix?
google-labs-jules[bot] (Migrated from github.com) reviewed 2026-04-04 19:21:38 +00:00
@ -2,0 +2,4 @@
import crypto from "node:crypto";
import type { HTMLAttributes } from 'astro/types';
type AriaRole = HTMLAttributes<'div'>['role'];
google-labs-jules[bot] (Migrated from github.com) commented 2026-04-04 19:21:38 +00:00

I have updated Keyword.astro to accept a role prop that defaults to term. For usages where it's not semantically a term (like the social links), passing role="none" will now omit the role attribute entirely.

I have updated `Keyword.astro` to accept a `role` prop that defaults to `term`. For usages where it's not semantically a term (like the social links), passing `role="none"` will now omit the role attribute entirely.
N0tAI commented 2026-04-04 19:39:17 +00:00 (Migrated from github.com)

/gemini review

/gemini review
gemini-code-assist[bot] (Migrated from github.com) reviewed 2026-04-04 19:40:55 +00:00
gemini-code-assist[bot] (Migrated from github.com) left a comment

Code Review

This pull request enhances the accessibility of the site by introducing ARIA roles and labels. The Keyword component now supports a role prop and uses crypto.randomUUID() to link tooltips with aria-describedby. Additionally, sections on the index page are now properly labeled using aria-labelledby. The review feedback suggests further accessibility refinements for the Keyword component when used in a presentational capacity (role='none'), specifically by defaulting tabindex to -1 and conditionally applying tooltip-related attributes to ensure a cleaner accessibility tree.

## Code Review This pull request enhances the accessibility of the site by introducing ARIA roles and labels. The Keyword component now supports a role prop and uses crypto.randomUUID() to link tooltips with aria-describedby. Additionally, sections on the index page are now properly labeled using aria-labelledby. The review feedback suggests further accessibility refinements for the Keyword component when used in a presentational capacity (role='none'), specifically by defaulting tabindex to -1 and conditionally applying tooltip-related attributes to ensure a cleaner accessibility tree.
@ -1,15 +1,34 @@
---
gemini-code-assist[bot] (Migrated from github.com) commented 2026-04-04 19:40:55 +00:00

medium

When role is set to "none", the component is intended to be purely presentational. In such cases, it is better to default tabindex to "-1" so the element is not focusable by default. A focusable element without a semantic role can be confusing for screen reader users.

const { text, role = "term", tabindex = role === "none" ? "-1" : "0" } = Astro.props;
![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg) When `role` is set to `"none"`, the component is intended to be purely presentational. In such cases, it is better to default `tabindex` to `"-1"` so the element is not focusable by default. A focusable element without a semantic role can be confusing for screen reader users. ``` const { text, role = "term", tabindex = role === "none" ? "-1" : "0" } = Astro.props; ```
gemini-code-assist[bot] (Migrated from github.com) commented 2026-04-04 19:40:55 +00:00

medium

To ensure a clean accessibility tree, aria-describedby and the tooltip role should only be applied when the component is acting as a semantic term (i.e., when role is not "none"). This avoids creating orphaned descriptions or tooltips for presentational elements, which is especially important when the component is used inside other interactive elements like links.

<span class="keyword" data-keyword tabindex={tabindex} aria-describedby={role !== "none" ? popupId : undefined} role={role !== "none" ? role : undefined}>
    <span class="keyword-display"><slot name="display">{text}</slot></span>
    <span class="popup-anchor">
        <span class="popup" id={popupId} role={role !== "none" ? "tooltip" : undefined}>
            (<slot />)
        </span>
    </span>
![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg) To ensure a clean accessibility tree, `aria-describedby` and the `tooltip` role should only be applied when the component is acting as a semantic term (i.e., when `role` is not `"none"`). This avoids creating orphaned descriptions or tooltips for presentational elements, which is especially important when the component is used inside other interactive elements like links. ``` <span class="keyword" data-keyword tabindex={tabindex} aria-describedby={role !== "none" ? popupId : undefined} role={role !== "none" ? role : undefined}> <span class="keyword-display"><slot name="display">{text}</slot></span> <span class="popup-anchor"> <span class="popup" id={popupId} role={role !== "none" ? "tooltip" : undefined}> (<slot />) </span> </span> ```
Sign in to join this conversation.
No description provided.