CSS-only solutions are not accessible

In response to my article on my quest for the best progressively-enhanced dropdown nav menu, I had a lot of people ask me about CSS-only options that use :hover or :focus-within to toggle visibility.
Today, I wanted to talk about why CSS-only solutions like that are often worse for accessibility than other options. Let’s dig in!
CSS-only dropdowns As a reminder, here’s the HTML structure from my subnav article…


This content originally appeared on Go Make Things and was authored by Go Make Things

In response to my article on my quest for the best progressively-enhanced dropdown nav menu, I had a lot of people ask me about CSS-only options that use :hover or :focus-within to toggle visibility.

Today, I wanted to talk about why CSS-only solutions like that are often worse for accessibility than other options. Let’s dig in!

CSS-only dropdowns

As a reminder, here’s the HTML structure from my subnav article…

<nav class="navbar">
	<a class="logo" href="/">Kelp</a>
	<ul>
		<li><a href="/about">About</a></li>
		<li>
			<a href="/services">Services</a>
			<ul>
				<li><a href="/consulting">Consulting</a></li>
				<li><a href="/coaching">Coaching</a></li>
				<li><a href="/courses">Courses</a></li>
			</ul>
		</li>
		<li><a href="/contact">Contact</a></li>
	</ul>
</nav>

I had a lot of folks suggest something like this…

.navbar li ul {
	display: none;
}

.navbar li:is(:focus-within, :hover) ul {
	display: block;
}

/* more styles to position it as a floating menu... */

With this kind of setup, if you hover over or focus on the menu link or an item in the subnav, it’s visible. Focus or hover away, and it closes.

This is not accessible

Ok, I should caveat that. Accessibility isn’t usually a pass/fail thing.

(Many) users can still get to the content. It can be accessed (usually).

But display: none hides content from assistive tech like screen readers, so a screen reader user will not be aware that there’s more content hidden in a subnav. When they focus and it becomes visible, the change in state of the newly visible content probably won’t be announced.

Removing visibility when the item loses :hover means that a mouse user with fine-motor skill issues is very likely to have the menu close on them by accident before they’ve clicked anything.

And what about touch screen devices?

The only way to expose the menu is to :hover (which doesn’t exist in many touch devices) or :focus (which you can only do by tapping the link, which navigates you somewhere else).

Fixes people suggested

You could arguably visually hide the content rather than use display: none.

This keeps the content in the accessibility tree, at least.

But it also means you’ve got content that shows and hides, with nothing in the HTML to actually communicate that behavior or expose the current state of it to assistive tech.

And some screen reader users can see, just at a reduced capacity. They’re now looking at a nav menu that the screen reader says has more content, but that they cannot visually see.

And that doesn’t address touch devices.

You could add a <button> with an icon that users can tap. This brings focus into the <li>, exposing the subnav.

But you now have a <button> that communicates nothing about what it does, what it controls, and what it’s current state is, like a proper disclosure pattern would.

CSS has limits

CSS is amazing! And there are some wonderful CSS-only solutions for things like animations and transitions.

What it’s not good at is communicating state, and when you have components with different states of being (visible/hidden, etc.) and elements that control that behavior, you need a way to communicate that state to screen readers.

Some HTML elements do that out-of-the-box (<details> and <summary>, the Popover API, text changes in input fields, etc.). CSS typically does not.

Anytime you see a CSS-only hack for things folks usually use JavaScript for, you should treat it with a healthy dose of skepticism.

It’s often less accessible than a JS-dependent version would be.

What I ended up doing in Kelp

I just release a .navbar component for Kelp, with support for subnav menus.

I ended up scandalously repurposing <details> and <summary> for these.

<li>
	<details>
		<summary>Services</summary>
		<ul>
			<li><a href="#">Overview</a></li>
			<li><a href="#">Consulting</a></li>
			<li><a href="#">Coaching</a></li>
			<li><a href="#">Courses</a></li>
		</ul>
	</details>
</li>

There are some accessibility concerns with doing this.

  1. They do announce their expanded/closed state (good!), but do not clearly announce that you’ve exposed a navigation menu (not so good).
  2. If you use the [name] attribute for exclusive disclosure groups, opening one closes another (good!), but clicking outside of one or pressing the Esc key does nothing (not so good).

I feel that issue 1 is an acceptable for subnav menus that are always accessible, even before JavaScript is available, and even on touch screen devices.

For option 2, I created a small web component that automatically closes subnavs when the user clicks outside of them or presses Esc. It also shifts focus back to the <summary> element if you press Esc while focused inside a <details> component.

Popover is the future

In a future version, I’d like to switch over to the Popover API.

  1. It announces state in a way that better aligns with screen reader expectations about the content and behavior.
  2. It automatically closes the expanded content when you click/tap outside of it.
  3. It automatically closes the expanded content and shifts focus back to the trigger when you press the Esc key.

All of the needed features, no JavaScript at all.

So… why not use it today?

Browser support isn’t quite there yet. It also pulls the associated content into a different layer, so positioning it relative to the trigger is impossible without JavaScript. The Anchor Positioning API will change that, but that’s even less ready for use than Popover.

For now, I’m misusing the <details> element, and look forward to replacing it in the future.

Like this? A Lean Web Club membership is the best way to support my work and help me create more free content.


This content originally appeared on Go Make Things and was authored by Go Make Things


Print Share Comment Cite Upload Translate Updates
APA

Go Make Things | Sciencx (2025-08-15T14:30:00+00:00) CSS-only solutions are not accessible. Retrieved from https://www.scien.cx/2025/08/15/css-only-solutions-are-not-accessible/

MLA
" » CSS-only solutions are not accessible." Go Make Things | Sciencx - Friday August 15, 2025, https://www.scien.cx/2025/08/15/css-only-solutions-are-not-accessible/
HARVARD
Go Make Things | Sciencx Friday August 15, 2025 » CSS-only solutions are not accessible., viewed ,<https://www.scien.cx/2025/08/15/css-only-solutions-are-not-accessible/>
VANCOUVER
Go Make Things | Sciencx - » CSS-only solutions are not accessible. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/08/15/css-only-solutions-are-not-accessible/
CHICAGO
" » CSS-only solutions are not accessible." Go Make Things | Sciencx - Accessed . https://www.scien.cx/2025/08/15/css-only-solutions-are-not-accessible/
IEEE
" » CSS-only solutions are not accessible." Go Make Things | Sciencx [Online]. Available: https://www.scien.cx/2025/08/15/css-only-solutions-are-not-accessible/. [Accessed: ]
rf:citation
» CSS-only solutions are not accessible | Go Make Things | Sciencx | https://www.scien.cx/2025/08/15/css-only-solutions-are-not-accessible/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.