Background
We are using #axe-core/react to work on ARIA accessibility in react application. By default, on page reload no issues are reported, but on Select click we are facing various issues with accessibility, according to axe-core/react.
Problems
serious: Page must have means to bypass repeated blocks
https://dequeuniversity.com/rules/axe/4.0/bypass?application=axeAPI
moderate: Document must have one main landmark
https://dequeuniversity.com/rules/axe/4.0/landmark-one-main?application=axeAPI
moderate: All page content must be contained by landmarks
https://dequeuniversity.com/rules/axe/4.0/region?application=axeAPI
In addition to problems, there are 2 more:
moderate: Page must contain a level-one heading https://dequeuniversity.com/rules/axe/4.0/page-has-heading-one?application=axeAPI
serious: Elements must have sufficient color contrast https://dequeuniversity.com/rules/axe/4.0/color-contrast?application=axeAPI
Code
import React from "react";
import ReactDOM from "react-dom";
import axe from "#axe-core/react";
import { BrowserRouter, Route, Link } from "react-router-dom";
import { MenuItem, Select, InputLabel } from "#material-ui/core";
const BypassRepeatedBlocks = () => (
<div aria-hidden="true">
<a href="#select-content" style={{ display: "none" }}>
bypass repeated blocks
</a>
</div>
);
const NavigationSample = () => (
// we must have <nav> here in order to put content inside landmark. Otherwise we would get issue.
<nav>
<ul>
<li>
<Link to="/home">Home</Link>
</li>
</ul>
</nav>
);
const HomePage = () => <div></div>;
const App = () => (
// BrowserRouter, Route and <Link> (NavigationSample) are the cause of bypass repeated blocks issue
<BrowserRouter>
{/**but with BypassRepeatedBlocks portion of code, it can be solved */}
<BypassRepeatedBlocks />
<NavigationSample />
<main>
<Route path="/home" component={HomePage} />
{/**without <h1> there is heading issue on page loading */}
<h1>H1</h1>
<InputLabel id="my-input" style={{ color: "black" }}>
Sort
</InputLabel>
<Select
id="select-content"
labelId="my-input"
value="item1"
style={{ width: "200px" }}
>
<MenuItem key="item1" value="item1">
item1
</MenuItem>
<MenuItem key="item2" value="item2">
item2
</MenuItem>
</Select>
</main>
</BrowserRouter>
);
axe(React, ReactDOM, 1000);
ReactDOM.render(<App />, document.getElementById("root"));
Observations
As with shown code, when drop-down menu is shown I can navigate through menuitems (options) with key down and key up from keyboard, and that is what is important, from accessibility point of view, right?
After clicking on Select and inspecting HTML elements of web page, I can see two things: first, that my main content gets hidden <div id="root" aria-hidden="true">, and second that new <div> is generated with role='presentation' which contains this drop-down content. And it is outside of <main> which sort of explains the issue All page content must be contained by landmarks, as well as Document must have one main landmark and Page must contain a level-one heading
In addition to previous observation, I tried to add MenuProps={{ disablePortal: true }} to Select element, and issue All page content must be contained by landmarks was removed but i don't know if and what negative impacts this can have
from what i read on internet on this topic, I find this case (select, drop-down) to be very specific and advance.
Questions
Is there a proper way to handle mentioned issues for Select element? How?
Is this one of the situations to be considered as a trade-off (don't have to / can't get rid of all issues)?
If 2. is true, is then that bug to axe-core/react tool?
Let me know if I can provide more details, or update some content.
Thanks.
EDIT
Github repo for reproduction: https://github.com/StefanZivkovic/aria-accessibility-problems.
When you git clone it from cmd, navigate to cd aria-accessibility-problems, execute npm i, thennpm start and wait for server to start. If you use VS code, from cmd do code . to open the files and check the code if you want.
By the way, i found how to overcome Page must have means to bypass repeated blocks, at least on this simple example. That particular issue was caused by react-router-dom, and its elements, Link, BrowserRouter and Route. Might be useful for some.
Related
I have been struggling to render images from contentful in my gatsby site. I have used gatsby-plugin-image to render the image from contentful. I cannot dynamically render the images. please help me.
I import
import { GatsbyImage, getImage } from "gatsby-plugin-image"
my graphQL query is.
const data = useStaticQuery(graphql`
query {
allContentfulBooks(sort: { bookTitle: ASC }) {
edges {
node {
bookTitle
author
date(formatString: "MMMM Do, YYYY")
type
bookCover {
gatsbyImageData(
width: 300
placeholder: NONE
quality: 75
layout: CONSTRAINED
)
}
slug
summary
}
}
}
contentfulBookHeading {
heading
mainText {
raw
}
}
}
`)
I try to render the book Cover here
<ol>
{data.allContentfulBooks.edges.map((edge) => {
const image = getImage(edge.node.bookCover.gatsbyImageData)
return (
<li className={bookStyles.books}>
<div className={bookStyles.bookThumbnail}>
<div className={bookStyles.bookCover}>
<GatsbyImage src={image} alt="Book Cover" />
</div>
<div>
<Link to={`/book/${edge.node.slug}`}>
<h3 className={bookStyles.title}>{edge.node.bookTitle}</h3>
</Link>
<h5 className={bookStyles.author}>
Author: {edge.node.author}
</h5>
<h6 className={bookStyles.type}>Type: {edge.node.type}</h6>
<p className={bookStyles.date}>Date Read: {edge.node.date}</p>
<p className={bookStyles.summary}> {edge.node.summary}</p>
<Link to={`/book/${edge.node.slug}`}>
<p className={bookStyles.fullnotes}>Read full book notes</p>
</Link>
</div>
</div>
<hr />
</li>
)
})}
</ol>
I tried to render the book cover image from contentful. but it's not working
My first thought when reading your ask was that you are looking to load data dynamically, as in at load time, my original answer would work for that. After rereading your question, I believe you are not having difficulty loading data dynamically at load time but, rather you are not seeing the images display using the useStaticQuery and loading the data at build time.
Steps to ensure you should see the media:
open http://localhost:8000/___graphql in a browser and run your query, ensure you see the media you are expecting.
If you do not see the media:
First, check Contentful and ensure that the media is published.
Next, try stopping your app and restarting it, ctrl c should cancel the running command, then start the site back up. This will ensure that you content is pulled in and ready to render.
If you see the media in graphical, my next suggestion is to debug your site and console.log the data. Try putting in console logs
console.log("gatsbyImageData", edge.node.bookCover.gatsbyImageData)
const image = getImage(edge.node.bookCover.gatsbyImageData)
console.log("image", image)
next open up the developer menu in your browser and click on console
reload the page and verify the output of the console logs. This should help you ensure that data is present.
If you are having a dynamic rendering issue the answer is, useStaticQuery, grabs all of your Contentful data at build time. This is by design and is part of why Gatsby is so fast. You will need to use something like Apollo in order to grab dynamic data in Gatsby. Check out this article that provides more details and a possible solution.
I have a react component looks like this:
<MenuSection backgroundurl="/images/home-slide-4-1920x800.jpg" fluid>
<MenuContainer>
<MenuRow>
{categoryItems.map(categoryItem => (
<MenuItemCard key={categoryItem.Id}>
<Link to={categoryItem.CategoryUrl}>
<MenuItemCardImg src={categoryItem.ImageUrl} />
<MenuItemCardBody>
<MenuItemTitle>{categoryItem.CategoryName}</MenuItemTitle>
</MenuItemCardBody>
</Link>
</MenuItemCard >
))}
</MenuRow>
</MenuContainer>
</MenuSection>
When I click the Link, the route is correct and it takes me to the correct page. However all the CSS in that following page is not applied. If I reload the page that 'Link' took me to the CSS is applied correctly.
When I change the 'Link' and replace it with an < a > tag, when I click the link everything works the next page has the css applied correctly with out any problems.
<MenuSection backgroundurl="/images/home-slide-4-1920x800.jpg" fluid>
<MenuContainer>
<MenuRow>
{categoryItems.map(categoryItem => (
<MenuItemCard key={categoryItem.Id}>
<a href={categoryItem.CategoryUrl}>
<MenuItemCardImg src={categoryItem.ImageUrl} />
<MenuItemCardBody>
<MenuItemTitle>{categoryItem.CategoryName}</MenuItemTitle>
</MenuItemCardBody>
</a>
</MenuItemCard >
))}
</MenuRow>
</MenuContainer>
</MenuSection>
I can't figure out why this is happening, this is the first React project I have done, so maybe I am missing something really obvious?
UPDATE:
When I use import {Nav} from "react-bootstrap"; (Nav.Link) - from react bootstrap, when I click the link the page navigates correctly and the CSS is applied correctly.
<MenuSection backgroundurl="/images/home-slide-4-1920x800.jpg" fluid>
<MenuContainer>
<MenuRow>
{categoryItems.map(categoryItem => (
<MenuItemCard key={categoryItem.Id}>
<Nav.Link href={categoryItem.CategoryUrl}>
<MenuItemCardImg src={categoryItem.ImageUrl} />
<MenuItemCardBody>
<MenuItemTitle>{categoryItem.CategoryName}</MenuItemTitle>
</MenuItemCardBody>
</Nav.Link>
</MenuItemCard >
))}
</MenuRow>
</MenuContainer>
So it just seems when I use Link from react-router-dom I get this strange behaviour.
For now I have found a solution by using Nav.Link, but I would really like to know why when I use Link the navigation works but the CSS is not applied untill I reload the page.
I'm new to Svelte. I have 2 Svelte components in an HTML parent-child relationship – as opposed to a Svelte P/C relationship (where 1 Svelte component imports another).
Ultimately, I want something like this (could have many Accs.):
<Accordion header={--property from SvelteComponent-- }>
<SvelteComponent />
</Accordion>
I would like the SvelteComponent to define the header used in the Accordion (it can be dynamic).
I can use stores for this but that seems too messy. Accordion does contain a slot but I can’t see how to pass information upward. Can anyone suggest a path forward?
One option is to use component bindings. This allows a two-way binding between a value in the scope of a container component and a prop of the contained component.
<Accordion {header}>
<SvelteComponent bind:header={header} />
</Accordion>
and in SvelteComponent.svelte:
<script>
export let header = "defaultHeader";
</script>
Whenever SvelteComponent makes a change to defaultHeader, it will propogate back up via the binding to the file which contains the Accordion and SvelteComponent, and apply the changes back downward.
https://svelte.dev/tutorial/component-bindings
Alternatively, you can provide a setHeader function as a prop to SvelteComponent, which sets the value of header:
//SvelteComponent.svelte
<script>
export let setHeader;
</script>
<div on:click={() => setHeader("myHeader")}>
My Svelte Component
</div>
Svelte is great at getting rid of boilerplate and unnecessary extra code. I want to take that as far as I can.
I believe using #skeletizzle answer that I would have to add a variable to the container of the Accordions, for each Accordion and its child. THAT is what I want to eliminate (it pollutes the namespace of the container for a trivial operation). Since the Accordion and its child are in a P/C relation - they know about each other and the compiler could make communication direct. Think of an implied interface where the Accordion looks in its child and it it finds a prop named, say 'header', automatically, reactively, uses that for its own header prop.
What I am going to do is use stores though as stated, I feel they are too heavy - but don't think I have an alternative.
WrappedComponent.svelte
<script context="module">
import { writable } from 'svelte/store'
export let thisComponentHeader = writable('default header')
</script>
App.svelte
import WrappedComponent, { thisComponentHeader } from './WrappedComponent.svelte'
...
<Accord header={$thisComponentHeader}>
<WrappedComponent />
</Accord>
There still is a variable. but it is socked away in the import definition
One thing this prevents is having more than 1 instance of the component.
What about store and context management? Instead of creating module level store, set it in the component context. https://svelte.dev/docs#run-time-svelte-setcontext
Accordion.svelte
<script>
import {writable} from "svelte/store";
import {setContext} from "svelte";
const header = writable('Default header');
setContext('header', header)
</script>
<div>{$header}</div>
WrappedComponent.svelte
<script>
import {getContext} from 'svelte';
const header = getContext('header');
if(header) $header = 'New fancy header'
</script>
App.svelte
<Accordion>
<WrappedComponent />
</Accordion>
How to specify className attribute for div which contains 'hello world':
<div dangerouslySetInnerHTML={{__html: '<div>hello world</div>'}} />
One way is to set is to outer div like so:
<div dangerouslySetInnerHTML={{__html: '<div>hello world</div>'}} className='class-name'/>
and then in css style the child:
.class-name div {
(css stuff here)
}
But I want to set className directly to the div with 'hello world'
EDIT: Adding class name to injected html does not solve the problem, for example:
let content = '<div class="class-name">hello world</div>'
<div dangerouslySetInnerHTML={{__html: content}}
does not work, because in case same content is used many times then CSS collisions occur (I am using style-loader with CSS modules on)
I came across this question after 2 years and I wanted to achieve the exact same results. I didn't find any accurate answers here but I came up with a solution thanks to #jash8506 due to the brilliant answer to this question.
We have to utilize two react hooks
useRef
useLayoutEffect
According to the documentation basically, useRef can be used to access the DOM elements in React and useLayoutEffect can be used to read layout from the DOM and synchronously re-render.
We can access the firstChildElement in the container div and add classes to it's classList
Here is how the completed code looks like
const containerRef = useRef();
useLayoutEffect(()=>{
if (containerRef.current){
containerRef.current.firstElementChild.classList.add('class-name');
}
});
return (
<div ref={elRef} dangerouslySetInnerHTML={{__html: '<div>hello world</div>'}} />
)
<div className="post-excerpt" dangerouslySetInnerHTML={{ __html: post.excerpt}}/>
I need to implement a Bootstrap-style collapsible panel in React that displays arrow glyphs in the panel header similar to this No JS required example.
I've implemented most of this functionality using react-bootstrap in an unintended way, that results in this browser warning:
< a> cannot appear as a descendant of < a>. See ProjectInfo > a > ... > Panel > a.
Here is the render function of my component:
render() {
const projectHeader =
<a className="accordion-toggle collapsed">
<h4 className="panel-title">Project Information</h4>
</a>;
return (
<Accordion>
<Panel header={projectHeader} eventKey="1">
Project info goes here.
</Panel>
</Accordion>
);
}
Can the requirement for toggling panel header icons be be met using react-bootstrap? If so, how? If not, how is it done using React?
UPDATED 07-DEC-2017: I solved my problem by adding an anchorClass property to the react-bootstrap Panel component in a forked repo.
This is final render function of my component:
render() {
const header = <h4>Project Information</h4>;
return (
<PanelGroup accordion>
<Panel header={header} anchorClass="panel-toggle" eventKey="1">
Project info goes here.
</Panel>
</PanelGroup>
);
}
The panel-toggle classes were defined like those in the No JS required example.