Accessibility as Performance

Estelle Weyl

http://estelle.github.io/minsk/

Simple. Fast. Accessible.

Features Solutions
Issues Prescriptions

Accessibility

Screen Reader experience

click here

Hearing impaired experience

Color Blindness experience

Disability: a mismatch in interaction between the features of a person’s body and the features of the environment in which they live. — Alistair Duggin (@dugboticus) April 14, 2016

We all benefit from the ADA

  • Created by iconsmind.comfrom the Noun Project

Disability is a spectrum

permanent
situational
  • Parapalegic
  • Broken Leg
  • Stillettos
  • Blind
  • Reading Glasses
  • Sun Glare
  • Dyslexic
  • Medicated
  • Distracted
  • Quadrapalegic
  • Broken Arm
  • Holding a Baby
  • Hard of Hearing
  • Aircraft Marshaller
  • Forgotten Headphones
permanent
situational

Accessible Rich Internet Applications

Performance

November 2010

In 2010, content was Images=416kb, HTML=34kb, Stylesheets=25kb, Scripts=113kb, Fonts=2kb, and Other=22kb for a total of 702kb

September 1, 2017

In 2017, content is Images=1799kb, HTML=51kb, Stylesheets=88kb, Scripts=455kb, Fonts=108kb, Video=774 and Other=17kb for a total of 3341 kb HTTPArchive

2010 vs 2017

4.75x

Prescriptions

Image Optimization

GZip
GZip works best for text-based content, while providing little to no byte shrinkage on previously-compressed assets. SVG is a text-based image format. Minifying and GZipping SVG image and font files can greatly reduce their SVG file size. Minify first. Then Gzip.
Remove Image Metadata
Cameras and software often add metadata to images that is not seen by the web user. This metadata can actually be a security risk by exposing geographical data. There’s free software that will strip EXIF metadata from multiple images at a time.
Resize Images
Serve the smallest image file for the screen size and resolution.
Optimize Images
Serve the smallest image size for the screen size and resolution. Images, no matter the size, should be compressed as much as possible without noticeably degrading the quality. Image compression programs include ImageOptim and ImageAlpha for pngs. Can be automated with machine learning with SmartVision.
Use the Right Image Format
Image types include SVG, GIF, JPEG, PNG, and WebP.
  • GIF: Palette of less than 256 colors, like a comic, for transparency and for low quality animation
  • SVG: Better than GIFS: Scalable, Animatable, unlimited color pallette
  • PNG: When you need transparency and have more than 256 colors.
  • JPEG: raster images
  • JPEG with a CSS mask: when you need transparency on a raster image and PNG-24 file size is huge
  • WebP: Animation, transparency, and great resolution with small file size, but only supported in Blink. Test WebP
  • JPEG-XR: For Edge
Reduce the Number of Images
Reduce HTTP requests with sprites. Also, consider reducing actual number of images your page requires: is your image worth 1,000 words or does your site work better without it?
Leverage CSS
Leverage CSS to reduce image size and number of images:
  • Image Masking: use image masking when you need a “transparent JPEG” without PNG-24 file size
  • CSS Effects: gradients, borders, outlines, rounded corners, shadows, etc. Create resolution-independent effects with minifiable & gZippable text
  • Sprites: until HTTP/2 is fully supported use image sprites and background-position to reduce number of requests
  • Animation: when animating GIFs, consider CSS animation using the steps() timing function and sprites as an alternative
  • Filters: use CSS filter effects instead of multiple versions of the same image if you want filters applied: save bytes, HTTP requests and time
  • Media Queries: Use media queries to serve hi-res images to hi-res devices

Note: Include an alt attribute on all foreground images, with empty alt attribute for decorative images.

Image Optimization Tips

Video Optimization

  • If possible, omit videos
  • Compress all videos
  • Optimize <source> order
  • Remove audio from muted heroes
http://www.standardista.com/web-performance-video-optimization/

PageSpeed

Minimize request size
GZip all requests.
Leverage browser caching.
Include caching headers for all requests with a future date so previously fetched responses can be reused instead of refetched.
Specify a cache validator
Specify a Last-Modified or ETag header to enable cache validation. ETags provide revalidation tokens automatically sent by client to check if there are file changes since last requested.
Minify CSS, JS & HTML
Removing unnecessary whitespace saves bandwidth
Defer parsing of JavaScript
inlcude async attribute in your <script> tags so client can continue downloading assets instead of waiting as scripts are downloaded, parsed and excecuted.
Optimize images
Choose best format and compression on a per image basis include: type of data being encoded, image format capabilities, quality settings, resolution, and more. Leverage SVG and CSS. See previous page.
Specify a character set
Including a content-type header, such as Content-Type: text/html; charset=UTF-8 reduces browser processing. Can start parsing HTML immediately when it knows which character set it has.
Remove query strings from static resources
Resources with a "?" in the URL are not cached by some proxy caching servers
Specify a Vary: Accept-Encoding header
In case client doesn't support compression, tells proxy server to store both compressed and uncompressed versions.
Avoid CSS @import
Only starts downing imported file once encountered rather than in parallel.
Avoid a character set in the meta tag
Better to include it in HTTP Content-Type header (IE8 rule)
Avoid bad requests
404 and 410 errors waste time. If requests are for blocking resources, it becomes a more serious problem.
Avoid landing page redirects
If your site requires redirects, do them server-side not client side, to reduce round trip requests. Removing HTTP redirects removes extra round trips.
Combine images into CSS sprites
Enable Keep-Alive
HTTP persistent connections allows a TCP connection to send and receive multiple HTTP requests, reducing latency for subsequent requests.
Enable compression
GZip!
Inline Small CSS & JS
Minimize redirects
Minimizing redirects removes additional RTTs and wait time for users.
Optimize the order of styles and scripts
Prefer asynchronous resources
Many JS libraries and frameworks were originally written as synchronous scripts, but now have asynchronous versions. Use the asynchronous versions.
Put CSS in the document head
Serve resources from a consistent URL
Serve scaled images
Properly sized images saves bandwidth.
Specify image dimensions
Enables rendering page before images are downloaded. Without proper dimensions, browser will need to reflow and repaint upon image downloaded.
Page Speed

Usability Rules

Avoid plugins
Configure the viewport
Optimize pages to display well on mobile devices by including a meta viewport in the head of the document specifying width=device-width, initial-scale=1.
<meta name=viewport content="width=device-width, initial-scale=1">
Size content to viewport
Scrolling websites vertically is Okay, but horizontally creates poor user experience. Use media queries and vh, vw and percents for width.
Size tap targets appropriately
Set a minimum tap target size of of 48 CSS pixels on a site with a properly-set mobile viewport. Make important tap targets large enough to be easy to press, ensuring there is extra spacing between smaller tap targets.
Use legible font sizes
Page Speed Insights

WCAG 1.0 (1998)

  1. Provide equivalent alternatives to auditory and visual content
  2. Don’t rely on color alone
  3. Use markup (HTML) and style sheets (CSS), and do so properly
  4. Clarify natural language usage
  5. Create tables that transform gracefully
  6. Ensure that pages featuring new technologies transform gracefully
  7. Ensure user control of time sensitive content changes
  8. Ensure direct accessibility of embedded user interfaces
  9. Design for device independence
  10. User interim solutions
  11. Use W3C technologies and guidelines
  12. Provide context and orientation information
  13. Provide clear navigation mechanisms
  14. Ensure that documents are clear and simple

WCAG 2.0 (2008)

Perceivable: Content & UI components

  1. Provide text alternatives for any non-text content so that it can be changed into other forms people need, such as large print, braille, speech, symbols or simpler language.
  2. Time-based media: Provide alternatives for time-based media.
  3. Create content that can be presented in different ways (for example simpler layout) without losing information or structure.
  4. Make it easier for users to see and hear content including separating foreground from background.

Operable: UI and Nav

  1. Make all functionality available from a keyboard.
  2. Provide users enough time to read and use content.
  3. Do not design content in a way that is known to cause seizures.
  4. Provide ways to help users navigate, find content, and determine where they are.

Understandable: Information & operation of UI

  1. Make text content readable and understandable.
  2. Make web pages appear and operate in predictable ways.
  3. Help users avoid and correct mistakes.

Robust: Content interprable by UAs, including AT.

  1. Maximize compatibility with current and future user agents, including assistive technologies.

Accessible Design Rules

  1. Plan Heading Structure Early
    Ensure all content and design fits into a logical heading structure.
  2. Consider Reading Order
    The reading order should be the same as the visual order.
  3. Provide Good Contrast
    Be especially careful with light shades of gray, orange, and yellow. Check your contrast levels with our color contrast checker.
  4. Use True Text Whenever Possible
    True text enlarges better, loads faster, and is easier to translate. Use CSS to add visual style.
  5. Watch the Use of CAPS
    All caps can be difficult to read and can be read incorrectly by screen readers.
  6. Use Adequate Font Size
    Font size can vary based on the font chosen, but 10 point is usually a minimum.
  7. Remember Line Length
    Don't make it too long or too short.
  8. Make Sure Links are Recognizable
    Differentiate links in the body of the page with underlines or something other than color alone.
  9. Design Link Focus Indicators
    Ensure keyboard users can visually identify a focused link. Use the standard dotted line or other non-color designators.
  10. Design a "Skip to Main Content" Link
    A link for keyboard users to skip navigation should be at the top of the page. It can be hidden, but should be visible when it receives keyboard focus.
  11. Ensure Link Text Makes Sense on Its Own
    Avoid "Click Here" in link text. Other ambiguous links, such as "More" or "Continue", can also be confusing.
  12. Motion and sound: Use Animation, Video, and Audio Carefully
    If used, provide a play/pause button. Avoid flashing or strobing content: It can cause seizures.
  13. Don't Rely on Color Alone
    Because users often can't distinguish or may override page colors, color cannot be the only way information is conveyed.
  14. Design Accessible Form Controls
    Ensure form controls have descriptive labels and instructions. Pay close attention to form validation errors and recovery mechanisms.
  15. Get Poster

Tips

UA Sniffing ≠ AT Sniffing

Managing focus is required. Has to be added with JavaScript

Use browser defaults as much as possible

Issues

RDD

CSS

8,596 lines, 627 KiB

@-webkit-keyframes fadeOutAndMoveDown {
    0% {
        top: 50%;
        opacity: 1;
        -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"
    }

    100% {
        top: 55%;
        opacity: 0;
        -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"
    }
}
Photo: Kenneth Lu

Solutions

My First Framework

My old portfolio

The code

var isDHTML, isID, isAll, isLayers;

if (document.getElementById) {
  isID = 1; isDHTML = 1;
} else {
  if (document.all) {
    isAll = 1; isDHTML = 1;
  } else {
    browserVersion = parseInt(navigator.appVersion);
    if ((navigator.appName.indexOf('Netscape') != -1) && (browserVersion == 4)) {
      isLayers = 1; isDHTML = 1;
    }
   }
}

function findDOM(objectID,withStyle) {
 if (withStyle == 1) {
   if (isID) { return (document.getElementById(objectID).style) ; }
   else { 
     if (isAll) { return (document.all[objectID].style); }
     else {
        if (isLayers) { return (document.layers[objectID]); }
     };
   }
 } else {
    if (isID) { return (document.getElementById(objectID)); 
    } else { 
       if (isAll) { 
          return (document.all[objectID]); 
       } else {
            if (isLayers) { return (document.layers[objectID]); }
       };
    }
  }
}

function setClass(objectID,newClass) {
    var dom =findDOM(objectID, 0);
    dom.className = newClass;
}

think

<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" 
    placeholder="XXXXX" aria-label="Zip Code: 5-digit zip code">
  </span>
</li>

on data entry...123XX  123XX

<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" placeholder="XXXXX"
     aria-label="Zip Code: 5-digit zip code">
  </span>
</li>

Create the Mask

  // replaces each masked input t with a shell 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'),
        title = input.getAttribute('title'),
        id = input.getAttribute('id'),
        placeholder = document.createTextNode(input.getAttribute('placeholder'));

    input.setAttribute('maxlength', placeholder.length);
    if(title){
      input.setAttribute('aria-label', document.querySelector("label[for= " + id + "]").innerText + ": " + title);
    }

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

    wrap.setAttribute('class', 'shell');
    wrap.appendChild(mask);
    input.parentNode.insertBefore( wrap, input );
    wrap.appendChild(t);
  },

Style the Masking

.shell { position: relative; line-height: 1; }
  .shell span {
    position: absolute; left: 3px; top: 1px;
    color: $themeColor;
    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 { /* make them match */
  background-color: transparent;
  font-size: 16px; font-family: monospace; padding-right: 10px; text-transform: uppercase; }

/* hide placeholder */
.shell .masked::-webkit-input-placeholder {color: transparent;}
.shell .masked::-moz-placeholder {color: transparent;}
.shell .masked:-ms-input-placeholder {color: transparent;}
.shell .masked::placeholder { color: transparent;}
  

ARIA Accessibilty

  • aria-labeledby="otherElement idValues"
  • aria-label="Read by Screen Reader"
  • <label for="idOfFormControl"> (or implicit label)
  • placeholder
  • title
  • not accessible
<label for="telephone">Telephone Number</label>
  <input type="tel" id="telephone" placeholder="(XXX) XXX-XXXX"
  aria-describedby="hint">
  <span class="hint" id="hint">10-digit phone number</span>
10-digit phone number

Failed example of RDD in Production

https://secure.checkout.visa.com/addressaddnew

Carousel script is 1.1kb

5 Accessibility Features

  1. Use the appropriate semantic elements (label and input)
  2. Label describes purpose of selection
  3. States (on / off) are distiguishable
  4. Keyboard access is supported
  5. Focus state is clearly visible
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

Questions?

estelle@weyl.org
@estellevw / @standardista

all talks at estelle.github.io/

Resources

Accessibility

Performance

Github Repos