Skiplink in Elementor WordPress sites

Op veel websites die gebouwd zijn met Elementor en het Hello Elementor theme werkt de skiplink (“Ga naar inhoud”) niet goed. In plaats van naar de hoofdinhoud te springen, blijft de focus bovenin hangen of landt hij op het logo in de header.

Dit gebeurt vooral op templates zoals archive, zoekresultaten of 404, omdat daar vaak géén <main>-element (en dus geen geldig linkdoel zoals #content) aanwezig is.

In dit artikel leggen we uit waarom dit gebeurt en delen we een praktische, site-wide oplossing die de skiplink weer correct laat werken volgens WCAG 2.4.1 Blokken omzeilen.

Auteur

Stefan van Dongen

Als specialist toegankelijke websites zorgt Stefan dat websites voor iedereen bruikbaar zijn. Hij begeleidt de praktische implementatie van de WCAG-richtlijnen en vertaalt complexe eisen naar concrete oplossingen.

Oorzaak (kort)

Hello elementor (en wordpress thema’s in het algemeen) gebruiken vaak een skiplink die naar #content wijst. Op elementor templates zoals archive ontbreekt echter vaak:

  • Een main landmark, en/of
  • Een element met id="content".

Daardoor kan de browser de in-page link niet correct afhandelen en verplaatst de toetsenbordfocus niet naar de hoofdinhoud.

Oplossing

Onderstaande code doet twee dingen:

  1. Maakt (indien nodig) een <main id="content" tabindex="-1"> aan en wrapt daar de elementor content in. Dit werkt voor: pages, single posts, archives, search results en 404 pagina’s.
  2. Verplaatst focus betrouwbaar naar het skipdoel en scrollt netjes naar de content (met respect voor prefers-reduced-motion).

Implementatie (aanrader)

Plaats de code site-wide via: elementor pro → custom code → add new

  • location: body end (</body> - end)
  • active: yes
  • display conditions: entire site

Geen elementor pro? Gebruik een code snippet plugin (bijv. code snippets) en voeg de js/css in via de site-wide header/footer-injectie.

Code (js + css)

Plak onderstaande code als één blok in je editor.

<script>
(function () {
  function getHeaderEl() {
    return document.querySelector('header, .elementor-location-header');
  }

  function getElementorContentRoot() {
    return document.querySelector(
      '[data-elementor-type="wp-page"],' +
      '[data-elementor-type="wp-post"],' +
      '[data-elementor-type="single"],' +
      '[data-elementor-type="single-post"],' +
      '[data-elementor-type="archive"],' +
      '[data-elementor-type="search-results"],' +
      '[data-elementor-type="error-404"]'
    );
  }

  function ensureMainTarget() {
    let main = document.querySelector('main, [role="main"]');

    if (!main) {
      const root = getElementorContentRoot();
      if (!root) return null;

      main = document.createElement('main');
      main.id = 'content';
      main.tabIndex = -1;

      root.parentNode.insertBefore(main, root);
      main.appendChild(root);
    }

    if (!main.id || main.id !== 'content') main.id = 'content';
    if (!main.hasAttribute('tabindex')) main.tabIndex = -1;

    return main;
  }

  function setHeaderOffsetVar() {
    const header = getHeaderEl();
    const h = header ? Math.ceil(header.getBoundingClientRect().height) : 0;
    document.documentElement.style.setProperty('--skip-offset', h + 'px');
  }

  function init() {
    const target = ensureMainTarget();
    setHeaderOffsetVar();
    window.addEventListener('resize', setHeaderOffsetVar);

    const skipLinks = document.querySelectorAll('a.skip-link[href^="#"], a[href="#content"]');
    if (!target || !skipLinks.length) return;

    skipLinks.forEach(function (link) {
      link.setAttribute('href', '#content');

      link.addEventListener('click', function () {
        setTimeout(function () {
          const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
          target.focus({ preventScroll: true });
          target.scrollIntoView({ block: 'start', behavior: reduceMotion ? 'auto' : 'smooth' });
        }, 0);
      });
    });
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }
})();
</script>

<style>
.skip-link {
  position: absolute;
  inset-block-start: 0.5rem;
  inset-inline-start: -9999px;
  z-index: 999999;
}
.skip-link:focus {
  inset-inline-start: 0.5rem;
  background: #ffeb85;
  color: #000;
  padding: 0.75rem 1rem;
  outline: 2px solid #000;
  text-decoration: none;
}
#content {
  scroll-margin-top: calc(var(--skip-offset, 0px) + 16px);
}
</style>

Waarom tabindex=”-1″?

Veel doel-elementen (zoals main) zijn niet standaard focusable. Zonder focus verplaatst een screenreader of toetsenbordfocus soms niet correct, zelfs als er wel gescrold wordt. tabindex="-1" maakt het element focusbaar zonder dat het in de normale tabvolgorde komt.

Testen (wcag 2.4.1)

  1. Herlaad de pagina en druk één keer op tab. De skiplink moet zichtbaar worden.
  2. Druk op enter. De pagina moet naar de hoofdinhoud springen en de focus moet daar staan.
  3. Druk opnieuw op tab: je moet verder tabben in de content, niet terug naar het menu.

Veelvoorkomende valkuilen

  • Dubbele skiplinks: hello en elementor kunnen er beide een outputten.
  • Canvas template: Deze fix werkt ook voor canvas templates.
  • Sticky header: De scroll-margin voorkomt dat content wegvalt.
function copyCode(button) {
const code = document.getElementById('code-block').innerText;
navigator.clipboard.writeText(code).then(() => {
button.innerText = 'Gekopieerd!';
setTimeout(() => {
button.innerText = 'Kopieer code';
}, 2000);
});
}

Jouw Elementor site beoordelen op toegankelijkheid?

In een toegankelijkheidsaudit controleren we o.a. skiplinks, landmarks, toetsenbordbediening en focusvolgorde op al je templates (pagina, archive, zoekresultaten, 404). Je krijgt een helder rapport met bevindingen, prioriteit en concrete fixes.