Accessibility tips with Svelte solutions
- Enrico Sacchetti Marketing DevOps - Fortinet
Do you find Svelte's a11y warnings get in your way? They're actually part of a greater mission shared by nations around the world to make the digital web universally accessible. In this talk, I will break down some of Svelte's a11y warnings, the user experiences they impact, and how to solve them.
You wouldn't mock your friends for ordering mild nachos.
You wouldn't watch anime without subs.
You wouldn't take the stairs just to flex on those choosing an elevator.
So why not ship accessible websites?
Thanks for stopping by at Accessibility Tips with Svelte Solutions.
My name is Enrico Sacchetti.
I am Marketing DevOps at Fortinet, or a full-stack developer, and I'm co-host of the Discord and YouTube show this week in Svelte.
Pleased to meet you.
I have been a software developer for about nine years, and three years of that was spent in design systems.
Lately, I've been an advocate for accessible digital experiences.
Fun fact about me is whenever I watch something, I always use subtitles, whenever possible.
So, welcome to the talk, and this talk is for you if you are involved in digital experiences, or you want to get better at coding accessible interactions, or you are a solo developer with lots of autonomy and control over your freelance or client work.
Could this be you?
Could this be me?
Could this be everyone?
Here's the problem at hand.
According to this project, webaim.
org/project/million, they have scanned the top million front pages of the internet, and in 2023, 96.
3% of those pages all have had WCAG 2 violations.
And in a few moments, we'll talk about what WCAG is.
But among those violations include low-contrast text, missing alternative text, empty links, missing form labels, empty buttons, and missing document language.
Most of these, as you might imagine, are very solvable and highly preventable.
Let's take a look at some of these violations highlighted by Svelte.
I'll just zoom right into that.
The first example gives you a yellow squiggly when missing an alternative text for an image.
All images should have alt text.
And the second one is empty links.
All hyperlinks should contain some content.
And lastly, missing form label.
These are some of the squigglies that are provided by Svelte and the Svelte Language Server, which is great because through static analysis, we can cover some of those common violations.
But what is the ideal solution?
In my opinion, it involves more policy and compliance laws worldwide, more executive-level directives to ship accessible digital experiences, more education for designers, developers, and everyone else involved, and better accessibility tooling.
The tooling out there is pretty nice, but there's always room for improvement.
Here are examples of some compliance laws.
In the United States of America, there's the Americans with Disabilities Act.
In the European Union, we have the EU Web Accessibility Directive.
And in Canada, where I live, we have ACA, and in Ontario, AODA.
In Germany, we have Equal Opportunity Act, and in Italy, the Stanka Act.
But this is just some cherry-picked examples.
In several other nations, there are related and similar accessibility initiatives that are bound by law.
So how much of this is the developer's responsibility?
Well, if you work on a team or within a large organization, it's not all on you, and I'm not here to pin this all on developers.
However, as developers, we are the last line of defense before something gets shipped to production.
Ideally, accessibility considerations are made before it is even tasked by you.
So before designing and before developing.
If you are a solo or freelance developer, then most of the power is yours.
You can ship accessible experiences and no one will stop you.
I mean, not really, right?
Here's a disclaimer.
Our tools alone cannot solve the world's digital accessibility problems.
That includes testing tooling and Svelte itself.
Nor can developers, part of a larger team, solve this on their own.
Organization directors and executives need to make accessibility their mission.
As promised, let's go over WCAG, the Web Contents Accessibility Guidelines.
These are freely available and provided by the W3C, and I just want to cover four key principles.
Perceivable, operable, understandable, and robust.
Starting with perceivable.
Information and interfaces must be presented in ways users can perceive through all senses.
Sight, touch, smell.
Okay, maybe not smell.
But in a way that the user interacts, such as via their eyes or the way a screen reader reads them out loud to them.
Interactions must be set up in a way that the user can perform with their interfaces, such as a mouse or a keyboard or a touch device like a cell phone.
Or a ticker tool, which is like a two-button interface.
All content presented can be interpreted at the user's level.
This includes the vocabulary level, so that it's understandable to a wide audience, and also information architecture, so the information someone's trying to find can be easily understood.
And lastly, robust.
Content can be interpreted reliably through a wide variety of user interfaces.
So whether you can read the text, or whether something is read out loud to you by a screen reader, it should all be possible.
So what role does Svelte play in accessibility?
Well, I'm glad to report that Svelte has decided to have a role in this.
However, accessibility is everyone's responsibility.
Some frameworks that developers use or libraries have documentation, such as here are some accessibility guidelines we encourage you to adopt.
But hardly anyone reads the documentation.
Secondly, some of them recommend linting tools, such as ESLint, Plugin, JSX, Ally, which is a numeronym for accessibility.
This tool is opt-in.
You have to either adopt this tool, having had discovered it first, or you happen to be working on a team already using it.
Otherwise, I doubt everyone's going to adopt it.
But, as for Svelte, Svelte is a compiler.
And it can tell you something's wrong before you build.
This is great, but can Svelte find every accessibility flaw?
No, only the ones related to HTML faux pas.
It cannot detect low color contrast, relevant focus management, error message placement such as within form inputs, animations that aren't subdued with prefers-reduced-motion, color blindness cases, non-underlined hyperlinks with bodies of content, and so much more.
This is barely scratching the surface.
But, could you imagine if Svelte could detect those things?
Of course, it would have to run a browser or something, but one can dream.
So what can Svelte detect?
Svelte uses static analysis.
It looks at your file's code, and it reads the code as is.
And at the time of this recording, through static analysis, Svelte provides up to 28 warnings.
And rather than go through all of them, let's go through some common ones, and I'll provide a checklist for you.
It's time to activate some warnings.
Let's start with this broad category of keyboard interactivity.
Take a look at this.
Here we have a div with a click handler, and already we have a yellow squiggly.
How can we improve this?
Can a user focus on interactive elements using tab on their keyboard?
Can they activate links using enter on key down?
Can they activate buttons using enter on key down, or space on key up?
There's a distinction.
Are there high contrast focus styles, such as inverted background or outline?
Are items tab-able in a predictable order that flows with the page's content?
With these considerations in mind, we can make some improvements.
Take a look at this.
17 lines of div.
Bear with me.
On line 2, we have role equals button, line 3, tab index equals 0.
On 4, we have our original click handler, line 5, we listen to the enter key on key down.
And on line 10, we listen to the space key on key up.
And there we go.
Oh, yes, we can just use button.
Okay, forget that div.
So with a regular button, you have all of that functionality built in.
It listens to the enter key, it listens to the spacebar key, it knows it's a button.
Screen readers will announce it's a button.
Because a button is a button is a button, and always has been.
And with that, we've prevented 5 out of the 28 warnings provided by Svelte.
Click events have key events, no non-interactive tab index, role has required aria-props, etc.
So yeah, you could write a really large div and avoid these warnings, or you can use a button.
Next category, forms.
Here's a form example with a couple of squigglies.
The first one is a label that's underlined, and it's associated with a radio input right below it on line 3.
The same exact thing is happening on lines 8 and 9.
How do we associate these labels?
What do we do?
Well, if you need something to be interactive on a page, use a button.
If you need a form field to auto-submit, such as a radio item, don't.
Don't do that, and I'll explain why.
Provide a submit button for a regular form, or use buttons that immediately submit.
Always include labels for your fields, and don't use the placeholder attribute.
Also, don't use the title global attribute.
Placeholder is not great because if you're using a text input, it's hard to tell for the user that this text input has already been filled.
And for the title, it's usually represented as a very small font, and if you're using a screen magnifier, which many users do use, they might miss it completely.
That's essential content that's way off the screen.
Here's a labeled example.
On line 3, we're using the for attribute for an explicit label.
Line 4, the input has an ID of light for a light-themed switcher, and line 3, it's using the light for attribute to let the screen reader know that this label is associated with the input on line 4.
Line 7 and 8, same thing.
We have our dark mode switcher, and it's using the proper explicit labeling.
On line 10, we have a form submission button.
So rather than the radio buttons listen to a change handler, we can submit the form using a button.
That is because radio buttons should not automatically submit forms.
They're meant to intake user controls and user information, and then the submit button lets the user be in control of submitting it.
However, it's not unreasonable for a theme switcher to immediately toggle light mode and dark mode.
So take a look at this improved example brought to you by Button, my old friend.
In SvelteKit, you can have a form on line 1, and on lines 2 and 3, each individual button within a single form can trigger a different form action, hence the form action attribute being used here.
And with this, you can have an immediately submittable theme switcher.
Button to the rescue once again.
And with all that, we've prevented one extra warning.
Label has associated control.
Now you know.
Next category is navigation and screen readers.
To go through this example, take a closer look at how to give your users an easier time.
Try to indicate the current page with aria-current=page, jump to the main element with a skip link, and make sure your top-level navigation also jumps to main if you're on the current page for that link.
Images should have a filled alt attribute and not an empty one.
Try not to truncate important information.
That's a lot, but we'll go through them right now.
To the right.
It's time for some live coding.
On the right, we have a Safari browser, and on the left is my source code.
So starting with these links, I have a couple of links here for the top-level navigation, home and about.
And on line 31, I already have an issue.
It says here, according to Svelte, the anchor element should have an href attribute.
Well, of course it should, so let's add one now.
And we're done.
No issues, no violations.
But we want to go a step further, so I have this already-cooked example that I'll bring on here.
Take a look at line 26, or starting with 23.
I'm using a Svelte feature called @const to define a boolean, whether or not a certain link in a loop of links is the current page.
So if it is the current page, have the href attribute jump to number-sign-main.
Otherwise, have it linked to the href defined.
On line 27, I set aria-current conditionally.
If current is true, then aria-current equals page.
Otherwise, it's undefined, and if an attribute is undefined in Svelte, it will not be rendered on the page DOM.
On line 37, we have a main element where all the main content is here, and it has an id of main, which is great because we want to make sure that the hyperlink on line 26 actually links to main.
So the server already did hot module reloading, and we have what we need.
So we have home and about.
If I click on home, nothing obvious happens.
You might think it re-navigates to the home page, but if you're using a screen reader, something very important happens.
Listen to this.
Okay, now that I have macOS voiceover enabled, which is designed to work with Safari, we can test these out.
>> Current page visited.
List two items.
>> Okay, when I hover the home link, it says current page visited.
When I press enter key, this happens.
>> Try these out.
You are currently on a text element.
>> That was control-option-space, and by doing so, it jumps to the main element, and it reads out loud the first paragraph.
Now let's go to the about page.
>> Voiceover off.
>> Okay, here's our about page.
Let's see what violations we can solve here.
The first one is about me, and there's a figure on line 3 and a fig caption on line 7.
This issue here is must be an immediate child of figure.
Okay, that's very easy.
I'm not going to be too offended by this deal this quickly.
I'll just move it on up.
And we're done.
The page reloads, and now the fig caption is part of the figure, which is also important for screen readers because we want to make sure that when we read the image, we know what the image is about and its associated commentary.
Let's open up screen reader once more.
Okay, when I navigate to the cat.
>> Photo by Kanashi on unsplash figure.
You are currently in a figure.
>> It says I'm currently in a figure.
Let's go inside.
>> Ragdoll cat image.
>> It highlights the image, and it reads the alt text, "Ragdoll cat.
" You don't have to say image of a ragdoll cat.
Just describe the image is good enough.
It's always important to describe images because sometimes a user has blurry vision.
>> Photo by link on link unsplash and a photo by Kanashi on unsplash.
>> And that is the association between figure and fig caption.
>> Voiceover off.
Further down here we have one last example.
I could like the cat by clicking on this button, but how could I know it's a button without hovering it with my mouse?
If you look at it without hovering, it looks like plain text.
But if I hover it, it looks like a button.
If you're on a cell phone or a touch device, it's impossible to know this is a button.
You cannot hover on a touch device.
And look at this second button.
It's written in Italian.
But I can't understand it because it's truncated.
So by truncating the text, a screen reader may be able to read that, but a sighted user cannot.
This is degraded.
How do we fix this?
The easiest solution is to remove the bad button class I set here.
And now the first one is a button.
It's a perceivable button with the affordance that it can be clicked on.
If you're on a touch device, you know that's clickable.
Of course, we can improve the styles, give it some better focus styles, but by default, it's more than what we had previously.
And how about this truncated text?
It looks like we're specifying something about this bad button.
It's got text overflow ellipses, it's got a max width of 10 characters, and it's got an overflow of hidden.
So what if we get rid of the overflow?
That's not what we want.
What about text overflow?
Okay, but it's still cut off.
Perhaps the height is not working to our preferences.
There we go.
We could make the max width a little bit better.
And there we go.
That's a reasonable width for a button.
And it's okay for a button to be multi-lined.
It's also okay for it to not have all of these styles.
So let's get rid of those.
It looks more like a button already.
Colors notwithstanding, the point is, you should let your buttons be multi-lined.
It's okay to have a multi-lined button.
Sometimes on a web browser, your users may change their font size from the default 16 pixels to something much greater, like 24 or even 36 pixels.
By allowing your buttons to have a variable height, text in other languages or text with increased font size will wrap and still be perceived as buttons.
And you don't have to truncate your text.
It all just works and it's all very adaptive.
This is great.
And that's the end of the example.
We have the AllyImageRedundantAlt.
We have InvalidAttribute, MissingAttribute, and Structure with the figure and fig caption.
This is great.
So a user with network issues can submit forms, and your theme can be changed, and etc.
Well, that was a good talk.
I guess we don't have a lot of time to cover the other ones, but at least if you know these many things, you can expect to make your websites much, much better.
I was just told that you won't be satisfied unless I go through all of the warnings.
Okay, why not?
You got me.
Let's do a lightning round.
So I'm going to categorize the remaining 18 or so warnings into three broad categories.
The first category is, "You're almost doing the right thing" warnings.
So the first one is, "Aria activeDescendant has tabIndex.
" If you have an activeDescendant element, such as in a combo box, just be sure to give it a tabIndex.
There you go.
If you have AllyMissplacedScope, the scope attribute is only for the th element.
So if you accidentally put it to a parent wrapper, such as a div or a child wrapper, all this does is nudge you in the correct direction so please put the scope in the th.
So the aria-hidden element.
Oh, sorry, the aria-hidden attribute must be either true or false.
It cannot be yes, no, or any other value.
So this is more like a linter.
And you're already used to this if you're using type checking.
If you have an h1 or something that requires content, this will help you.
It'll say, "Hey, you gotta fill in the content.
This is more like a spell checker, because you're on your way to write a good attribute, but you may have misspelled something, so the fix is quite simple.
Among the list of roles that are possible, it won't let you mistype or misspell something, such as tooltips, plural.
Should be tooltip, singular.
The next category is "You probably shouldn't do that" warnings.
And these are a bit more aggressive and related to the previous examples, such as AllyAccessKey.
You may not even know what this is, because I didn't know what this is either.
AccessKey is a somewhat obsolete attribute that sets key bindings to something.
It's much better to actually use a button and set your own custom keydowns.
So this one is a.
Some people debate this one, but I'll tell you that when you use autofocus, it takes away predictability of the web experience.
If you want an input or something to be focused immediately when the user lands on the page, then it takes away their control and it takes away their expectations.
Because if you have something like a form error, like in my example right here, let them be in control.
First, show them the errors when they submit the form.
The form submits, it focuses on the error box, and then in the error box, there can be a hyperlink that says "Go to first error".
And with that hyperlink, the user can be in control and go to the error themselves.
So this div here has a positive tab index greater than zero.
Div is a contrived example because you should never have a tab index on a div because it's not an interactive element.
It's okay though to have a tab index of -1.
Such as element.
focus, the function.
This nudges you into providing transcriptions and subtitles for something like a video or audio file.
And here's a 3-in-1.
MouseEvents have key events.
Non-interactive elements should not be interactive suddenly.
No static element interactions.
This was initiated by the example here in red.
The div has a mouseover event.
If you have a mouseover event, you also need a focus event.
But if you have those things on the div, the compiler will say, "Well, you also need a role.
" And then we're back to the button example all over again.
So once again, just use a button.
And the last category is.
What was I thinking?
On a meta tag?
The user can't even see the meta tag.
You don't need attributes there.
This H2HasAriaHidden equals true.
You don't need to hide something that's essential content, like a heading.
This button has a role of button, so that the user knows that when they click on the button, the button tells the user that the button is a button is a button.
But they already know it's a button.
You don't need to do a redundant role.
Don't use marquee.
This is not 2004 anymore.
So this role does not belong in the meta tag.
Nothing really belongs there except SEO.
So this list item here has Aria required, but that's not something you can apply to an ally.
So just remove it.
Most of the time you won't run into these because you'd have to accidentally put it there.
And we did it!
Plus 18 warnings prevented.
And I already read them out to you so I won't read them again, but we made it here.
I hope you're satisfied that we covered all 28 Svelte warnings.
But here's my personal wish list.
What more could Svelte do with static analysis?
Well for one, it can discourage the use of placeholder.
In a text input placeholder confuses users whether it's filled or not.
It doesn't quite replace the semantics of a label, and the hints within the label are proper.
If you want to describe how to fill an input, put it on the label.
I hope it can also discourage the title attribute because the global title attribute is completely invisible on touch devices.
If you're on a cell phone and you touch some truncated text, it will not show the title ever.
You need a mouse.
It's also very small and difficult to read, and often out of frame when you use a screen magnifier.
But if you were to remember one thing from this talk, it should be start with the user and work backwards from the problem to find a solution, and constantly test ideally with your users or at least using some tools.
And with all that said, thank you so much for listening to my talk.
If you want to reach out to me, my website is theetrain.
ca, or here at Svelte Summit, ping me, @theetrain on Discord.
Thank you so much.