build prev
This commit is contained in:
@ -4,12 +4,79 @@
|
||||
<subtitle>Lee Cattarin... on the internet!</subtitle>
|
||||
<link href="https://leecat.art/feed.xml" rel="self" />
|
||||
<link href="https://leecat.art/" />
|
||||
<updated>2026-02-19T00:00:00Z</updated>
|
||||
<updated>2026-03-22T00:00:00Z</updated>
|
||||
<id>https://leecat.art/</id>
|
||||
<author>
|
||||
<name>Lee Cattarin</name>
|
||||
<email>lee.cattarin@gmail.com</email>
|
||||
</author>
|
||||
<entry>
|
||||
<title>accessible image modals</title>
|
||||
<link href="https://leecat.art/accessible-image-modals/" />
|
||||
<updated>2026-03-22T00:00:00Z</updated>
|
||||
<id>https://leecat.art/accessible-image-modals/</id>
|
||||
<content type="html"><p>Recently I've been working on a single-page digital rendition of a zine complete with many hand-drawn images. The author wanted to be able to bring images up to a full-screen view, to either zoom in or to put the whole enlarged image on one screen with no scrolling. It was a real struggle to find resources on how to do this in an accessible manner, so I'm writing up what I did.</p>
|
||||
<blockquote>
|
||||
<p>Fair warning: This solution is likely imperfect.</p>
|
||||
</blockquote>
|
||||
<h2 id="the-dialog-element">the <code>dialog</code> element</h2>
|
||||
<p>do you know how many tutorials want you to roll your own modals? It's a not-insignificant amount. W3Schools, top of the search results in many cases, recommends it in two places - <a href="https://www.w3schools.com/howto/howto_css_modal_images.asp" target="_blank">image modals</a> and <a href="https://www.w3schools.com/css/css3_images_modal.asp" target="_blank">responsive image modals</a>. Several search results for &quot;image modal&quot; pop up div solutions - <a href="https://stackoverflow.com/questions/75598914/how-to-display-an-image-clicking-on-it-using-modal-window-on-html-css-and-js" target="_blank">div modal 1</a>, <a href="https://dev.to/salehmubashar/create-an-image-modal-with-javascript-2lf3" target="_blank">div modal 2</a>, <a href="https://www.youtube.com/watch?v=Y9TNHynFjaQ" target="_blank">div modal 3</a>.</p>
|
||||
<p>If you don't know of the existence of <code>&lt;dialog&gt;</code>, a search for image modals will not get you there quickly.</p>
|
||||
<p>If you search for <em>accessible</em> image modals, you'll still hear about <code>&lt;div&gt;</code>s. Hell, <a href="https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/dialog/" target="_blank">W3C's ARIA Authoring Practices Guide uses a <code>&lt;div&gt;</code></a>. You kinda have to go digging to read about the <a href="https://www.scottohara.me/blog/2023/01/26/use-the-dialog-element.html" target="_blank">dialog element from Scott O'Hara</a> or find <a href="https://accessibleweb.dev/modals" target="_blank">AccessibleWeb.dev's piece on modals</a>. Or you can go <a href="https://adrianroselli.com/2025/06/where-to-put-focus-when-opening-a-modal-dialog.html" target="_blank">straight for Adrian Roselli</a> and find examples that use the native <code>&lt;dialog&gt;</code> element. Thanks Adrian!</p>
|
||||
<p>Did I roll my own modal at first? Regretfully, yes. I'd used the <code>&lt;dialog&gt;</code> element before... several years ago... in a project I don't have access to anymore... Needless to say, I had forgotten about its existence.</p>
|
||||
<p>Anyway, I got there. Eventually. So let's talk about modals and <code>&lt;dialog&gt;</code>.</p>
|
||||
<h3 id="what-does-dialog-give-us">what does <code>&lt;dialog&gt;</code> give us?</h3>
|
||||
<p>so, what do we get from the <code>&lt;dialog&gt;</code> element that we don't get from a <code>&lt;div&gt;</code> modal?</p>
|
||||
<ol>
|
||||
<li>a semantically meaningful element</li>
|
||||
<li>a backdrop that fills the screen behind the modal, styleable with <code>::backdrop</code></li>
|
||||
<li>automatic use of the <code>Esc</code> key to close the modal</li>
|
||||
<li>automatic focus trapping that prevents any tabbing within the page behind the modal (users can tab off the page into the browser buttons)</li>
|
||||
<li>the JS functions <code>.showModal()</code> and <code>.close()</code></li>
|
||||
<li>if <code>closedby</code> is set to <code>any</code>, clicking outside the modal (anywhere on the backdrop) will also close the modal</li>
|
||||
<li>automatic return of focus to the element that triggered the modal</li>
|
||||
<li>the <code>autofocus</code> attribute for in-modal elements to set which element should receive focus on opening the modal</li>
|
||||
<li>the <code>open</code> attribute which is set on the <code>&lt;dialog&gt;</code> when open and removed when closed (when you use the functions mentioned previously)</li>
|
||||
</ol>
|
||||
<p>and more. This is just the pieces in use for me. There's also things like <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog#additional_notes" target="_blank">setting forms so that submission closes the dialog</a> (point 1 in that list).</p>
|
||||
<h3 id="what-doesnt-it-give-us">what doesn't it give us?</h3>
|
||||
<ol>
|
||||
<li>a close button - gotta roll your own and attach the requisite <code>.close()</code> call (or rely on users hitting escape or clicking the backdrop)</li>
|
||||
<li>prevention of scroll on the rest of the page</li>
|
||||
</ol>
|
||||
<h3 id="a-bug">a bug</h3>
|
||||
<p>MDN warns:</p>
|
||||
<blockquote>
|
||||
<p>Do not add the <code>tabindex</code> property to the <code>&lt;dialog&gt;</code> element as it is not interactive and does not receive focus.</p>
|
||||
</blockquote>
|
||||
<p>— <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog#additional_notes" target="_blank"><code>&lt;dialog&gt;</code>: additional notes</a> (point 3 in that list).</p>
|
||||
<p>despite this, I found that in Firefox (but not Edge), the <em><code>&lt;dialog&gt;</code> itself was focusable</em>. I have no idea why, but I tested this on a totally unstyled and unmodified page and still found it to be true. As a focusable element, it made no sense. It had no interactivity and could not be activated.</p>
|
||||
<p>I'm still torn: do I add <code>tabindex=&quot;-1&quot;</code>? MDN specifically says not to, but I'm pretty sure they're warning against making it <em>focusable</em>. Why warn against making it nonfocusable when <em>it's not supposed to be focusable in the first place</em>, after all?</p>
|
||||
<p>At current, I'm ambivalent, but I've added <code>tabindex=&quot;-1&quot;</code> to handle Firefox's poor behavior. Making the dialog focusable is unhelpful and confusing.</p>
|
||||
<h2 id="in-addition-to-the-dialog">in addition to the <code>&lt;dialog&gt;</code></h2>
|
||||
<p>here's what else I wrote...</p>
|
||||
<h3 id="html">html</h3>
|
||||
<p>besides the <code>&lt;dialog&gt;</code>, I gave my <code>&lt;img&gt;</code> elements <code>tabindex=&quot;0&quot;</code> to make them focusable.</p>
|
||||
<h3 id="js">js</h3>
|
||||
<p>in <code>modal.js</code>, I created an <code>openDialog()</code> function that takes in the clicked image. It:</p>
|
||||
<ul>
|
||||
<li>creates a new <code>&lt;img&gt;</code> element and copies over the <code>src</code> and <code>alt</code> attributes - importantly, it doesn't copy the full <code>&lt;img&gt;</code> because we <em>don't</em> want to copy that <code>tabindex</code> attribute</li>
|
||||
<li>replaces the current <code>&lt;img&gt;</code> in the modal with our new copy with <code>replaceChild()</code> (or if there's no current one, it just appends)</li>
|
||||
<li>calls <code>dialog.showModal()</code></li>
|
||||
</ul>
|
||||
<p>I gave my close button two event listeners, one that listens for click events and one that listens for a keydown of the space or enter keys. In the case of the keydown, it calls <code>event.preventDefault()</code> to stop the space key from scrolling the underlying page.</p>
|
||||
<p>I also looped through all images and attached my <code>openDialog()</code> function to any image with a <code>tabindex</code> attribute (I had some images that weren't intended to be fullscreened, so they lacked <code>tabindex</code>). Again, I gave them listeners on both click and keydown.</p>
|
||||
<h3 id="css">css</h3>
|
||||
<p>here's the most relevant parts of the CSS:</p>
|
||||
<ul>
|
||||
<li><code>dialog::backdrop</code> was given a <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Colors/Using_relative_colors" target="_blank">relatively calculated</a> color - <code>rgba(from var(--color-bg) r g b / .8)</code> - as well as a blur</li>
|
||||
<li><code>body:has(dialog[open])</code> has <code>overflow: hidden</code> set</li>
|
||||
<li><code>dialog img</code> uses a <code>max-height</code> as well as <code>object-fit: contain</code></li>
|
||||
</ul>
|
||||
<h2 id="errors-questions">errors? questions?</h2>
|
||||
<p>reach out!</p>
|
||||
</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>eleventy lessons</title>
|
||||
<link href="https://leecat.art/eleventy-lessons/" />
|
||||
@ -420,14 +487,6 @@ eleventyExcludeFromCollections: true
|
||||
<updated>2026-01-18T00:00:00Z</updated>
|
||||
<id>https://leecat.art/fire-and-ice-handspun/</id>
|
||||
<content type="html"><p>Fiber from <a href="https://www.etsy.com/shop/JakiraFarms" target="_blank" rel="external">Jakira Farms</a> in Fire &amp; Ice colorway. 100% merino.</p>
|
||||
</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>dyeing fiber</title>
|
||||
<link href="https://leecat.art/dyeing-fiber/" />
|
||||
<updated>2026-01-18T00:00:00Z</updated>
|
||||
<id>https://leecat.art/dyeing-fiber/</id>
|
||||
<content type="html"><p>hand-dyed with acid dyes</p>
|
||||
</content>
|
||||
</entry>
|
||||
</feed>
|
||||
Reference in New Issue
Block a user