Hashnode, Ytho? (Part 3)

In Web Content Accessibility Guidelines (WCAG) principle terms, I reviewed Perceivable and Operable. I only covered the reader experience. I will need to repeat the process for the authoring experience. I will also want to check if author customization can lead to problems not already found.

Accessibility Review (Part 3): Organization and Readability


Now we can review content, the principle of Understandable. Unlike web tech, how people read does not change much over time. In general:

  • Sighted people without cognitive disabilities read between 220 and 280 words per minute (wpm) in their native language.
  • Sighted people, with a left-to-right (ltr) native language, scan documents using the 'F' pattern.
  • Blind and low-vision folks have a wide range of preferences, assistive technologies (AT), and reading speeds. Well-structured works better than sloppy.
  • People with dyslexia, cognitive disabilities, or vestibular disorders may need to reduce distractions, like animation, or modify content presentation to read it. Following "plain language" guidance will help everyone.


Some bookmarklets, like ANDI, and extensions can help you review landmarks, but I usually just use a script to do it.

(function (roles, tags) {
    const cssRoles = roles.map(r => `[role="${r}"]`).join(',');
    const cssTags = tags.join(',');
    const landmarks = $$(`${cssRoles},${cssTags}`);
    return landmarks.map(l => {
        const role = l.getAttribute('role') || roles[tags.indexOf(l.tagName.toLowerCase())];
        let label = l.getAttribute('aria-label') || '';
        const labelledby = l.getAttribute('aria-labelledby');
        const labels = labelledby ? labelledby.split(' ') : []
        label += labels.flatMap(
            ll => $$(`#${ll}`).map(el => el ? el.textContent.trim() : [])
        ).join(' ');
        return [`${role}${ label ? ` (${label})` : ''}`, l];

When you compare Hashnode (a single banner role) with a site like Vox, you see a big difference in the structure.

  1. banner proper use of the "header"
  2. navigation (Vox main menu) proper labels here for the duplicate nav form
  3. region the responsive menu
  4. main great target for a skip link
  5. complementary good use of aside
  6. form
  7. complementary aside
  8. complementary aside
  9. form
  10. navigation (Pagination) proper labels here for the duplicate nav
  11. contentinfo footer

You can find a good guide for this stuff on a11yproject. I consider this a violation, but I don't feel comfortable suggesting the fix. I'm skipping opening an issue for now.


Providing descriptive headings shows up in WCAG under Operable > Navigable. This makes sense from the perspective of "navigating" the document using AT, like VoiceOver's Headings rotor menu.

But when you look at how headings end up on the page, it's often a content author or editor, not the designer or developer. It also falls under the category of "needs a human". Automated scans can only tell if a heading level was skipped. So, I place it in my "Content" tests.

Because CSS styling often confuses me, I use a little script in the devtools console to show me the headings.

$$('h1,h2,h3,h4,h5,h6,[role="heading"][aria-level]').map(el => [
    `${el.tagName.replace(/\D/, '')}${el.getAttribute('aria-level') || ''}`,

I ran this on Hashnode and it's a mess. Individual articles will reflect the author's knowledge of proper heading use. But more needs to be done to Hashnode's main page. Try the same script on other pages that collect several articles but have better structure.


Under WCAG, authors must make their content readable. Loads of apps can measure readability using one or more of half-a-dozen algorithms. Heydon Pickering implemented a command line interface (CLI) readability checker tool that I use often because it can take a url.

npx readability-checker https://ytho.dev

Hashnode can't rewrite everything for its authors, but it could offer tools or suggest the use of browser plugins like Grammarly. It could use analyzers to warn authors that their content is difficult to read. Hashnode could also display an estimated reading time based on the word count (divide number of words by 240). If they're using UnifiedJS under the hood to convert markdown to HTML, the community already has several plugins that can help.

Alt Text

Images need to have an alt attribute, under WCAG Text Alternatives. If the image is only presentational, you can use alt="". Many sites just repeat the image name, which is the default behavior for Hashnode's image upload feature. This leans to things like:


Where the alt text becomes "ytho.jpg". That's not helpful to screen reader users or search engines. Hashnode could empty the alt text by default, drop the file extension and hope for the best, or raise a warning when the file name was not changed. It may not make things perfect, but it could help.

Hashnode must change how profile images are marked up. So, I'm opening an issue. I used axe to find this, but you can use a script in the console too.



Links need an accessible name describing where the user will navigate on click. The calculation for accessible name is complicated, but for links it's generally the text content or the name of the image inside it. For Hashnode, the empty links on the page are the profile images. Since I already opened an issue for the alt on those images, I amended the fix option to give the link a name.


Simple content with an organized structure help readers keep their wpm above 200. If reading speed drops below that, studies show reading comprehension drops as well. Designers, developers, and content authors need to work together to identify the proper semantic structure of the page. This helps everyone.

No Comments Yet