Last active
September 13, 2020 08:17
-
-
Save MaybeThisIsRu/98ae4883467081f582ba15a13c22524f to your computer and use it in GitHub Desktop.
a11y while adding categories
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- | |
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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 thebutton
, but thinking twice, their solution is okay-ish because :✕
(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):
I have this CSS utility class in all projects, which is the ultimate way to hide content visually, but not for screen readers.
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:
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. 😅