Blog Page Accessibility Deep Dive

The Problems

I’m fixing issues I discovered in part 1 of this series, while auditing, as well as issues I discovered while fixing my site for parts 2, 3, and 4. For good measure, I’m also investigating a few things I wanted to revisit the last time I rewrote this page. As I wrote this, the list of problems grew, some fixes solved problems for headings I hadn’t gotten to yet, and I probably reordered the sections 5 times as I went. Because of the sprawling nature of this blog, I’ve mapped the problems to the headings with their fixes:

The Solutions

Refactor

The last time I rewrote this page, I noticed that my SingleBlog and FullBlog components were very similar. The only real difference was I passed a blog id to my SingleBlog component to display one blog and my FullBlog component displayed all the blogs by default.

 const chooseComponent = (component) => {
if (component.component === “SingleBlog”) {
setSingle(true)
setSingleBlogID(component.id)
setSingleShow(“FullBlog”)
} else if (component === “FullBlog”) {
setSingle(true)
setSingleBlogID(0)
setSingleShow(component)
} else {
setSingle(true)
setSingleShow(component)
}
}
if (props.id !== 0) {
fetchSingleBlog(props.id)
} else {
fetchBlogs()
}
<button className=”preview_button” onClick={() => chooseComponent({component: “SingleBlog”, id: blog.id})}>{blog.title}</button>
if (!state.isLoading && state.blogs !== null) {
let blogList
if (state.blogs.length > 1) {
blogList = state.blogs.map((blog) => {
let blogBody = parse(blog.body_html)
return (
<li key={blog.id} className=”blog”>
<h1>{blog.title}</h1>
{blogBody}
</li>
)
})
} else {
let blogBody = parse(state.blogs.body_html)
blogList =
<li key={state.blogs.id} className=”blog”>
<h1>{state.blogs.title}</h1>
{blogBody}
</li>
}
return (
<section aria-label=”Full list of Abbey’s blog posts” className=”full-blog”>
<ul>
{blogList}
</ul>
</section>
)
} else if (!state.isLoading && state.error) {
return (
<Error />
)
} else {
return (
<Loading />
)
}

Markdown or HTML?

I wanted to revisit this decision for a couple reasons. First, because of the short deadline I was on, I didn’t really have time to look at the markdown parsing solutions available to me. I balked when I saw the reviews saying they could be buggy and usually used dangerouslySetInnerHTML. Second, when I was building it, I was getting fairly regular 429, too many requests, responses from the DEV API because I’m grabbing each blog by id to get the HTML. However, I’m not seeing those anymore.

const axios = require(‘axios’)
const API_KEY = process.env.API_KEY
exports.handler = async function (event, context) {
let articles
try {
articles = await axios.get(‘https://dev.to/api/articles/me', {
headers: {
“Api-Key”: API_KEY,
“Content-Type”: ‘application/json’
}
})
} catch (err) {
console.log(err)
return {
statusCode:err.statusCode || 500,
body: err.message,
headers: {
“Access-Control-Allow-Origin”: “https://abbeyperini.dev”,
“Access-Control-Allow-Methods": “GET”
}
}
}
return {
statusCode: 200,
body: JSON.stringify({
data: articles.data
}),
headers: {
“Access-Control-Allow-Origin”: “https://abbeyperini.dev",
“Access-Control-Allow-Methods”: “GET”
}
}
}
let markdown = state.blogs.body_markdown
blogList =
<li key={state.blogs.id} className=”blog”>
<h1>{state.blogs.title}</h1>
<ReactMarkdown children={markdown} remarkPlugins={[remarkGfm]}></ReactMarkdown>
</li>

Sections, Articles, and Headings, Oh My

Turns out the myth that having <section>s negates the need to avoid multiple <h1>s on a page persists because the HTML specs say that’s true and the browsers never implemented it. First, I updated my main page with <h2>s around my section headings. Then I double check I’m not skipping around in heading hierarchy in any of the content of the sections. I ended up updating about 16 headings.

the title and heading of Abbey’s blog 8 things I learned in a legacy codebase on DEV
function replaceHeadings(markdown) {
let newHeadings
newHeadings = markdown.replace(/\s#{5}\s/g, “\n###### “)
newHeadings = newHeadings.replace(/\s#{4}\s/g, “\n##### “)
newHeadings = newHeadings.replace(/\s#{3}\s/g, “\n#### “)
newHeadings = newHeadings.replace(/\s#{2}\s/g, “\n### “)
return newHeadings
}
blogList = state.blogs.map((blog) => {
let markdown = blog.body_markdown
let replaced = replaceHeadings(markdown)
return (
<article key={blog.id} className=”blog”>
<h2>{blog.title}</h2>
<ReactMarkdown children={replaced} remarkPlugins={[remarkGfm]}></ReactMarkdown>
</article>
)
})

Links on Links on Links

While refactoring, I saw that the DEV url is included in each blog object returned by the API. Now I just need to figure out how I want to display it. I settle on a share button. For now, I’ll open the DEV link in a new tab, but I’ve added copying the link to the user’s clipboard and a hover label saying “copied!” to this Github issue. For now, I’ve got a “Share” button under each blog heading.

<article key={blog.id} className=”blog”>
<h2>{blog.title}</h2>
<a href={blog.url} target=”_blank” rel=”noreferrer”><button className=”preview_button”>Share</button></a>
<ReactMarkdown children={replaced} remarkPlugins={[remarkGfm]}></ReactMarkdown>
</article>
let SVGID = “ShareExternalLink” + Math.random().toString(16).slice(2)

The CSS Mess

Time to revisit my blog CSS a third time.

abbeyperini.dev with the blog styling looking like a disaster
abbeyperini.dev with the blog styling looking nice

No to Reflow

My <pre> blocks are set to 100% width in my last media query. My headings have no width rules. It looks strange when they’re all different lengths, and one or both are probably the source of my reflow issues.

.blog pre, .blog p, .blog blockquote, .blog h2, .blog h3, .blog h4, .blog ul, .blog ol {
max-width: 250px;
}
.blog {
min-width: 280px;
}
a {
word-wrap: break-word;
overflow-wrap: break-word;
}

Text Formatting

For this section, I retest with ARC Toolkit and IBM Equal Access Accessibility Checker. While I was checking for skipped headings in my blogs on DEV, I removed line breaks and the italicized lines about when the blog was originally published on Medium. This cut down significantly on the number of warnings about <em> elements. The <q> and <quoteblock> warnings are about places in my blogs where I quote myself, present a hypothetical or mantra, or put quotations around text you would see on the screen or that I’m adding to my site. The places where I quote other people are properly surrounded by <quoteblock>. The “use more list elements” warnings are about places where a lot of links or code blocks appear under an <h3> wrapped in a <p>. They wouldn’t make sense as lists, so those are fine.

![a lacy Dowland shawl knit in sparkly burgundy yarn](https://dev-to-uploads.s3.amazonaws.com/i/yrjo5xbfu5gbsh5yzc0m.jpg “Knit by Abbey Perini, pattern by Dowland by Dee O’Keefe, yarn is Meeker Street by The Jewelry Box”)
*Knit by Abbey Perini, pattern by Dowland by Dee O’Keefe, yarn is Meeker Street by The Jewelry Box*
function replaceHeadings(markdown) {
let newHeadings
newHeadings = markdown.replace(/\s#{5}\s/g, “\n###### “)
newHeadings = newHeadings.replace(/\s#{4}\s/g, “\n##### “)
newHeadings = newHeadings.replace(/\s#{3}\s/g, “\n#### “)
newHeadings = newHeadings.replace(/\s#{2}\s/g, “\n### “)
newHeadings = newHeadings.replace(/<kbd>/g, “”)
newHeadings = newHeadings.replace(/<\/kbd>/g, “”)
return newHeadings
}

The Long Alt-Text

I get 11 “alt-text longer than 150 characters” warnings from IBM Equal Access Accessibility Checker. What can I say, I want to make sure screenreader users get all the information. I could come up with a regex solution of some sort to make a D-link or replace alt with an aria-describedby attribute, but I’d rather shorten 11 alt-texts at this point in my accessibility audit journey. Using Word Counter to get a character count and cmd + F in the elements console in dev tools on my site to find the offenders, I am able workshop them all down. You can tell when I’m proud of an image or code project I’ve made, because I get verbose.

Caption: “Me: explains polymorphism Friend: So the subclass the same as the superclass? Me:” A claymation pirate saying “Well yes, but actually no”

Skipping Around

I want to add a skip link for my blog preview component and for my FullBlog component when I return all of my blogs. I start by adding CSS classes provided by Carnegie Museums:

/* skip links */.screenreader-text {
position: absolute;
left: -999px;
width: 1px;
height: 1px;
top: auto;
}
.screenreader-text:focus {
color: black;
display: inline-block;
height: auto;
width: auto;
position: static;
margin: auto;
}
function makeID(title) {
title = title.toLowerCase()
let replaced = title.replace(/\s+/g, “-”)
replaced = replaced.replace(/#/g, “”)
return replaced
}
if (!state.isLoading && state.blogs !== null) {
let blogList
let skipLinks = []
if (state.blogs.length > 1) {
blogList = state.blogs.map((blog) => {
let SVGID = “ShareExternalLink” + Math.random().toString(16).slice(2)
let markdown = blog.body_markdown
let replaced = replaceHeadings(markdown)
let blogID = makeID(blog.title)
let Href = `#${blogID}`
let skipLinkID = blogID + Math.random().toString(16).slice(2)
let skipLink = <li id={skipLinkID}><a href={Href}>{blog.title}</a></li>
skipLinks.push(skipLink)
return (
<article className=”blog”>
<h2 id={blogID}>{blog.title}</h2>
<a href={blog.url} target=”_blank” rel=”noreferrer”><button className=”preview_button”>Share <ExternalLink className=”external-link” id={SVGID} focusable=”false”/></button></a>
<ReactMarkdown children={replaced} remarkPlugins={[remarkGfm]}></ReactMarkdown>
</article>
)
})
return (
<section aria-label=”Full list of Abbey’s blog posts” className=”full-blog”>
<div className=”screenreader-text”>
Skip directly to a blog:
<ol>
{skipLinks}
</ol>
</div>
{blogList}
</section>
)
} else {
let markdown = state.blogs.body_markdown
let replaced = replaceHeadings(markdown)
return (
<section aria-label=”Full list of Abbey’s blog posts” className=”full-blog”>
<article key={state.blogs.id} className=”blog”>
<h2>{state.blogs.title}</h2>
<a href={state.blogs.url} target=”_blank” rel=”noreferrer”><button className=”preview_button”>Share <ExternalLink className=”external-link” id=”ShareExternalLink” focusable=”false”/></button></a>
<ReactMarkdown children={replaced} remarkPlugins={[remarkGfm]}></ReactMarkdown>
</article>
</section>
)
} else if (!state.isLoading && state.error) {
return (
<Error />
)
} else {
return (
<Loading />
)
}
return (
<section aria-label=”Blog Previews” className=”container_blog”>
<h2 aria-label=”button to open full blog page” ><button className=”blog-section_title” onClick={() => chooseComponent(“FullBlog”)}>Blog</button></h2>
<a className=”screenreader-text” href=’#about’>Skip directly to the next section.</a>
<div className=”scroll-cropper”>
<ul aria-label=”previews of Abbey’s blog posts” className=”blog-preview”>
{blogPreviewList}
</ul>
</div>
</section>
)
when focused, “Skip directly to a blog:” appears and focusing on the skip links cycles through links to all the blogs on the page

Conclusion

This blog in particular and the series as a whole has been a massive endeavor. I took a break for a few days after part 4 because I sorely needed it. Still, at this point I’ve written over 11,000 words about accessibility auditing and coded a long list of fixes in 20 days across 5 blogs. I typically only manage a few hundred to 2,000 words a month. While I am looking forward to wrapping this series up, it has been nice to get back to frontend code for the first time in a while.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Abbey Perini

Abbey Perini

…did someone say animated CSS button?