Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure code blocks (<pre>) are keyboard focusable #1636

Merged
merged 3 commits into from
Apr 5, 2024

Conversation

gabalafou
Copy link
Collaborator

@gabalafou gabalafou commented Jan 8, 2024

Fixes #1100.
Also closes one of the subtasks of #1428.
Inspired by #1104.

Whitespace-preserving blocks in our theme, such as code blocks, currently do not have an easy way for keyboard users to focus on them and use the arrow keys to scroll the block to the right in order to read long lines that overflow the width of the block. This is also an accessibility issue; if you have to use a mouse, a pointer, or your finger—anything other than just your keyboard—to scroll a code block, then that's a violation of WCAG 2.1.1.


Check the end user effects of this PR on the Read the Docs preview build of the Kitchen Sink Blocks page

Copy link
Collaborator Author

@gabalafou gabalafou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done self-reviewing

src/pydata_sphinx_theme/translator.py Show resolved Hide resolved
Comment on lines +52 to +53
html_string = self.body[-1]
self.body[-1] = html_string.replace("<pre", '<pre tabindex="0"')
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kinda dirty but... it seems to work 🤷

I'll leave it up to the reviewers to decide if it's too dirty

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am having a bit of trouble following here - is the goal of these to find and traverse any hidden/nested pretags then add the tabindex?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly. When the parent class's visit_literal_block method is called, it appends an HTML string to self.body. So the very last item in the self.body list is just an HTML string containing a bunch of markup that represents the literal block. A lot of the tags in that markup are there to provide syntax highlighting, line numbers, captions, etc. The relevant tags inside that markup are the pre tags, although I would guess there's only ever one pre tag. However, I add tabindex="0" to any pre tag I find because the nature of a pre tag is that it preserves the whitespace, which means it might be scrollable (depending on how long the lines inside of it are) and should therefore be keyboard-focusable.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't seem that "dirty" to me.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto not dirty just working with what we have

Copy link
Collaborator Author

@gabalafou gabalafou Apr 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read something since working on this PR that makes me wonder if this is the right thing to do. The thing I read was about tables but the same could be said about pre-blocks:

Of course, we don't want to make the table container focusable unless its contents overflow. Otherwise we're adding a tab stop to the focus order which doesn't do anything. In my opinion, that would be a fail under 2.4.3 Focus Order. Giving keyboard users elements to focus which don't actually do anything is confusing and obstructive.
Source: Inclusive Components - Data Tables - Only Focusable Where Scrollable

Which makes me wonder if we should put a data attribute on the pre-tag and then check it with JavaScript after it loads to determine if it should really be a tab stop or not.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/pydata_sphinx_theme/translator.py Show resolved Hide resolved
src/pydata_sphinx_theme/translator.py Outdated Show resolved Hide resolved
Comment on lines +52 to +53
html_string = self.body[-1]
self.body[-1] = html_string.replace("<pre", '<pre tabindex="0"')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am having a bit of trouble following here - is the goal of these to find and traverse any hidden/nested pretags then add the tabindex?

@trallard trallard added the tag: accessibility Issues related to accessibility issues or efforts label Jan 8, 2024
Copy link
Collaborator Author

@gabalafou gabalafou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I just revisited this PR and realized that I had a bunch of pending comments

src/pydata_sphinx_theme/translator.py Outdated Show resolved Hide resolved
Comment on lines +52 to +53
html_string = self.body[-1]
self.body[-1] = html_string.replace("<pre", '<pre tabindex="0"')
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly. When the parent class's visit_literal_block method is called, it appends an HTML string to self.body. So the very last item in the self.body list is just an HTML string containing a bunch of markup that represents the literal block. A lot of the tags in that markup are there to provide syntax highlighting, line numbers, captions, etc. The relevant tags inside that markup are the pre tags, although I would guess there's only ever one pre tag. However, I add tabindex="0" to any pre tag I find because the nature of a pre tag is that it preserves the whitespace, which means it might be scrollable (depending on how long the lines inside of it are) and should therefore be keyboard-focusable.

src/pydata_sphinx_theme/translator.py Show resolved Hide resolved
Copy link
Collaborator

@drammock drammock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a pretty efficient solution. I think another possible route would be to define a "post-transform" which alters the docutils.document before it gets to the translator. But IDK if that would actually be cleaner/easier, and this seems to give the desired result, so 🤷🏻

src/pydata_sphinx_theme/translator.py Show resolved Hide resolved
Comment on lines +49 to +51
# If the super method raises nodes.SkipNode, then we know it
# executed successfully and appended to self.body a string of HTML
# representing the code block, which we then modify.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so... docutils raises an exception when the method executes successfully? What happens when it fails? 😅

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would raise a different exception but I'm not sure.

I think it has something to do with the way that docutils traverses the doc tree, see docutils/nodes.py::Node.walkabout()

Comment on lines +52 to +53
html_string = self.body[-1]
self.body[-1] = html_string.replace("<pre", '<pre tabindex="0"')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't seem that "dirty" to me.

@trallard
Copy link
Collaborator

trallard commented Apr 5, 2024

I think this PR is in a good shape to merge and has been approved so will go ahead and merge 🚀

there is one question from Dan somewhere but it is non-blocking at all

@drammock
Copy link
Collaborator

drammock commented Apr 5, 2024

only fail is codecov; in it goes! thanks @gabalafou

@drammock drammock merged commit 525e52d into pydata:main Apr 5, 2024
18 of 19 checks passed
@gabalafou gabalafou deleted the pre-tabindex branch April 19, 2024 14:24
Carreau pushed a commit that referenced this pull request May 27, 2024
#1787)

One of many fixes for the failing accessibility tests (see #1428).

The accessibility tests were still reporting some violations of: 
 - Scrollable region must have keyboard access (https://dequeuniversity.com/rules/axe/4.8/scrollable-region-focusable) even after merging #1636 and #1777. 

These were due to Jupyter notebook outputs that have scrollable content. This PR extends the functionality of PRs #1636 and #1777 to such outputs.

- Adds a test for tabindex = 0 on notebook outputs after page load

This also addresses one of the issues in #1740: missing horizontal scrollbar by:

- Adding CSS rule to allow scrolling
- Add ipywidgets example to the examples/pydata page
ivanov pushed a commit to ivanov/pydata-sphinx-theme that referenced this pull request Jun 5, 2024
* Ensure code blocks (<pre>) are keyboard focusable

* Also cover <pre> tags not covered by starttag

* Update src/pydata_sphinx_theme/translator.py

Co-authored-by: gabalafou <[email protected]>

---------

Co-authored-by: Tania Allard <[email protected]>
ivanov pushed a commit to ivanov/pydata-sphinx-theme that referenced this pull request Jun 5, 2024
pydata#1787)

One of many fixes for the failing accessibility tests (see pydata#1428).

The accessibility tests were still reporting some violations of: 
 - Scrollable region must have keyboard access (https://dequeuniversity.com/rules/axe/4.8/scrollable-region-focusable) even after merging pydata#1636 and pydata#1777. 

These were due to Jupyter notebook outputs that have scrollable content. This PR extends the functionality of PRs pydata#1636 and pydata#1777 to such outputs.

- Adds a test for tabindex = 0 on notebook outputs after page load

This also addresses one of the issues in pydata#1740: missing horizontal scrollbar by:

- Adding CSS rule to allow scrolling
- Add ipywidgets example to the examples/pydata page
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tag: accessibility Issues related to accessibility issues or efforts
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ENH - Make code blocks focusable
4 participants