<?xml version="1.0" encoding="utf-8"?>
  <rss version="2.0"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
    xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
    xmlns:georss="http://www.georss.org/georss"
    xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
  >
    <channel>
      <title>Piccalilli - Accessibility topic archive</title>
      <link>https://piccalil.li/</link>
      <atom:link href="https://piccalil.li/category/accessibility.xml" rel="self" type="application/rss+xml" />
      <description>We are Piccalilli. A publication dedicated to providing high quality educational content to level up your front-end skills.</description>
      <language>en-GB</language>
      <copyright>Piccalilli - Accessibility topic archive 2026</copyright>
      <docs>https://www.rssboard.org/rss-specification</docs>
      <pubDate>Fri, 06 Mar 2026 12:18:29 GMT</pubDate>
      <lastBuildDate>Fri, 06 Mar 2026 12:18:29 GMT</lastBuildDate>

      
      <item>
        <title>Finding an accessibility-first culture in npmx</title>
        <link>https://piccalil.li/blog/finding-an-accessibility-first-culture-in-npmx/?ref=accessibility-category-rss-feed</link>
        <dc:creator><![CDATA[Abbey Perini]]></dc:creator>
        <pubDate>Tue, 03 Mar 2026 13:55:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/finding-an-accessibility-first-culture-in-npmx/?ref=accessibility-category-rss-feed</guid>
        <description><![CDATA[<p>Back in January, I found myself longing for an accessibility-first culture and for a while, I’ve also been looking for ways to learn senior-level accessibility skills. I expected to find one of those needs, but not <em>both</em>, when I joined <a href="https://npmx.dev/">npmx</a> on a whim. Today is the <a href="https://npmx.dev/blog/alpha-release">alpha release of npmx</a>, and I’m happy to share how they’ve already exemplified an accessibility-first community.</p>
<h2>Missing an accessibility-first community</h2>
<p>It's hard to put into words how lonely it is when you’re the only person ensuring that there aren't click handlers on generic elements. I've come to accept that the bar for "accessibility advocate" is just "still cares about accessibility in spite of it all." I'm used to being the only one giving accessibility feedback when joining a new project. It's exhausting having to explain the same small issues over — the same issues that are easily caught by automated accessibility testing tools. Sometimes, it's tempting to just delete the repository and start over.</p>
<p>When you know anything about digital accessibility, you know that compliance doesn't always have to be a never-ending slog of fixes. It <em>can</em> be baked in from the start.</p>
<p><img src="https://piccalil.b-cdn.net/images/blog/npmx-eggs-slide.jpg" alt="A purple and yellow cupcake You can't sprinkle eggs on the cupcake after baking it. Fixing accessibility later is messy, slow, ineffective! “Later” and “messy, slow, ineffective”" title="Stef Walter’s AxeCon talk, &lt;a href='https://www.deque.com/axe-con/sessions/how-to-convince-people-to-care-and-invest-in-accessibility/'&gt;How to Convince People to Care and Invest in Accessibility&lt;/a&gt;" /></p>
<p>In many ways, I am lucky. At work, my recommendations are respected and my coworkers want to learn. When I come across hard questions, I can always reach out to former coworkers from my previous job at a digital accessibility company. When I need someone to check my assumptions, I just talk to the accessibility expert friends who always support me. I’ve never asked for permission to fix accessibility issues or build accessibly because accessibility has consistently improved the user experience, that’s worked in my favor.</p>
<p>Recently however, I got the chance to catch up with some of my former coworkers. There was a moment when I said "I don't know if the automated captions are better or worse." One of them responded "Yeah, they're really wrong - a live captioner would be a lot better." I closed my eyes, leaned back in my chair, and sighed "It's nice not to have to explain." Then, I remembered how nice it was to have a live captioner in our meetings. The next week, I started looking at expensive certifications. I wanted a reason to start learning deeply about accessibility again.</p>
<p></p>
<h2>Finding an accessibility-first community</h2>
<p>When I joined npmx the next month, I was curious and hoped I'd get to learn from brilliant developers. At <a href="https://vueconf.us/">VueConf 2025</a>, I had learned that <a href="https://bsky.app/profile/danielroe.dev">Daniel Roe</a> wrote a script that automatically registers an <a href="https://docs.npmjs.com/packages-and-modules">npm package</a> every time he buys a domain. I figured he’d probably know a thing or two about npm.</p>
<p>From an accessibility standpoint, I was expecting more of the same. I assumed I would still be the only person insisting "accessibility is a right, not a privilege." Explaining that screen reader users use several lists of things to navigate a webpage: links, headings, <a href="https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/">landmark regions</a>. That’s why your <code>&lt;header&gt;</code> should be outside of <code>&lt;main&gt;</code> and your link text shouldn’t all be “click here!”</p>
<p>Inside the <a href="https://chat.npmx.dev/">npmx Discord</a>, I didn't expect to see a dedicated accessibility channel. I was delighted to see automated accessibility tests when I opened up my first pull request for review. There were other people already hard at work making sure there were no click handlers on generic elements. I started to feel things that I hadn't felt since working in digital accessibility full time.</p>
<p>Immediately, I started learning deeply about accessibility again. Some things were small, like HTML’s struggle with headings inside <code>&lt;details&gt;</code>/<code>&lt;summary&gt;</code> but I was also getting to have the big conversations about architecture and design that I missed having. I didn’t even have to explain what a <a href="https://rightbadcode.com/accessibility-conformance-reports-setting-expectations">VPAT</a> was. Most importantly, our conversations are being treated as important. If there's a possible accessibility issue in another conversation, accessibility experts are tagged in. It’s all filling a bucket I hadn’t even realized was empty.</p>
<p>Of course, there are things we weren’t able to fix before the alpha release — that’s how software is: perpetually unfinished, never 100% accessible — but I don’t have to justify a fix with a business logic bug because we’re already auditing. Earlier this week, someone asked how the accessibility channel felt about accessible color contrast. I had already typed out a justification for sufficient color contrast before I realized it was an invitation to review a pull request.</p>
<p></p>
<h2>Wrapping up with some tips for building an accessibility-first culture</h2>
<p>For many reasons, the first step is writing documentation. In your onboarding documentation, encourage developers to install <a href="https://dev.to/steady5063/automated-accessibility-part-1-linting-5378">accessibility linters</a>. Document the accessibility problems you’ve already solved, patterns for interactive controls and colors that have sufficient contrast together.</p>
<p>Bonus points if you have the documentation available in multiple forms (e.g. text, diagrams, and videos). </p>
<p>Tests are another form of documentation and useful for preventing regressions and always use <a href="https://docs.cypress.io/app/guides/accessibility-testing">accessibility testing tools</a>. Write your components and types in ways that enforce accessibility. An <code>&lt;input&gt;</code> should always have a label, so the <code>labelText</code> prop for your component with an <code>&lt;input&gt;</code> should be required, right?</p>
<p>Think of accessibility concerns as early as possible, such as in the design and planning phases. Accessibility auditing is part of quality assurance (QA). If you have the budget, pay accessibility testing experts to do manual testing and also, testing with users with disabilities. When accessibility experts raise concerns, <strong>take them seriously</strong>. Also celebrate the little wins — sufficient color contrast and <a href="https://accessibility.huit.harvard.edu/describe-content-images">alt text</a> mean a <em>lot</em> to a lot of people.</p>
<p>Finally, respond with humility when you get it wrong, because you <em>will</em> get it wrong. Use those moments as learning and improvement opportunities because your ultimate goal <em>should</em> be inclusion. It makes a lot of sense listen to the people who are excluded.</p>
        
        ]]></description>
        
      </item>
    
      <item>
        <title>You might not need role=&quot;presentation&quot;</title>
        <link>https://piccalil.li/blog/you-might-not-need-rolepresentation/?ref=accessibility-category-rss-feed</link>
        <dc:creator><![CDATA[Steve Frenzel]]></dc:creator>
        <pubDate>Thu, 12 Feb 2026 11:55:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/you-might-not-need-rolepresentation/?ref=accessibility-category-rss-feed</guid>
        <description><![CDATA[<p>For a brief moment in time, my main job was to assess the compliance of a selection of websites and document this in a report that was handed over to the customer. I carried out this assessment using the so-called <a href="https://bitvtest.de/en/tests-und-beratung/bik-bitv-test-web">BIK BITV test</a> and, in test step <a href="https://bitvtest.de/pruefschritt/bitv-20-plus-web/bitv-20-plus-web-9-4-1-2-name-rolle-wert-verfuegbar">9.4.1.2 Name, Role, Value</a>, I noticed time and again that <code>role="presentation"</code> was used in a way that was either technically questionable or even degraded accessibility.</p>
<p>Because I have seen this pattern so often, I'm going to to use a couple of real-life examples to find out why this is the case. The examples have been edited and shortened to make them easier to understand.</p>
<p>I will use the code to assess what the intention for using <code>role="presentation"</code> might have been and what could have been done differently. In my experience, there’s only a few real use cases for this attribute value, which we will get to later in the article.</p>
<h2>What you might read about this attribute</h2>
<p>We will use the specifications to take a closer look at what <code>role="presentation"</code> does, how it is used in the wild, and what it should actually be used for.</p>
<h2>The official specs</h2>
<div><h2>FYI</h2>
<p>Whenever you see a blockquote following a code sample in this section, it is an excerpt from the <a href="https://www.w3.org/TR/wai-aria-1.1/#presentation">specification</a>.</p>
</div>
<p>If you take a look at the <a href="https://www.w3.org/TR/wai-aria-1.2/#presentation">specification</a>, you will immediately notice the prominent note right at the beginning (emphasis ours):</p>
<blockquote>
<p>In ARIA 1.1, the working group introduced none as a synonym to the presentation role, due to author confusion surrounding the intended meaning of the word "presentation" or "presentational." Many individuals erroneously consider <code>role="presentation"</code> to be synonymous with <code>aria-hidden="true"</code>, and we believe <code>role="none"</code> conveys the actual meaning more unambiguously.</p>
</blockquote>
<p>This gives us an initial indication of why this attribute might be used incorrectly.</p>
<p>Authors seem to understand “presentational” as “only to be seen”; not heard. This is the behaviour of <code>aria-hidden</code>, which hides an element in the accessibility tree so that it is no longer announced when assistive technology is used. <code>role="presentation"</code> only affects the role, but not an element and its content.</p>
<blockquote>
<p>The intended use is when an element is used to change the look of the page but does not have all the functional, interactive, or structural relevance implied by the element type, or may be used to provide for an accessible fallback in older browsers that do not support WAI-ARIA.</p>
</blockquote>
<p>Spoiler: they’re describing layout tables here, which we’ll get into later in the article.</p>
<blockquote>
<p>An element whose content is completely presentational (like a spacer image, decorative graphic, or clearing element);</p>
</blockquote>
<p>The first two examples sound like image elements. If it is a decorative <code>svg</code> element, <code>aria-hidden="true"</code> would make more sense. Whereas for an <code>img</code> element <code>alt=""</code> would be more appropriate for hiding it, as this makes the intention much more obvious and it comes for free with the <code>img</code> element.</p>
<p>“Clearing element” might refer to using an element to clear float behaviour, where you would use <code>clear: both;</code> after using <code>float: left;</code> or <code>float: right;</code>, so it wouldn’t get dragged up the flow and break the layout.</p>
<p></p>
<blockquote>
<p>An image that is in a container with the <code>img</code> role and where the full text alternative is available and is marked up with <code>aria-labelledby</code> and (if needed) <code>aria-describedby</code>;</p>
</blockquote>
<p>To be completely honest, I have never seen the following example in the wild, and it is much easier to implement — and for users to read — if you use the <code>alt</code> attribute for alternative text in an image.</p>
<p>In the following example, the <code>hidden</code> attribute is used to hide the <code>&lt;span&gt;</code> element visually. However, it’s still available in the DOM, so it can be referenced using <code>aria-labelledby</code> , up there in the sibling <code>&lt;div&gt;</code> element, for example.</p>
<pre><code>&lt;div role="img" aria-labelledby="image-title"&gt;
  &lt;img src="example-image.jpg" role="presentation"&gt;
  &lt;span id="image-title" hidden&gt;A beautiful sunset over the ocean&lt;/span&gt;
&lt;/div&gt;
</code></pre>
<p>In the accessibility tree, in that context, the image is displayed correctly with the corresponding alternative text, but not the source URL. If you use <a href="https://www.deque.com/axe/devtools/linter/">axe-linter</a> in development, removing <code>role="presentation"</code> will trigger an alarm that images need alternative text.</p>
<blockquote>
<p>An element used as an additional markup "hook" for CSS [...]</p>
</blockquote>
<p>This means that in CSS, you use this attribute to target an element with <code>role="presentation"</code>, for example like this:</p>
<pre><code>table[role=presentation] {
  /* Do something */
}
</code></pre>
<blockquote>
<p>A layout table and/or any of its associated rows, cells, etc.</p>
</blockquote>
<p>Again, we’ll get into this pattern later on.</p>
<p>The specification contains many more explanations and examples, which you are welcome to look at yourself. One of the most important sentences for me is this one: <em>“[…] the presentation role causes a given element to be treated as having no role or to be removed from the accessibility tree, but does not cause the content contained within the element to be removed from the accessibility tree.”</em></p>
<p>The following example is not intended for use in production, but is only meant to illustrate that when using <code>role="presentation"</code>, the element itself and the elements within it lose their semantic meaning, but the content is still available:</p>
<pre><code>&lt;!-- Output: One generic element containing 
two generic elements with content. --&gt;
&lt;ul role="presentation"&gt;
  &lt;li&gt; Sample Content &lt;/li&gt;
  &lt;li&gt; More Sample Content &lt;/li&gt;
&lt;/ul&gt;

&lt;!-- Output: Unordered list element containing two list items. --&gt;
&lt;ul&gt;
  &lt;li&gt; Sample Content &lt;/li&gt;
  &lt;li&gt; More Sample Content &lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>The semantic HTML is still displayed in the source code, however, if you look at them in the accessibility tree, they are displayed as generic elements, so they could just as well be <code>&lt;div&gt;</code> or <code>&lt;span&gt;</code> elements.</p>
<p>Enough theory, let’s have a look at some real world examples!</p>
<h2>Questionable examples</h2>
<p>Some of these examples won’t create any real barriers for users and I consider them not nice, but more <em>harmless</em>. Others will cause problems, because <code>role="presentation"</code> has removed important semantic meaning, which can cause more issues because of nested elements.</p>
<p>You will quickly learn that I removed this attribute in <em>all</em> of my suggested fixes, as it’s just <strong>not necessary</strong>. It was also sometimes impossible for me to guess why developers used <code>role="presentation"</code> in the first place.</p>
<h3>The “invisible” <code>iframe</code> element</h3>
<pre><code>&lt;iframe src="..." title role="presentation" loading="eager" style="width: 0px; height: 0px; border: 0px; display: none;"&gt;
  &lt;!-- More code  --&gt;
&lt;/iframe&gt;
</code></pre>
<p>This <code>iframe</code> element contains a single <code>&lt;script&gt;</code> element for cookie logic. The intention here was most likely to hide it from assistive technology, as the CSS code suggests.</p>
<p>However, the content is still available because only the semantic meaning was removed from the <code>iframe</code> element. In addition, a <code>title</code> attribute without content was used here, which is incorrect.</p>
<p>Instead, the desired output could be implemented as follows:</p>
<pre><code>&lt;iframe aria-hidden="true" src="..." loading="eager"&gt;
  &lt;!-- More code  --&gt;
&lt;/iframe&gt;
</code></pre>
<h3>The list with no items</h3>
<pre><code>&lt;nav&gt;
	&lt;ul&gt;
	  &lt;li role="presentation"&gt;
	  	&lt;a href="/"&gt;To homepage&lt;/a&gt;
	  &lt;/li&gt;
	  &lt;!-- More code  --&gt;
	&lt;/ul&gt;
&lt;/nav&gt;
</code></pre>
<p>Why <code>role="presentation"</code> is applied to a list element here is beyond me. Especially since this is a navigation element and the semantics here have to be on point. Fortunately, it can be easily fixed by removing the attribute and using <code>aria-current="page"</code> for the current page.</p>
<h3>The “hidden” image</h3>
<pre><code>&lt;a href="/shop" aria-label="Shop"&gt;
  &lt;svg role="presentation"&gt;
    &lt;title&gt;Shopping cart&lt;/title&gt;
    &lt;!-- More code  --&gt;
  &lt;/svg&gt;
&lt;/a&gt;
</code></pre>
<p>As we learned from the <a href="https://www.w3.org/TR/wai-aria-1.1/#presentation">specification</a>, <code>role="presentation"</code> only removes the semantics of an element, but not its content, so the content of the <code>&lt;title&gt;</code> element should still be available, right? As <code>aria-label</code> is present, an accessible name is provided by this attribute, but <em>not</em> the <code>&lt;title&gt;</code> element itself.</p>
<p>If you would remove <code>aria-label</code>, it would have no accessible name at all, meaning <code>role="presentation"</code> has completely purged the <code>&lt;title&gt;</code> element from the accessibility tree! 🤯</p>
<p>The goal here was probably to hide the alternative text of the <code>&lt;svg&gt;</code> element, which worked. However, there are several ways to solve this use case more cleanly, and I would do it this way:</p>
<pre><code>&lt;a href="/shop"&gt;
  &lt;svg&gt;
    &lt;title&gt;Shop&lt;/title&gt;
    &lt;!-- More code  --&gt;
  &lt;/svg&gt;
&lt;/a&gt;
</code></pre>
<p>In this case, we do not need <code>aria-label</code>, as the alternative text of the image is used as the accessible name.</p>
<h3>Make it semantic, then undo it</h3>
<pre><code>&lt;a href="/retail"&gt;
  &lt;p role="presentation"&gt;Search store&lt;/p&gt;
&lt;/a&gt;
</code></pre>
<p>This is an awkward solution, but it does not present any real barriers for users, as the link text is still available. The developers probably wanted to hide the <code>&lt;p&gt;</code> element semantically, which is exactly what happens here. However, it could also have been done like this:</p>
<pre><code>&lt;a href="/retail"&gt;
  Search store
&lt;/a&gt;
</code></pre>
<h3>Technically meh, but still working</h3>
<pre><code>&lt;div role="presentation"&gt;
  &lt;section role="presentation" aria-label="Header"&gt;
    &lt;!-- More code  --&gt;
  &lt;/section&gt;
&lt;/div&gt;
</code></pre>
<p>This is a very interesting case! First of all, I don't understand why you would remove the semantic meaning but still use a semantic element.</p>
<p>In theory, <code>&lt;section&gt;</code> would be a generic element with an aria-label, which is prohibited according to the <a href="https://www.w3.org/TR/wai-aria-1.2/#generic">specification</a>, which would result in a failure of test step <a href="https://www.w3.org/WAI/WCAG21/Understanding/name-role-value">4.1.2 Name, Role, Value (Level A)</a>.</p>
<p>But because <code>aria-label</code> is present, this is ignored by the browser and the element is displayed correctly in the accessibility tree. Amazing! A technically clean solution would look like this:</p>
<pre><code>&lt;div&gt;
  &lt;section aria-label="Header"&gt;
    &lt;!-- More code  --&gt;
  &lt;/section&gt;
&lt;/div&gt;
</code></pre>
<h3>Adding an accessible name and then removing it</h3>
<pre><code>&lt;a href="/profile"&gt;
  &lt;div&gt;
    &lt;div&gt;
      &lt;img alt="Go to profile" role="presentation" src="..."&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/a&gt;
</code></pre>
<p>The intention is not clear to me here either, because there are some problems due to <code>role="presentation"</code>.</p>
<p>The <code>&lt;a&gt;</code> element needs an accessible name, which is not available because the <code>&lt;img&gt;</code> element has no semantic meaning and the <code>alt</code> attribute cannot be applied to a generic element.</p>
<p>Fortunately, these problems can be solved by removing <code>role="presentation"</code>! I also removed the <code>&lt;div&gt;</code> elements because personally, I don’t like the taste of <a href="https://cdn.matthewjamestaylor.com/img/gmail-div-soup.png">div soup</a>.</p>
<pre><code>&lt;a href="/profile"&gt;
  &lt;img alt="Go to profile" src="..."&gt;
&lt;/a&gt;
</code></pre>
<p>The alternative text is now linked to the image, and the accessible name of the link is provided by the alternative text. This one is similar to the “hidden” image example from earlier.</p>
<p></p>
<h2>The few good use cases of this attribute</h2>
<p>There are a couple of suggestions in the specs when you <em>could</em> use <code>role="presentation"</code>, but I think only a few use cases could apply: <a href="https://inclusive-components.design/tabbed-interfaces/">Tabbed interfaces</a>, <a href="https://www.stevefrenzel.dev/posts/menu-and-navigation-the-difference/#menu-example">menu pattern</a> and a layout table. Let’s have a closer look at a layout table, which is using a <code>&lt;table&gt;</code> element for page layout.</p>
<p>Thanks to <a href="https://nerdy.dev/cascading-secret-sauce">modern CSS</a>, there’s no real need to use layout tables for websites anymore! We can produce <a href="https://every-layout.dev/">flexible and robust designs by using flexbox and / or grid</a>. The one use case where <code>role="presentation"</code> might come in handy <em>could</em> be email templates though.</p>
<p>If you are forced to use layout tables for your emails, Harvard University has a great article for you on <a href="https://accessibility.huit.harvard.edu/creating-accessible-emails">creating accessible emails</a>. The most relevant part for this article is the following sentence (emphasis ours):</p>
<blockquote>
<p>If you must use tables for layout, add the attribute <code>role="presentation"</code> on every table element to ensure screen readers won’t treat them as data tables.</p>
</blockquote>
<p>In the <a href="https://www.w3.org/WAI/tutorials/tables/tips/">W3C Tips and Tricks section for tables</a>, they’re also <em>very</em> clear on the usage of layout tables (emphasis ours):</p>
<blockquote>
<p>Tables should not be used for layout purposes. Use Cascading Style Sheets (CSS) for layout. If there are already layout tables present, don’t use structural elements (like <code>&lt;th&gt;</code> or <code>&lt;caption&gt;</code>) [...], and do add <code>role="presentation"</code> to the <code>&lt;table&gt;</code> element.</p>
</blockquote>
<p>The <a href="https://accessibility.umich.edu/basics/concepts-principles/tables">University of Michigan</a> keeps it more brief but same advice:</p>
<blockquote>
<p>Only use tables to present tabular data, not to create a visual layout. Use CSS or styling options to handle layout.</p>
</blockquote>
<p>In <a href="https://brothercake.com/Ref/presentational-tables.html">Screen reader heuristics for presentational tables</a>, you’ll find some examples on how to use <code>role="presentation"</code> in the context of a layout table.</p>
<h2>Wrapping up</h2>
<p>Thanks to a detailed analysis of the specifications, we have learned what <code>role="presentation"</code> does and how few use cases there actually are for this attribute. We have also seen from real-life examples how misunderstood the functionality of this attribute is among developers.</p>
<p>The only real use cases would be tabbed interfaces, the menu pattern and email templates, but even here, one could (if possible) switch to plain text emails and avoid using <code>role="presentation"</code>.</p>
<hr />
<p>Many thanks to <a href="https://heydonworks.com/">Heydon Pickering</a> for their help checking over this article and lending us their endless accessibility wisdom.</p>
        
        ]]></description>
        
      </item>
    
      <item>
        <title>A guide to creating accessible PDFs using free tools</title>
        <link>https://piccalil.li/blog/a-guide-to-creating-accessible-pdfs-using-free-tools/?ref=accessibility-category-rss-feed</link>
        <dc:creator><![CDATA[Steve Frenzel]]></dc:creator>
        <pubDate>Thu, 02 Oct 2025 10:55:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/a-guide-to-creating-accessible-pdfs-using-free-tools/?ref=accessibility-category-rss-feed</guid>
        <description><![CDATA[<p>Before we look at what free alternatives there are for creating and checking PDFs, you should ask yourself the following question: do I really need to create a PDF, or could I create a website or write an email instead?</p>
<p>If the answer to that is “no” or even “I don't know”, I recommend reading <em><a href="https://blog.pope.tech/2023/05/01/inaccessible-pdfs-how-to-know-when-to-use-html-webpages-instead-of-pdfs/">Inaccessible PDFs? How to know when to use HTML webpages instead of PDFs</a></em> by <em>Whitney Lewis</em>.</p>
<p>If the answer was “yes”, I’m going to show you how to do the following things using a practical example:</p>
<ul>
<li>Creating an accessible PDF using <a href="https://www.libreoffice.org/">LibreOffice</a></li>
<li>Doing a quick structure check with a text editor</li>
<li>Testing for PDF/UA and WCAG conformance on the <a href="https://check.axes4.com">axes4</a> website</li>
<li>Checking our documents with different screenreaders (JAWS, NVDA and VoiceOver)</li>
</ul>
<h2>Why I’m writing this</h2>
<p>As I write this, I am in the application phase of finding a new role. Every now and then, I am offered the option to have form fields filled in automatically by uploading my resume.</p>
<p>I created my resume in <a href="https://support.apple.com/en-gb/guide/pages/welcome/mac">Pages</a> and selected the “On” option under “Advanced Options” and “Accessibility” when exporting. I did this in the hope that it would be accessible and machine-readable from a technical point of view.</p>
<p>After uploading the resume, my first name was always displayed as “Mastodon” and many fields were filled in incorrectly, or not at all. Since “Mastodon” is not my first name, I suspected that there was some kind of technical problem. So, for the nth time, I researched how to create and test accessible PDFs, and for the nth time, I was told that this was only possible with Adobe Acrobat Reader Pro. I didn't feel like throwing my money at a big corporation, so I frantically continued searching for a way to make my wish come true.</p>
<p>I quickly found an answer <a href="https://mastodon.online/@stvfrnzl/114991699718172415">after asking on Mastodon</a>, thanks to Grant, who pointed me to the official accessibility documentation and <a href="https://mastodon.online/@yatil@yatil.social">Eric Eggert</a>, who showed me how to test it for <a href="https://www.digitalaccesstraining.com/pages/articles?p=an-introduction-to-the-pdfua-standard">PDF/UA</a> and <a href="https://www.w3.org/TR/WCAG20-TECHS/pdf">WCAG conformance</a>.</p>
<p></p>
<h2>Using LibreOffice to create and export accessible PDFs</h2>
<p>Often ridiculed by me and rarely needed, I wanted to give LibreOffice a <em>real</em> chance this time, so I transferred my resume manually and first got an overview of what the problems actually are.</p>
<h3>Requirements for accessible PDFs in LibreOffice</h3>
<p>PDFs are subject to very similar accessibility requirements as websites. Let's take a look at them (taken from the <a href="https://help.libreoffice.org/latest/en-US/text/shared/01/ref_pdf_export_universal_accessibility.html">Universal Accessibility (PDF/UA) documentation</a>):</p>
<blockquote>
<ul>
<li>The document title is set.</li>
</ul>
</blockquote>
<p>This is not the visible main heading, but the title of the document, think <em>metadata</em>.</p>
<blockquote>
<ul>
<li>The document language is set, or all styles in use have the language property set.</li>
</ul>
</blockquote>
<p>The language for the document is set automatically. However, if there are sections in other languages, these must be adjusted accordingly. Go to "Tools", "Languages" and select the appropriate option.</p>
<blockquote>
<ul>
<li>All images, graphics, OLE objects have an alternate (alt) text or a title.</li>
</ul>
</blockquote>
<p>Graphics must either have a meaningful description or be marked as decorative. Got to "Format", "Alt Text..." and either enter the text or tick the "Decorative" box. Check out <a href="https://design102.blog.gov.uk/2022/01/14/whats-the-alternative-how-to-write-good-alt-text/">What’s the alternative? How to write good alt text</a> if you need help writing alternative text.</p>
<blockquote>
<ul>
<li>Tables do not contain split or merged cells.</li>
<li>Only integrated numbering is used, no manual numbering. For example, do not type "1.", "2.", "3." at the beginning of paragraphs.</li>
</ul>
</blockquote>
<p>If numbers are generated, this should happen automatically and they should not be added manually. Example: Multiple lines of text are converted into a semantically correct element using “Ordered List” instead of adding the numbers yourself. Go to "Styles" and select "Numbering 123 List Style".</p>
<blockquote>
<ul>
<li>Hyperlink texts without the underlying hyperlinks.</li>
</ul>
</blockquote>
<p>Texts marked as hyperlinks should contain the corresponding path. This warning appears when you create a hyperlink and then remove the path it contains using “Remove Hyperlink,” which you shouldn't; just leave it be!</p>
<blockquote>
<ul>
<li>The contrast between text and the background meets the WCAG specification.</li>
</ul>
</blockquote>
<p>See <a href="https://www.section508.gov/test/color-contrast-in-nonweb-documents-images/">Testing Color Contrast in Non-Web Documents and Images</a>.</p>
<blockquote>
<ul>
<li>No blinking text.</li>
<li>No footnotes or endnotes.</li>
<li>Headings must increase sequentially with no skips, for example, you cannot have Heading 1, Heading 3, and no Heading 2.</li>
<li>Text does not convey additional meaning with (direct) formatting.</li>
</ul>
</blockquote>
<p>If the font or style for text is changed, this must not be applied to the text itself, but only to the semantic element itself representing this element. Example: All Heading 2 elements should be italic and in Comic Sans. Select the appropriate element either directly in the document or in the right-hand sidebar, go to "Styles" and select "Edit Style…" and change it accordingly.</p>
<p>Some of these points apply to our use case, as we will need headings, paragraphs, lists, hyperlinks, and a decorative image for the resume. If you work in web development and know a little about accessibility, these pain points should be very familiar to you.</p>
<p>Now that we know all the requirements, we can get started!</p>
<h3>Displaying accessibility problems and warnings</h3>
<p>On the right-hand side, you can click on “Accessibility Check” to display the corresponding warnings and notes. I have also selected “Entire page” under “Zoom” and “Formatting Marks” under “View.” This gives me an overview of the entire page and allows me to see where elements such as paragraphs, line breaks, or lists are located.</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-08-29%20at%2007.46.09.png" alt="Screenshot of the LibreOffice user interface showing a document with text and an Accessibility Check panel on the right hand side. Inside the panel are one error and multiple warnings shown." /></p>
<p>Not very pretty, but the good thing is that we only have one problem and a handful of warnings! The problem with the missing page title is quickly solved by clicking on the “Fix...” button and entering the title for the document, “Resume Steve Frenzel”:</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-08-29%20at%2007.53.29.png" alt="Screenshot of the LibreOffice user interface highlighting a dialog for the document title. The input field contains the words “Resume Steve Frenzel”." /></p>
<h3>Creating a heading structure</h3>
<p>Before we look at the warnings for links, I want to start by creating a hierarchy using headings. On the right-hand side, you can either select “Properties” or use the select element at the top left of the toolbar.</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-08-29%20at%2007.54.44.png" alt="Screenshot of the LibreOffice user interface with two big arrows. One points to a select element in the toolbar on top, one points to the same element inside the panel on the right hand side. Both of them have the option “Default Paragraph Style” selected." /></p>
<p>To do this, we highlight the relevant text and select, for example, “Heading 1,” “Heading 2,” and so on.</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-08-29%20at%2008.02.08.png" alt="Screenshot of the LibreOffice user interface highlighting two warnings inside the Accessibility Check panel, “Avoid new empty lines between numbered paragraphs.”." /></p>
<p>That looks much clearer already. However, there are now two warnings for “Avoid new empty lines between numbered paragraphs.”</p>
<p>Before we look at how to solve this, let's add the lists.</p>
<h3>Adding lists</h3>
<p>Lists can be added either via “Format,” “Lists,” and “Unordered List,” or via the toolbar and the button element with the list icon:</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-08-29%20at%2008.08.23.png" alt="Screenshot of the LibreOffice user interface highlighting the icon to create unordered lists with a big arrow. Below are multiple “Avoid new empty lines between numbered paragraphs.” Warnings highlighted." /></p>
<p>Our structure is coming along nicely, but what’s that: <em>more</em> warnings?! Let’s see how we can get rid of those.</p>
<h3>Avoiding empty lines between paragraphs</h3>
<p>By removing the empty paragraphs between headings, lists, and so on, we can significantly tidy up the “Accessibility Check” section:</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-08-29%20at%2008.11.39.png" alt="Screenshot of the LibreOffice user interface showing text with different kinds of headings, as well as unordered lists in-between. Inside the Accessibility check panel are multiple warnings for “Missing ‘name’ property of hyperlink.” and “Hyperlink text is the same as the link address”." /></p>
<p>The warnings have disappeared, but now the spacing between the “Heading 3” elements is virtually non-existent. “Heading 1” and “Heading 2” are fine for me.</p>
<p>To create a better visual hierarchy, we can go to “Styles” and “Edit styles...” and adjust the spacing under “Spacing” in “Indents &amp; Spacing.”</p>
<p>I chose a value of 0.20 and checked the box next to “Do not add space between paragraphs of the same style” so that these values are applied above and below the element.</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-08-29%20at%2008.16.00.png" alt="Screenshot of the LibreOffice user interface with a “Paragraph Style” dialog open. Inside, the “Spacing” section is highlighted, containing two options: “Above paragraph” and “Below paragraph”. Both values are 0.2." /></p>
<p>Now our document looks less squashed and we can take care of the warning about the links.</p>
<h3>Implementing links correctly</h3>
<p>There are two warnings, namely “Missing ‘name’ property of hyperlink.” and “Hyperlink text is the same as the link address [...]”. Here is our list of links:</p>
<ul>
<li>Email: <code>stvfrnzl@duck.com</code></li>
<li>Website: <a href="https://stevefrenzel.dev/">https://stevefrenzel.dev/</a>
<ul>
<li>Blog: <a href="https://stevefrenzel.dev/blog/">https://stevefrenzel.dev/blog/</a></li>
</ul>
</li>
<li>LinkedIn: <a href="https://linkedin.com/in/stevefrenzel/">https://linkedin.com/in/stevefrenzel/</a></li>
<li>GitHub: <a href="https://github.com/stevefrenzel/">https://github.com/stevefrenzel/</a></li>
<li>Mastodon: <a href="https://mastodon.online/@stvfrnzl/">https://mastodon.online/@stvfrnzl/</a></li>
</ul>
<p>What we need to do is give the link itself an accessible name and replace the URL with a different word. For example:</p>
<ul>
<li><a href="mailto:stvfrnzl@duck.com">Email</a></li>
<li><a href="https://stevefrenzel.dev/">Website</a>
<ul>
<li><a href="https://stevefrenzel.dev/blog/">Blog</a></li>
</ul>
</li>
<li><a href="https://linkedin.com/in/stevefrenzel/">LinkedIn</a></li>
<li><a href="https://github.com/stevefrenzel/">GitHub</a></li>
<li><a href="https://mastodon.online/@stvfrnzl/">Mastodon</a></li>
</ul>
<p>To do this, select the relevant word and open the context menu via “Insert” and “Hyperlink” in the toolbar (or <kbd>CMD</kbd> + <kbd>K</kbd> on macOS). For the email address, select “Mail” and enter the email address under “Recipient:”.</p>
<p>For hyperlinks, we select “Internet” and paste the URL under “Hyperlink settings” and “URL” (it is automatically pasted from the clipboard).</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-08-29%20at%2008.21.31.png" alt="Screenshot of the LibreOffice user interface with two big arrows pointing to the accessibility warning “Hyperlink test is too short.”. It also highlights a “Hyperlink” dialog, where one can enter values for websites, emails and documents." /></p>
<p>Although the warning about the hyperlink text has disappeared, two of the six links are now too short. It's “only” a warning, but my goal is to have zero errors or warnings, so we're going to fix that. “Email” becomes “Send email” and ‘Blog’ becomes “Read blog.”</p>
<div><h2>FYI</h2>
<p>To avoid issues with <a href="https://www.w3.org/TR/WCAG21/#label-in-name">WCAG success criterion 2.5.3 (Label in Name (Level A))</a>, the visible and accessible name are exactly the same here. This is very important for speech recognition users, because if these two are not, or only partially the same, users might have a hard time selecting this element.</p>
</div>
<p>Nice! All that's missing now is the accessible name for the links. To do this, we click the corresponding “Fix…” button and enter the appropriate name:</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-08-29%20at%2008.27.21.png" alt="Screenshot of the LibreOffice user interface highlighting a dialog titled “Enter a name of the hyperlink.”. It contains a text input with the value “Send email” in it." /></p>
<h3>Adding a different font</h3>
<p>This resume looks very plain, doesn't it? I'm not interested in winning a design award, but I would like to make it a little more appealing by using a different font. I've chosen “Graphik” for the headings and “Atkinson Hyperlegible Next” for the paragraphs.</p>
<p>Let's start by applying the style to the main heading:</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-08-29%20at%2008.36.33.png" alt="Screenshot of the LibreOffice user interface with a big arrow pointing to the accessibility warning “The text formatting conveys additional meaning.”" /></p>
<p>It looks better (in my opinion), but we got the warning “The text formatting conveys additional meaning.” This warning appears when we apply the new font <em>directly</em> to the text instead of using it <em>globally</em> for all main headings.</p>
<p>A better approach is to change the font under “Styles,” “Edit Style...,” “Font,” and “Family.” Alternatively, you can select the corresponding element, right-click, and access it in the context menu via ‘Paragraph’ and “Edit Style...”.</p>
<h3>Adding a decorative image</h3>
<p>Perfect, no more accessibility problems or warnings anymore! Now let's add a visual accent to the document with a visual separator. The toolbar has a button called “Insert line” (visualized by a diagonal line), which we can use to add the decorative image:</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-08-29%20at%2009.03.30.png" alt="Screenshot of the LibreOffice user interface with a big arrow pointing to a button inside the tool bar on top. The icon of the button shows a diagonal line." /></p>
<p>Interestingly, we do not receive a warning in the “Accessibility Check” section that this image has no alternative text or that it has not been marked as decorative. However, as we are aware of this issue, we will fix it immediately.</p>
<p>By right-clicking on the image (or selecting “Format” and “Alt Text...”), we can check the “Decorative” box. Done!</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-08-29%20at%2009.06.20.png" alt="Screenshot of the LibreOffice user interface showing a dialog called “Alt Text”. It contains input fields for “Text” and “Alt Text”, as well as a ticked checkbox labelled “Decorative”. A big arrow points to that checkbox." /></p>
<h3>Exporting it as accessible PDF</h3>
<p>Before exporting, we check whether we have implemented our elements correctly. To do this, we open the side panel under “Tools” and “Navigator,” which shows us semantic elements in the document. Although no paragraphs are displayed, all headings, hyperlinks, and images are shown as expected. Nice, moving on!</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-09-04%20at%2011.38.26.png" alt="Screenshot of the LibreOffice user interface showing eleven highlighted heading elements. The “Navigator” side panel visualises the hierarchy of these elements, as well as hyperlinks and images." /></p>
<p>Go to “File”, “Export As” and “Export as PDF...” and tick the box labelled “Universal Accessibility (PDF/UA)” to make sure it’s meeting the technical requirements for an accessible PDF:</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-08-29%20at%2009.09.21.png" alt="Screenshot of the LibreOffice user interface showing a dialog titled “PDF Options”. A big arrow is pointing to a ticked checkbox labelled “Universal Accessibility (PDF/UA)”." /></p>
<h2>Testing the PDF</h2>
<p>Let's start with a very simple test to make sure our document structure is correct. As I'm on MacOS, I'm using the native <a href="https://support.apple.com/en-gb/guide/preview/welcome/mac">Preview</a> and <a href="https://support.apple.com/en-gb/guide/textedit/welcome/mac">TextEdit</a> apps here but you can choose whatever is available to you.</p>
<p>After opening the PDF with Preview, we select all the text via <kbd>CMD</kbd> + <kbd>A</kbd> and copy it using <kbd>CMD</kbd> + <kbd>C</kbd>.</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-09-09%20at%2007.41.16.png" alt="Screenshot of the PDF opened in Preview on MacOS with all text highlighted in light blue." /></p>
<p>Now, we're going to paste it into an empty TextEdit document with <kbd>CMD</kbd> + <kbd>V</kbd>.</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-09-09%20at%2007.42.11.png" alt="Screenshot of TextEdit on MacOS showing all text of the PDF, including custom fonts, font sizes, formatting, lists and links." /></p>
<p>So, what are we looking for? We’re checking for:</p>
<ul>
<li>A logical reading order without mixed up content</li>
<li>Random text or characters</li>
</ul>
<p>As both are not the case, we can assume our document is fine. Let’s move on to check if it fulfils the PDF/UA and WCAG requirements on axes4.</p>
<h3>Checking PDF/UA and WCAG requirements on axes4</h3>
<p>Head over to the <a href="https://check.axes4.com/en/">axes4</a> website and either drag and drop your document where it says “Drag and drop your PDF here” or upload it by selecting the “select file” button.</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-08-29%20at%2009.20.28.png" alt="Screenshot of the axes4 website showing the drag and drop area for PDFs. A big arrow is pointing to the words “Drag and drop your PDF here”." /></p>
<p>Great, our document meets all requirements! In the “Document” and “Test Report” sections, you will find detailed information about the document and which criteria were checked.</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-08-29%20at%2009.21.53.png" alt="Screenshot of the axes4 website showing the results for the check. Both PDF/UA and WCAG are fulfilled. Below is more detailed information of the report." /></p>
<p>To be <em>really</em> sure that everything is as we intended, we can check our document with a screen reader to see how it interacts with this type of assistive technology.</p>
<h3>Testing it with different screen readers</h3>
<div><h2>FYI</h2>
<p>I'm neither dependent on a screen reader, nor am I an expert user. I use them mainly for checking the structure of a website, and to get a feeling how screen reader users <em>may</em> experience a website. Your screen reader experience might be very different than mine and the same goes for the setup of your preferred screen reader.</p>
</div>
<p>There are many screen reader apps out there and I chose to test with <a href="https://www.freedomscientific.com/products/software/jaws/">JAWS</a>, <a href="https://www.nvaccess.org/">NVDA</a> and <a href="https://support.apple.com/en-gb/guide/voiceover/welcome/mac">VoiceOver</a>, as these are the three most popular ones according to the <a href="https://webaim.org/projects/screenreadersurvey9/#primary">WebAIM Screen Reader User Survey</a>. A great resource on screen reader usage is <a href="https://adrianroselli.com/2022/11/your-accessibility-claims-are-wrong-unless.html#SRs">Your Accessibility Claims Are Wrong, Unless...</a> by <em>Adrian Roselli</em>.</p>
<p>I'm using MacOS Sequoia 15.6 and running Windows 11 on Mac with <a href="https://www.parallels.com/">Parallels</a>. Thanks to <a href="https://www.sarasoueidan.com/blog/testing-environment-setup/">this wonderful explainer by Sara Soueidan</a>, I was able to set it up quickly.</p>
<h4>Screen reader and browser combinations</h4>
<p>Regarding JAWS and NVDA: as I want to keep it as simple as possible, I open the PDF with Microsoft Edge, as it comes pre-installed with Windows 11.</p>
<p>However, some screen readers work better with certain browsers. These are some of the most popular combinations according to the <a href="https://webaim.org/projects/screenreadersurvey10/#browsercombos">WebAIM Screen Reader User Survey #10</a>, which are also recommended by the <a href="https://www.accessibilityassociation.org/sfsites/c/resource/WASBoK_PDF">IAAP (WAS Body of Knowledge, page 45)</a>:</p>
<ul>
<li>JAWS with Chrome</li>
<li>NVDA with Firefox (or Chrome)</li>
<li>VoiceOver with Safari</li>
</ul>
<h4>JAWS (Version 2025.2508.120) on Windows 11</h4>
<p>By pressing <kbd>H</kbd>, I’m moving through all the headings in the document, which worked as expected. We can also check the speech output by opening the <a href="https://www.freedomscientific.com/training/jaws/hotkeys/#:~:text=WINDOWS%20Key%2BC-,show%20speech%20history,-INSERT%2BSPACEBAR%20followed">Speech History</a> via <kbd>Insert</kbd> + <kbd>Spacebar</kbd> + <kbd>H</kbd>.</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-09-09%20at%2008.07.58.png" alt="Screenshot of the Speech History window of JAWS screen reader, with the announced headings highlighted. It shows eleven headings in total." /></p>
<p>Next, let’s check the lists by pressing <kbd>L</kbd>. It should announce seven lists:</p>
<ul>
<li>Five lists with three items</li>
<li>One list with two</li>
<li>One list with five items</li>
</ul>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-09-10%20at%2006.59.45.png" alt="Screenshot of the Speech History window of JAWS screen reader, with the announced lists highlighted. It shows seven lists in total." /></p>
<p>Sweet, this looks good as well! Lastly, let’s check if the links are working as expected by pressing <kbd>TAB</kbd> to traverse through the document:</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-09-09%20at%2008.10.17.png" alt="Screenshot of the Speech History window of JAWS screen reader, with the announced links highlighted. It shows six links in total." /></p>
<p>Looks like we’re good here! As we formatted our “Send email” hyperlink as a link to send an email straight away, JAWS announces “Send Mail Link” after the accessible name.</p>
<h4>NVDA (Version 2025.2) on Windows 11</h4>
<p>As before, I’m opening the PDF in Microsoft Edge and press <kbd>H</kbd> to traverse through the headings. All of them are announced correctly, let’s have a look at them in the <a href="https://download.nvaccess.org/documentation/en/userGuide.html#SpeechViewer">Speech Viewer</a>:</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-09-10%20at%2007.14.19.png" alt="Screenshot of the Speech Viewer window of NVDA screen reader, with the announced headings highlighted. It shows eleven headings in total." /></p>
<p>Moving on to the lists by pressing <kbd>L</kbd>, they are all recognised and announced correctly. However, NVDA also announces the bullet points here. Which is fine, as the most important thing are lists being announced as lists.</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-09-10%20at%2007.15.13.png" alt="Screenshot of the Speech Viewer window of NVDA screen reader, with the announced lists highlighted. It shows seven lists in total." /></p>
<p>When tabbing through the document using <kbd>TAB</kbd>, all links are announced as expected. Great, let’s check how this document behaves with VoiceOver.</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-09-10%20at%2007.15.57.png" alt="Screenshot of the Speech Viewer window of NVDA screen reader, with the announced links highlighted. It shows six links in total." /></p>
<h4>VoiceOver (Version 10) on MacOS Sequoia 15.6</h4>
<p>I am opening the document in <a href="https://support.apple.com/en-gb/guide/preview/welcome/mac">Preview</a> and use the so called <a href="https://support.apple.com/en-gb/guide/voiceover/mchlp2719/mac">Rotor</a> feature in VoiceOver to get an overview of elements like headings, links, images and many more. Let’s have a look at our heading structure:</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-09-04%20at%2011.44.41.png" alt="“Heading” section of the VoiceOver Rotor feature showing multiple heading levels from one to three." /></p>
<p>Everything looks as expected, layers and names are correct! Let's continue with the links:</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-09-04%20at%2011.44.47.png" alt="“Link” section of the VoiceOver Rotor feature showing six links." /></p>
<p>No surprises here either, wonderful. Let's take a look at the “Images” section:</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-09-04%20at%2011.44.55.png" alt="“Image” section of the VoiceOver Rotor feature showing nothing but the heading." /></p>
<p>Nothing is displayed here because we have marked the image as decorative.</p>
<p>Since the lists are not displayed here in Rotor, I navigated directly through the document using VoiceOver, and the lists were announced to me correctly.</p>
<p><img src="https://piccalil.b-cdn.net/images/screenshots/Screenshot%202025-09-04%20at%2012.02.00.png" alt="Text version of the VoiceOver output saying “content list 3 items”" /></p>
<h2>Wrapping up</h2>
<p>Anyone familiar with Microsoft Word or similar word processing programs should have no problem quickly finding their way around LibreOffice. It is also a small step toward digital sovereignty, moving away from the products of US tech giants.</p>
<p>The example with my resume may not be very complex, but it should contain enough examples for everyday use to quickly and easily create an accessible PDF.</p>
<p>Thanks to axes4, we can test our PDFs without ever having to think about the words “Adobe Acrobat Reader Pro”. If you wan't to make extra sure, you now know how to check the document with different screen readers too.</p>
        
        ]]></description>
        
      </item>
    
      <item>
        <title>Use transparent borders and outlines to assist with high contrast mode</title>
        <link>https://piccalil.li/blog/use-transparent-borders-and-outlines-to-assist-with-high-contrast-mode/?ref=accessibility-category-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Thu, 11 Mar 2021 00:00:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/use-transparent-borders-and-outlines-to-assist-with-high-contrast-mode/?ref=accessibility-category-rss-feed</guid>
        <description><![CDATA[<p>Say you’ve got this nice little button.</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/YzpdLRm">Button with shadow-based focus that’s not accessible in high contrast mode</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<p>Because <code>outline</code> doesn’t clip to the shape of your button if you use <code>border-radius</code>, it can be tempting to use a sharp <code>box-shadow</code> for focus styles.</p>
<pre><code>button:focus {
  outline: none;
  box-shadow: 0px 0px 0px 3px #192a56;
}
</code></pre>
<p>With standard operating system and browser settings this looks fine. There’s plenty of contrast and the focus style is very clear. Job done, right? Unfortunately not because in <a href="https://support.microsoft.com/en-us/windows/use-high-contrast-mode-in-windows-10-fedc744c-90ac-69df-aed5-c8a90125e696">Windows High Contrast Mode</a>, both the background and focus styles probably won’t show up.</p>
<figure>
<video></video>
<figcaption><p>Because the background isn’t showing up in high contrast mode, the user has no visual indication of hover or focus events.</p></figcaption>
</figure>
<p>There is a solution to this: transparent borders and outlines! First, we add a transparent border to our button:</p>
<pre><code>button {
  border: 1px solid transparent;
  /* all the other CSS */
}
</code></pre>
<p>This border is completely invisible and you probably won’t even notice it, but the difference in Windows High Contrast Mode is huge.</p>
<p><img src="https://piccalil.b-cdn.net/images/quick-tips/high-contrast-button/good-button.jpg" alt="The much better button with styles as described in windows high contrast mode" /></p>
<p>This doesn’t fix the focus style though. It’s best to provide genuine contrast with focus styles so when a keyboard user <kbd>tab</kbd>s to your element, they can see that it is focused. To improve the initial focus style, we need to tweak it a bit:</p>
<pre><code>button:focus {
  outline: 2px solid transparent;
  outline-offset: 4px;
  box-shadow: 0px 0px 0px 3px #192a56;
}
</code></pre>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/VwmqXgd">Button with shadow-based focus that’s still accessible in high contrast mode</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<p>We first set a 2 pixel <code>outline</code> and then using the ever-handy <code>outline-offset</code>, push it out by another 4 pixels. Now, when a user focuses our button, it looks great both in a standard operating system and browser configuration <strong>and</strong> it looks great in Windows High Contrast Mode.</p>
<figure>
<video></video>
<figcaption><p>The transparent border helps the button show up better to start with, then the outline adds an even better visual indicator that there has been a focus event.</p></figcaption>
</figure>
<p>Here’s all the CSS for the button:</p>
<pre><code>button {
  display: inline-block;
  padding: 0.5rem 2rem;
  font: inherit;
  font-weight: bold;
  border-radius: 2rem;
  border: 1px solid transparent;
  background: #3f25c4;
  color: #ffffff;
  min-width: 10rem;
  cursor: pointer;
}

button:hover,
button:focus {
  background: #8c7ae6;
  color: #000000;
}

button:focus {
  outline: 2px solid transparent;
  outline-offset: 4px;
  box-shadow: 0px 0px 0px 3px #192a56;
}
</code></pre>
<p>This is a quick win, for everyone: the visual design is left almost untouched <em>and</em> it’s accessible.</p>
        
        ]]></description>
        
      </item>
    
      <item>
        <title>Load all focusable elements with JavaScript</title>
        <link>https://piccalil.li/blog/load-all-focusable-elements-with-javascript/?ref=accessibility-category-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Wed, 13 Jan 2021 00:00:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/load-all-focusable-elements-with-javascript/?ref=accessibility-category-rss-feed</guid>
        <description><![CDATA[<p>It’s handy to know what elements are focusable when you are building accessible, interactive user interface elements. This is especially the case if you are planning to trap focus, or toggle your interactive element’s state when user focus escapes it.</p>
<p>This little helper function will find all focusable child elements—specifically, <strong>user-focusable elements</strong>—of a passed parent element:</p>
<pre><code>/**
 * Returns back a NodeList of focusable elements
 * that exist within the passed parent HTMLElement, or
 * an empty array if no parent passed.
 *
 * @param {HTMLElement} parent HTML element
 * @returns {(NodeList|Array)} The focusable elements that we can find
 */
const getAllFocusableElements = parent =&gt; {
  if (!parent) {
    console.warn('You need to pass a parent HTMLElement');
    return []; // Return array so length queries will work
  }

  return parent.querySelectorAll(
    'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled]), details:not([disabled]), summary:not(:disabled)'
  );
};
</code></pre>
        
        ]]></description>
        
      </item>
    
    </channel>
  </rss>
