{"id":198,"date":"2021-05-16T12:29:19","date_gmt":"2021-05-16T12:29:19","guid":{"rendered":"https:\/\/a11y-tools.com\/blog\/?p=198"},"modified":"2026-04-06T13:18:46","modified_gmt":"2026-04-06T13:18:46","slug":"enough-with-the-role-play-lets-get-back-to-the-basics","status":"publish","type":"post","link":"https:\/\/a11y-tools.com\/blog\/2021\/05\/enough-with-the-role-play-lets-get-back-to-the-basics\/","title":{"rendered":"Enough with the role-play\u2014let\u2019s get back to the basics"},"content":{"rendered":"<p><strong>This article originally featured on the TPGi blog but lost images\/some formatting during a site update and rebrand, so I am re-publishing it here (minus any comments that were added at the time) until such a time as the original article is fixed.<\/strong><\/p>\n<p>Having read Steve Faulkner\u2019s post a short while ago about the <a href=\"https:\/\/html5accessibility.com\/stuff\/2021\/05\/01\/html-developers-please-consider-in-the-year-of-2021\/\">often-questionable use of roles in HTML<\/a> when a more suitable native HTML element would be far better, I was prompted to come up with something that would help in some small way to alleviate this problem. Immediately, I had the idea of creating a user style sheet that specifically called out potentially misused roles.<\/p>\n<p>But before we continue any further, let\u2019s just take a step back and briefly remind ourselves why using a native element is better than redefining a nonsemantic <code>&lt;div&gt;<\/code>. For this, we will use the classic case of the button (and apologies to all those for whom this is old hat).<\/p>\n<h2>How to make a button \u2013 the hard way<\/h2>\n<p>To create a button from scratch, you <em> could <\/em> add a <code>role<\/code> of \u201cbutton\u201d to a <code>&lt;div&gt;<\/code>. You would also need to add a <code>tabindex<\/code> attribute so that keyboard-only users can activate this link, so you end up with this:<\/p>\n<pre>\r\n<code>&lt;div role=\"button\" tabindex=\"0\"&gt;Details&lt;\/div&gt;<\/code>\r\n<\/pre>\n<p>By default, this will neither look nor behave like a button, which isn\u2019t exactly ideal. Here is said button as viewed in the browser:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/a11y-tools.com\/blog\/wp-content\/uploads\/2026\/04\/button-no-style.png\" alt=\"The word 'Details' in black text on a white background, looking nothing like a button, and the browser dev tools showing it marked up as a div element with the role of button\"><\/p>\n<p>So, we can (and should) style it to look more like a button by adding some padding, a border, some hover and focus styles (the usual buttony traits) and eventually you will end up with something like this:<\/p>\n<pre>\r\n<code>[role=\"button\"] { margin: 10px 20px; display: inline-block; padding: 10px; border: 1px solid gray; background: #efefef; text-align: center; border-radius: 5px; font-size: 20px; }<\/code>\r\n<\/pre>\n<figure>\n<img decoding=\"async\" src=\"https:\/\/a11y-tools.com\/blog\/wp-content\/uploads\/2026\/04\/styled-button.png\" alt=\"A basic-looking button with its hover and focus states also shown\"><figcaption>\nWith some hand-rolled CSS added, this now looks like a button (hover and focus styles\u2013not shown in the CSS exmple above\u2013also need to be crafted)<br \/>\n<\/figcaption><\/figure>\n<p>The problem here is that when you click on it or try to activate it using the keyboard, it does nothing. You still need to hook up some JavaScript to capture the click events and allow the button to be activated with <kbd>Enter<\/kbd> key or <kbd>Space<\/kbd>. So it\u2019s time to roll up the sleeves and write some code. Here is a script that does this:<\/p>\n<pre>\r\n<code>function doButtonyThings(){\r\n alert(\"Something button-like happens\");\r\n}\r\n\r\nconst allPseudoButtons = document.querySelectorAll('[role=\"button\"]');\r\nArray.from(allPseudoButtons).forEach(pseudoButton => {\r\n pseudoButton.addEventListener('click', event => {\r\n  doButtonyThings();\r\n });\r\n pseudoButton.addEventListener('keyup', event => {\r\n  if ((event.keyCode===13)||(event.keyCode===32)) {\r\n   doButtonyThings();\r\n   if (event.keyCode===32) {\r\n    event.preventDefault();\r\n   }\r\n  }\r\n });\r\n});\r\n<\/code>\r\n<\/pre>\n<p>Note that we also have to add even more script to make sure that when <kbd>Space<\/kbd> is pressed, it doesn\u2019t do the default action of scrolling the page, something that\u2019s often forgotten even when Space is supported. This usually results in the button action taking place and then the page immediately scrolling away from wherever that action presented itself. Frustrating.<\/p>\n<p>In reality, the script that adds keyboard support will probably be much more complex than the example above. In most cases where people are creating buttons out of <code>&lt;div&gt;<\/code>s, they aren\u2019t hand-crafting small pieces of JavaScript. Rather, they are relying on heavy JavaScript frameworks to take care of this (think sledgehammers and nuts). But let\u2019s assume that one way or the other we now have something that looks like a button and behaves like a button. Because it is exposed as a button to assistive technology through its <code>role<\/code> attribute and it responds to keypresses, it is accessible. It took some effort to get here but job done, right?<\/p>\n<h2>How to make a button \u2013 the simpler way<\/h2>\n<p>Let\u2019s not beat around the bush, though\u2014it would\u2019ve been far simpler to do this:<\/p>\n<pre>\r\n<code>&lt;button type=\"button\"&gt;Details&lt;\/button&gt;<\/code>\r\n<\/pre>\n<p>About the hardest thing to do here is to remember to add <code>type=\"button\"<\/code> to ensure that the button won\u2019t act as a submit button when it\u2019s inside a <code>&lt;form&gt;<\/code> element. But with just that markup, you get a button that\u2019s exposed to assistive technology as a button, looks like a button and responds to keypresses without having to add more JavaScript.<\/p>\n<p>So instead of reinventing the wheel each time, let\u2019s try to use what the browser already provides.<\/p>\n<h2>User stylesheets to the rescue<\/h2>\n<p>I have a number of user style sheets that I use in my daily work that I can toggle on and off to highlight certain aspects of a page. It\u2019s not as robust a process as interrogating the actual markup in developer tools, but it does provide a quick at-a-glance view of the page, and it can give you very high-level overview about how the page has been developed. For example, I have user style sheets that indicate structural elements such as sections, lists, headings and tables. I have another style sheet that specifically looks for <a href=\"https:\/\/www.w3.org\/TR\/wai-aria-practices-1.2\/\"> aria authoring patterns<\/a> such as <a href=\"https:\/\/www.w3.org\/TR\/wai-aria-practices-1.2\/#tabpanel\"> tabs<\/a>, <a href=\"https:\/\/www.w3.org\/TR\/wai-aria-practices-1.2\/#TreeView\">tree menus<\/a> and suchlike, and it attempts to visually indicate where certain patterns have not been adhered to. These ones are less robust, but as before, they can sometimes alert me to problems that require further investigation even before I run any automated testing tools.<\/p>\n<p>With the use of roles, I knew that there was a possibility that one of these diagnostic style sheets could be used to alert where a <code>role<\/code> is used and, furthermore, indicate even more clearly cases where a <code>role<\/code> has been applied where a native HTML equivalent would have been a far better option. I set about creating this style sheet, an excerpt of which you can see below (covering just buttons and links):<\/p>\n<pre>\r\n<code>[role=\"button\"]:before,\r\n[role=\"link\"]:before {\r\n  content:\"\ud83d\udea8 ROLE:\" attr(role);\r\n  border:2px solid darkred;\r\n  color:darkred;\r\n  display: inline-block;\r\n  padding: 0 5px;\r\n  margin: 2px 5px 2px 0;\r\n  border-radius: 5px;\r\n  background: white;\r\n  font-size: 16px;\r\n  font-family: sans-serif;\r\n  font-weight: bold;\r\n}\r\nbutton[role=\"button\"]:before,\r\na[role=\"link\"]:before {\r\n  content:\"\u26a0\ufe0f ROLE:\" attr(role);\r\n  border-color:orange;\r\n  color:black;\r\n}<\/code>\r\n<\/pre>\n<p>In essence, it\u2019s looking for <code>role<\/code> attributes that we know have native HTML equivalents, and it screams at you. If the <code>role<\/code> is applied to the equivalent HTML element, that scream is downgraded to a general grumble and some shifty, judgemental looks.<\/p>\n<p>Here\u2019s an example of it put to use on Gmail:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/a11y-tools.com\/blog\/wp-content\/uploads\/2026\/04\/gmail-1-css-diagnostics.png\" alt=\"Gmail user interface with numerous elements flagged as potential problems\"><\/p>\n<p>In the spirit of openness and also so that others might be able to correct any heinous mistakes that I may have made, I\u2019ve <a href=\"https:\/\/github.com\/lloydi\/Diagnostic-CSS-files\" rel=\"noopener\"> made this role-highlighting user style sheet available on GitHub<\/a>. Look for the CSS file named \u2018reveal-roles.css\u2019. (And also feel free to extend and enhance with pull requests as you see fit.)<\/p>\n<h2>You\u2019ve found some issues. Now what?<\/h2>\n<p>It\u2019s one thing to highlight issues or potential issues on a page, but we ultimately want to be able to fix those. If you can 100% reliably identify a problem on the page, then you should be able to rectify it in a reasonably robust manner. With this in mind, I created a tool which I have called <a href=\"https:\/\/a11y-tools.com\/role-reverser\/index.html\" rel=\"noopener\">Role Reverser<\/a>. Using the same set of selectors used in the diagnostic style sheet, the tool can be used to interrogate markup that you paste into it, then replace any nonsemantic role-attribute-based HTML elements with their native HTML equivalents. Here\u2019s a before-and-after example:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/a11y-tools.com\/blog\/wp-content\/uploads\/2026\/04\/changed-markup.png\" alt=\"Side-by-side comparison of div-based markup and semantic HTML version of the same markup\"><\/p>\n<p>If you\u2019re a developer, this at least helps you understand what you might need to change on the page to make your markup more accessible by default. It also helps an accessibility practitioner quickly provide that semantic version in remediation advice that they give when auditing a site.<\/p>\n<p>The tool lets you paste markup into it to generate a more semantic version of the source markup, but you can also run a bookmarklet version of the tool on any page to see the effect of changing to native HTML elements. Going back to our old friend Gmail, here\u2019s the summary of what the tool finds on the page:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/a11y-tools.com\/blog\/wp-content\/uploads\/2026\/04\/gmail-2-alert.png\" alt=\"JavaScript alert indicates the number and type of issues found\"><\/p>\n<p>The alert reads:<\/p>\n<ul>\n<li>253 elements in original markup have <code>role<\/code> attributes.<\/li>\n<li>251 elements were swapped back to native HTML equivalents.<\/li>\n<li>2 elements have (possibly superfluous) `role` attributes applied<\/li>\n<li>150 <code>tabindex<\/code> attributes removed from (now) natively focusable elements<\/li>\n<\/ul>\n<p>If you hit the \u2018OK\u2019 button, it will visually highlight all swapped elements on the page that are now native HTML equivalents (pressing \u2018Cancel\u2019 leaves the swapped elements in place\u2014it just doesn\u2019t outline them in lurid, tasteless lime green):<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/a11y-tools.com\/blog\/wp-content\/uploads\/2026\/04\/gmail-3-swapped-items.png\" alt=\"The Gmail user interface now awash with green outlines indicating all the HTML elements that have been swapped for more semantic native HTML equivalents\"><\/p>\n<p>What it cannot do, of course, is fix the problem at its source. So if a page is written with a framework of some description, and the framework uses templates to generate the markup, then the end result from running this tool will only give you an indication of what should be in the page\u2019s markup. It is a target, something to aim for.<\/p>\n<p>Another consideration to make is that some developers might create custom markup using <code>&lt;div&gt;<\/code> elements because they have an aversion to working with <code>&lt;button&gt;<\/code>s and other native controls\u2014because of their default styling. It would appear that for many of these developers, the task of resetting some of those default styles is more of a headache to them than creating a button from the ground up using <code>&lt;div&gt;<\/code> elements, and then applying custom JavaScript and CSS to make it look and behave like a button.<\/p>\n<p>Using this tool to replace one of these pretend buttons with a real <code>&lt;button&gt;<\/code> element may introduce styling and behaviour issues. Sometimes making a swap like this can be seamless. It really depends on how the CSS has been created on a given site and whether any global CSS resets have been used.<\/p>\n<p>In summary, don\u2019t make things hard for yourself as a developer by putting in all that extra legwork to recreate these native elements from scratch. Take the simple approach and use what the browser gives you for free. If it looks like a button, it can be clicked like a button, and it smells like a button\u2026 then it should probably be a <code>&lt;button&gt;<\/code>. And the same goes for you, all you pretend tables and phony links!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This article originally featured on the TPGi blog but lost images\/some formatting during a site update and rebrand, so I am re-publishing it here (minus any comments that were added at the time) until such a time as the original article is fixed. Having read Steve Faulkner\u2019s post a short while ago about the often-questionable [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11],"tags":[10],"class_list":["post-198","post","type-post","status-publish","format-standard","hentry","category-tpgi-backup","tag-tpgi-backup"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/a11y-tools.com\/blog\/wp-json\/wp\/v2\/posts\/198","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/a11y-tools.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/a11y-tools.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/a11y-tools.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/a11y-tools.com\/blog\/wp-json\/wp\/v2\/comments?post=198"}],"version-history":[{"count":7,"href":"https:\/\/a11y-tools.com\/blog\/wp-json\/wp\/v2\/posts\/198\/revisions"}],"predecessor-version":[{"id":211,"href":"https:\/\/a11y-tools.com\/blog\/wp-json\/wp\/v2\/posts\/198\/revisions\/211"}],"wp:attachment":[{"href":"https:\/\/a11y-tools.com\/blog\/wp-json\/wp\/v2\/media?parent=198"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/a11y-tools.com\/blog\/wp-json\/wp\/v2\/categories?post=198"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/a11y-tools.com\/blog\/wp-json\/wp\/v2\/tags?post=198"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}