<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Filip Peralov</title>
    <description>The latest articles on DEV Community by Filip Peralov (@peralov).</description>
    <link>https://dev.to/peralov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3963412%2F43c6975a-eb02-4185-856c-373b5b79446c.jpeg</url>
      <title>DEV Community: Filip Peralov</title>
      <link>https://dev.to/peralov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/peralov"/>
    <language>en</language>
    <item>
      <title>Accessible Web Apps: A Div Is Not a Button</title>
      <dc:creator>Filip Peralov</dc:creator>
      <pubDate>Tue, 02 Jun 2026 10:05:16 +0000</pubDate>
      <link>https://dev.to/peralov/accessible-web-apps-a-div-is-not-a-button-5gf9</link>
      <guid>https://dev.to/peralov/accessible-web-apps-a-div-is-not-a-button-5gf9</guid>
      <description>&lt;p&gt;Modern frontend frameworks give us structure to build complex, scalable web platforms. But if you look under the hood of many enterprise applications, you will find hundreds of &lt;strong&gt;clickable&lt;/strong&gt; &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; &lt;strong&gt;elements&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Relying on the most generic layout container for core user interactions is more than lazy - it breaks the web for users who rely on assistive technologies, worsens native browser performance, and introduces technical debt.&lt;/p&gt;

&lt;p&gt;The examples used in this article are using &lt;em&gt;Angular&lt;/em&gt;, but the same principle applies in &lt;em&gt;any other frontend framework&lt;/em&gt; or plain html.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ban Clickable Divs
&lt;/h2&gt;

&lt;p&gt;I have been guilty of abusing click events on &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; elements just like anybody else, but after fixing hundreds of &lt;a href="https://www.w3.org/WAI/standards-guidelines/wcag/" rel="noopener noreferrer"&gt;&lt;em&gt;WCAG&lt;/em&gt;&lt;/a&gt; issues as a result of this pattern, I have learned my lesson.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;❌ Looks like a button but breaks accessibility
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; 
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn-primary"&lt;/span&gt;
  &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"saveChanges()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Save Changes
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Attaching click events to non-interactive tags like &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; , &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;, breaks core browser accessibility and forces you to reinvent the wheel with messy fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't reinvent the button
&lt;/h2&gt;

&lt;p&gt;If you were forced to make a generic &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; match the accessibility and functionality of a native &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, you cannot just use a simple &lt;code&gt;(click)&lt;/code&gt; binding. You would have to manually implement all the native browser logic yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- ❌ The over-engineered, high-maintenance "accessible" div --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; 
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn-primary"&lt;/span&gt; 
  &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; 
  &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
  &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"saveChanges()"&lt;/span&gt;
  &lt;span class="na"&gt;(keydown.enter)=&lt;/span&gt;&lt;span class="s"&gt;"$event.preventDefault(); saveChanges()"&lt;/span&gt;
  &lt;span class="na"&gt;(keydown.space)=&lt;/span&gt;&lt;span class="s"&gt;"$event.preventDefault(); saveChanges()"&lt;/span&gt;
  &lt;span class="na"&gt;[class.disabled]=&lt;/span&gt;&lt;span class="s"&gt;"isSaving"&lt;/span&gt;
  &lt;span class="na"&gt;[attr.aria-disabled]=&lt;/span&gt;&lt;span class="s"&gt;"isSaving"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Save Changes
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On top of writing extra html just to handle keyboard tracking and basic screen reader roles, &lt;strong&gt;you have to write additional code&lt;/strong&gt; in your component controller and &lt;strong&gt;custom css styles&lt;/strong&gt; because a generic &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; doesn't natively understand how to be interactive, focused, or disabled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a button
&lt;/h2&gt;

&lt;p&gt;For actions that change data or trigger a state, use a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;. As one of the base HTML elements the button serves a much greater purpose than just being a styled box on the screen.&lt;/p&gt;

&lt;p&gt;It brings a lot of pre-built behaviours. It maps click events to appropriate keyboard shortcuts, handles automatic form submission, prevents accidental text highlighting and can be easily disabled.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- ✅ 100% accessible, focusable, and semantic out of the box --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; 
  &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; 
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn-primary"&lt;/span&gt; 
  &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"saveChanges()"&lt;/span&gt; 
  &lt;span class="na"&gt;[disabled]=&lt;/span&gt;&lt;span class="s"&gt;"isSaving"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Save Changes
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By simply choosing a &lt;strong&gt;native&lt;/strong&gt; &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; element paired with Angular's native property binding &lt;code&gt;[disabled]="isSaving"&lt;/code&gt;, the browser takes care of all this automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid: The clickable table row 🫩
&lt;/h2&gt;

&lt;p&gt;Another place where this anti-pattern frequently appears into dashboards is data grids. Developers may attach a &lt;code&gt;(click)&lt;/code&gt; binding to an entire table row (&lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt;) to toggle an expanded view, open a sidebar, or navigate to a detail page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- ❌ Is it a row or a button? --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;tr&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"toggleRow(row.id)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;{{ row.name }}&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;{{ row.status }}&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"deleteRow(row.id)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Delete&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A table row has an inherent structural &lt;strong&gt;role&lt;/strong&gt; of "row". The element roles get conflicted. Is this a row in a data grid, or is it a button?&lt;/p&gt;

&lt;p&gt;To make this row function remotely close to an accessible control for keyboard or screen reader users, you can't just use a simple template binding. You are forced to manually inject lots of boilerplate directly onto the structural table tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- ❌ The over-engineered, high-maintenance "accessible" table row --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;tr&lt;/span&gt; 
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"custom-row-button"&lt;/span&gt; 
  &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; 
  &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
  &lt;span class="na"&gt;[attr.aria-expanded]=&lt;/span&gt;&lt;span class="s"&gt;"row.expanded"&lt;/span&gt;
  &lt;span class="na"&gt;[attr.aria-label]=&lt;/span&gt;&lt;span class="s"&gt;"(row.expanded ? 'Collapse' : 'Expand') + row.name"&lt;/span&gt;
  &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"toggleRow(row.id)"&lt;/span&gt;
  &lt;span class="na"&gt;(keydown.enter)=&lt;/span&gt;&lt;span class="s"&gt;"$event.preventDefault(); toggleRow(row.id)"&lt;/span&gt;
  &lt;span class="na"&gt;(keydown.space)=&lt;/span&gt;&lt;span class="s"&gt;"$event.preventDefault(); toggleRow(row.id)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;{{ row.expanded ? '-' : '+' }}&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;{{ row.name }}&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;{{ row.status }}&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Event interception to stop row triggers --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; 
      &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; 
      &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn-danger"&lt;/span&gt;
      &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"$event.stopPropagation(); deleteRow(row.id)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Delete
    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a screen reader user navigates to this row, the browser attempts to read every single data cell inside the row back-to-back as one single, continuous, chaotic button description.&lt;/p&gt;

&lt;h3&gt;
  
  
  Event Bubbling Issues
&lt;/h3&gt;

&lt;p&gt;Placing a real &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; inside an already clickable &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; creates a severe JavaScript event propagation bug. Because of standard DOM event bubbling, clicking that inner "Delete" button will trigger your &lt;code&gt;deleteRow()&lt;/code&gt; logic, and then bubble up and trigger &lt;code&gt;toggleRow()&lt;/code&gt; on the parent row. To stop this, you are forced to do &lt;code&gt;$event.stopPropagation()&lt;/code&gt; somewhere in the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sometimes the UX is the problem
&lt;/h2&gt;

&lt;p&gt;Instead of writing heavy JavaScript or complex TypeScript decorators to patch a flawed UI layout, &lt;strong&gt;bring the problem to your design and product teams.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Show them how making a giant container or a table row clickable introduces &lt;strong&gt;severe user experience barriers&lt;/strong&gt; for screen readers and mobile touch devices. Propose a design change: add an explicit, native &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; or a clear text link &lt;em&gt;inside&lt;/em&gt; the layout to cleanly isolate the action.&lt;/p&gt;

&lt;p&gt;Keep your data grid structure pure and preserve standard &lt;em&gt;HTML&lt;/em&gt; table layout trees. Place an explicit, accessible &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; inside the very first cell to handle the expansion behaviour.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- ✅ Pure table semantics with native controls --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; 
      &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; 
      &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn-toggle"&lt;/span&gt;
      &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"toggleRow(row.id)"&lt;/span&gt; 
      &lt;span class="na"&gt;[attr.aria-expanded]=&lt;/span&gt;&lt;span class="s"&gt;"row.expanded"&lt;/span&gt;
      &lt;span class="na"&gt;[attr.aria-label]=&lt;/span&gt;&lt;span class="s"&gt;"(row.expanded ? 'Collapse' : 'Expand') + row.name"&lt;/span&gt;
      &lt;span class="err"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;row.expanded&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="na"&gt;-&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt; &lt;span class="na"&gt;:&lt;/span&gt; &lt;span class="err"&gt;'+'&lt;/span&gt; &lt;span class="err"&gt;}}&lt;/span&gt;
    &lt;span class="err"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="na"&gt;button&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;{{ row.name }}&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;{{ row.status }}&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; 
      &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; 
      &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn-danger"&lt;/span&gt; 
      &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"deleteRow(row.id)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Delete
    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Can we add an additional click on the container ?
&lt;/h2&gt;

&lt;p&gt;Can we maybe attach an &lt;code&gt;(click)&lt;/code&gt; event to a &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; &lt;em&gt;only&lt;/em&gt; as a visual shortcut for mouse and mobile users, as long there is a dedicated button for the same action? In theory if we don’t add &lt;code&gt;role="button"&lt;/code&gt; or &lt;code&gt;tabindex="0"&lt;/code&gt;, the accessibility tree ignores the container, while mouse users get a larger, touch-friendly click target.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- ❌ Hidden visual trap --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; 
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"custom-card"&lt;/span&gt; 
  &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"viewProduct(product.id)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; 
      &lt;span class="na"&gt;[src]=&lt;/span&gt;&lt;span class="s"&gt;"product.imageUrl"&lt;/span&gt;
      &lt;span class="na"&gt;[alt]=&lt;/span&gt;&lt;span class="s"&gt;"product.imageDescription"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;{{ product.name }}&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{{ product.description }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
      &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; 
      &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn-fav"&lt;/span&gt;
      &lt;span class="na"&gt;[attr.aria-label]=&lt;/span&gt;&lt;span class="s"&gt;"'View ' + product.name"&lt;/span&gt;
      &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"$event.stopPropagation(); viewProduct(product.id)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;View&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;But this pattern introduces a hidden trap for low-vision users.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Consider a user who has partial vision and uses the mouse to explore the app but relies on a screen reader for the content bellow the cursor. When they hover over your clickable custom-card &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, the mouse tells them the area is interactive, but the screen reader treats it as regular div. If they click it, the layout suddenly changes without warning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Like many frontend developers, I viewed semantic HTML in web apps as an afterthought and accessibility as a post-development checklist. It took a client demanding accessibility compliance to finally force it onto my radar.&lt;/p&gt;

&lt;p&gt;But after spending a year fixing an inaccessible web application, I realised something critical: &lt;strong&gt;building accessible software is a fundamental developer responsibility, not an optional feature&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Engineering accessible apps starts by respecting the very building blocks of the browser. &lt;strong&gt;Choosing a native&lt;/strong&gt; &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; over a lazy &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; wrapper isn't a minor stylistic preference - it is a &lt;strong&gt;foundational architectural choice&lt;/strong&gt;. It means letting the browser handle user interaction, keyboard navigation, and assistive technologies out of the box instead of reinventing the wheel.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>a11y</category>
      <category>html</category>
    </item>
  </channel>
</rss>
