HTML preprocessors can make writing HTML more powerful or convenient. For instance, Markdown is designed to be easier to write and read for text documents and you could write a loop in Pug.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. So you don't have access to higher-up elements like the <html>
tag. If you want to add classes there that can affect the whole document, this is the place to do it.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. If you need things in the <head>
of the document, put that code here.
The resource you are linking to is using the 'http' protocol, which may not work when the browser is using https.
CSS preprocessors help make authoring CSS easier. All of them offer things like variables and mixins to provide convenient abstractions.
It's a common practice to apply CSS to a page that styles elements such that they are consistent across all browsers. We offer two of the most popular choices: normalize.css and a reset. Or, choose Neither and nothing will be applied.
To get the best cross-browser support, it is a common practice to apply vendor prefixes to CSS properties and values that require them to work. For instance -webkit-
or -moz-
.
We offer two popular choices: Autoprefixer (which processes your CSS server-side) and -prefix-free (which applies prefixes via a script, client-side).
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.
You can apply CSS to your Pen from any stylesheet on the web. Just put a URL to it here and we'll apply it, in the order you have them, before the CSS in the Pen itself.
You can also link to another Pen here (use the .css
URL Extension) and we'll pull the CSS from that Pen and include it. If it's using a matching preprocessor, use the appropriate URL Extension and we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
JavaScript preprocessors can help make authoring JavaScript easier and more convenient.
Babel includes JSX processing.
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.
You can apply a script from anywhere on the web to your Pen. Just put a URL to it here and we'll add it, in the order you have them, before the JavaScript in the Pen itself.
If the script you link to has the file extension of a preprocessor, we'll attempt to process it before applying.
You can also link to another Pen here, and we'll pull the JavaScript from that Pen and include it. If it's using a matching preprocessor, we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
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.
Using packages here is powered by esm.sh, which makes packages from npm not only available on a CDN, but prepares them for native JavaScript ESM usage.
All packages are different, so refer to their docs for how they work.
If you're using React / ReactDOM, make sure to turn on Babel for the JSX processing.
If active, Pens will autosave every 30 seconds after being saved once.
If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.
If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.
Visit your global Editor Settings.
<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><html></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><a href="#main"></code>) and an element with that id (like <code><main id="main"></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><html></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><html>
<header>
<a href="#main-content">Skip to main content</a>
</header>
<main id="main-content" tabindex="-1">
<h1>Fancy title for your content</h1>
</main>
</html> </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>
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%);
}
}
Also see: Tab Triggers