Simple. Fast. Accessible.

Estelle Weyl (@estellevw)

https://estelle.github.io/concat/

HTML5 and CSS3 for the Real World Animations and Transitions with CSS MObile HTML5 Web Performance Daybook Flexbox in CSS CSS: The Definitive Guide
MDN

2011

<script src="jquery.js"></script>
          
<script type="text/javascript">                                         
   $('li:first').addClass('first');     
</script>

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

Radio Buttons

Accessibility is

Accessible Rich Internet Applications

The 1st rule of ARIA use is if you can use a native HTML element or attribute with the semantics behavior already built in, do that instead!

HTML is by default accessible. And fast.
Our job as developers is to not fuck that up!

Performance

I remember when 3G was primary mobile speed. It was slow but still worked, so why now when my phone says 3G it becomes completely useless.

— Stephen Gundee (@StephenGundee)

November 2010

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

February 15, 2018

In 2018, content is Images=1434kb, HTML=76kb, Stylesheets=73kb, Scripts=613kb, Fonts=99kb, Video=1204 and Other=3kb for a total of 3,508 kb HTTPArchive

2010 vs 2018

5x

Prescriptions

Image Optimization

  • If possible, omit images
  • Compress all videos
    • GZip
    • image meta data
    • Many image sizes
    • Best image format
    • <picture> and source set
    • avoid browser resizing
  • Leverage CSS
    • sprites
    • Image effects
    • instead of animated gifs
    • media queries
  • Only load images as needed
    • Lazy load

Essential Image Optimization

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/

Performance Tools

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 2

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.

Tips

UA Sniffing ≠ AT Sniffing

Managing focus is required. Has to be added with JavaScript

Use browser defaults as much as possible

HTML > CSS > JS

Images, Videos, & Scripts

In 2018, content is Images=1434kb, HTML=76kb, Stylesheets=73kb, Scripts=613kb, Fonts=99kb, Video=1204 and Other=3kb for a total of 3,508 kb HTTPArchive

Issues

43 Dependencies

  • React
  • Backbone
  • jQuery
  • Bootstrap
  • jQuery input masking
  • drop down select
  • kitchen sink
  • left pad
  • susy
  • learn how to @#$%ing code
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)"
    }
}

Images != JavaScript
Photo: Kenneth Lu

WebPageTest: Comparison

Solutions

2003 JS Library

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

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

Basic HTML

Radio Buttons are

HTML (1993)

"RADIO: For attributes which can take a single value from a set of alternatives. Each radio button in the group should be given the same NAME."

Application.hbs

{{#radio-button-group-component}}

  <div class="radio-row">
    {{#radio-button value=true}}
      <span class="form-label radio-label">True</span>
    {{/radio-button}}
  </div>

  <div class="radio-row">
    {{#radio-button value=false}}
      <span class="form-label radio-label">False</span>
    {{/radio-button}}
  </div>

{{/radio-button-group-component}

Semantic.hbs

{{#fieldset-radio-button-group-component legend="Fieldset Legend"}}

  <label class="radio-row">
    {{#radio-button}}
      True
    {{/radio-button}}
  </label>

  <label>
    {{#radio-button}}
      False
    {{/radio-button}}
  </label>

{{/radio-button-group-component}
Carousel script is 1.1kb

How it works

function mgrChange (name, value) {
  var ul = document.querySelector('.' + name);
  ul.setAttribute('class', name + ' left' + value);
}

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

https://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;
    }
  }
}
<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>
  <input id="zip" type="tel" name="zipcode" class="masked" 
  placeholder="XXXXX" pattern="\d{5}" title="5-digit zip code">
</li>

add magical JavaScript sprinkle sauce

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

The magical sauce

<span aria-hidden="true" id="zipMask"><i></i>XXXXX</span>

on data entry...123XX  123XX

<span aria-hidden="true" id="zipMask"><i>123</i>XX</span>

Github Repo »  

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

CSS and Semantic HTML

Changing the layout changes the semantics:
table {
  display: grid;
}
is no longer read as a table by screen readers.

think

Photo: Kenneth Lu

Resources

Accessibility

Performance

Github Repos

MDN