Warnings

[autoplay] should probably not be used

Description

A time-based media like <audio> or <video> should not [autoplay], because it can be quite surprising for the user.

References

Selector

video[autoplay],
audio[autoplay]

Test

<video autoplay controls src=""></video><span></span>

[controls] would be helpful

Description

A time-based media like <audio> or <video> would be easier to use if [controls] are activated for the user.

References

Selector

video:not([controls]),
audio:not([controls])

Test

<video src=""></video><span></span>

Decorative image shouldn't have an accessible name

Description

Any decorative image — with [aria-hidden="true"] or empty [alt] — shouldn't have any of those:

This test currently can't check if any <title> nor <desc> child is present since <svg> is a replaced elements. See Edge cases and known issues on a11y.css' wiki.

References

Selector

img[alt=""][title],
img[alt=""][aria-label],
img[alt=""][aria-labelledby],
img[alt=""][aria-describedby],
area:not([href])[alt]:not([alt=""]),
area:not([href])[alt=""][title],
area:not([href])[alt=""][aria-label],
area:not([href])[alt=""][aria-labelledby],
area:not([href])[alt=""][aria-describedby],
svg[aria-hidden="true"][title],
svg[aria-hidden="true"][aria-label],
svg[aria-hidden="true"][aria-labelledby],
svg[aria-hidden="true"][aria-describedby],
canvas[aria-hidden="true"][title],
canvas[aria-hidden="true"][aria-label],
canvas[aria-hidden="true"][aria-labelledby],
canvas[aria-hidden="true"][aria-describedby],
embed[type="image"][aria-hidden="true"][title],
embed[type="image"][aria-hidden="true"][aria-label],
embed[type="image"][aria-hidden="true"][aria-labelledby],
embed[type="image"][aria-hidden="true"][aria-describedby],
object[type="image"][aria-hidden="true"][title],
object[type="image"][aria-hidden="true"][aria-label],
object[type="image"][aria-hidden="true"][aria-labelledby],
object[type="image"][aria-hidden="true"][aria-describedby],

Test

<svg width="12cm" height="4cm" viewBox="0 0 1200 400"
     xmlns="https://www.w3.org/2000/svg" version="1.1"
     aria-hidden="true" title="Decorative SVG, you punk!">
  <rect x="400" y="100" width="400" height="200"
        fill="forestgreen" stroke="darkgreen" stroke-width="10"  />
</svg><span></span>

summary must be a details's:first-child

Description

<summary> must be a <details>'s:first-child. Always.

References

Selector

details > *:not(summary):first-child,
details > summary:not(:first-child)

Test

I'm not a summary.
<details>
  <legend>I'm not a summary.</legend>
</details>

[alt] can be empty but has to be checked

Description

An alternative has to be empty when image is decorative only. In any other case, [alt] must be defined. That should be double-checked.

References

Selector

img[alt=""],
area[alt=""],
input[type="image"][alt=""],
embed[type="image"][alt=""],
object[type="image"][alt=""]

Test

<img alt="" src="static/ffoodd.png" width="144" height="144" /><span></span>

Most of DOM nodes shouldn't be :empty

Description

Obviously void elements are empty, as well as <iframe> and <textarea> could be :empty. Any other :empty tag that is not hidden is probably useless, and should be deleted.

Please note that it's disabled for tags owning a source through [src]. It's pretty opinionated, but it's meant to avoid false positives on tags such as <video> or <audio> which may be empty if they have at least one source specified through [src].

Notes

References

Selector

body *:empty:not([hidden]):not([aria-hidden]):not([src]):not(button):not(a):not(iframe):not(textarea):not(area):not(base):not(br):not(col):not(command):not(embed):not(hr):not(img):not(input):not(keygen):not(link):not(meta):not(param):not(source):not(track):not(wbr):not(title)
body *:blank:not([hidden]):not([aria-hidden]):not([src]):not(button):not(a):not(iframe):not(textarea):not(area):not(base):not(br):not(col):not(command):not(embed):not(hr):not(img):not(input):not(keygen):not(link):not(meta):not(param):not(source):not(track):not(wbr):not(title)
body *:-moz-only-whitespace:not([hidden]):not([aria-hidden]):not([src]):not(button):not(a):not(iframe):not(textarea):not(area):not(base):not(br):not(col):not(command):not(embed):not(hr):not(img):not(input):not(keygen):not(link):not(meta):not(param):not(source):not(track):not(wbr):not(title)

Test

<p id="empty-node_code"></p>

abbr should have a [title]

Description

Any abbreviation should give an explanation about its meaning, at least on its first occurrence.

References

Selector

abbr:not([title]),
abbr[title=" "],
abbr[title=""]

Test

Do you know about W3C?

<p>Do you know about <abbr>W3C</abbr>?</p>

legend must be a fieldset's:first-child

Description

<legend> must be a <fieldset>'s:first-child. Always.

References

Selector

fieldset > *:not(legend):first-child,
fieldset > legend:not(:first-child)

Test

<fieldset>
  <label>I'm not a legend.</label>
</fieldset>

figure without the group ARIA role

Description

<figure> needs [role="group"] for accessibility reason.

References

Selector

figure:not([role="group"])

Test

Figcaption test
I'm a figcaption
<figure>
  <img src="static/ffoodd.png" width="144" height="144" alt="Figcaption test"/>
  <figcaption>I'm a figcaption</figcaption>
</figure>

[alt] containing file name

Description

A file name in [alt] is probably wrongly automated… and would never ever help any user.

References

Selector

img[alt$=".pdf"],
area[alt$=".pdf"],
input[type="image"][alt$=".pdf"],
img[alt$=".doc"],
area[alt$=".doc"],
input[type="image"][alt$=".doc"],
img[alt$=".png"],
area[alt$=".png"],
input[type="image"][alt$=".png"],
img[alt$=".jpg"],
area[alt$=".jpg"],
input[type="image"][alt$=".jpg"],
img[alt$=".gif"],
area[alt$=".gif"],
input[type="image"][alt$=".gif"],
img[alt$=".mp3"],
area[alt$=".mp3"],
input[type="image"][alt$=".mp3"],
img[alt$=".mp4"],
area[alt$=".mp4"],
input[type="image"][alt$=".mp4"],
img[alt$=".mov"],
area[alt$=".mov"],
input[type="image"][alt$=".mov"],
img[alt$=".ogg"],
area[alt$=".ogg"],
input[type="image"][alt$=".ogg"],
img[alt$=".xls"],
area[alt$=".xls"],
input[type="image"][alt$=".xls"],
img[alt$=".txt"],
area[alt$=".txt"],
input[type="image"][alt$=".txt"],
img[alt$=".zip"],
area[alt$=".zip"],
input[type="image"][alt$=".zip"],
img[alt$=".rar"],
area[alt$=".rar"],
input[type="image"][alt$=".rar"],
img[alt$=".docx"],
area[alt$=".docx"],
input[type="image"][alt$=".docx"],
img[alt$=".webp"],
area[alt$=".webp"],
input[type="image"][alt$=".webp"],
img[alt$=".apng"],
area[alt$=".apng"],
input[type="image"][alt$=".apng"],
img[alt$=".svg"],
area[alt$=".svg"],
input[type="image"][alt$=".svg"],
img[alt$=".svgz"],
area[alt$=".svgz"],
input[type="image"][alt$=".svgz"],
embed[type="image"][alt$=".pdf"],
object[type="image"][alt$=".pdf"],
embed[type="image"][alt$=".doc"],
object[type="image"][alt$=".doc"],
embed[type="image"][alt$=".png"],
object[type="image"][alt$=".png"],
embed[type="image"][alt$=".jpg"],
object[type="image"][alt$=".jpg"],
embed[type="image"][alt$=".gif"],
object[type="image"][alt$=".gif"],
embed[type="image"][alt$=".mp3"],
object[type="image"][alt$=".mp3"],
embed[type="image"][alt$=".mp4"],
object[type="image"][alt$=".mp4"],
embed[type="image"][alt$=".mov"],
object[type="image"][alt$=".mov"],
embed[type="image"][alt$=".ogg"],
object[type="image"][alt$=".ogg"],
embed[type="image"][alt$=".xls"],
object[type="image"][alt$=".xls"],
embed[type="image"][alt$=".txt"],
object[type="image"][alt$=".txt"],
embed[type="image"][alt$=".zip"],
object[type="image"][alt$=".zip"],
embed[type="image"][alt$=".rar"],
object[type="image"][alt$=".rar"],
embed[type="image"][alt$=".docx"],
object[type="image"][alt$=".docx"],
embed[type="image"][alt$=".webp"],
object[type="image"][alt$=".webp"],
embed[type="image"][alt$=".apng"],
object[type="image"][alt$=".apng"],
embed[type="image"][alt$=".svg"],
object[type="image"][alt$=".svg"],
embed[type="image"][alt$=".svgz"],
object[type="image"][alt$=".svgz"]

Test

static/ffoodd.png
<img alt="static/ffoodd.png" src="static/ffoodd.png" width="144" height="144" /><span></span>

JS [href]

Description

The [href] attribute should not start with "javascript". Should probably be a <button> or at least a [role="button"], don't you think? The only exception shall be bookmarklets.

References

Selector

a[href^="javascript"]:not([role="button"])

Test

<a href="javascript:(function(){a11ycss=document.createElement('LINK');a11ycss.href='https://rawgit.com/ffoodd/a11y.css/master/css/a11y-en.css';a11ycss.rel='stylesheet';a11ycss.media='all';document.body.appendChild(a11ycss);})();">Please use my bookmarklet ;)</a>

Invalid sibling in a definition list

Description

<dt> and <dd> should be direct adjacent siblings, and nothing else. Although multiple <dd> may follow a single <dt>.

References

Selector

dt + :not(dd),
:not(dt):not(dd) + dd

Test

I need a definition, don't you think?
  • I'm a list item.
  • <dl>
      <dt>I need a definition, don't you think?</dt>
      <li>I'm a list item.</li>
    </dl>

    Invalid nesting in a definition list

    Description

    <div>, <dt> and <dd> should be direct children of <dl>. Any other imbrication may be a crime somewhere.

    References

    Selector

    :not(dl) > dt,
    :not(dl) > dd,
    dl > :not(dt):not(dd):not(div)

    Test

  • I'm a list item.
  • <dl>
      <li>I'm a list item.</li>
    </dl>

    figcaption outside a figure

    Description

    <figcaption> doesn't make sense outside a <figure>.

    References

    Selector

    :not(figure) > figcaption

    Test

    I'm captionning something, isn't it?
    <figcaption>I'm captionning something, isn't it?</figcaption>

    Invalid nesting in a list

    Description

    The only child allowed in <ul> and <ol> is <li> - and the converse is also true.

    References

    Selector

    ul > :not(li),
    ol > :not(li),
    :not(ul):not(ol) > li

    Test

      I feel like I'm lost.

    <ul>
      <p>I feel like I'm lost.</p>
    </ul>

    Invalid nesting

    Description

    Some nestings are forbidden, and do not have their own test case for now:

    Maybe other invalid nestings to test. Stay tuned.

    References

    Selector

    nav main,
    aside main,
    footer main,
    header main,
    article main,
    :not(tr) > td,
    :not(tr) > th,
    colgroup *:not(col),
    :not(colgroup) > col,
    tr > :not(td):not(th),
    optgroup > :not(option),
    :not(select) > optgroup,
    :not(fieldset) > legend,
    select > :not(option):not(optgroup),
    :not(select):not(optgroup) > option,
    table > *:not(thead):not(tfoot):not(tbody):not(tr):not(colgroup):not(caption),
    address h1,
    address h2,
    address h3,
    address h4,
    address h5,
    address h6,
    address nav,
    address aside,
    address header,
    address footer,
    address address,
    address article,
    address section

    Test

    I'm an legend. Am I?
    <legend>I'm an legend. Am I?</legend>

    Mismatching [dir] and [lang] attributes

    Description

    Some languages — like Arabic or Hebrew — requires a text direction switch usin [dir="rtl"], as default text direction is left-to-right. This also requires to check any in-page language change (marked up with [lang]) in Arabic or Hebrew content to define [dir] too.

    References

    Selector

    [lang="ar"]:not([dir="rtl"]),
    [lang="he"]:not([dir="rtl"]),
    [lang="ar"] [lang]:not([dir="ltr"]),
    [lang="he"] [lang]:not([dir="ltr"]),
    [dir="rtl"]:not([lang="ar"]):not([lang="he"])

    Test

    Well, I'm kinda disoriented…

    <p dir="rtl" lang="en">Well, I'm kinda disoriented…</p>

    Misplaced div

    Description

    Did you know that you shouldn't add a <div> inside any inline element? You could use a <span> instead.

    References

    Selector

    b div,
    i div,
    q div,
    em div,
    abbr div,
    cite div,
    code div,
    span div,
    small div,
    label div,
    strong div

    Test

    Hey ya!
    <b><div>Hey ya!</div></b>

    Missing head for data table

    Description

    <thead> is strongly needed if <tbody> is present.

    Selector

    table:not([role="presentation"]) > caption + tbody,
    table:not([role="presentation"]) > tbody:first-child

    Test

    Missing thead
    I'm a table without thead. I'm a table without thead.
    I'm a table without thead. I'm a table without thead.
    <table>
      <caption>Missing thead</caption>
      <tbody>
        <tr>
          <td>I'm a table without thead.</td>
          <td>I'm a table without thead.</td>
        </tr>
        <tr>
          <td>I'm a table without thead.</td>
          <td>I'm a table without thead.</td>
        </tr>
      </tbody>
    </table>

    Nested tables

    Description

    There's no good reason to nest data tables: thus it probably means we're facing a layout table…

    References

    Selector

    table table

    Test

    I'm a caption :3
    Oh boy…
    I'm a table-cell!
    I'm a caption too!
    Oh boy…
    I'm a table-cell!
    I'm a table-cell!
    <table>
      <caption>I'm a caption :3</caption>
      <thead>
        <tr>
          <th scope="col">Oh boy…</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>I'm a table-cell!</td>
        </tr>
        <tr>
          <td>
            <table>
              <caption>I'm a caption too!</caption>
              <thead>
                <tr>
                  <th scope="col">Oh boy…</th>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td>I'm a table-cell!</td>
                </tr>
                <tr>
                  <td>I'm a table-cell!</td>
                </tr>
            </table>
          </td>
        </tr>
      </tbody>
    </table>

    Missing [aria-level]

    Description

    Though [aria-level] is not required by ARIA specification, it's actually better to specify it.

    References

    Selector

    [role="heading"]:not([aria-level])

    Test

    Heading with undefined level
    <strong role="heading">Heading with undefined level</strong>

    A role is needed for svg

    Description

    Any <svg> should either have [aria-hidden="true"] if decorative, or a [role="img"] if informative.

    References

    Selector

    svg:not([aria-hidden="true"]):not([role="img"])

    Test

    <svg width="12cm" height="4cm" viewBox="0 0 1200 400"
         xmlns="https://www.w3.org/2000/svg" version="1.1"
         aria-label="Decorative SVG, you punk!">
      <rect x="400" y="100" width="400" height="200"
            fill="forestgreen" stroke="darkgreen" stroke-width="10"  />
    </svg><span></span>

    A label without a [for] attribute

    Description

    A <label> is labelling something, in theory. Although implicitly labelling form controls is accepted, it's probably better to double-check that form control is really nested in its <label>.

    References

    Selector

    label:not([for])

    Test

    <label>Guess what?</label>

    Bad computed value

    Description

    Don't laugh, shit happens.

    Selector

    [id*="NaN"],
    [id*="null"],
    [class*="NaN"],
    [class*="null"],
    [id*="undefined"],
    [class*="undefined"]

    Test

    Oups, something went wrong.

    <p class="undefined">Oups, something went wrong.</p>

    [role=presentation] shouldn't be used on image

    Description

    Any decorative image should be marked up with [aria-hidden="true"] (or empty [alt] if <img>). [role=presentation] shall do the trick but at the time of writing, its support is too low compared to empty [alt] or [aria-hidden=true].

    References

    Selector

    img[role="presentation"],
    svg[role="presentation"],
    area[role="presentation"],
    embed[role="presentation"],
    canvas[role="presentation"],
    object[role="presentation"]

    Test

    <svg width="12cm" height="4cm" viewBox="0 0 1200 400"
         xmlns="https://www.w3.org/2000/svg" version="1.1"
         role="presentation">
      <rect x="400" y="100" width="400" height="200"
            fill="forestgreen" stroke="darkgreen" stroke-width="10"  />
    </svg><span></span>

    Misused sectioning tags

    Description

    <section>, <aside>, <article> are sectioning tags. They must not be used as wrappers!

    References

    Selector

    aside > aside:first-child,
    article > aside:first-child,
    aside > article:first-child,
    aside > section:first-child,
    section > section:first-child,
    article > section:first-child,
    article > article:first-child

    Test

    <aside>
      <section>I'm wrapping, you know.</section>
    </aside>

    spacer.gif used

    Description

    Believe me, this still have to be tested.

    References

    Selector

    img[src*="1px.gif"]:not([role="presentation"]),
    img[src*="1x1.gif"]:not([role="presentation"]),
    img[src*="clear.gif"]:not([role="presentation"]),
    img[src*="spacer.gif"]:not([role="presentation"]),
    img[src*="dotclear.gif"]:not([role="presentation"]),
    img[src*="transparent.gif"]:not([role="presentation"]),
    img[src*="pixel-1x1-clear.gif"]:not([role="presentation"])

    Test

    Spacer.gif
    <img src="static/spacer.gif" alt="Spacer.gif" width="100" height="100"/><span></span>

    [style] attribute

    Description

    Your styles should be driven by a CSS file. That's it. And no, JS shouldn't manipulate styles: it's better to play with classes, for example.

    References

    Selector

    [style]

    Test

    I'm red. I really feel dirty.
    <div style="color: red;">I'm red. I really feel dirty.</div>

    Every data table must have a caption

    Description

    <caption> is needed for data <table>. And it must be the :first-child, by the way.

    References

    Selector

    table:not([role="presentation"]) > caption:not(:first-child),
    table:not([role="presentation"]) > *:first-child:not(caption)

    Test

    I'm a table without a caption! I'm a table without a caption!
    I'm a table without a caption!
    I'm a table without a caption!
    <table>
      <thead>
        <tr>
          <th id="th-one">I'm a table without a caption!</th>
          <th id="th-two">I'm a table without a caption!</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td colspan="2" headers="th-one th-two">I'm a table without a caption!</td>
        </tr>
        <tr>
          <td colspan="2" headers="th-one th-two">I'm a table without a caption!</td>
        </tr>
      </tbody>
    </table>

    A single line table may be used for layout

    Description

    A lonely <tr> can be a symptom of a table used for layout. Should be double checked!

    References

    Selector

    table:not([role="presentation"]) > tr:only-child,
    table:not([role="presentation"]) > tbody > tr:only-child

    Test

    I'm a caption :3
    Oh boy…
    I'm a poor lonesone table-roooow!
    <table>
      <caption>I'm a caption :3</caption>
      <thead>
        <tr>
          <th scope="col">Oh boy…</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>I'm a poor lonesone table-roooow!</td>
        </tr>
      </tbody>
    </table>

    Invalid table structure

    Description

    <thead>, <tfoot> and <tbody> must be in this order.

    References

    Selector

    table > tfoot ~ thead,
    table > tbody ~ tfoot,
    table > tbody ~ thead,
    table > tfoot ~ colgroup,
    table > tbody ~ colgroup,
    table > tbody ~ colgroup

    Test

    I'm a caption
    Where's my foot?
    I'm a table with tfoot done wrong. I'm a table with tfoot done wrong.
    I'm a table with tfoot done wrong. I'm a table with tfoot done wrong.
    I'm a table with tfoot done wrong. I'm a table with tfoot done wrong.
    <table>
      <caption>I'm a caption</caption>
      <thead>
        <tr>
          <th scope="col">Where's my foot?</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>I'm a table with tfoot done wrong.</td>
          <td>I'm a table with tfoot done wrong.</td>
        </tr>
        <tr>
          <td>I'm a table with tfoot done wrong.</td>
          <td>I'm a table with tfoot done wrong.</td>
        </tr>
      </tbody>
      <tfoot>
        <th id="th-1">I'm a table with tfoot done wrong.</th>
        <th id="th-2">I'm a table with tfoot done wrong.</th>
      </tfoot>
    </table>

    th without [scope] or [id]

    Description

    <th> strongly needs an [id] or a [scope].

    References

    Selector

    th:not([scope]):not([id])

    Test

    Need for a [scope] or [id]
    I'm a th without [scope] or [id]. I'm a th with [scope].
    I'm a td missing something. I'm a td missing something.
    I'm a td feeling well. I'm a td feeling well.
    <table>
      <caption>Need for a [scope] or [id]</caption>
      <thead>
        <tr>
          <th>I'm a th without [scope] or [id].</th>
          <th scope="col">I'm a th with [scope].</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>I'm a td missing something.</td>
          <td>I'm a td missing something.</td>
        </tr>
        <tr>
          <td>I'm a td feeling well.</td>
          <td>I'm a td feeling well.</td>
        </tr>
      </tbody>
    </table>

    Unsecured [target=_blank]

    Description

    [target="_blank"] links might be used for phishing. This is not actually an accessibility related issue, but everything helping users is welcome.

    References*

    Selector

    [target$="blank"]:not([rel]),
    [target$="blank"]:not([rel*="noopener"]),
    [target$="blank"]:not([rel*="noreferrer"])

    Test

    <a href="/" target="_blank">I feel vulnerable…</a>