ナビゲーション
スキップリンク導線
キーボード操作時に主要コンテンツへ素早く移動できるスキップリンクの設計と状態を確認するレシピです。
動きを試す
プレビュー
必要なファイルを選ぶ
コード
index.html
利用前に変更する箇所
- ターゲットIDと文言、レイアウトを対象画面へ合わせる
<!doctype html>
<html lang="ja" data-theme="light">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>スキップリンク導線</title>
<style>
:root {
--bg: #f7f8fa;
--surface: #ffffff;
--surface-muted: #eff2f6;
--text: #18212f;
--muted: #5d6b7a;
--border: #d7dee8;
--accent: #245fd6;
--focus: #0077cc;
}
:root[data-theme="dark"] {
--bg: #0f141a;
--surface: #171d26;
--surface-muted: #202834;
--text: #f2f5f8;
--muted: #aab4c0;
--border: #354152;
--accent: #7ea7ff;
--focus: #65c7ff;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 18px;
background: var(--bg);
color: var(--text);
font-family: Arial, sans-serif;
}
main {
max-width: 700px;
margin: 0 auto;
}
.surface {
padding: 14px;
border: 1px solid var(--border);
border-radius: 12px;
background: var(--surface);
}
.skip-link {
position: absolute;
top: 8px;
left: 8px;
background: var(--surface);
border: 2px solid var(--accent);
border-radius: 8px;
padding: 10px 14px;
transform: translateY(-170%);
transition: transform 0.15s ease;
}
.skip-link:focus-within {
transform: translateY(0);
}
.skip-link a {
color: var(--accent);
}
.skip-link a:focus-visible,
.target:focus-visible,
button:focus-visible,
[data-set-state]:focus-visible {
outline: 3px solid var(--focus);
}
#content {
margin-top: 18px;
}
.state-desc {
min-height: 1.4em;
}
[data-state-panel][hidden] {
display: none;
}
p {
color: var(--muted);
}
button {
margin-right: 8px;
min-height: 44px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--surface-muted);
color: var(--text);
}
@media (max-width: 360px) {
body {
padding: 10px;
}
}
@media (prefers-reduced-motion: reduce) {
* {
transition: none !important;
animation: none !important;
}
}
</style>
</head>
<body>
<main>
<p class="skip-link"><a href="#main-content" data-skip-link>本文をスキップ</a></p>
<section class="surface">
<h1>スキップリンクの確認</h1>
<p>通常表示、フォーカス時表示、移動完了の3状態を持ちます。</p>
<div data-state-panel="hidden" hidden>
<h2>非表示</h2>
<p>リンクは通常は見えません。Tabを進めると表示されます。</p>
</div>
<div data-state-panel="focus">
<h2>表示</h2>
<p>現在の状態はリンク表示。リンクをアクティブにできます。</p>
</div>
<div data-state-panel="moved" hidden>
<h2>移動完了</h2>
<p>本文の主要領域へフォーカスを移動しました。</p>
</div>
<div class="state-desc" role="status" data-status></div>
<div style="margin-top: 10px">
<button type="button" data-set-state="hidden">hidden</button>
<button type="button" data-set-state="focus">focus</button>
<button type="button" data-set-state="moved">moved</button>
</div>
<section id="main-content" tabindex="-1" class="target" style="margin-top: 18px">
<h2>本文の開始</h2>
<p>この領域へ直接移動します。</p>
</section>
</section>
</main>
<script>
(() => {
const params = new URLSearchParams(window.location.search);
const instance = params.get("instance") ?? "missing-instance";
const panels = Array.from(document.querySelectorAll("[data-state-panel]"));
const status = document.querySelector("[data-status]");
const buttons = document.querySelectorAll("[data-set-state]");
const skipLink = document.querySelector('[data-skip-link]');
const target = document.getElementById("main-content");
const validStates = new Set(["hidden", "focus", "moved"]);
const setState = (nextState) => {
if (!validStates.has(nextState)) return;
document.documentElement.dataset.state = nextState;
for (const panel of panels) {
panel.hidden = panel.dataset.statePanel !== nextState;
}
if (status) {
status.textContent = `状態: ${nextState}`;
}
};
const setTheme = (theme) => {
if (theme === "dark" || theme === "light") {
document.documentElement.dataset.theme = theme;
}
};
for (const button of buttons) {
button.addEventListener("click", () => {
setState(button.dataset.setState ?? "hidden");
});
}
if (skipLink) {
skipLink.addEventListener("focus", () => setState("focus"), { passive: true });
skipLink.addEventListener("click", () => {
setState("moved");
target?.focus({ preventScroll: true });
});
}
window.addEventListener("message", (event) => {
if (
event.source !== window.parent ||
typeof event.data !== "object" ||
event.data === null ||
event.data.source !== "wpl-host" ||
event.data.instance !== instance
) {
return;
}
if (event.data.type === "wpl-preview-state") {
setState(event.data.value);
}
if (event.data.type === "wpl-preview-theme") {
setTheme(event.data.value);
}
});
setTheme(params.get("theme") === "dark" ? "dark" : "light");
setState(validStates.has(params.get("state")) ? (params.get("state") ?? "hidden") : "hidden");
window.parent.postMessage({ source: "wpl-preview", type: "wpl-preview-ready", instance }, "*");
})();
</script>
</body>
</html>
プレビューで試せる動き
- 非表示: 初期は見えず、フォーカス時に現れます。
- 表示: キーボードフォーカスを受け取ると表示されます。
- 移動: ターゲットへ遷移した後、注視可能な状態にします。
使い分けを確認する
このレシピが合う場面
適する場面
- 長いページで本文最初の入力欄へ移動したい場面
- ナビゲーションを経由せず主要作業に進みたい場面
- キーボード利用が主となる画面で導線を確認する
適さない場面
- 単一セクションしかないページ
- 主要なターゲットDOMが動的に入れ替わるページでid固定が不安定な場合
