Responsive disclosure navigation
NavigationA navigation bar that folds neatly into a keyboard-friendly disclosure on small screens.
HTML + CSS + JavaScript
aria-expanded="false" Navigation · Complete runnable example
Give keyboard users a direct route past repeated navigation and into the page.
This preview runs in an isolated document. Clipboard writes and saved preferences may be blocked, and printing requires the standalone file. A dialog affects only the example. Reset reloads the original example state.
Use a skip link near the start of a page with repeated navigation. It provides a quick route to the main content while remaining visually unobtrusive until it receives keyboard focus.
The preview, copied text, and downloaded file use this same document.
<!doctype html>
<!--
MIT License
Copyright (c) 2026 MRHTML
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-->
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; img-src data:; connect-src 'none'; form-action 'none'; object-src 'none'; base-uri 'none'"
/>
<title>MRHTML Notes | Skip to main content</title>
<style>
:root {
color-scheme: light;
--ink: #172334;
--muted: #536276;
--blue: #164bbb;
--pale: #e8effe;
--paper: #f7f6f1;
--line: #d8dee7;
--header-clearance: 110px;
font-family: Arial, Helvetica, sans-serif;
}
* {
box-sizing: border-box;
}
html {
scroll-padding-top: var(--header-clearance);
}
body {
margin: 0;
background: var(--paper);
color: var(--ink);
font-size: 16px;
line-height: 1.6;
}
button {
font: inherit;
}
a {
color: var(--blue);
text-underline-offset: 4px;
}
h1,
h2,
h3,
p {
margin: 0;
}
h1,
h2,
h3 {
line-height: 1.12;
letter-spacing: -0.045em;
}
h1 {
font-size: clamp(37px, 6vw, 66px);
}
h2 {
font-size: clamp(27px, 4vw, 39px);
}
h3 {
font-size: 20px;
}
button,
a,
summary {
-webkit-tap-highlight-color: transparent;
}
:focus-visible {
outline: 3px solid var(--blue);
outline-offset: 5px;
}
[hidden] {
display: none !important;
}
::selection {
color: #fff;
background: var(--blue);
}
.wrap {
width: min(1040px, 100%);
margin-inline: auto;
padding-inline: clamp(22px, 5vw, 60px);
}
.eyebrow {
color: var(--blue);
font-size: 11px;
letter-spacing: 0.12em;
font-weight: 700;
text-transform: uppercase;
}
.muted {
color: var(--muted);
}
.button {
display: inline-flex;
align-items: center;
justify-content: space-between;
gap: 25px;
min-height: 46px;
padding: 11px 18px;
border: 1px solid var(--blue);
border-radius: 7px;
background: var(--blue);
color: white;
text-decoration: none;
font-size: 13px;
font-weight: 700;
transition:
background 0.18s,
transform 0.18s;
}
.button:hover {
background: #10398e;
transform: translateY(-2px);
}
.button span {
font-size: 19px;
line-height: 1;
}
.brand {
display: inline-flex;
gap: 10px;
align-items: center;
text-decoration: none;
color: var(--ink);
font-size: 18px;
font-weight: 800;
letter-spacing: -0.7px;
}
.brand-mark {
display: grid;
place-items: center;
background: var(--blue);
color: white;
border-radius: 8px;
width: 32px;
height: 32px;
font-size: 17px;
letter-spacing: -2px;
padding-right: 2px;
}
.skip-link {
position: fixed;
top: 12px;
left: 12px;
z-index: 100;
background: var(--blue);
color: white;
padding: 12px 18px;
border: 2px solid white;
border-radius: 7px;
box-shadow: 0 4px 20px #17233430;
transform: translateY(-160%);
font-weight: 700;
font-size: 14px;
}
.skip-link:focus {
transform: translateY(0);
}
.footer {
font-size: 11px;
color: var(--muted);
display: flex;
justify-content: space-between;
gap: 15px;
padding-block: 24px;
border-top: 1px solid var(--line);
}
main:focus-visible,
section[tabindex='-1']:focus-visible {
outline: 3px solid var(--blue);
outline-offset: -3px;
border-radius: 8px;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation: none !important;
transition: none !important;
scroll-behavior: auto !important;
}
}
.site-header {
position: sticky;
top: 0;
z-index: 10;
background: #fff;
border-bottom: 1px solid var(--line);
}
.header-row {
min-height: 80px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 25px;
}
.site-nav ul {
display: flex;
gap: 8px;
padding: 0;
margin: 0;
list-style: none;
}
.site-nav a {
display: block;
font-size: 12px;
text-decoration: none;
color: var(--ink);
padding: 12px;
border-radius: 6px;
}
.site-nav a:hover {
color: var(--blue);
background: var(--pale);
}
.guide-label {
font-size: 11px;
color: var(--muted);
padding-block: 21px;
border-bottom: 1px solid var(--line);
display: flex;
justify-content: space-between;
gap: 20px;
}
.guide-label strong {
color: var(--blue);
font-weight: 600;
}
.article {
padding-block: 45px 60px;
}
.article-header {
display: grid;
grid-template-columns: 1.6fr 1fr;
gap: 35px;
align-items: center;
}
.article h1 {
margin-top: 16px;
}
.article h1 span {
color: var(--blue);
}
.article-intro {
font-size: 16px;
color: var(--muted);
margin-top: 22px;
max-width: 530px;
}
.article .button {
margin-top: 25px;
}
.article-art {
aspect-ratio: 1;
background: var(--pale);
border-radius: 50%;
position: relative;
display: grid;
place-items: center;
}
.path {
position: absolute;
inset: 23%;
border: 2px dashed #718dc5;
border-radius: 50%;
}
.path::before,
.path::after {
content: '';
position: absolute;
width: 15px;
height: 15px;
border-radius: 50%;
background: #f5bc64;
border: 4px solid #fff;
box-sizing: content-box;
}
.path::before {
top: 0;
left: 0;
}
.path::after {
right: 0;
bottom: 0;
background: var(--blue);
}
.art-arrow {
position: relative;
font-size: clamp(65px, 10vw, 110px);
font-weight: 400;
color: var(--blue);
line-height: 1;
transform: rotate(-10deg);
}
.article-body {
display: grid;
grid-template-columns: 1.55fr 1fr;
gap: 45px;
margin-top: 48px;
}
.article-body h2 {
font-size: 29px;
margin-bottom: 16px;
}
.article-body p {
font-size: 14px;
color: var(--muted);
}
.article-body p + p {
margin-top: 16px;
}
.note {
padding: 25px;
background: #fff;
border: 1px solid var(--line);
border-radius: 12px;
align-self: start;
}
.note .eyebrow {
font-size: 10px;
}
.note p:last-child {
font-size: 14px;
line-height: 1.6;
margin-top: 12px;
color: var(--ink);
}
.check-section {
margin-top: 40px;
padding: 28px;
background: white;
border: 1px solid var(--line);
border-radius: 12px;
}
.check-section h2 {
font-size: 28px;
}
.check-section > p {
font-size: 14px;
color: var(--muted);
margin-top: 12px;
max-width: 650px;
}
.reading-checklist {
margin: 22px 0 0;
padding-left: 24px;
font-size: 14px;
}
.reading-checklist li {
padding: 7px 0 7px 6px;
}
.reading-checklist kbd {
font:
12px Arial,
Helvetica,
sans-serif;
border: 1px solid #b9c5d8;
background: #f3f6fc;
padding: 2px 5px;
border-radius: 4px;
}
.check-section > details {
margin-top: 20px;
border-top: 1px solid var(--line);
padding-top: 13px;
}
.check-section summary {
min-height: 44px;
cursor: pointer;
color: var(--blue);
font-size: 13px;
font-weight: 700;
}
.check-section details p {
font-size: 13px;
color: var(--muted);
margin-bottom: 10px;
max-width: 650px;
}
.related {
margin-top: 35px;
padding-top: 25px;
border-top: 1px solid var(--line);
}
.related h2 {
font-size: 24px;
margin-bottom: 12px;
}
.related p {
font-size: 14px;
color: var(--muted);
}
.related a {
display: inline-block;
padding-block: 12px;
font-size: 13px;
}
.article:focus-visible {
outline-offset: -4px;
outline-width: 3px;
}
@media (max-width: 640px) {
:root {
--header-clearance: 150px;
}
.header-row {
padding-block: 18px;
min-height: 0;
flex-wrap: wrap;
gap: 13px;
}
.site-nav {
width: 100%;
}
.site-nav ul {
gap: 9px;
}
.site-nav a {
padding: 8px 10px;
min-height: 40px;
}
.guide-label {
font-size: 10px;
gap: 15px;
}
.article {
padding-top: 32px;
}
.article-header {
grid-template-columns: 1fr;
gap: 27px;
}
.article-art {
width: 160px;
aspect-ratio: 1;
justify-self: end;
margin-top: -5px;
}
.art-arrow {
font-size: 75px;
}
.article-intro {
font-size: 15px;
}
.article-body {
grid-template-columns: 1fr;
gap: 23px;
margin-top: 30px;
}
.article-body h2 {
font-size: 27px;
}
.note {
padding: 22px;
}
.check-section {
padding: 23px;
margin-top: 28px;
}
.check-section h2 {
font-size: 25px;
}
.footer {
flex-wrap: wrap;
}
}
@media (max-width: 360px) {
.site-nav ul {
gap: 1px;
}
.site-nav a {
font-size: 11px;
}
.guide-label > span:last-child {
max-width: 90px;
text-align: right;
}
}
</style>
</head>
<body>
<a class="skip-link" id="skip-link" href="#main">Skip to main content</a>
<header class="site-header">
<div class="wrap header-row">
<a class="brand" href="#main" aria-label="MRHTML Notes home"
><span class="brand-mark" aria-hidden="true">m.</span>MRHTML Notes</a
>
<nav class="site-nav" aria-label="Main navigation">
<ul>
<li><a href="#reading">Design notes</a></li>
<li><a href="#checklist">Practice</a></li>
<li><a href="#related">More to explore</a></li>
</ul>
</nav>
</div>
</header>
<div class="wrap guide-label">
<span><strong>FIELD GUIDE 01</strong> / Thoughtful interfaces</span
><span>A short read with a useful shortcut.</span>
</div>
<main
class="wrap article"
id="main"
tabindex="-1"
aria-labelledby="article-title"
>
<div class="article-header">
<div>
<p class="eyebrow">Small details, better experiences</p>
<h1 id="article-title">
Less friction.<br /><span>Better focus.</span>
</h1>
<p class="article-intro">
A good interface helps people get where they need to go. Sometimes
the most useful path is the one that lets them skip straight to the
point.
</p>
<a class="button" id="main-first-link" href="#checklist"
>Try the reading checklist <span aria-hidden="true">↓</span></a
>
</div>
<div
class="article-art"
role="img"
aria-label="A blue arrow takes a direct path between two points"
>
<span class="path"></span
><span class="art-arrow" aria-hidden="true">↘</span>
</div>
</div>
<section
class="article-body"
id="reading"
tabindex="-1"
aria-labelledby="reading-title"
>
<div>
<h2 id="reading-title">Make the direct route easy.</h2>
<p>
Navigation helps a first-time visitor explore a site. On a return
visit, the same links can become a long detour for someone moving
through the page with a keyboard.
</p>
<p>
A skip link offers a direct route to the main content. It comes
before the site header in the document and becomes visible when it
receives keyboard focus.
</p>
<p>
The destination matters as much as the shortcut. It should receive
focus, remain visible below a sticky header and let the next Tab
continue through the content.
</p>
</div>
<aside class="note" aria-label="A useful design principle">
<p class="eyebrow">Worth keeping in mind</p>
<p>
Good shortcuts do not replace clear navigation. They give people
another useful way through it.
</p>
</aside>
</section>
<section
class="check-section"
id="checklist"
tabindex="-1"
aria-labelledby="checklist-title"
>
<h2 id="checklist-title">A quick keyboard check.</h2>
<p>
Start from the beginning of the document, then follow the route a
keyboard user can take.
</p>
<ol class="reading-checklist">
<li>Press <kbd>Tab</kbd> once to reveal “Skip to main content.”</li>
<li>
Press <kbd>Enter</kbd> to move focus to this article's main content.
</li>
<li>
Press <kbd>Tab</kbd> again to reach the first link inside the
article.
</li>
<li>
Continue through the content without repeating the header links.
</li>
</ol>
<details>
<summary>Why the destination has tabindex="-1"</summary>
<p>
It lets the main content receive focus from the skip link while
keeping the main landmark out of the normal Tab sequence. The next
Tab moves to its first interactive descendant.
</p>
</details>
</section>
<section
class="related"
id="related"
tabindex="-1"
aria-labelledby="related-title"
>
<h2 id="related-title">Keep the journey simple.</h2>
<p>
Use readable labels, visible focus and a logical content order
alongside the skip link.
</p>
<a href="#reading"
>Return to the design note <span aria-hidden="true">↑</span></a
>
</section>
</main>
<footer class="wrap footer">
<span>MRHTML / Original skip-link example</span
><span>First focus. Direct route. Clear destination.</span>
</footer>
<script>
// MRHTML: native fragment links still work without this focus enhancement.
const header = document.querySelector('.site-header');
const updateClearance = () => {
document.documentElement.style.setProperty(
'--header-clearance',
`${header.getBoundingClientRect().height + 20}px`,
);
};
new ResizeObserver(updateClearance).observe(header);
updateClearance();
document.addEventListener('click', (event) => {
const link = event.target.closest('a[href^="#"]');
if (!link) return;
const destination = document.getElementById(
link.getAttribute('href').slice(1),
);
if (!destination) return;
// Use explicit focus so the next Tab continues inside the target.
// This also keeps the exact document working in an opaque-origin srcdoc preview.
event.preventDefault();
destination.focus({ preventScroll: true });
destination.scrollIntoView({ block: 'start', behavior: 'auto' });
});
</script>
</body>
</html>