Skip to content

Instantly share code, notes, and snippets.

@MaybeThisIsRu
Last active September 13, 2020 08:17
Show Gist options
  • Save MaybeThisIsRu/98ae4883467081f582ba15a13c22524f to your computer and use it in GitHub Desktop.
Save MaybeThisIsRu/98ae4883467081f582ba15a13c22524f to your computer and use it in GitHub Desktop.
a11y while adding categories
<!--
Problem statement:
I'm writing an interactive category field.
I have three major things on play here:
1. A text input that is described by a paragraph above it
2. A datalist of categories received from the server
3. A list of checked checkboxes for each new category
Why #3? Well, I don't want to have to manage my form on the frontend before sending it to the backend. A checkbox with the same name attribute automatically takes care of sending multiple categories.
1. My main concern is indicating to users that the list of entered categories is *here* -- how do I connect the two?
2. A secondary concern is also making sense of what this "x" button *does* and for which particular value.
-->
<script>
import { onMount } from "svelte";
let categories = [];
$: userEnteredCategories = [];
let currentCategoryName = "";
onMount(async () => {
const response = await fetch(
"/api/v1/micropub/category/"
).catch((error) => console.error(error));
const data = await response
.json()
.catch((error) => console.error(error));
if (data && data.error) console.error(data.error_description);
else categories = data;
});
const handleKeyUp = (event) => {
if (event.code === "Comma") {
userEnteredCategories = [
...userEnteredCategories,
currentCategoryName.slice(0, -1),
];
currentCategoryName = "";
}
};
const deleteUserCategory = (event) => {
userEnteredCategories = userEnteredCategories.filter((cat) =>
event.target.dataset.value === cat ? false : true
);
};
</script>
<input
type="text"
id="field-group__category"
aria-describedby="field-group__description--category"
list="category--suggestions"
on:keyup|preventDefault={handleKeyUp}
bind:value="{currentCategoryName}"
/>
<datalist class="category" id="category--suggestions">
{#each categories as category}
<option value="{category}">{category}</option>
{/each}
</datalist>
<!-- To avoid handling the form manually just for categories, we build a list of checkboxes and have HTML do the heavy lifting for us -->
<!-- ! Concerns around accessibility -->
<div class="category" id="category--user">
{#each userEnteredCategories as userEnteredCategory, i}
<div class="user-category">
<button
class="user-category__remove"
on:click|preventDefault={deleteUserCategory}
data-value="{userEnteredCategory}"
>
x
</button>
<label class="user-category__name" for="{userEnteredCategory}_{i}"
>{userEnteredCategory}</label
>
<input
type="checkbox"
name="category"
id="{userEnteredCategory}_{i}"
value="{userEnteredCategory}"
class="user-category__value hidden"
checked
/>
</div>
{/each}
</div>
@meduzen
Copy link

meduzen commented Aug 25, 2020

Oh, I skipped the part where it has to create a new category, indeed. That makes it more complex. 😅

I’m not fan of a x as content for the button, but thinking twice, their solution is okay-ish because :

  • they properly ARIA-ed it, I think;
  • it’s not a the x letter, it’s a (multiplication sign) but I guess most humans won’t read it “multiplication sign”. 😄

So, to sum up: adding aria-label will more likely be the right way to do it. 👍

What I usually do in that situation is this (but I’m gonna reconsider):

<button>
    <span class="visually-hidden>real button label</span <!-- this is read by screen readers -->
    <svg></svg> <!-- fancy “x” icon -->
</button>

I have this CSS utility class in all projects, which is the ultimate way to hide content visually, but not for screen readers.

.visually-hidden:not(:focus):not(:active) {
  position: absolute;
  size: 1px 1px;
  margin: -1px;
  padding: 0;

  clip: rect(0 0 0 0);
  clip-path: inset(100%);
  clip-path: polygon(0 0, 0 0, 0 0, 0 0);
  overflow: hidden;

  border: 0;

  white-space: nowrap;
}

This way there’s no doubt about the semantic vs a x or whatever else label we could have, for both readers and maintainers. I often use it to hide headings that are not shown but should structurally exist in the DOM for understanding, like:

<section>
    <h2 class="visually-hidden">title that is not visible but still read by screen readers</h2>
    <p>some content</p>
</section>

However, Scott O’Hara explains when we should use ARIA and when we should use something else like this class, and I’m probably not using it correctly in some (most?) cases. 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment