Skip to content

Instantly share code, notes, and snippets.

@jssteinberg
Last active March 1, 2021 13:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jssteinberg/e1c67f8a49251947fcd5b08e27ab5a4c to your computer and use it in GitHub Desktop.
Save jssteinberg/e1c67f8a49251947fcd5b08e27ab5a4c to your computer and use it in GitHub Desktop.
<script context="module">
import { writable } from 'svelte/store';
/** @type {string} a11yTitle - A store to use for the title to announce on
* route change for screen readers. */
export const a11yTitle = writable('');
</script>
<script>
import { stores } from '@sapper/app';
import { tick } from 'svelte';
const { page } = stores();
const accessibleRouteChange = async () => {
if (!elWrapper) return;
// Two ticks are needed for some reason for Sapper to finish route change
// stuff, including scrolling to top
await tick(); await tick();
// Focus wrapper without scroll (Sapper does that)
elWrapper.focus({ preventScroll: true });
};
const focusMain = () => {
if (!elMain) return;
elMain.focus({ preventScroll: true });
elMain.scrollIntoView({ behavior: 'smooth' });
};
const focusTop = () => {
if (!elWrapper) return;
elWrapper.focus({ preventScroll: true });
elWrapper.scrollIntoView({ behavior: 'smooth' });
};
let classes = null;
let language = null;
let elMain;
let elWrapper;
let pathname = '';
export {
classes as class,
language as lang,
};
export let toTopLabel = 'Top of page';
page.subscribe(val => {
pathname = val.path.replace(/\/$/, '');
accessibleRouteChange();
});
</script>
<div
tabindex="-1"
bind:this={elWrapper}
class="{classes ? classes : undefined}"
lang="{language ? language : undefined}"
>
<a
href={pathname}#main
class="sr-only-focusable link-skip to-main"
aria-label="Skip to main content"
on:click|preventDefault={focusMain}
>
<span class=inner-wrapper aria-hidden=true>
Main content
</span>
</a>
<header>
<slot name="header"></slot>
</header>
<main id="main" tabindex="-1" bind:this="{elMain}">
<slot></slot>
</main>
<footer>
<slot name="footer">
<hr>
<!-- Part of a11y SPA routing: announce title (equivalent) on route change. -->
<p aria-live="polite" role="region">
<em>
{$a11yTitle || pathname.replace(/^\//, '').replace(/^\w/, c => c.toUpperCase()) || 'Front'}
</em>
</p>
</slot>
<a
href="{pathname}#"
class="link-skip to-top"
on:click|preventDefault={focusTop}
>
{toTopLabel}
</a>
</footer>
</div>
<style>
[tabindex="-1"] {
outline: none;
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment