Search topics
Emoji read out by their names, not their meaning
A bakery marks each bake with emoji for wheat, milk, peanuts and vegan, so a screen reader reads the pictures' names, such as "sheaf of rice" and "seedling", and the decorative sparkles in the heading too.
<span class="marks">🌾 🌱</span> The problem
Screen reader users can't tell which bakes contain wheat or milk, or which are vegan, because they hear the names of the pictures instead.
A bakery takes orders on its website. Under the heading "✨ Fresh today ✨", each bake ends with a few small emoji instead of an allergen list, and a key under the list says what they mean: 🌾 for contains wheat, 🥛 for contains milk, 🥜 for contains peanuts and 🌱 for vegan. A sighted customer matches the marks to the key and sees that the sourdough loaf is vegan and the croissant has milk in it. A screen reader reads the heading as "sparkles Fresh today sparkles", then "Sourdough loaf, sheaf of rice, seedling", "Butter croissant, sheaf of rice, glass of milk" and "Peanut cookie, peanuts, seedling". Every emoji is read by the name of its picture, not by what the bakery means by it. A customer who must avoid wheat hears "rice" at every bake; a vegan hears "seedling". The key is read last, as "Key: sheaf of rice Contains wheat" and so on, so the meanings come only after the list, and only to someone who remembers which picture name stood where. Only the peanut happens to be read as what it means.
An emoji is a character in the text, but it works like a small picture: its meaning comes from what it shows, and here from a code the bakery made up. Screen readers read each emoji by a standard name for its picture. NVDA, for example, takes these names from the Unicode CLDR list, where 🌾 is "sheaf of rice" and 🌱 is "seedling". That name is all the text the mark has in its row. 1.1.1 Non-text Content asks for a text alternative that serves the same purpose as the mark, and the purpose here is "contains wheat" and "vegan": in the rows neither is said, and the key, which says it, is read after the list. The sparkles fail it the other way round: they only decorate, and 1.1.1 asks for decoration to be implemented so assistive technology can ignore it, but they are read twice, around the heading's words, every time someone moves to the heading.
The marks look clear to the person who chose them, but their meaning is a convention: a seedling for vegan, a sheaf of rice for wheat, written down only in the key. Emoji are also drawn differently on Windows, Apple and Android, so the picture the bakery chose is not exactly the picture every customer sees.
2 of 10 groups affected
- No vision (affected)
- Low vision (not affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (not affected)
- Reach, strength (not affected)
- Cognitive (affected)
- Seizures (not affected)
- Without vision Screen reader users hear "sheaf of rice" where the shop means wheat and "seedling" where it means vegan, so a customer avoiding wheat is told about rice.
- Limited language, cognitive and learning abilities People who don't know the shop's code for its marks, that a seedling means vegan or a glass of milk means it contains milk, have to look each one up in the key, row by row, when the meaning isn't written beside the mark.
axe and similar tools have no rule for emoji: the heading and the list items have text, so the page passes. Even a tool that found every emoji couldn't tell a mark that carries meaning from one that decorates, or say what it means here. This audit reports emoji in headings, links, buttons, labels, list items and table cells as a lead for a person to check, and words written in look-alike mathematical letters as a finding. A quick AI fix tends to do one of two things: label each emoji with its picture ("wheat", "seedling"), which says what it shows, not what it means, or hide every emoji to silence the reading, which removes the allergen information completely. Only someone who knows the menu can say which marks mean something and what.
Try it
Compare the marks you see with what a screen reader reads.
The panel shows what a screen reader reads, top to bottom. Which bakes contain wheat, and which are vegan?
Broken Can you fix this?
✨ Fresh today ✨
- Sourdough loaf 🌾🌱
- Butter croissant 🌾🥛
- Peanut cookie 🥜🌱
Key: 🌾 Contains wheat🥛 Contains milk🥜 Contains peanuts🌱 Vegan
Screen reader says:
- sparkles Fresh today sparkles, heading
- list, 3 items
- Sourdough loaf, sheaf of rice, seedling
- Butter croissant, sheaf of rice, glass of milk
- Peanut cookie, peanuts, seedling
- Key: sheaf of rice Contains wheat, glass of milk Contains milk, peanuts Contains peanuts, seedling Vegan
The fix
Name every emoji that carries meaning by what it means here, and hide every emoji that only decorates with aria-hidden="true".
- 1Every emoji is named or hidden, never left bare.
- 2Name what the mark means on this page, as an aria-label or as words beside a hidden emoji, not what it pictures.
- 3Hide decorative emoji with aria-hidden="true", so headings and links are read by their words.
<h2>✨ Fresh today ✨</h2><ul class="bakes"> <li> Sourdough loaf <span class="marks">🌾 🌱</span> </li> <li> Butter croissant <span class="marks">🌾 🥛</span> </li> <li> Peanut cookie <span class="marks">🥜 🌱</span> </li></ul><p class="key"> Key: 🌾 Contains wheat 🥛 Contains milk 🥜 Contains peanuts 🌱 Vegan</p>Name each mark by its meaning
<h2> <span aria-hidden="true">✨</span> Fresh today <span aria-hidden="true">✨</span></h2><ul class="bakes"> <li> Sourdough loaf <span class="marks"> <span role="img" aria-label="Contains wheat">🌾</span> <span role="img" aria-label="Vegan">🌱</span> </span> </li> <li> Butter croissant <span class="marks"> <span role="img" aria-label="Contains wheat">🌾</span> <span role="img" aria-label="Contains milk">🥛</span> </span> </li> <li> Peanut cookie <span class="marks"> <span role="img" aria-label="Contains peanuts">🥜</span> <span role="img" aria-label="Vegan">🌱</span> </span> </li></ul><p class="key"> Key: <span aria-hidden="true">🌾</span> Contains wheat <span aria-hidden="true">🥛</span> Contains milk <span aria-hidden="true">🥜</span> Contains peanuts <span aria-hidden="true">🌱</span> Vegan</p>Each mark's meaning is written only in the key, after the list, and in the rows its only text is the name of its picture. The sparkles carry nothing, but they are read as part of the heading.
Each mark is wrapped in a span with role="img", so it is one image, and an aria-label that says what it means on this menu. The label replaces the emoji's own name: a screen reader reads "Sourdough loaf, Contains wheat, Vegan", with "image" or "graphic" after each mark, depending on the screen reader. The sparkles are wrapped in aria-hidden="true", so the heading is read as "Fresh today". The emoji in the key are hidden the same way, because the words beside them already say what each means. The page looks exactly as before, and that is this fix's limit: the meaning reaches screen readers, but on screen each mark is still a picture that customers have to look up in the key, and some page translators leave aria-label untranslated.
Both options replace every picture name with its meaning and silence the sparkles, so both meet 1.1.1. Naming the marks changes only markup and keeps the row as compact as it was, but its words exist only for assistive technology, and everyone who sees the page still has to look up in the key that a seedling means vegan. Writing the meaning beside each emoji is plain text, reaches everyone who reads the page, and leaves nothing for ARIA to carry; it costs room in the row. Use it. Keep named emoji for places that truly have no room for words, such as a dense table, and then put a visible key nearby.
Name or hide, in one component. Most sites add emoji through a few templates: menu marks, badges, headings. Give them one shared component that takes a label: with a label, it renders role="img" and aria-label; without one, aria-hidden="true". Then nobody has to decide afresh for each emoji, and a bare one stands out in review.
Symbols as well as emoji. The same applies to symbol characters used as marks: ✓ and ✗ in a comparison, ★ for a rating, → as "next". Some are read by a name such as "check mark", some are skipped, and which depends on the screen reader and its punctuation setting. Name them by their meaning or write the meaning beside them.
Emoji in what people write. Emoji in reviews, comments and chat messages are the writer's own words, and the page can't know what each one means to them. Leave them as they are; this guide is about the marks and decoration the site itself adds.
Fancy letters. Generators that turn text into 𝐛𝐨𝐥𝐝, 𝘪𝘵𝘢𝘭𝘪𝘤 or 𝓈𝒸𝓇𝒾𝓅𝓉 letters swap each letter for a look-alike character from Unicode's mathematical letters. A screen reader reads them letter by letter by their names, or skips them (W3C failure F71). Write ordinary text and style it with CSS. The same goes for ASCII art and emoticons built from characters, such as ¯\_(ツ)_/¯: wrap them in an element with role="img" and an aria-label, as W3C technique H86 shows (failure F72 is ASCII art left without one).
The picture varies. Each platform draws its own emoji: the same character can look like a different plant on another phone. The meaning can't depend on fine detail in the picture, which is one more reason to write it in words.
Fixing this with an AI coding assistant? Get this guide as Markdown, or get every guide in your coding agent.
Design ideas
Marks can stay small and still say what they mean.
Three more ways to show the same two marks, and two looks that only seem to say them. Each shows what a screen reader gets in its note.
In the design file
- Write each mark’s meaning in words beside or under it, not only the picture.
- Label every emoji in the file as meaning or decoration, so the developer knows which to name and which to hide.
- Keep one list of the marks with their words, for the menu’s key and for the developer.
- Set bold and italic with the text styles, never with letters copied from a fancy-text generator.
Passes
- Emoji tiles with the word under each Each mark is a soft square with the emoji over a short word, like the allergen grids on food apps. The emoji is hidden from screen readers and the word is read, so the picture can stay large.
- A quiet line of words under the name No emoji at all: “Vegan · Contains wheat” in a smaller brown, #6b5e52 at 6.27:1 on white. It takes no more room than the marks and needs no key.
- Outlined tags in small capitals Thin outlined tags in tracked capitals, a common look on modern menus. Write the words in ordinary case and let the style set the capitals: some screen readers spell out words typed in capitals letter by letter.
Fails
- Emoji in coloured circlesWCAG 1.1.1 Pastel circles make the marks look finished, but they hold only the emoji, so a screen reader still says “sheaf of rice, seedling”. Put the word in or beside each circle, or name it by its meaning.
- A “new” tag in fancy-text lettersWCAG 1.1.1 Bold letters pasted from a fancy-text generator are mathematical symbols that only look like letters: screen readers skip them or spell out their names. Set the tag in bold with the text style instead.
Verify the fix
4 checks, no mouse.
Test with: Screen reader
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat whenever the menu's marks or the component that renders them change.
Limits
This is a starting pattern, not a guarantee for every application. Class names, colours and the bakes are placeholders. Screen readers name emoji from different lists, and some versions or settings skip them, so test with the assistive technology your users have. An image icon or SVG used the same way follows the same rule; see SVG icons with no name, or read when they should be silent. Whether an emoji decorates or means something depends on the page around it, and only someone who knows the content can judge that. Test the complete journey with your target assistive technology.
This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.
References
- WCAG Understanding: non-text-content (opens in a new tab)
- WCAG Technique ARIA6 (opens in a new tab)
- WCAG Technique F71 (opens in a new tab)
- WCAG Technique F72 (opens in a new tab)
- WCAG Technique H86 (opens in a new tab)
- unicode.org: emoji-list (opens in a new tab)
- developer.mozilla.org: img_role (opens in a new tab)
Keep this fixed as your code changes.
Turn guides like this into rules your coding agents and CI follow, then have the result retested with a keyboard and screen readers.