Straightforward Accessibility

Estelle Weyl

http://instartlogic.github.io/p/oscon/solutions.html

Solutions

Photo: Kenneth Lu

think

input types

Original HTML

  • button
  • checkbox
  • file
  • hidden
  • image
  • password
  • radio
  • reset
  • submit
  • text

HTML5

  • color
  • date
  • datetime
  • datetime-local
  • email
  • month
  • number
  • range
  • search
  • tel
  • time
  • url
  • week
Learn More

HTML Attributes

  • name
  • :disabled
  • type
  • maxlength
  • readonly
  • size
  • value
  • alt
  • src
  • height
  • width
  • :checked
  • align**

    FORMS
  • action
  • method
  • accept-charset
  • enctype
  • target

HTML5 Attributes

  • autocomplete
  • spellcheck
  • autofocus
  • pattern
  • :required
  • placeholder
  • dirname
  • multiple
  • list
  • min
  • max
  • step
  • low
  • high
  • optimum
  • form
  • minlength
  • novalidate
  • formnovalidate
  • formaction
  • formenctype
  • formmethod
  • formtarget
Learn More

ARIA Accessibility

  • aria-labeledby="otherElement idValues"
  • aria-label="Read by Screen Reader"
  • <label for="idOfFormControl"> (or implicit label)
  • placeholder
  • title
  • not accessible

Form Control

<label for="telephone">Telephone Number</label>
  <input type="tel" id="telephone" 
  placeholder="(XXX) XXX-XXXX"
  pattern="(\d{3}) \d{3}\-\d{4}"
  aria-describedby="hint">
  <span class="hint" id="hint">10-digit phone number</span>
10-digit phone number

Form Control

<label for="telephone">Telephone Number</label>
  <input type="tel" id="telephone" 
  placeholder="(XXX) XXX-XXXX"
  pattern="(\d{3}) \d{3}\-\d{4}"
  title="10-digit phone number in the format of (XXX) XXX-XXXX"
<ul>
  <li>
    <label for="expiration">Credit Card Expiration Month</label>
    <input id="expiration" type="tel" placeholder="MM/YY" class="masked" 
    pattern="(1[0-2]|0[1-9])\/\d\d" data-valid-example="11/18" 
    title="2-digit month and 2-digit year greater than 01/15">
  </li>

<li>
  <label for="zip">Zip Code</label>
  <input id="zip" type="tel" name="zipcode" class="masked" 
  placeholder="XXXXX" pattern="\d{5}" title="5-digit zip code">
</li>
<li>
  <label for="zipca">Canadian Zip Code</label>
  <input id="zipca" type="text" name="zipcodeca"  class="masked"
  placeholder="XXX XXX" pattern="\w\d\w \d\w\d" data-charset="_X_ X_X" 
  title="6-character alphanumeric zip code in the format of A1A 1A1">
</li>
<li>
  <label for="tel">Telephone</label>
  <input id="tel" type="tel" name="phone"  class="masked"
  placeholder="(XXX) XXX-XXXX" pattern="\(\d{3}\) \d{3}\-\d{4}" title="10-digit number">
</li>
<li>
  <label for="cc">Credit Card Number</label>
  <input id="cc" type="tel" name="ccnumber"  class="masked"
  placeholder="XXXX XXXX XXXX XXXX" pattern="\d{4} \d{4} \d{4} \d{4}" title="16-digit number">
</li>
</ul>
Input Masking README.md
<li>
  <label for="zip">Zip Code</label>
  <span class="shell">
    <span aria-hidden="true" id="zipMask"><i></i>XXXXX</span>
    <input id="zip" type="tel" name="zipcode" pattern="\d{5}" 
    class="masked" title="5-digit zip code" maxlength="5" data-placeholder="XXXXX">
  </span>
</li>

on data entry...

<li>
  <label for="zip">Zip Code</label>
  <span class="shell">
    <span aria-hidden="true" id="zipMask"><i>123</i>XX</span>
    <input id="zip" type="tel" name="zipcode" pattern="\d{5}" 
    class="masked" title="5-digit zip code" maxlength="5" data-placeholder="XXXXX">
  </span>
</li>
// replaces each masked input with a shall containing the input and it's mask.
  createShell : function (input) {
    var wrap = document.createElement('span'),
        mask = document.createElement('span'),
        emphasis = document.createElement('i'),
        inputClass = input.getAttribute('class'),
        placeholderText = input.getAttribute('placeholder'),
        placeholder = document.createTextNode(placeholderText);

    input.setAttribute('maxlength', placeholder.length);
    input.setAttribute('data-placeholder', placeholderText);
    input.removeAttribute('placeholder');
if ( !inputClass || ( inputClass && inputClass.indexOf('masked') === -1 ) ) {
      input.setAttribute( 'class', inputClass + ' masked');
    }

    mask.setAttribute('aria-hidden', 'true');
    mask.setAttribute('id', input.getAttribute('id') + 'Mask');
    mask.appendChild(emphasis);
    mask.appendChild(placeholder);

    wrap.setAttribute('class', 'shell');
    wrap.appendChild(mask);
    input.parentNode.insertBefore( wrap, input );
    wrap.appendChild(input);
  },
.shell {
  position: relative;
  line-height: 1; }
  .shell span {
    position: absolute;
    left: 3px;
    top: 1px;
    color: #ccc;
    pointer-events: none;
    z-index: -1; }
    .shell span i {
      /* any of these 3 will work */
      color: transparent;
      opacity: 0;
      visibility: hidden; 
      }

.shell span, 
input.masked {
  background-color: transparent;
  font-size: 16px;
  font-family: monospace;
  padding-right: 10px;
  text-transform: uppercase; 
  }
Carousel script is 1.1kb
Merry Go Round
Ceci n'est pas un carousel

Merry-go-round

http://github.io/estelle/merry-go-round

5.8kb for the dropkick js file with jquery dependencyof 38.6 kb minified
<section id="footer-listbox" role="listbox" aria-label="Select your country" class="footer-languageSelector">
  <section role="option" tabindex="-1" aria-selected="true" 
    id="footerLanguageOption-0" data-value="AR" class="footer-languageSelector-item">
    <label>Argentina</label>
    <i class="footer-languageSelector-item-icon footer-img-AR"></i>
  </section>
  <section role="option" tabindex="-1" aria-selected="false"  
    id="footerLanguageOption-1" data-value="AU" class="footer-languageSelector-item">
    <label>Australia</label>
    <i class="footer-languageSelector-item-icon footer-img-AU"></i>
  </section>
  <section role="option" tabindex="-1" aria-selected="false"  
    id="footerLanguageOption-2" data-value="BR" class="footer-languageSelector-item">
    <label>Brazil</label>
    <i class="footer-languageSelector-item-icon footer-img-BR"></i>
  </section>
  <section role="option" tabindex="-1" aria-selected="false"  
    id="footerLanguageOption-3" data-value="CA" class="footer-languageSelector-item">
    <label>Canada</label>
    <i class="footer-languageSelector-item-icon footer-img-CA"></i>
  </section>
  <section role="option" tabindex="-1" aria-selected="false"  
    id="footerLanguageOption-4" data-value="CL" class="footer-languageSelector-item">
    <label>Chile</label>
    <i class="footer-languageSelector-item-icon footer-img-CL"></i>
  </section>
  <section role="option" tabindex="-1" aria-selected="false"  
    id="footerLanguageOption-5" data-value="CN" class="footer-languageSelector-item">
    <label>China</label>
    <i class="footer-languageSelector-item-icon footer-img-CN"></i>
  </section>
  <section role="option" tabindex="-1" aria-selected="false"  
    id="footerLanguageOption-6" data-value="CO" class="footer-languageSelector-item">
    <label>Colombia</label>
    <i class="footer-languageSelector-item-icon footer-img-CO"></i>
  </section>
  <section role="option" tabindex="-1" aria-selected="false"  
    id="footerLanguageOption-7" data-value="HK" class="footer-languageSelector-item">
    <label>Hong Kong</label>
    <i class="footer-languageSelector-item-icon footer-img-HK"></i>
  </section>
  <section role="option" tabindex="-1" aria-selected="false"  
    id="footerLanguageOption-8" data-value="MY" class="footer-languageSelector-item">
    <label>Malaysia</label>
    <i class="footer-languageSelector-item-icon footer-img-MY"></i>
  </section>
  <section role="option" tabindex="-1" aria-selected="false"  
    id="footerLanguageOption-9" data-value="MX" class="footer-languageSelector-item">
    <label>Mexico</label>
    <i class="footer-languageSelector-item-icon footer-img-MX"></i>
  </section>
  <section role="option" tabindex="-1" aria-selected="false"  
    id="footerLanguageOption-10" data-value="NZ" class="footer-languageSelector-item">
    <label>New Zealand</label>
    <i class="footer-languageSelector-item-icon footer-img-NZ"></i>
  </section>
  <section role="option" tabindex="-1" aria-selected="false"  
    id="footerLanguageOption-11" data-value="PE" class="footer-languageSelector-item">
    <label>Peru</label>
    <i class="footer-languageSelector-item-icon footer-img-PE"></i>
  </section>
  <section role="option" tabindex="-1" aria-selected="false"  
    id="footerLanguageOption-12" data-value="SG" class="footer-languageSelector-item">
    <label>Singapore</label>
    <i class="footer-languageSelector-item-icon footer-img-SG"></i>
  </section>
  <section role="option" tabindex="-1" aria-selected="false"  
    id="footerLanguageOption-13" data-value="ZA" class="footer-languageSelector-item">
    <label>South Africa</label>
    <i class="footer-languageSelector-item-icon footer-img-ZA"></i>
  </section>
  <section role="option" tabindex="-1" aria-selected="false"  
    id="footerLanguageOption-14" data-value="AE" class="footer-languageSelector-item">
    <label>United Arab Emirates</label>
    <i class="footer-languageSelector-item-icon footer-img-AE"></i>
  </section>
</section>
Selector script is 917 bytes
<fieldset class="languageSelector">
  <legend>Select your country:</legend>
  <ul>
    <li>
        <input type="radio" name="langSelect"  id="langAR" value="AR">
        <label for="langAR" class="langAR">Argentina</label>
    </li>
    <li>
      <input type="radio" name="langSelect" id="langAU" value="AU">
        <label for="langAU" class="langAU">Australia</label>
    </li>
    <li>
      <input type="radio" name="langSelect" id="langBR" value="BR">
        <label for="langBR" class="langBR">Brazil</label>
    </li>
    <li>
      <input type="radio" name="langSelect" id="langCA" value="CA">
        <label for="langCA" class="langCA">Canada</label>
    </li>
    <li>
      <input type="radio" name="langSelect" id="langCL" value="CL">
        <label for="langCL" class="langCL">Chile</label>
    </li>
    <li>
      <input type="radio" name="langSelect" id="langCN" value="CN">
        <label for="langCN" class="langCN">China</label>
    </li>
    <li>
      <input type="radio" name="langSelect" id="langCO" value="CO">
        <label for="langCO" class="langCO">Colombia</label>
    </li>
    <li>
      <input type="radio" name="langSelect" id="langHK" value="HK">
        <label for="langHK" class="langHK">Hong Kong</label>
    </li>
    <li>
      <input type="radio" name="langSelect" id="langMY" value="MY">
        <label for="langMY" class="langMY">Malaysia</label>
    </li>
    <li>
      <input type="radio" name="langSelect" id="langMX" value="MX">
        <label for="langMX" class="langMX">Mexico</label>
    </li>
    <li>
      <input type="radio" name="langSelect" id="langNZ" value="NZ">
        <label for="langNZ" class="langNZ">New Zealand</label>
    </li>
    <li>
      <input type="radio" name="langSelect" id="langPE" value="PE">
        <label for="langPE" class="langPE">Peru</label>
    </li>
    <li>
      <input type="radio" name="langSelect" id="langSG" value="SG">
        <label for="langSG" class="langSG">Singapore</label>
    </li>
    <li>
      <input type="radio" name="langSelect" id="langZA" value="ZA">
        <label for="langZA" class="langZA">South Africa</label>
    </li>
    <li>
      <input type="radio" name="langSelect" id="langAE" value="AE">
        <label for="langAE" class="langAE">United Arab Emirates</label>
    </li>
    <li>
      <input type="radio" name="langSelect" id="langUS" value="US">
        <label for="langUS" class="langUS">United States</label>
    </li>
  </ul>
</fieldset>
footer {
  fieldset {
      width: 215px;
      height: 160px;
      padding-right: 7px;
    li {
      font-size: $small;
      width: 100%;
      padding-left: 5px;
      border-top: 1px solid $label;
      line-height: 34px;
    }
    label {
      color: $white;
      background-position: 95% 50%;
      display: block;
      padding-left: 3px;
    }
  }
}
footer {
  &.open {
    height: 168px; 
  }
  &.closed fieldset {
    transform: scale(1,0); 
  }
  fieldset {
    input[type=radio], legend {
    opacity: 0.01;
    position: absolute; right: 0px;
  }
    label:active,
    label:focus,
    label:hover,
    :checked + label {
      outline: 1px dotted $white;
      background-color: $mediumLightGray;
    }
  }
}

think

Photo: Kenneth Lu

Tools

Screen Readers Development Tools

  • Safari with VoiceOver - Command + F5
  • Chrome with ChromeVox
  • NVDA on Windows

Tools

Testing

  • This element does not support ARIA roles, states and properties
  • aria-owns should not be used if ownership is implicit in the DOM
  • Elements with ARIA roles must be in the correct scope
  • Audio elements should have controls
  • This element has an invalid ARIA attribute
  • ARIA state and property values must be valid
  • Elements with ARIA roles must use a valid, non-abstract ARIA role
  • Controls and media elements should have labels
  • An element's ID must be unique in the DOM
  • These elements are focusable but either invisible or obscured by another element
  • The web page should have the content's human language indicated in the markup
  • Images should have a text alternative or presentational role
  • The purpose of each link should be clear from the link text
  • Text elements should have a reasonable contrast ratio
  • role=main should only appear on significant elements
  • Meaningful images should not be used in element backgrounds
  • An element's ID must not be present in more that one aria-owns attribute at any time
  • A label element may not have labelable descendants other than its labeled control.
  • ARIA attributes which refer to other elements by ID should refer to elements which exist in the DOM
  • The web page should have a title that describes topic or purpose
  • Elements with ARIA roles must have all required attributes for that role
  • Elements with ARIA roles must ensure required owned elements are present
  • Avoid positive integer values for tabIndex
  • Elements with onclick handlers must be focusable