# Smiley-face ratings announced only as "button"

Source: https://easeweb.dev/learn/face-ratings
Topics: Custom inputs > Star and face ratings can be chosen with the keyboard and say each value (WCAG 2.1.1, 4.1.2, 1.3.1, 1.1.1)
The fix: Name each face by its value in words, and make the question the name of the group. Either keep the design and put the value in each button as text: the two end words moved inside the end buttons, and visually hidden words in the middle three; or write a word under every face, inside its button. Either way, the question becomes the legend of a fieldset around the faces.
Test with: screen reader, speech input, keyboard, touch
References: https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html https://www.w3.org/WAI/WCAG22/Techniques/failures/F68 https://www.w3.org/TR/accname-1.2/ https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/ https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/fieldset

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.

## What happens

A recipe site shows a suggested recipe, and under it a small card asks "How is this suggestion?" Five round faces sit in a row, from a scowl to a broad grin, with "Very bad" under the first and "Very good" under the last. Faces are quicker to answer than a form, and a row of them fits in a corner of the page. A click on a face sends the answer.

Tab into the card with a screen reader on. It says "button", then "button" again, five times, once for each face. Nothing says which face is which, or which end is the good one. Read from the top, the card is "How is this suggestion?", then "button" five times, and only then "Very bad" and "Very good", as two lines of text after all the buttons, tied to neither end. A voice control user says "click Very good", and nothing happens: no button has that name.

## Why it fails

Each face is a real `button`, so Tab reaches it and Enter presses it. What it lacks is a name. A browser builds a button's name from its `aria-labelledby`, then its `aria-label`, then a `label` tied to it, then the text inside it, and as a last resort from its `title`. Here the only thing inside is the face drawing, hidden with `aria-hidden="true"`, and the `title` is there but empty, so every source comes up empty and the button is announced by its role alone. An empty `title` suggests a shared icon-button component: it has a place for a name, and the card left it blank.

That fails three criteria. 4.1.2 Name, Role, Value: each face is a control with a role and no name, so software can't say what pressing it does. WCAG lists this as failure F68. 1.1.1 Non-text Content: the faces are pictures that carry the answer, and a picture that works as a control needs a text alternative that says its purpose. Here "Bad", "Okay" and "Good" exist only as the shape of a mouth. 1.3.1 Info and Relationships: the layout shows that the question above governs the five faces, and that "Very bad" belongs to the first face and "Very good" to the last. In the code, the question is a `div` tied to nothing, the faces aren't grouped under it, and the two words sit in a `div` of their own after all the buttons. That is why they are read last, attached to no face.

## Who is affected

Short feedback cards come at the end of something: a video, a support chat, a suggestion. When the faces have no names, people who can't see them skip the question or press a face at random, so their answers are either missing or a guess, and the site reads the faces' meaning only from people who can see them.

## What automation and AI miss

axe reports each face as `button-name`: a button whose only content is hidden with `aria-hidden`, and whose `title` is empty, has no name. EaseWeb's audit runs axe and reports the five faces as `axe:button-name`. That is as far as a scan goes. It can't tell whether a name gives the value: `aria-label="Frowning face"`, `"face-2"` from the icon's file name, or `"1"` all pass. Nor can it tell that the question is tied to nothing, or that the end words are left loose after the buttons. Asked to clear the finding, AI coding tools can name the faces after their drawings; add `aria-label` to the hidden `svg` instead of the button, which leaves the button with no name, because a hidden element adds nothing to it; or name only the two ends, from the words under them, and leave the middle three blank. Tab through the faces with a screen reader and check that each one says its value.

## Before: faces with an empty title

```html
<form class="survey" action="/feedback" method="post">
  <div>
    <div class="question">How is this suggestion?</div>
    <div class="faces">
      <button class="face" name="answer" value="1" title="">
        <svg aria-hidden="true">
          <use href="#face-very-bad"></use>
        </svg>
      </button>
      <button class="face" name="answer" value="2" title="">
        <svg aria-hidden="true">
          <use href="#face-bad"></use>
        </svg>
      </button>
      <button class="face" name="answer" value="3" title="">
        <svg aria-hidden="true">
          <use href="#face-okay"></use>
        </svg>
      </button>
      <button class="face" name="answer" value="4" title="">
        <svg aria-hidden="true">
          <use href="#face-good"></use>
        </svg>
      </button>
      <button class="face" name="answer" value="5" title="">
        <svg aria-hidden="true">
          <use href="#face-very-good"></use>
        </svg>
      </button>
    </div>
    <div class="ends">
      <div>Very bad</div>
      <div>Very good</div>
    </div>
  </div>
</form>
```

```css
.survey {
  inline-size: fit-content;
  max-inline-size: 100%;
  padding: 1rem 1.25rem;
  border-radius: 0.75rem;
  background: #ffffff;
  box-shadow: 0 2px 12px rgb(0 0 0 / 0.15);
}
.question {
  margin: 0 0 0.75rem;
  padding: 0;
  font-weight: bold;
}
.faces {
  display: flex;
  gap: 0.5rem;
}
/* A face: a round 48px button around a 36px drawing in #1f2937, 14.68:1 on white */
.face {
  display: grid;
  place-items: center;
  inline-size: 3rem;
  block-size: 3rem;
  margin: 0;
  border: 0;
  border-radius: 50%;
  padding: 0;
  background: none;
  color: #1f2937;
  font: inherit;
  cursor: pointer;
}
.face svg {
  inline-size: 2.25rem;
  block-size: 2.25rem;
}
/* The words under the two ends: #4b5563, 7.56:1 */
.ends {
  display: flex;
  justify-content: space-between;
  margin-top: 0.25rem;
  font-size: 0.8125rem;
  color: #4b5563;
}
```

Each button holds only a drawing hidden from assistive technology and an empty `title`, so none of the five has a name. The question and the two words are separate `div`s, tied to no button: on screen they frame the row, and in the code they are text before and after it.

## After: the values in the buttons, the design kept

```html
<form class="survey" action="/feedback" method="post">
  <fieldset>
    <legend class="question">How is this suggestion?</legend>
    <div class="faces">
      <button class="face" name="answer" value="1">
        <svg aria-hidden="true">
          <use href="#face-very-bad"></use>
        </svg>
        <span class="end">Very bad</span>
      </button>
      <button class="face" name="answer" value="2">
        <svg aria-hidden="true">
          <use href="#face-bad"></use>
        </svg>
        <span class="visually-hidden">Bad</span>
      </button>
      <button class="face" name="answer" value="3">
        <svg aria-hidden="true">
          <use href="#face-okay"></use>
        </svg>
        <span class="visually-hidden">Okay</span>
      </button>
      <button class="face" name="answer" value="4">
        <svg aria-hidden="true">
          <use href="#face-good"></use>
        </svg>
        <span class="visually-hidden">Good</span>
      </button>
      <button class="face" name="answer" value="5">
        <svg aria-hidden="true">
          <use href="#face-very-good"></use>
        </svg>
        <span class="end">Very good</span>
      </button>
    </div>
  </fieldset>
</form>
```

```css
.survey {
  inline-size: fit-content;
  max-inline-size: 100%;
  padding: 1rem 1.25rem;
  border-radius: 0.75rem;
  background: #ffffff;
  box-shadow: 0 2px 12px rgb(0 0 0 / 0.15);
}
/* min-inline-size: a fieldset otherwise never narrows below its content */
fieldset {
  min-inline-size: 0;
  margin: 0;
  border: 0;
  padding: 0;
}
.question {
  margin: 0 0 0.75rem;
  padding: 0;
  font-weight: bold;
}
/* Room under the row for the words of the two ends */
.faces {
  display: flex;
  gap: 0.5rem;
  padding-block-end: 1.25rem;
}
/* A face: a round 48px button around a 36px drawing in #1f2937, 14.68:1 on white */
.face {
  position: relative;
  display: grid;
  place-items: center;
  inline-size: 3rem;
  block-size: 3rem;
  margin: 0;
  border: 0;
  border-radius: 50%;
  padding: 0;
  background: none;
  color: #1f2937;
  font: inherit;
  cursor: pointer;
}
.face svg {
  inline-size: 2.25rem;
  block-size: 2.25rem;
}
/* The words under the two ends, now inside their buttons: #4b5563, 7.56:1 */
.end {
  position: absolute;
  inset-block-start: calc(100% + 0.25rem);
  font-size: 0.8125rem;
  color: #4b5563;
  white-space: nowrap;
}
.face:first-child .end {
  inset-inline-start: 0;
}
.face:last-child .end {
  inset-inline-end: 0;
}
.visually-hidden {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
```

The card looks as it was drawn: the same faces, and the same two words under the ends. Each button now holds its value as text, so a screen reader says "Very bad, button" through to "Very good, button", and voice control presses a face when its word is said. The two end words have moved inside the first and last buttons, still drawn under them, so the words on screen are those buttons' names and are no longer read loose after the row. The middle three have their words in visually hidden text. The question is the `legend` of a `fieldset` around the faces, so screen readers typically name the group when Tab moves focus into it, with something like "How is this suggestion?, group". The empty `title` is gone: it named nothing.

## After: a word under every face

```html
<form class="survey" action="/feedback" method="post">
  <fieldset>
    <legend class="question">How is this suggestion?</legend>
    <div class="faces">
      <button class="face" name="answer" value="1">
        <svg aria-hidden="true">
          <use href="#face-very-bad"></use>
        </svg>
        Very bad
      </button>
      <button class="face" name="answer" value="2">
        <svg aria-hidden="true">
          <use href="#face-bad"></use>
        </svg>
        Bad
      </button>
      <button class="face" name="answer" value="3">
        <svg aria-hidden="true">
          <use href="#face-okay"></use>
        </svg>
        Okay
      </button>
      <button class="face" name="answer" value="4">
        <svg aria-hidden="true">
          <use href="#face-good"></use>
        </svg>
        Good
      </button>
      <button class="face" name="answer" value="5">
        <svg aria-hidden="true">
          <use href="#face-very-good"></use>
        </svg>
        Very good
      </button>
    </div>
  </fieldset>
</form>
```

```css
.survey {
  inline-size: fit-content;
  max-inline-size: 100%;
  padding: 1rem 1.25rem;
  border-radius: 0.75rem;
  background: #ffffff;
  box-shadow: 0 2px 12px rgb(0 0 0 / 0.15);
}
/* min-inline-size: a fieldset otherwise never narrows below its content */
fieldset {
  min-inline-size: 0;
  margin: 0;
  border: 0;
  padding: 0;
}
.question {
  margin: 0 0 0.75rem;
  padding: 0;
  font-weight: bold;
}
.faces {
  display: flex;
  gap: 0.25rem;
}
/* A face over its word: a 36px drawing and 13px words in #1f2937, 14.68:1 on white */
.face {
  display: grid;
  justify-items: center;
  align-content: start;
  gap: 0.25rem;
  flex: 0 1 4rem;
  margin: 0;
  border: 0;
  border-radius: 0.5rem;
  padding: 0.375rem 0.125rem;
  background: none;
  color: #1f2937;
  font: inherit;
  font-size: 0.8125rem;
  line-height: 1.2;
  text-align: center;
  cursor: pointer;
}
.face svg {
  inline-size: 2.25rem;
  block-size: 2.25rem;
}
```

This changes the look: each face has its word under it, "Very bad", "Bad", "Okay", "Good", "Very good", and the row is a little wider for it. The words are the buttons' text, so they are also their names: what is on screen is what a screen reader says and what voice control listens for, with nothing hidden to keep in step. The question is the group's `legend`, as in the first fix. Each face can now be chosen without reading its expression.

## Which option to use

Both pass all three criteria. The values in the buttons keep the card exactly as drawn, and suit a design that has no room to grow. A word under every face changes the resting look, and gives the meaning of each face to everyone who reads the card: someone who finds faces hard to read, someone who sees the row through a magnifier a face at a time, someone unsure what the middle faces mean on this site. Choose it when the card has the room; on a narrow phone, check in the design file that "Very good" fits under its face, or draw it on two lines.

## Implementation decisions

These faces send the answer the moment one is pressed, so they are buttons: one press, one answer, as the real card behaves. When the scale is one question in a longer form that is sent later, or the answer can be changed before it is sent, build it as a group of radio buttons instead, as [star ratings that keyboards and screen readers skip](/learn/star-ratings) shows for stars; the faces are drawn over the radio buttons the same way. The keys differ with the build. Tab moves from one face button to the next, and Enter or Space sends that answer; a radio group is one Tab stop, and the arrow keys move the choice. That is why a radio group must not send on its own: the arrow keys check each face they pass, so a keyboard user moving from "Very bad" to "Good" would send "Very bad" on the first press. A radio group needs a button to send it.

Filling in the empty `title` would also name each button, since browsers fall back on it when nothing else gives a name, and it passes 4.1.2. Many browsers show its tooltip only when a mouse rests on the face, so sighted people on a keyboard or a touch screen don't see the words. Use it only when the component takes nothing else. An `aria-label` on each button names it the same way hidden text does; [icon-only buttons with no accessible name](/learn/icon-buttons) compares the two. Whichever is used, it goes on the button: an `aria-label` on an `svg` with `aria-hidden="true"` is ignored.

A `fieldset` can group buttons as well as fields. Where the layout can't take one, an element with `role="group"` and `aria-labelledby` pointing at the question does the same job. Name the faces in the order they are drawn, from bad to good, so the names run the same way as the row. The words of the scale are the site's to choose: if the card asks "How useful was this?", the names are "Not useful" to "Very useful", the same words as the ends.

## Verify the fix

1. With a screen reader on, Tab into the card: the question is announced as the group's name, then each face as its value and "button", "Very bad" to "Very good", in the order they are drawn.
2. Read the card from the top with the screen reader's reading keys: the question comes first, then each face with its value, and no word is read on its own after the buttons.
3. With voice control, say "click Okay": that face is pressed. Try the two ends and one more face in the middle.
4. On a phone with its screen reader on, touch each face: its value is read, and a double tap sends it. The card fits the screen, with every word showing.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the card or its icon component changes.

## Limits

This is a starting pattern, not a guarantee for every application. The CSS is a sketch: check the size of the faces, the space between them and their focus ring against your own design. Such a card may also have a close button drawn as an X with no name; that is the failure covered in [icon-only buttons with no accessible name](/learn/icon-buttons). Whether the thanks that replaces the faces after an answer is heard is covered in [status messages that are shown but never announced](/learn/status-messages). Faces told apart only by colour, such as a red, a yellow and a green circle, are a different failure, left to the topic on colour alone, whose guide is in progress. Test the whole card with a screen reader, voice control and a phone.

---

Get this guide in your coding agent: MCP https://easeweb.dev/mcp (setup at https://easeweb.dev/services/developers)
