Keyboard-accessible tabs
NavigationPreloaded panels with one tab stop and predictable arrow-key navigation.
HTML + CSS + JavaScript
role="tablist" Navigation · Complete runnable example
A navigation bar that folds neatly into a keyboard-friendly disclosure on small screens.
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 this pattern for a small set of ordinary site links that should remain easy to reach on a narrow screen. It keeps navigation as a semantic nav landmark and uses a button to reveal its links.
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 Studio | Responsive navigation</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: 82px;
display: flex;
align-items: center;
gap: 25px;
}
.site-nav {
margin-left: auto;
}
.site-nav ul {
display: flex;
gap: 8px;
list-style: none;
margin: 0;
padding: 0;
}
.site-nav a {
display: block;
padding: 12px 14px;
font-size: 13px;
color: var(--ink);
text-decoration: none;
border-radius: 6px;
}
.site-nav a:hover {
background: var(--pale);
color: var(--blue);
}
.site-nav li:last-child a {
background: var(--ink);
color: #fff;
margin-left: 5px;
}
.menu-toggle {
margin-left: auto;
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
min-height: 44px;
padding: 9px 14px;
border: 1px solid var(--line);
border-radius: 6px;
background: #fff;
color: var(--ink);
font-size: 13px;
cursor: pointer;
}
.menu-icon {
width: 16px;
height: 14px;
position: relative;
background: linear-gradient(currentColor, currentColor) center / 100%
2px no-repeat;
}
.menu-icon::before,
.menu-icon::after {
content: '';
position: absolute;
left: 0;
right: 0;
height: 2px;
background: currentColor;
transition: transform 0.18s;
}
.menu-icon::before {
top: 0;
}
.menu-icon::after {
bottom: 0;
}
.menu-toggle[aria-expanded='true'] .menu-icon {
background: none;
}
.menu-toggle[aria-expanded='true'] .menu-icon::before {
transform: translateY(6px) rotate(45deg);
}
.menu-toggle[aria-expanded='true'] .menu-icon::after {
transform: translateY(-6px) rotate(-45deg);
}
.hero {
padding-block: 65px 45px;
display: grid;
grid-template-columns: 1.45fr 1fr;
gap: 40px;
align-items: center;
}
.hero h1 {
margin-top: 16px;
}
.hero h1 span {
color: var(--blue);
}
.hero p:not(.eyebrow) {
max-width: 400px;
font-size: 15px;
color: var(--muted);
margin-top: 20px;
}
.hero .button {
margin-top: 26px;
}
.hero-art {
aspect-ratio: 1;
background: var(--pale);
border-radius: 24px;
position: relative;
overflow: hidden;
}
.shape-a {
position: absolute;
left: 18%;
top: 17%;
width: 50%;
height: 62%;
background: var(--blue);
border-radius: 50% 50% 12px 12px;
transform: rotate(-12deg);
box-shadow: 12px 20px 0 #b1c5f1;
}
.shape-b {
position: absolute;
width: 36%;
aspect-ratio: 1;
background: #f5bc64;
border-radius: 50%;
right: 11%;
bottom: 12%;
border: 7px solid var(--pale);
}
.art-label {
position: absolute;
left: 20px;
top: 17px;
font-size: 9px;
color: #304b80;
font-weight: 700;
letter-spacing: 1px;
}
.section {
padding-block: 35px 45px;
}
.section-heading {
display: flex;
align-items: end;
justify-content: space-between;
gap: 20px;
margin-bottom: 22px;
}
.section-heading > p {
font-size: 12px;
color: var(--muted);
max-width: 195px;
}
.project-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.project {
padding: 28px;
background: white;
border: 1px solid var(--line);
border-radius: 12px;
}
.project-number {
font-size: 11px;
color: var(--blue);
font-weight: 700;
display: block;
margin-bottom: 27px;
}
.project p {
color: var(--muted);
font-size: 14px;
margin-top: 12px;
}
.process {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 35px;
border-top: 1px solid var(--line);
}
.process p {
font-size: 14px;
color: var(--muted);
margin-top: 14px;
max-width: 430px;
}
.process ol {
margin: 0;
padding-left: 23px;
font-size: 14px;
}
.process li {
padding: 9px 0 9px 9px;
}
.contact {
background: var(--ink);
color: #fff;
border-radius: 14px;
padding: 32px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 30px;
margin-bottom: 45px;
}
.contact h2 {
font-size: 29px;
}
.contact p {
color: #c4cfdf;
font-size: 13px;
margin-top: 12px;
max-width: 340px;
}
.contact .button {
background: #fff;
color: var(--ink);
border-color: white;
flex-shrink: 0;
}
.contact .button:focus-visible {
outline-color: #f5bc64;
}
.contact:focus-visible {
outline-color: #f5bc64;
}
@media (max-width: 680px) {
:root {
--header-clearance: 235px;
}
.header-row {
min-height: 76px;
flex-wrap: wrap;
gap: 0;
padding-block: 16px;
}
.site-nav {
width: 100%;
margin: 12px 0 0;
}
.site-nav ul {
flex-direction: column;
gap: 2px;
}
.site-nav a {
padding: 10px 13px;
}
.site-nav li:last-child a {
margin: 3px 0 0;
}
.hero {
grid-template-columns: 1fr;
gap: 28px;
padding-top: 40px;
}
.hero-art {
aspect-ratio: 1.7;
max-width: 440px;
}
.shape-a {
top: 14%;
height: 67%;
width: 32%;
left: 28%;
}
.shape-b {
width: 25%;
bottom: 11%;
right: 22%;
}
.project-grid,
.process {
grid-template-columns: 1fr;
}
.project {
padding: 23px;
}
.project-number {
margin-bottom: 18px;
}
.section-heading {
align-items: start;
flex-direction: column;
gap: 13px;
}
.section-heading > p {
max-width: none;
}
.process {
gap: 15px;
}
.contact {
padding: 25px;
align-items: start;
flex-direction: column;
gap: 22px;
}
.footer {
flex-wrap: wrap;
}
.section {
padding-top: 25px;
}
}
</style>
</head>
<body>
<a class="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 Studio home"
><span class="brand-mark" aria-hidden="true">m.</span>MRHTML Studio</a
><button
class="menu-toggle"
id="menu-toggle"
type="button"
aria-expanded="false"
aria-controls="site-navigation"
hidden
>
Menu <span class="menu-icon" aria-hidden="true"></span>
</button>
<nav class="site-nav" id="site-navigation" aria-label="Main navigation">
<ul>
<li><a href="#work">Our work</a></li>
<li><a href="#process">Our process</a></li>
<li>
<a href="#contact"
>Start a project <span aria-hidden="true">↗</span></a
>
</li>
</ul>
</nav>
</div>
</header>
<main class="wrap" id="main" tabindex="-1">
<section class="hero" aria-labelledby="hero-title">
<div>
<p class="eyebrow">Small studio. Clear ideas.</p>
<h1 id="hero-title">
Good design<br />starts with<br /><span>a little clarity.</span>
</h1>
<p>
Thoughtful websites for people making something useful. A simple
studio page, with navigation that fits the screen.
</p>
<a class="button" href="#work"
>Explore our work <span aria-hidden="true">↘</span></a
>
</div>
<div
class="hero-art"
role="img"
aria-label="An abstract blue arch and golden circle"
>
<span class="art-label">FORM + PURPOSE</span
><span class="shape-a"></span><span class="shape-b"></span>
</div>
</section>
<section
class="section"
id="work"
tabindex="-1"
aria-labelledby="work-title"
>
<div class="section-heading">
<h2 id="work-title">A considered approach.</h2>
<p>Two sample directions, made to be your own.</p>
</div>
<div class="project-grid">
<article class="project">
<span class="project-number">01 / DIGITAL EXPERIENCES</span>
<h3>A more useful first impression</h3>
<p>
Clear structure, thoughtful details and a path that helps visitors
find what matters.
</p>
</article>
<article class="project">
<span class="project-number">02 / BRAND EXPRESSION</span>
<h3>A point of view, made visible</h3>
<p>
Simple visual systems that give good ideas a distinct and
consistent presence.
</p>
</article>
</div>
</section>
<section
class="section process"
id="process"
tabindex="-1"
aria-labelledby="process-title"
>
<div>
<p class="eyebrow">How ideas take shape</p>
<h2 id="process-title">Listen. Explore. Refine.</h2>
<p>
A practical starting point for a small studio's process, written as
sample content for this MRHTML example.
</p>
</div>
<ol>
<li>Understand the people and the purpose.</li>
<li>Make a few promising directions tangible.</li>
<li>Test the details and simplify the experience.</li>
</ol>
</section>
<section
class="contact"
id="contact"
tabindex="-1"
aria-labelledby="contact-title"
>
<div>
<h2 id="contact-title">Make room for your next idea.</h2>
<p>
This is a static demo contact section. Add your own verified contact
destination when adapting it.
</p>
</div>
<a class="button" href="#process"
>See the process <span aria-hidden="true">↑</span></a
>
</section>
</main>
<footer class="wrap footer">
<span>MRHTML / Original navigation example</span
><span>Built around real links and a simple disclosure.</span>
</footer>
<script>
// MRHTML: start with visible links, then enhance narrow-screen navigation.
const toggle = document.getElementById('menu-toggle');
const nav = document.getElementById('site-navigation');
const header = document.querySelector('.site-header');
const compact = window.matchMedia('(max-width: 680px)');
const updateClearance = () => {
document.documentElement.style.setProperty(
'--header-clearance',
`${header.getBoundingClientRect().height + 20}px`,
);
};
const setOpen = (open) => {
const visible = !compact.matches || open;
nav.hidden = !visible;
toggle.setAttribute('aria-expanded', String(visible));
updateClearance();
};
const syncViewport = () => {
const active = document.activeElement;
if (compact.matches) {
toggle.hidden = false;
// Move focus before hiding the navigation that contains it.
if (nav.contains(active)) toggle.focus({ preventScroll: true });
setOpen(false);
} else {
setOpen(true);
// The disclosure disappears on wide screens; keep focus on a visible link.
if (active === toggle)
nav.querySelector('a').focus({ preventScroll: true });
toggle.hidden = true;
updateClearance();
}
};
toggle.addEventListener('click', () =>
setOpen(toggle.getAttribute('aria-expanded') !== 'true'),
);
header.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && compact.matches && !nav.hidden) {
event.preventDefault();
toggle.focus({ preventScroll: true });
setOpen(false);
}
});
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;
event.preventDefault();
if (compact.matches) setOpen(false);
destination.focus({ preventScroll: true });
destination.scrollIntoView({ block: 'start', behavior: 'auto' });
});
compact.addEventListener('change', syncViewport);
new ResizeObserver(updateClearance).observe(header);
syncViewport();
</script>
</body>
</html>