Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <header>
    <div class="clamp">
      <a href="#main">Skip to main content</a>
    </div>
      <nav aria-label"Article related">
        <div class="clamp">
          <a href="https://codepen.io/Moiety/pen/dyWPJbz?editors=1100" aria-current="true">View on CodePen</a>
          <a href="https://twitter.com/ZoeBijl">Zoë on Twitter</a>
        </div>
      </nav>
  </header>

  <div class="layout">
    <main id="main" tabindex="-1">
        <header>
          <h1>Short note on skip links with sticky headers</h1>
          <time datetime="2021-07-02"><span>Updated on </span>2nd of July 2021</time>
        </header>
        <p>TL;DR Prevent your sticky header from obscuring your main content with some <code>scroll-padding</code> on the <code>&lt;html&gt;</code> element.</p>
        
        <figure>
            <img src="https://assets.codepen.io/154571/before.png" alt="">
            <figcaption>What we want to prevent: our heading being chopped in half by our sticky header.</figcaption>
        </figure>
       
        <aside>
            <h2>Table of contents</h2>
            <p>(These are also skip links!)</p>
            <ul>
                <li>
                    <a href="#skip link">What is a skip link?</a>
                </li>
                <li>
                    <a href="#sticky-headers">How to make skip links work with sticky headers</a>
                </li>
                <li>
                    <a href="#support">Browser Support</a>
                </li>
                <li>
                    <a href="#resources">Related resources</a>
                </li>
            </ul>
        </aside>
        
        <section id="skip link" aria-labelledby="skip link-heading" tabindex="-1">
            <h2 id="skip link-heading">What is a skip link?</h2>

            <p>
              A skip link lets keyboard users skip over groups of interactive elements.
              You can try this yourself on a lot of pages around the web—this one included!
              If you load <a href="https://cdpn.io/Moiety/debug/dyWPJbz">this Pen in debug mode</a> and press the <kbd>Tab</kbd> key,
              your focus will be set to a skip link titled “Skip to main content”.
              Activating this link will move your focus to the main content and past the links in the navigation bar,
              no extra presses needed!
            </p>
            
            <figure class="note">
              <figcaption>
                Note: If you’re on macOS,
                you might need to enable “<a href="https://www.a11yproject.com/posts/2017-12-29-macos-browser-keyboard-navigation/">Use keyboard navigation</a>” before you’re be able to focus controls
                (such as skip links)
              </figcaption>
              
              <div class="split">
                <img src="https://assets.codepen.io/154571/macOS-keyboard-settings.png" alt="macOS Settings, Keyboard, Shortcuts, Use keyboard navigation to move between controls">
                <img src="https://assets.codepen.io/154571/safari-settings.png" alt="Safari Preferences, Advanced, 'Press tab to highlight elements' indicated">
              </div>
            </figure>
            
            <p>
              Websites use this technique to make life a little bit easier for people who rely on keyboards or assistive technologies.
              Skip links use a function that’s built in to your browser.
              All that is required for a skip link is an anchor that points to an id on the page (like <code>&lt;a href="#main"&gt;</code>) and an element with that id (like <code>&lt;main id="main"&gt;</code>).
              Activating the anchor will move focus to the target element.
            </p>
            
            <p>
              The big benefit is that your next press of the <kbd>Tab</kbd> key will set focus to the first focusable element
              (such as a link, button, or form field) within the target element.
              You’ve skipped over the repetitive focusable elements earlier in the page (such as those navigation links), hence the name.
            </p>
        </section>
        
        <section id="sticky-headers" aria-labelledby="sticky-headers-heading" tabindex="-1">
            <h2 id="sticky-headers-heading">How to make skip links work with sticky headers</h2>
            
            <p>
              A common issue with this technique is that,
              by default,
              the target element is scrolled to the top of the <a href="https://developer.mozilla.org/en-US/docs/Glossary/Viewport">viewport</a> when the skip link is activated.
              This is an issue if the page has a fixed element—such as a sticky header—that obscures the top of the viewport.
              Luckily, there’s a CSS property to fix this: <a href="https://drafts.csswg.org/css-scroll-snap-1/#propdef-scroll-padding"><code>scroll-padding</code></a>!
            </p>
            
            <p>
              <code>scroll-padding</code> specifies offsets that lets you tell the browser which parts of your page are obscured by things like UI elements (such as a sticky header).
              Like <code>padding</code> and <code>margin</code>, <code>scroll-padding</code> is a shorthand property.
              This means that you can also use the longhand version to define specific sides, like so: <code>scroll-padding-top: 2em;</code>.
            </p>
            
            <p>
              To use <code>scroll-padding</code>,
              we’ll need to know how much space we need to bridge.
              The sticky header on this page is <code>5rem</code> high.
              To get the browser to skip this part when it calculates where to scroll the page to,
              we use <code>scroll-padding-top: 5rem;</code>.
            </p>
            
            <aside class="note">
              <p>
                Note: you’re free to use other units.
                This page was built with <code>em</code> and <code>rem</code>,
                which is why those units are also used in the <code>scroll-padding</code> declaration.
              </p>
            </aside>
            
            <p>
              The padding specified by <code>scroll-padding</code> will be applied to the ‘<a href="https://drafts.csswg.org/css-overflow-3/#scroll-container">scroll container</a>’ that it’s added to.
              My understanding of this part of the specification is a bit fuzzy (feel free to explain it to me).
              But it seems like this would be the <code>&lt;html&gt;</code> element for skip links as described and used in this article.
            </p>
            
            <section id="the-code">
                <h3>The Code</h3>

                <p>Our CSS would end up looking something like this:</p>

<pre><code>html {
    scroll-padding-top: 5em;
}</code></pre>

                <p>HTML:</p>

<pre><code>&lt;html&gt;
    &lt;header&gt;
        &lt;a href="#main-content"&gt;Skip to main content&lt;/a&gt;
    &lt;/header&gt;
    &lt;main id="main-content" tabindex="-1"&gt;
        &lt;h1&gt;Fancy title for your content&lt;/h1&gt;
    &lt;/main&gt;
&lt;/html&gt; </code></pre>

                <aside class="note">
                    <p>
                      Note: the <code>tabindex="-1"</code> on the main content element isn’t needed to make skip links work.
                      It is, however, needed to get the animated focus state.
                    </p>
                </aside>
            </section>

            <section>
                <h3>The Result</h3>

                <p>
                  If we did everything right,
                  our heading (or other content) should no longer be chopped in half when we activate a skip link!
                </p>

                <figure>
                    <img src="https://assets.codepen.io/154571/after.png" alt="">
                    <figcaption>
                      What we wanted to achieve:
                      the newly focused content is visible in full;
                      no more overlapping from the sticky header.
                    </figcaption>
                </figure>
            </section>
        </section>
        
        <section id="support" aria-labelledby="support-heading" tabindex="-1">
            <h2 id="support-heading">Browser Support</h2>
            <p>Seems to work everywhere except for Internet Explorer 11.</p>
            
            <section>
              <h3>Couldn’t browsers calculate this for us?</h3>
            
              <p>The short answer is “probably not”—though wouldn’t that be a great feature to have?</p>
              
              <p>
                The CSS Scroll Snap specification—which <code>scroll-padding</code> is defined in—mentions an <code>auto</code> value that might at first seem helpful,
                but it turns out that <code>auto</code> is merely <code>scroll-padding</code>’s default value:
              </p>

              <blockquote>
                  <p>
                    [<code>auto</code>] indicates that the offset for the corresponding edge of the scrollport is UA-determined.
                    This should generally default to a used length of <code>0px</code>,
                    but UAs may use heuristics to detect when a non-zero value is more appropriate.
                  </p>
              </blockquote>
              
              <p>
                The wording here means that while browsers are allowed to calculate <code>scroll-padding</code> values other than <code>0px</code>,
                it means they almost certainly won’t.
              </p>
              
              <p>
                With that in mind,
                your best bet is to specify <code>scroll-padding</code> values yourself.
                Who knows what the future might hold, though.
              </p>
        </section>
        
        <section id="resources" aria-labelledby="resources-heading" tabindex="-1">
            <h2 id="resources-heading">Related resources</h2>
            <ul>
                <li>
                    <a href="https://drafts.csswg.org/css-scroll-snap-1/#propdef-scroll-padding">CSS Scroll Snap Module Level 1</a>
                </li>
                <li>
                    <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-padding">MDN article on ‘scroll-padding’</a>
                </li>
                <li>
                  <a href="https://www.w3.org/TR/WCAG21/#bypass-blocks">WCAG: §2.4.1 Bypass Blocks</a>
                </li>
            </ul>
        </section>
    </main>
</div>
<footer>
    <p>Words by <a href="../">Zoë Bijl</a>. Edited by <a href="https://www.friendlyediting.com/">Ashley Bischoff</a></p>
</footer>
              
            
!

CSS

              
                html {
	scroll-padding-top: 5rem; /* the star of this article! */
	/* enable the line below to see what happens without the scroll-padding! */
	/*scroll-padding-top: 0;*/
}

body {
	margin: 0; /* reset the body’s default margin  */
}

body > header {
	position: fixed; /* this is how we make our header sticky */
}

div.layout {
	padding-top: 5rem; /* this is how the visual offset created by the sticky header is usually fixed */
}

/* This provides the nice thicc border the main element gets on focus */
main:focus {
	animation: 3s linear 1 running main-focus;
}

@keyframes main-focus {
	0% {
		box-shadow: inset 0 0 0 1em var(--content-focus-full);
	}
	
	100% {
		box-shadow: inset 0 0 0 1em var(--content-focus-null);
	}
}

/* Same thing for the sections but with background colours */
section:focus {
	outline: 1em transparent;
	animation: 1.5s linear 1 running section-focus;
}

@keyframes section-focus {
	0% {
		background: var(--content-focus-full);
	}
	
	100% {
		background: var(--content-focus-null);
	}
}

/* transparent focus outline visible in High Contrast Modes */
main:focus,
section:focus {
	outline: transparent solid 1em;
	outline-offset: -1em;
}

/* Styles not necessarily related to the technique */
* {
	box-sizing: border-box;
}

html {
	color: var(--body-text);
	background: var(--body-background);
}

body {
	font-family: sans-serif;
	line-height: 1.5;
}

main,
footer {
	margin: 0 auto;
	padding: 1em;
	max-width: var(--page-max-width);
}

div.clamp {
	margin-right: auto;
	margin-left: auto;
	padding-right: 1em;
	padding-left: 1em;
	max-width: var(--page-max-width);
}

body > header {
	top: 0;
	right: 0;
	left: 0;
	z-index: 2;
	padding-top: .5rem;
	background: var(--body-background);
}

body > header a {
	color: inherit;
}

nav {
	margin: .5rem -1rem 0;
	padding: .5rem 1rem;
	color: white;
	text-align: right;
	background: var(--header-background);
}

nav a + a {
	margin-left: 1em;
}

aside.note {
	padding: .25em;
	padding-left: 1em;
	border-left: .5em solid;
}

aside.note,
figure.note {
	border-left-color: #e0cb52;
	background: rgba(255, 255, 255, .05);
}

a {
	color: var(--link-text);
}

h1 {
	margin-top: 0;
}

h2,
h3 {
	margin-top: 2rem;
}

pre {
	max-width: 100%;
	overflow-x: auto;
}

code,
kbd {
	display: inline-block;
	padding: .125em .25em;
	border: .0675em solid var(--code-border);
	border-radius: .25em;
	color: var(--code-text);
	font-family: "Courier", monospace;
	font-size: .9em;
	font-weight: 700;
	line-height: 1.25;
	background: var(--code-background);
}

kbd {
	color: var(--kbd-text);
}

a code {
	padding: inherit;
	border: none;
	color: inherit;
	line-height: inherit;
	text-decoration: underline;
	vertical-align: text-bottom;
	background: inherit;
}

img {
	max-width: 100%;
}

figure {
	margin: 1em 0;
	padding: 1em;
	border-left: .5em solid var(--figure-border);
	background: var(--figure-background);
}

figure > img {
	padding: .125em .25em;
	border: .0675em solid var(--figure-img-border);
	border-radius: .25em;
}

figcaption {
	margin-top: .5em;
	font-style: italic;
}

@media (min-width: 40em) {
	main header {
		display: flex;
		align-items: baseline;
		justify-content: space-between;
	}
	main header time {
		position: relative;
		margin-top: 0;
		padding-left: 1.6em;
		text-align: right;
	}
	main header time span {
		clip: rect(0 0 0 0);
		position: absolute;
		overflow: hidden;
		width: 1px;
		white-space: nowrap;
	}
}

/* Colour Definitions */
:root {
	--page-max-width: 41em;
	--body-text: black;
	--body-background: white;
	--link-text: royalblue;
	--header-background: purple;
	--content-focus-full: rgba(128, 0, 128, .5);
	--content-focus-null: rgba(128, 0, 128, 0);
	--code-border: #666;
	--code-text: rebeccapurple;
	--code-background: whitesmoke;
	--kbd-text: #ff2c6d;
	--figure-border: var(--content-focus-full);
	--figure-background: rgba(128, 0, 128, .2);
	--figure-img-border: #666;
}

/* Dark Mode */
@media (prefers-color-scheme: dark) {
	:root {
		--body-text: white;
		--body-background: hsl(260, 15%, 16%);
		--link-text: #7DC2FF;
		--header-background: hsl(268, 22%, 46%);
		--content-focus-full: hsla(262, 16%, 57%, .7);
		--content-focus-null: hsla(262, 16%, 57%, 0);
		--code-border: #000;
		--code-text: hsl(270, 59%, 74%);
		--code-background: #191A19;
		--kbd-text: hotpink;
		--figure-border: var(--content-focus-full);
		--figure-background: hsl(260, 16%, 23%);
		--figure-img-border: hsl(263, 17%, 69%);
	}
}
              
            
!

JS

              
                
              
            
!
999px

Console