<a class=skip_lnk href="#main_h1">Skip to content</a>

<nav class=fakeNav>
  <ul class=fakeNav_lst>
    <li class=fakeNav_li><a class=fakeNav_lnk href>dummy nav link</a></li>
    <li class=fakeNav_li><a class=fakeNav_lnk href>dummy nav link</a></li>
  </ul>
</nav>

<main class=main>

  <!-- tabindex = -1 is required for iOS with JS unavailable -->
  <h1 class=main_h1 id=main_h1>Accessible skip-to-content link for desktop and mobile</h1>
  
  <p class=main_p>Assistive technologies struggle with skip-links on mobile and touch devices.</p>
  
  <p class=main_p>Activating the skip-link anchor (only available via tab key) moves the focus to its <code class="main_code language-html">href</code> which is problematic on iOS and Android devices.</p>

  <p class=main_p>I've written a futherence to this piece to include accessible <a class=fancyLnk rel="me noopener" target=_blank title="[new window]" href="https://codepen.io/2kool2/pen/JjYGdmZ">In-page anchors for desktop &amp; mobile</a> (includes skip-to-content).</p>
  
  <p class=main_p>These solutions correct experienced issues across desktop, iOS and Android.</p>
  
  <h2 class=main_h2>Skip-link to the content heading</h2>
  
  <figure>
    <figcaption>HTML</figcaption>
    <pre><code class=language-markup spellcheck=false contenteditable>&lt;a class=skip_lnk href="#main_title"&gt;Skip to main content&lt;/a&gt;

&lt;!-- Nav, etc --&gt;

&lt;h1 class="main_title" id="main_title" tabindex="-1"&gt;Page heading&lt;/h1&gt;
</code></pre>
  </figure>
  
  <p class=main_p><code class="main_code language-html">tabindex="-1"</code> resolves iOS issues.</p>
  
  <p class=main_p>Note: I'm using the <code class="main_code language-html">h1</code> as the focus point.<br>Wherever possible avoid moving focus onto blocks of content, such as <code class="main_code language-html">main</code>, <strong>especially</strong> if it contains focusable elements: links, inputs, video, etc.</p>

  <figure>
    <figcaption>CSS</figcaption>
    <pre><code class=language-css spellcheck=false contenteditable>.skip_lnk {
  position: absolute;
  padding: .5rem 1rem;
  color: #fff;
  background-color: #000;
  text-decoration: none;
  font-weight: bold;
  z-index: 10;
  transform: translate3d(.125rem, -5rem, 0);
  transition: transform .3s ease-out;
}
.skip_lnk:focus {
  transform: translate3d(.125rem, .125rem, 0);
  outline: #fff solid .125rem;
}
@media print {
  .skip_lnk {
    display: none;
  }
}
</code></pre>
  </figure>
  
  <p class=main_p>JavaScript is required to resolve Android issues.</p>

  <figure>
    <figcaption>JavaScript (ES6)</figcaption>
    <pre><code class=language-javascript spellcheck=false contenteditable>(() => {
  const skip_lnk = document.querySelector('.skip_lnk');
  if (!skip_lnk) return false;
  skip_lnk.addEventListener('click', (e) => {
    e.preventDefault();
    const to_obj = document.getElementById(skip_lnk.href.split('#')[1]);
    if (to_obj) to_obj.focus();
  });
})();
</code></pre>
  </figure>

  <figure>
    <figcaption>JavaScript (ES5) - Google closure compiled 194 bytes</figcaption>
    <pre><code class=language-javascript spellcheck=false contenteditable>(function(){var a=document.querySelector(".skip_lnk");if(!a)return!1;a.addEventListener("click",function(b){b.preventDefault();(b=document.getElementById(a.href.split("#")[1]))&&b.focus()})})();
</code></pre>
  </figure>


  <h2 class=main_h2>Skip-link to a content block</h2>

  <p class=main_p>If you cannot link to the main heading, and have to link to a content block (which may contain focusable elements), then avoid the use of <code class="main_code language-html">tabindex</code> in the HTML.</p>
  <p class=main_p>Instead add and remove it upon demand via JavaScript.</p>

  <figure>
    <figcaption>HTML</figcaption>
    <pre><code class=language-markup spellcheck=false contenteditable>&lt;a class=skip_lnk href="#main"&gt;Skip to main content&lt;/a&gt;

&lt;!-- Nav, etc --&gt;

&lt;main class="main" id="main"&gt;
  &lt;!-- May contain focusable elements --&gt;
&lt;/main&gt;
</code></pre>
  </figure>

  <figure>
    <figcaption>JavaScript (ES6)</figcaption>
    <pre><code class=language-javascript spellcheck=false contenteditable>(_ => {
  const skip_lnk = document.querySelector('.skip_lnk');
  if (!skip_lnk) return false;
  skip_lnk.addEventListener('click', e => {
    e.preventDefault();
    const to_obj = document.getElementById(skip_lnk.href.split('#')[1]);
    if (to_obj) {
      to_obj.setAttribute('tabindex', '-1');
      to_obj.addEventListener('blur', e => {
        to_obj.removeAttribute('tabindex');
      }, {once: true});
      to_obj.focus();
    }
  });
})();
</code></pre>
  </figure>


  <p class=main_p>For improved accessibility, the on-demand addition and removal of <code class="main_code language-html">tabindex</code> should be applied to <strong>all page anchors</strong>, not just skip-to-content links.</p>
  <p class=main_p>I've rewritten this piece to do exactly that in an accessible manner: <a class=fancyLnk rel="me noopener" target=_blank title="[new window]" href="https://codepen.io/2kool2/pen/JjYGdmZ">In-page anchors for desktop &amp; mobile</a></p>

  
  <h2 class=main_h2>Credits</h2>

  <p class=main_p>For a full description of the issue faced on mobile devices, and who it affects, please see the original article by Hampus Sethfors: <a class=fancyLnk rel=noreferrer target=_blank href="https://axesslab.com/skip-links/">Your skip links are broken</a>.<br>The code here replaces the article's jQuery version with vanilla JavaScript and was extended to include on-demand tabindexing.</p>

</main>



<!-- Footer codepen include -->
[[[https://codepen.io/2kool2/pen/mKeeGM]]]
html {box-sizing: border-box;}
*,
*::before,
*::after {box-sizing: inherit;}
body {

  /* Fix Safari bug with viewport units in calc() */
  min-height: 0vw;

  /* Fluidly variable between: 16px @ 320px and 32px @ 1920px */
  --space: calc(1rem + ((1vw - 0.2em) * 1));

  margin: 0;
  color: #666;
  font-size: var(--space);
  font-family: sans-serif;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: hsl(269,19%,30%);
  background-color: hsla(32,100%,85%,.35);
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg width='100%25' xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.4'/%3E%3C/filter%3E%3C/defs%3E%3C!-- %3Cpath filter='url(%23a)' opacity='.3' d='M0 0h1200v256H0z'/%3E--%3E%3Crect filter='url(%23a)' opacity='.3' width='100%25' height='100%25'/%3E%3C/svg%3E");
}
::-moz-selection {
  color: #fff;
  background-color: #000;
}
::selection {
  color: #fff;
  background-color: #000;
}


/* Skip link */

.skip_lnk {
  position: absolute;
  padding: .5rem 1rem;
  color: #fff;
  background-color: #3B2D4A;
  text-decoration: none;
  font-weight: bold;
  z-index: 10;
  transform: translate3d(.125rem, -5rem, 0);
  transition: transform .3s ease-out;
}
.skip_lnk:focus {
  transform: translate3d(.125rem, .125rem, 0);
  outline: #fff solid .125rem;
}
@media print {
  .skip_lnk {
    display: none;
  }
}


/* Fake nav */
.fakeNav {
  color: #fff;
  background-color: #463b4c;
  border: 2px solid #463b4c;
}
.fakeNav_lst {
  margin: 0;
  padding: 0;
  display: flex;
  justify-content: space-around;
  list-style: none;
}
.fakeNav_li {}
.fakeNav_lnk {
  display: block;
  font-weight: bold;
  color: #fff;
  padding: .5em;
}
.fakeNav_lnk:focus {
  outline: #fff solid .125rem;
}


/* Main */
.main {padding: 0 1rem;}
.main_h1 {
  font-weight: 100;
  line-height: 1.3;
  text-align: center;
  margin-top: 2rem;
}
.main_h2 {
  font-size: 2rem;
  font-weight: 100;
  line-height: 1.3;
  text-align: center;
  margin-top: 3rem;
}

/* Just so you can see where the focus lands */
[tabindex="-1"]:focus {
  color: red;
  outline: #fff solid .125rem;
}

.main_h1 + .main_p {
  font-size: larger;
  line-height: 1.3;
  text-align: center;
}
.main_p {
  text-align: left;
  max-width: 65ch;
  margin: 1rem auto;
}
.main_lnk,
.main_lnk:visited {
  white-space: nowrap;
  color: #236ECE;
}
.main_lnk:hover,
.main_lnk:focus {
  color: #014cac;
}
.main_code {font-size:1rem}




/* Prism - is used for code highlighting from an external pen: https://codepen.io/2kool2/pen/MEbeEg */

figure {
  max-width: 65ch;
  margin: 1.5rem auto;
}
figure + figure {
  margin-top: 2rem;
}
figcaption {
  font-weight: 700;
  padding: 0 0 0 .5rem;
}
pre[class*="language-"] {
/*   background-position: top -1.2rem left -.25rem; */
}
pre,
pre > code {
  background-color: transparent !important;
}
pre:focus {
  outline: #fff solid .125rem;
}
pre > code {
  display:block;
  margin: 0 0 0.75rem;
  padding-left: 0.5rem;
}
code[class*="language-"] {
  line-height: 2rem;
}

/* Fancier text links */
body {
  --fancyColor: hsla(214,72%,48%,1);
  --fancyColorHover: hsla(214,72%,20%,1);
  --fancyBgHover: hsla(214,100%,80%,1);
  --fancyUnderline: hsla(214,72%,48%,1);
}
@media (prefers-color-scheme: dark) {
  body:not([data-lightMode="light"]) {
    --fancyColor: hsla(214,100%,72%,1);
    --fancyColorHover: #fff;
    --fancyBgHover: hsla(214,100%,25%,1);
    --fancyUnderline: hsla(214,100%,72%,.4);
  }
}
body[data-lightMode="dark"] {
  --fancyColor: hsla(214,100%,72%,1);
  --fancyColorHover: #fff;
  --fancyBgHover: hsla(214,100%,25%,1);
  --fancyUnderline: hsla(214,100%,72%,.4);
}
.fancyLnk:link,
.fancyLnk:visited {
  color: var(--fancyColor);
  white-space: nowrap;
  position: relative;
  text-decoration: none;
  border-bottom: 1px solid var(--fancyUnderline);
  transition: color .3s ease-out;
}
.fancyLnk:hover,
.fancyLnk:focus {
  color: var(--fancyColorHover);
}
.fancyLnk:focus {
  outline: none;
}
.fancyLnk::before,
.fancyLnk::after {
  content: '';
  position: absolute;
  left: 0;
  width: 100%;
  opacity: .5;
  transform: scaleX(0);
  transition: none;
  /* Set to the delayed-on time, or as close as possible */
  transition: all .15s linear;
}
.fancyLnk::before {
  background-color: var(--fancyBgHover);
  outline-offset: -.25rem;
  outline: var(--fancyBgHover) solid .5rem;
  height: 100%;
  bottom: 0;
  z-index: -1;
}
.fancyLnk::after {
  background-color: var(--fancyColor);
  bottom: -0.1rem;
  height: 0.1rem;
}
.fancyLnk:hover::before,
.fancyLnk:focus::before,
.fancyLnk:hover::after,
.fancyLnk:focus::after {
  opacity: 1;
  transform: scaleX(1);
  /* Delayed to reduce annoying unintentional activations */
  transition: all .3s ease-in .15s;
}
@media (prefers-reduced-motion: reduce) {
  .fancyLnk::before,
  .fancyLnk::after {
    transition: none;
  }
}
// JS required to resolve Android issues

// (_ => {
//   const skip_lnk = document.querySelector('.skip_lnk');
//   if (!skip_lnk) return false;
//   skip_lnk.addEventListener('click', e => {
//     e.preventDefault();
//     const to_obj = document.getElementById(skip_lnk.href.split('#')[1]);
//     if (to_obj) to_obj.focus();
//   });

// })();


// ES5 version
/*
var skip_to_content_link = (function () {
  var skip_lnk = document.querySelector('.skip_lnk');
  if (!skip_lnk) return false;
  skip_lnk.addEventListener('click', function(e) {
    e.preventDefault();
    var to_obj = document.getElementById(skip_lnk.href.split('#')[1]);
    if (to_obj) to_obj.focus();
  });
})();
*/


// ES5 version - Google closure compiled 175 bytes gzipped (194 bytes uncompressed)
/*
(function(){var a=document.querySelector(".skip_lnk");if(!a)return!1;a.addEventListener("click",function(b){b.preventDefault();(b=document.getElementById(a.href.split("#")[1]))&&b.focus()})})();
*/


// ES6 version for content blocks (writes tabindex on demand)
(_ => {
  const skip_lnk = document.querySelector('.skip_lnk');
  if (!skip_lnk) return false;
  skip_lnk.addEventListener('click', e => {
    e.preventDefault();
    const to_obj = document.getElementById(skip_lnk.href.split('#')[1]);
    if (to_obj) {
      to_obj.setAttribute('tabindex', '-1');
      to_obj.addEventListener('blur', _ => {
        to_obj.removeAttribute('tabindex');
      }, {once: true});
      to_obj.focus();
    }
  });
})();



/* Prism - is used for code highlighting from an external pen: https://codepen.io/2kool2/pen/MEbeEg */
// Force Prism to use light mode 
document.body.setAttribute('data-lightmode', 'light');
Run Pen

External CSS

  1. https://codepen.io/2kool2/pen/MEbeEg.css

External JavaScript

  1. https://codepen.io/2kool2/pen/MEbeEg.js