Warnings

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, ol) > li

Test

    I feel like I'm lost.

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

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, 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, dd, 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>
    

    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>
    

    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, th),
    optgroup > :not(option),
    :not(select) > optgroup,
    :not(fieldset) > legend,
    select > :not(option, optgroup),
    :not(select, optgroup) > option,
    table > *:not(thead, tfoot, tbody, tr, colgroup, 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>
    

    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>
    

    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>
    

    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>
    

    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>
    

    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>
    

    [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>
    

    [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>
    

    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="http://www.w3.org/2000/svg"
         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>
    

    [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="http://www.w3.org/2000/svg"
         role="presentation">
      <rect x="400" y="100" width="400" height="200"
            fill="forestgreen" stroke="darkgreen" stroke-width="10"  />
    </svg><span></span>
    

    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"], [role="img"])
    

    Test

    <svg width="12cm" height="4cm" viewBox="0 0 1200 400"
         xmlns="http://www.w3.org/2000/svg"
         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>
    

    [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>
    

    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

    We could use the :blank pseudo-class, but at this time its support is very poor. We could also use the :-moz-only-whitespace pseudo-class, but support is limited to Firefox and thus could invalidate our selectors stack…

    References

    Selector

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

    Test

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

    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>
    

    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>
    

    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>
    

    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>
    

    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>
    

    th without [scope] or [id]

    Description

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

    References

    Selector

    th:not([scope], [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>
    

    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>
    

    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>
    

    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

    Please use my bookmarklet ;)
    <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>
    

    # [href]

    Description

    The [href] attribute is only containing #. Should probably be a <button> or at least a [role="button"], don't you think?

    References

    Selector

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

    Test

    Oh boy, I don't mean anything
    <a href="#">Oh boy, I don't mean anything</a>
    

    [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>
    

    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

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

    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 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>
    

    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"], [lang="he"])
    

    Test

    Well, I'm kinda disoriented…

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