Focus — the skip link
Variant: Skip link
The one control on a page that exists solely for a keyboard and stays invisible until it is asked for, which makes it the rare entrance doing real explanatory work rather than decoration. The chip is not hidden with display or opacity — it is parked above its own top edge, under the header’s z-index, and revealed by the page’s overflow, so it drops out of the chrome it was always behind instead of materialising in front of it. It sits at -135% rather than -100% because it carries a shadow, and a chip parked at exactly its own height leaves that shadow as a smudge under the header. 197ms in and 136ms out: arriving is the interface answering a key it was not sure would ever be pressed, leaving is housekeeping. The ring on the first nav link starts fading up 88ms before the chip has finished retracting, so the handover is one event and focus is never nowhere — which is the whole promise a skip link makes.
The source
A complete HTML document. Paste it into an empty .html file and it plays — no stylesheet, script, font, or image to fetch.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Focus — the skip link</title>
<style>
:root {
/* Neutrals — fixed, never remixed. */
--ivory: #faf9f5;
--paper: #ffffff;
--oat: #e3dacc;
--slate: #141413;
/* Knobs — the remix API. A knob exists iff declared here and used below. */
--hue: 38.8;
--lift: 0;
--chroma: 1;
--round: 1;
--speed: 1;
/* Accents — derived from the knobs with the family's offsets baked in,
so one hue roll moves them together. Defaults reproduce the original hex. */
--clay: oklch(calc(0.6724 + var(--lift)) calc(0.1308 * var(--chroma)) var(--hue));
--clay-d: oklch(calc(0.5797 + var(--lift)) calc(0.127 * var(--chroma)) var(--hue));
--olive: oklch(
calc(0.6118 + var(--lift)) calc(0.0713 * var(--chroma)) calc(var(--hue) + 88.3)
);
--rust: oklch(
calc(0.5408 + var(--lift)) calc(0.1357 * var(--chroma)) calc(var(--hue) - 10.3)
);
/* Clock — 6.8s. The chip arrives in 197ms and leaves in 136ms: arriving
is the interface answering a key it was not sure would ever be
pressed, leaving is housekeeping. */
--cycle: 6.8s;
--t: calc(var(--cycle) / var(--speed));
--ease-accent: cubic-bezier(0.32, 0.72, 0, 1);
--_bar: 32px;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
min-height: 100vh;
display: grid;
place-items: center;
background: var(--ivory);
font-family:
system-ui,
-apple-system,
'Segoe UI',
Roboto,
sans-serif;
color: var(--slate);
}
.scene {
display: grid;
justify-items: center;
gap: 12px;
}
/* overflow: hidden is load-bearing, not tidiness — it is what makes the
parked chip disappear instead of hanging above the page. */
.page {
position: relative;
overflow: hidden;
width: clamp(220px, 78vw, 292px);
border-radius: calc(10px * var(--round));
background: var(--paper);
border: 1px solid color-mix(in srgb, var(--slate) 12%, transparent);
}
.bar {
position: relative;
z-index: 3;
display: flex;
align-items: center;
gap: 12px;
height: var(--_bar);
padding: 0 11px;
background: color-mix(in srgb, var(--oat) 52%, var(--paper));
border-bottom: 1px solid color-mix(in srgb, var(--slate) 10%, transparent);
}
.mark {
width: 9px;
height: 9px;
flex: none;
border-radius: 999px;
background: var(--clay);
}
.nav {
display: flex;
align-items: center;
gap: 12px;
margin-left: auto;
font-size: 10.5px;
color: color-mix(in srgb, var(--slate) 58%, transparent);
}
.n1 {
position: relative;
}
/* The ring lands on the first real link the moment the chip leaves —
focus is never nowhere, which is the entire promise a skip link makes. */
.n1::after {
content: '';
position: absolute;
inset: -4px -6px;
border: 2px solid var(--clay);
border-radius: calc(6px * var(--round));
opacity: 0;
transform: scale(1.08);
animation: navfocus var(--t) infinite;
}
/* Parked above its own top edge and under the header's z-index, which is
where a skip link lives in a real document: present in the DOM, first
in tab order, and invisible until something asks for it. */
.skip {
position: absolute;
z-index: 2;
top: var(--_bar);
left: 11px;
padding: 5px 10px;
border-radius: calc(7px * var(--round));
background: var(--paper);
border: 2px solid var(--clay);
box-shadow: 0 2px 5px -2px color-mix(in srgb, var(--slate) 45%, transparent);
font-size: 11px;
font-weight: 550;
letter-spacing: -0.005em;
white-space: nowrap;
transform: translateY(-135%);
animation: drop var(--t) infinite;
}
.content {
display: grid;
gap: 7px;
padding: 13px;
}
.line {
height: 7px;
border-radius: 999px;
background: color-mix(in srgb, var(--slate) 11%, transparent);
}
.line.head {
height: 9px;
width: 54%;
background: color-mix(in srgb, var(--slate) 22%, transparent);
}
.key {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 9px;
border-radius: calc(6px * var(--round));
background: var(--paper);
border: 1px solid color-mix(in srgb, var(--slate) 14%, transparent);
box-shadow: 0 1px 0 color-mix(in srgb, var(--slate) 10%, transparent);
font-size: 11px;
color: color-mix(in srgb, var(--slate) 55%, transparent);
animation: tap var(--t) infinite;
}
.key b {
font-weight: 600;
color: color-mix(in srgb, var(--slate) 80%, transparent);
}
/* -135% rather than -100%: the chip has a shadow, and a chip parked
exactly at its own height leaves that shadow visible as a smudge under
the header. */
@keyframes drop {
0%,
9% {
transform: translateY(-135%);
animation-timing-function: var(--ease-accent);
}
11.9%,
42% {
transform: translateY(0);
animation-timing-function: var(--ease-accent);
}
44%,
100% {
transform: translateY(-135%);
}
}
/* Starts at 1.08 and settles, never at 0 — the ring is arriving from
somewhere, and the overshoot direction says the somewhere was outside
the link rather than inside it. The two motions overlap by 88ms so the
handover reads as one event. */
@keyframes navfocus {
0%,
42.6% {
opacity: 0;
transform: scale(1.08);
animation-timing-function: var(--ease-accent);
}
44.6%,
72% {
opacity: 1;
transform: scale(1);
animation-timing-function: ease;
}
74%,
100% {
opacity: 0;
transform: scale(1);
}
}
@keyframes tap {
0%,
7.8% {
transform: translateY(0);
background: var(--paper);
animation-timing-function: ease;
}
8.4% {
transform: translateY(1px);
background: color-mix(in srgb, var(--clay) 15%, var(--paper));
animation-timing-function: ease;
}
10.6%,
40.8% {
transform: translateY(0);
background: var(--paper);
animation-timing-function: ease;
}
41.4% {
transform: translateY(1px);
background: color-mix(in srgb, var(--clay) 15%, var(--paper));
animation-timing-function: ease;
}
43.6%,
100% {
transform: translateY(0);
background: var(--paper);
}
}
@media (prefers-reduced-motion: reduce) {
/* The chip stops sliding and simply is or is not there, on the same
timings — a skip link that fades in place is a perfectly ordinary
skip link, and the slide was only ever an explanation of where it
came from. The ring keeps its fade and drops its overshoot. */
.skip {
transform: translateY(0);
opacity: 0;
animation-name: drop-flat;
}
.n1::after {
transform: scale(1);
animation-name: navfocus-flat;
}
.skip,
.n1::after,
.key {
animation-duration: calc(var(--t) * 1.6);
}
@keyframes drop-flat {
0%,
9% {
opacity: 0;
animation-timing-function: ease;
}
11.9%,
42% {
opacity: 1;
animation-timing-function: ease;
}
44%,
100% {
opacity: 0;
}
}
@keyframes navfocus-flat {
0%,
42.6% {
opacity: 0;
animation-timing-function: ease;
}
44.6%,
72% {
opacity: 1;
animation-timing-function: ease;
}
74%,
100% {
opacity: 0;
}
}
}
</style>
</head>
<body>
<div
class="scene"
role="img"
aria-label="A skip-to-content chip dropping out from behind a page header on the first Tab, then retracting as focus hands over to the first navigation link"
>
<div class="page">
<div class="bar">
<span class="mark"></span>
<span class="nav">
<span class="n1">Docs</span>
<span>Pricing</span>
<span>Log in</span>
</span>
</div>
<span class="skip">Skip to content</span>
<div class="content">
<span class="line head"></span>
<span class="line"></span>
<span class="line"></span>
<span class="line" style="width: 88%"></span>
<span class="line" style="width: 46%"></span>
</div>
</div>
<span class="key"><b>Tab</b> from the top of the page</span>
</div>
</body>
</html>
The variant family
Same idea, one axis moved. Compare them side by side before you commit.