In Safari with VoiceOver, a menu marked up as a <ul> stops being announced as a list once CSS removes its bullets. Why does that happen, and what is the markup fix?
answer
- the DOM is unchanged, the exposure is not
- a browser heuristic about presentational use
- suppressed markers trigger it
- re-declare what the element already meant
- repair, not re-implementation
basics
~10 sWebKit deliberately drops list semantics for a list whose markers are removed with list-style, treating it as presentational. Adding an explicit role="list" to the <ul> re-declares the semantics and restores the announcement.
solid answer
~50 sWebKit made a deliberate decision that a `<ul>` with its markers suppressed — `list-style: none` or `list-style-type: none` — is being used for layout rather than as a list, so it removes the `list` role from what VoiceOver sees. The list is still a `<ul>` in the DOM; only its exposure changes, and only in that browser. The fix is to state the semantics explicitly: put `role="list"` on the `<ul>`, which overrides the heuristic and brings the item count back. This is one of the few places where adding a role that duplicates a native element is correct — you are repairing semantics that a stylesheet removed, not re-implementing them. It is worth knowing because the affected pattern is extremely common: almost every navigation menu and card grid is a bulletless list. If you never suppress markers, the issue does not arise.
code
html · 4 lines<ul role="list" class="cards">
<li><a href="/a">Card A</a></li>
<li><a href="/b">Card B</a></li>
</ul>go deeper
Know that removing bullets is a styling choice and that the element still means "list"; if something looks wrong in a screen reader, ask someone senior rather than replacing the list with divs.
Explain that the accessibility tree is derived from markup plus rendering, so it does not always mirror the DOM, and that role="list" re-declares semantics a heuristic suppressed.
Show the judgment: identify the affected pattern, distinguish an ARIA repair from an ARIA re-implementation, and say that you would confirm in the accessibility tree or a real screen reader rather than the DOM.
Own the systemic answer — encode the repair in shared components and make accessibility-tree verification part of the review path, so a single browser behaviour does not need rediscovering by every team.
## The behaviour In WebKit, a list whose markers are removed via `list-style`/`list-style-type: none` is treated as presentational, and the `list` role is not exposed to VoiceOver. The user hears the links, but not "list, six items", not a position within the set, and not a boundary at the end. Nothing in the DOM changed — `document.querySelectorAll('li')` still finds every item — so the bug is invisible to markup review and to any test that inspects the DOM rather than the accessibility tree. The reasoning behind the decision is that authors overwhelmingly reach for `<ul>` as a generic repeating-container element and then strip the bullets, so verbose list announcements on every such block were judged to be noise. Whether you agree or not, the practical consequence is what matters in an interview: the pattern you almost certainly ship — a bulletless navigation list or card grid — is the exact pattern affected. ## The fix ```html <ul role="list" class="menu"> <li><a href="/">Home</a></li> <li><a href="/pricing">Pricing</a></li> </ul> ``` An explicit `role="list"` is authored intent rather than an inference, so the heuristic no longer applies and the list is exposed normally. The children are still `<li>` elements, so their `listitem` roles are intact and no per-item attribute is required in the ordinary case. ## Why this does not contradict "don't use ARIA when native works" The first rule of ARIA says to prefer a native element over role attributes — and this case does not violate it, because the native element is still doing the work. What you are adding is a repair: a redundant declaration that survives a stylesheet-driven downgrade. The distinction worth articulating is between **re-implementing** semantics with ARIA (`<div role="list">` with `<div role="listitem">` children, which you should not do) and **restoring** semantics on an element that already has them. Only the second is defensible, and only where a concrete browser behaviour makes it necessary. The alternative, of course, is to not suppress the markers — visually hiding them by other means, or accepting them. If a design genuinely has no markers, the explicit role is the cheaper answer than fighting the stylesheet. ## How to catch it This class of bug does not show up in the DOM, which is why teams miss it. It shows up in the accessibility tree — the browser's own accessibility inspector, or a screen-reader pass in the affected browser. Two practices help. First, put the repair in the shared component rather than at call sites: if your design system emits every menu, breadcrumb, and card grid from one list component, `role="list"` is added once. Second, treat the accessibility tree, not the DOM, as the artefact you review for semantics questions, because several browser behaviours — this one, elements hidden from the tree, roles altered by other display changes — are only visible there. ## Scope and honesty about the claim Be precise about the boundaries when you answer: this is a WebKit/VoiceOver behaviour tied to marker suppression, not a universal rule, and not something other engines are known to do in the same way. Do not generalise it into "CSS can strip any element's semantics" — the accurate statement is narrower and more useful: *some* browser behaviours mean the accessibility tree does not always mirror the markup one-to-one, so verifying semantics requires looking at the tree. ## The senior signal What an interviewer is listening for here is not the trivia. It is whether you know that markup semantics can be affected by rendering decisions at all, whether you can distinguish a legitimate ARIA repair from ARIA-as-reimplementation, and whether your instinct for verifying accessibility goes past "the element is correct in the DOM". A candidate who says "I would check the accessibility tree in that browser before assuming the markup is enough" has answered the real question even if they do not recall the specific behaviour.
- Isn't adding role="list" to a <ul> exactly the redundant ARIA the first rule warns against?Normally yes, but the rule is about not re-implementing semantics you could get natively. Here the native element is still in use and the role only restores an exposure a rendering heuristic removed. Re-implementing — `<div role="list">` around `<div role="listitem">` — is the thing to avoid; repairing a real `<ul>` is not.
- Why does testing this in the DOM not catch the problem?Because the DOM never changes: the `<ul>` and every `<li>` are exactly where you wrote them. The difference lives in the accessibility tree, which the browser derives from markup plus rendering decisions. Verifying semantics means opening the accessibility inspector or running a screen reader, not querying the DOM.
- Where should the repair live in a codebase that ships dozens of bulletless lists?In the shared list or menu component, so the attribute is authored once and every consumer inherits it. Scattering it at call sites guarantees the new card grid someone builds next quarter will miss it. This is the general shape of accessibility fixes: encode them where the markup is produced.
saying these in an interview costs you the question
- Claims CSS cannot affect accessibility semantics at all
- Rebuilds the menu with divs and full ARIA roles instead
- Assumes the DOM inspector proves the list is exposed
- States the behaviour applies to every browser equally
- Removes the ul entirely because bullets were unwanted