Simple. Fast. Accessible.
Estelle Weyl (@estellevw)
https://estelle.github.io/concat/
https://estelle.github.io/concat/
<script src="jquery.js"></script> <script type="text/javascript"> $('li:first').addClass('first'); </script>
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!
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)
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
HTML > CSS > JS
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
<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
"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."
{{#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}
{{#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}
function mgrChange (name, value) { var ul = document.querySelector('.' + name); ul.setAttribute('class', name + ' left' + value); }
<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>
<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>
<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>
.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;}
Changing the layout changes the semantics:
table { display: grid; }
is no longer read as a table by screen readers.
think
Accessibility
Performance
Github Repos