Making web and mobile applications
fast, secure, and easy to operate.
Features | Solutions |
Issues | Prescriptions |
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!
— Estelle Weyl (@estellevw) February 25, 2016
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) March 23, 2016
Note: Include an alt attribute on all foreground images, with empty alt attribute for decorative images.
Image Optimization TipsLast-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. async
attribute in your <script>
tags so client can continue downloading assets instead of waiting as scripts are downloaded, parsed and excecuted.Content-Type: text/html; charset=UTF-8
reduces browser processing. Can start parsing HTML immediately when it knows which character set it has.width=device-width, initial-scale=1
.
<meta name=viewport content="width=device-width, initial-scale=1">
vh
, vw
and percents for width.
Perceivable: Content & UI components
Operable: UI and Nav
Understandable: Information & operation of UI
Robust: Content interprable by UAs, including AT.
UA Sniffing ≠ AT Sniffing
Managing focus is required. Has to be added with JavaScript
Use browser defaults as much as possible
By default HTML is accessible. As developers, our job is to not fuck it up.
— Smelly (@last8years) October 17, 2016
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)" } }
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>
// 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); },
.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;}
<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
<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>
It's Monday 3rd of April 2016 and...
— Steve Faulkner (@stevefaulkner) April 4, 2016
First rule of ARIA, is still:https://t.co/bsiN798ARq pic.twitter.com/gXwDXAKGjU
<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
estelle@instartlogic.com
@estellevw / @webdevtips
all talks at instartlogic.github.io/
Accessibility
Performance
Github Repos