Somewhere inside Odoo’s interface, a screen reader user clicks a button and hears… nothing. Or worse, they hear the raw CSS content value of an icon font — a meaningless string of characters that the browser dutifully reads aloud because nobody told it not to. Multiply that experience across 1,682 icons in Odoo’s template files, and you start to understand the scale of the accessibility debt that the platform has been quietly carrying.
That number — 984 icons in the community edition and 698 in enterprise — comes from a recent internal count that accompanied a set of new mandatory code review rules. For the first time, Odoo’s automated review system will flag any icon that ships without proper accessibility markup. It’s not a suggestion. It’s a gate.
Why Icons Are an Accessibility Minefield
Modern web interfaces are dense with icons. Edit buttons, status indicators, navigation arrows, warning badges, close buttons — they’re everywhere. For sighted users, they’re intuitive. For users relying on screen readers, they’re often invisible, misleading, or actively confusing.
The problem is technical. When an icon is rendered through CSS — using the content property on a ::before or ::afterpseudo-element, which is how most icon font systems work, including Odoo’s Material Symbols — screen readers try to announce it. If the icon font maps to a Unicode Private Use Area codepoint, the reader might say nothing. If it maps to a regular Unicode character, the reader announces whatever that character represents, which bears no relation to what the icon visually depicts. A pencil icon might be announced as “Latin small letter e with acute” depending on the codepoint.
The fix isn’t complicated in isolation. Decorative icons need role="img" and aria-hidden="true"so screen readers skip them entirely. Informative icons — ones that convey meaning a sighted user would understand — need role="img" with an aria-label and a title attribute describing what they represent. Icon-only buttons need to be real <button> elements with the label on the button itself, while the icon inside carries aria-hidden.
The challenge is doing it 1,682 times across a codebase maintained by hundreds of developers who are focused on business logic, not accessibility semantics.
Enforcement Over Education
Odoo’s approach is pragmatic: rather than publishing guidelines and hoping developers follow them, they’ve wired the rules directly into their automated code review system. Every file that touches template code now gets checked against three specific patterns, and violations block the review.
The first rule handles decorative icons. If an icon exists purely for visual polish — a status dot, a decorative divider, an ornamental flourish — it must carry aria-hidden="true". No exceptions. A screen reader should never waste a user’s time announcing something that carries no information.
The second rule handles informative icons. If an icon communicates something — a warning triangle, a lock indicating permissions, a check mark confirming success — it must have an aria-labelthat describes the meaning in plain language. Not the icon name. Not the CSS class. The actual semantic meaning: “Warning: field required” or “Locked: insufficient permissions.”
The third rule is the strictest: icon-only interactive elements. If a user can click it, it must be a <button>. Not a <span> with an onclick handler. Not a <div> with a cursor style. A semantic button element, with the label on the button and the icon hidden from assistive technology. This rule alone will force changes across significant portions of the interface, because clickable icon-spans are endemic in web development.
Two More Rules for Code That Breaks Quietly
The accessibility rules shipped alongside two other enforcement patterns that address different failure modes — both of the “works on my machine, breaks in production” variety.
The first targets JavaScript sort comparators. A comparator function that returns 1for both comparison directions — or returns 0for distinct items — produces different results in different browsers. Chrome’s V8 engine uses TimSort, Firefox uses merge sort, and Safari uses a hybrid approach. The same array, sorted by the same function, can come out in different orders depending on which browser the user opens. In an ERP, where sorted lists drive everything from inventory allocation to billing sequence, inconsistent sort order is a data integrity issue.
The new rule requires sort comparators to return 0when items are equal, return the opposite sign when arguments are swapped, and always end with a unique tiebreaker key — typically the record’s id. An internal scan found 210 sort comparators in the community codebase and 84 in enterprise, with few of them using id as a tiebreaker. Each one is a potential source of non-deterministic behavior.
The second rule addresses a pattern that costs performance without any visible error: awaiting an RPC call inside a loop. When client-side code fetches records one at a time in a forloop, each iteration waits for a network round trip before starting the next. Fetching 50 records this way on a connection with 100ms latency takes five seconds of pure waiting. A single batched call takes 100ms. The internal count found 12 such patterns in the community code and 13 in enterprise — each one a multiplier on page load time that scales linearly with data volume.
The new rule mandates that repeated operations use a single batched call or, when the calls are truly independent, run in parallel with Promise.all. Exceptions are allowed for device interactions and external service calls where batching isn’t possible, and for dependent calls where the second needs the first’s result.
The Bigger Picture
These three rules — icon accessibility, sort stability, and RPC batching — look unrelated on the surface. An accessibility fix, a correctness fix, and a performance fix. But they share a common thread: all three address categories of bugs that don’t throw errors, don’t crash the application, and often don’t get caught in manual testing. They’re the kind of issues that accumulate silently over years in a large codebase, each one individually minor, collectively creating the gap between “this software works” and “this software works well for everyone.”
The fact that Odoo is publicizing the numbers — 1,682 icons, 294 comparators, 25 loop-awaited RPCs — rather than quietly fixing them suggests a shift in how the team thinks about code quality. These aren’t embarrassing metrics to hide. They’re the honest baseline from which improvement gets measured. And by making the rules automated and mandatory, they’re ensuring that the count only goes down from here.
For screen reader users navigating Odoo daily, the icon rules can’t land soon enough. For developers building on the platform, the sort and RPC rules are the kind of guardrails that prevent hours of debugging mysterious production behavior. And for the Odoo ecosystem as a whole, mandatory enforcement beats voluntary compliance — every time.