Skip to main content Accessibility Feedback

Semantic inline lists

Sometimes, you want an list to be displayed in a line (like a nav menu) rather than vertically.

<ul class="list-inline">
	<li>Merlin</li>
	<li>Ursula</li>
	<li>Radagast</li>
</ul>

To do that, you typically set list-style to none, then style the list items with some padding in some way (using Flexbox or margins).

.list-inline {
	list-style: none;
	margin-left: 0;

	/* This approach uses flexbox */
	display: flex;
	align-items: center;
	column-gap: 1em;
	flex-wrap: wrap;

}

/* 
	You could alternatively style the list items themselves 
	This isn't needed if you use flexbox
*/
.list-inline li {
	display: inline-block;
}

.list-inline li:not(:last-child) {
	margin-right: 1em;
}

But… in Safari, list semantics are removed when you use list-style: none.

Sometimes that makes sense, but sometimes it doesn’t. Using a navigation list as an example, list semantics probably should be used there.

Historically, there have been two ways to deal with this. You can add a [role="list"] attribute to the element.

<ul class="list-inline" role="list">
	<li>Merlin</li>
	<li>Ursula</li>
	<li>Radagast</li>
</ul>

This restores list semantics, but is also an extra attribute on every element you want to use the class with. It’s also a thing you might forget to do.

There used to be another CSS-based hack that Scott O’Hara had come up with that spared you from having to add a role to every element, but Scott recommends not using it anymore.

Previously (prior to this post being updated in November 2019), this section outlined some hacks you could do with CSS ::before pseudo elements to force Webkit to re-instate list semantics when their list markers were removed.

Since the original Unfettered Thoughts article and this post were written, some things have changed with the way the CSS hacks were impacting how VoiceOver announced lists that contained only static text content.

Due to these changes, and not wanting to promote hacks which could result in less than ideal user experiences, the solutions have been removed.

This is what I’ve been using for years (based on Scott’s article), and it looks like I should remove that from all of my sites.

But this week, Manuel Matuzović discovered that using list-style-type: "" with an empty string instead of list-style: none achieves the same desired result without the semantic implications.

.list-inline {
	list-style-type: "";
	margin-left: 0;

	/* This approach uses flexbox */
	display: flex;
	align-items: center;
	column-gap: 1em;
	flex-wrap: wrap;

}

But as Scott noted in his original article…

CSS can have some damaging affects on the way HTML elements are exposed to the browser’s accessibility API, and developers can be none the wiser to them.

I’m not fully sure if this is the right move or not, but it’s certainly more developer-friendly.

Scott also notes…

There have been additional updates to how Safari exposes list semantics based on their nesting context. For instance, if a list is a descendant of a <nav> element, then even if the list styles are removed, Safari/VoiceOver will expose this as a list to users…

As Adrian Roselli notes on Twitter a lack of list semantics “…may not be a big deal unless user testing says you really need a list.” While this behavior can be unwelcome in some situations, let’s also not spend too much effort over correcting an over correction which was in response to an over use of unnecessary semantics.

Which is to say, it might make sense to either just wrap your inline lists in a nav, or not worry about it at all.