Wiki — Version / Language Switcher
A self-contained docs header cluster pairing an accessible version dropdown (v3.2 latest, v3.1, v3.0, v2.x with latest and EOL badges) and a language dropdown (English, Español, 日本語, Deutsch, Português). Selecting an older release raises a dismissible outdated-version banner with a one-click jump to latest, while picking a language swaps a localized last-updated string. Both menus open with click or keyboard, support arrow, Home, End, Enter and Escape navigation, manage focus correctly, and close on outside click.
MCP
程式碼
:root {
--bg: #ffffff;
--bg-2: #f7f8fa;
--panel: #ffffff;
--ink: #1a1a1f;
--ink-2: #3a3a42;
--muted: #6b7280;
--line: rgba(20, 20, 30, 0.10);
--line-2: rgba(20, 20, 30, 0.18);
--link: #2563eb;
--link-hover: #1d4ed8;
--accent: #2563eb;
--note: #2563eb;
--tip: #16a34a;
--warn: #d97706;
--danger: #dc2626;
--code-bg: #f4f4f6;
--kbd-bg: #eceef2;
--r-sm: 6px;
--r-md: 10px;
--r-lg: 14px;
--font-ui: "Inter", system-ui, -apple-system, sans-serif;
--font-serif: "Source Serif 4", Georgia, serif;
--font-mono: "JetBrains Mono", ui-monospace, "SF Mono", monospace;
--topbar-h: 60px;
--sidebar-w: 256px;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: var(--font-ui);
font-size: 15px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a { color: var(--link); text-decoration: none; }
a:hover { color: var(--link-hover); text-decoration: underline; }
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: var(--r-sm);
}
.skip-link {
position: absolute;
left: -999px;
top: 8px;
z-index: 100;
background: var(--panel);
border: 1px solid var(--line-2);
padding: 8px 14px;
border-radius: var(--r-sm);
font-weight: 600;
}
.skip-link:focus { left: 12px; }
/* ───────── Topbar ───────── */
.topbar {
position: sticky;
top: 0;
z-index: 40;
height: var(--topbar-h);
background: rgba(255, 255, 255, 0.86);
backdrop-filter: saturate(180%) blur(10px);
border-bottom: 1px solid var(--line);
}
.topbar-inner {
height: 100%;
display: flex;
align-items: center;
gap: 14px;
padding: 0 18px;
max-width: 1280px;
margin: 0 auto;
}
.nav-toggle {
display: none;
flex-direction: column;
justify-content: center;
gap: 4px;
width: 38px;
height: 38px;
background: transparent;
border: 1px solid var(--line);
border-radius: var(--r-sm);
cursor: pointer;
padding: 0 9px;
}
.nav-toggle span {
display: block;
height: 2px;
background: var(--ink-2);
border-radius: 2px;
transition: transform 0.2s, opacity 0.2s;
}
.brand {
display: flex;
align-items: center;
gap: 8px;
font-weight: 800;
color: var(--ink);
}
.brand:hover { text-decoration: none; }
.brand-mark { color: var(--accent); font-size: 18px; }
.brand-name { letter-spacing: -0.01em; }
.brand-tag {
font-weight: 600;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted);
background: var(--bg-2);
border: 1px solid var(--line);
padding: 2px 7px;
border-radius: 999px;
}
.topbar-switchers {
margin-left: auto;
display: flex;
align-items: center;
gap: 10px;
}
/* ───────── Switcher (custom dropdown) ───────── */
.switcher { position: relative; }
.switcher-btn {
display: inline-flex;
align-items: center;
gap: 9px;
height: 38px;
padding: 0 11px;
background: var(--panel);
border: 1px solid var(--line-2);
border-radius: var(--r-md);
cursor: pointer;
font-family: var(--font-ui);
color: var(--ink);
transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
}
.switcher-btn:hover { border-color: var(--accent); background: var(--bg-2); }
.switcher-btn[aria-expanded="true"] {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.14);
}
.switcher-icon { font-size: 14px; opacity: 0.85; }
.switcher-text {
display: flex;
flex-direction: column;
line-height: 1.1;
text-align: left;
}
.switcher-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
}
.switcher-value { font-size: 13.5px; font-weight: 700; }
.chev {
font-size: 10px;
color: var(--muted);
transition: transform 0.18s ease;
}
.switcher-btn[aria-expanded="true"] .chev { transform: rotate(180deg); }
/* Badges */
.badge {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.04em;
padding: 2px 6px;
border-radius: 999px;
line-height: 1.4;
white-space: nowrap;
}
.badge-latest { color: #166534; background: rgba(22, 163, 74, 0.12); border: 1px solid rgba(22, 163, 74, 0.3); }
.badge-eol { color: #991b1b; background: rgba(220, 38, 38, 0.10); border: 1px solid rgba(220, 38, 38, 0.3); }
/* Menu */
.switcher-menu {
position: absolute;
top: calc(100% + 8px);
right: 0;
min-width: 248px;
margin: 0;
padding: 6px;
list-style: none;
background: var(--panel);
border: 1px solid var(--line-2);
border-radius: var(--r-lg);
box-shadow: 0 12px 32px rgba(20, 20, 30, 0.16), 0 2px 6px rgba(20, 20, 30, 0.06);
z-index: 50;
transform-origin: top right;
animation: menuIn 0.14s ease;
}
@keyframes menuIn {
from { opacity: 0; transform: translateY(-4px) scale(0.98); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
.switcher-menu[hidden] { display: none; }
.switcher-opt {
display: grid;
grid-template-columns: auto 1fr auto auto;
align-items: center;
gap: 4px 9px;
padding: 9px 10px;
border-radius: var(--r-md);
cursor: pointer;
position: relative;
}
.switcher-opt:hover,
.switcher-opt.is-active {
background: var(--bg-2);
}
.switcher-opt.is-active { outline: 2px solid rgba(37, 99, 235, 0.4); outline-offset: -2px; }
.opt-flag { font-size: 16px; grid-row: span 2; }
.opt-main { font-weight: 700; font-size: 14px; grid-column: 2; }
.opt-meta, .opt-sub {
grid-column: 2;
font-size: 11.5px;
color: var(--muted);
}
.switcher-opt .badge { grid-column: 3; }
.opt-check {
grid-column: 4;
grid-row: 1 / -1;
color: var(--accent);
font-weight: 800;
opacity: 0;
}
.switcher-opt.is-selected .opt-check { opacity: 1; }
.switcher-opt.is-selected .opt-main { color: var(--accent); }
/* ───────── Layout ───────── */
.layout {
display: flex;
max-width: 1280px;
margin: 0 auto;
align-items: flex-start;
}
.sidebar {
position: sticky;
top: var(--topbar-h);
width: var(--sidebar-w);
flex: none;
height: calc(100vh - var(--topbar-h));
border-right: 1px solid var(--line);
background: var(--bg);
}
.sidebar-scroll {
height: 100%;
overflow-y: auto;
padding: 22px 14px 40px;
}
.nav-group { margin-bottom: 22px; }
.nav-title {
margin: 0 0 8px;
padding: 0 10px;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--muted);
}
.nav-group ul { list-style: none; margin: 0; padding: 0; }
.nav-group li a {
display: block;
padding: 6px 10px;
border-radius: var(--r-sm);
color: var(--ink-2);
font-size: 14px;
font-weight: 500;
}
.nav-group li a:hover { background: var(--bg-2); color: var(--ink); text-decoration: none; }
.nav-group li a.is-current {
background: rgba(37, 99, 235, 0.10);
color: var(--link);
font-weight: 600;
box-shadow: inset 2px 0 0 var(--accent);
}
.scrim {
position: fixed;
inset: 0;
background: rgba(15, 15, 25, 0.4);
z-index: 30;
opacity: 0;
transition: opacity 0.2s;
}
/* ───────── Content ───────── */
.content {
flex: 1 1 auto;
min-width: 0;
padding: 28px clamp(20px, 5vw, 56px) 64px;
}
/* Outdated banner */
.banner {
display: flex;
align-items: flex-start;
gap: 12px;
max-width: 760px;
margin: 0 auto 26px;
padding: 13px 15px;
background: rgba(217, 119, 6, 0.08);
border: 1px solid rgba(217, 119, 6, 0.32);
border-left: 4px solid var(--warn);
border-radius: var(--r-md);
animation: bannerIn 0.2s ease;
}
@keyframes bannerIn {
from { opacity: 0; transform: translateY(-6px); }
to { opacity: 1; transform: translateY(0); }
}
.banner[hidden] { display: none; }
.banner-icon { color: var(--warn); font-size: 17px; line-height: 1.4; }
.banner-text { margin: 0; font-size: 13.5px; color: var(--ink-2); line-height: 1.5; }
.banner-text strong { color: var(--ink); }
.banner-x {
margin-left: auto;
background: transparent;
border: none;
color: var(--muted);
cursor: pointer;
font-size: 13px;
padding: 2px 6px;
border-radius: var(--r-sm);
}
.banner-x:hover { background: rgba(217, 119, 6, 0.14); color: var(--ink); }
.article { max-width: 760px; margin: 0 auto; }
.crumbs {
display: flex;
flex-wrap: wrap;
gap: 7px;
font-size: 12.5px;
color: var(--muted);
margin-bottom: 16px;
}
.crumbs a { color: var(--muted); }
.crumbs a:hover { color: var(--link); }
.doc-head { margin-bottom: 22px; }
.doc-meta {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
.pill {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--accent);
background: rgba(37, 99, 235, 0.10);
padding: 3px 9px;
border-radius: 999px;
}
.updated { font-size: 12.5px; color: var(--muted); }
.article h1 {
font-family: var(--font-ui);
font-size: 33px;
font-weight: 800;
letter-spacing: -0.02em;
line-height: 1.15;
margin: 0 0 14px;
}
.lede {
font-family: var(--font-serif);
font-size: 18px;
line-height: 1.6;
color: var(--ink-2);
margin: 0;
}
.article h2 {
font-family: var(--font-ui);
font-size: 22px;
font-weight: 700;
letter-spacing: -0.01em;
margin: 38px 0 12px;
padding-top: 10px;
scroll-margin-top: calc(var(--topbar-h) + 14px);
}
.article p {
font-family: var(--font-serif);
font-size: 16.5px;
line-height: 1.65;
color: var(--ink);
margin: 0 0 16px;
}
.article :not(pre) > code,
.lede code, p code, td code {
font-family: var(--font-mono);
font-size: 0.86em;
background: var(--code-bg);
border: 1px solid var(--line);
border-radius: var(--r-sm);
padding: 1px 5px;
color: var(--ink);
}
.code {
font-family: var(--font-mono);
background: var(--code-bg);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 14px 16px;
overflow-x: auto;
margin: 0 0 18px;
line-height: 1.6;
}
.code code { font-size: 13px; color: var(--ink); }
kbd {
font-family: var(--font-mono);
font-size: 12px;
background: var(--kbd-bg);
border: 1px solid var(--line-2);
border-bottom-width: 2px;
border-radius: var(--r-sm);
padding: 1px 6px;
color: var(--ink-2);
}
.callout {
margin: 0 0 20px;
padding: 13px 16px;
border: 1px solid var(--line);
border-left: 4px solid var(--muted);
border-radius: var(--r-md);
background: var(--bg-2);
}
.callout p { font-family: var(--font-ui); font-size: 14.5px; line-height: 1.55; margin: 0; }
.callout-note { border-left-color: var(--note); background: rgba(37, 99, 235, 0.05); }
.callout-tip { border-left-color: var(--tip); background: rgba(22, 163, 74, 0.05); }
.callout-warn { border-left-color: var(--warn); background: rgba(217, 119, 6, 0.05); }
.table-wrap { overflow-x: auto; margin: 0 0 20px; border: 1px solid var(--line); border-radius: var(--r-md); }
table { border-collapse: collapse; width: 100%; font-family: var(--font-ui); font-size: 14px; }
th, td { text-align: left; padding: 10px 14px; border-bottom: 1px solid var(--line); }
thead th { background: var(--bg-2); font-weight: 700; font-size: 12.5px; color: var(--ink-2); }
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--bg-2); }
.content-foot {
max-width: 760px;
margin: 44px auto 0;
padding-top: 18px;
border-top: 1px solid var(--line);
font-size: 13px;
color: var(--muted);
}
/* ───────── Toast ───────── */
.toast {
position: fixed;
left: 50%;
bottom: 24px;
transform: translateX(-50%) translateY(10px);
background: var(--ink);
color: #fff;
font-size: 13.5px;
font-weight: 500;
padding: 10px 16px;
border-radius: var(--r-md);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22);
opacity: 0;
pointer-events: none;
transition: opacity 0.22s, transform 0.22s;
z-index: 80;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
/* ───────── Responsive ───────── */
@media (max-width: 820px) {
.nav-toggle { display: flex; }
.sidebar {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 280px;
max-width: 84vw;
z-index: 35;
background: var(--panel);
border-right: 1px solid var(--line-2);
box-shadow: 0 0 40px rgba(0, 0, 0, 0.18);
transform: translateX(-100%);
transition: transform 0.24s ease;
}
.sidebar-scroll { padding-top: 18px; }
body.nav-open .sidebar { transform: translateX(0); }
body.nav-open .scrim { display: block; opacity: 1; }
.switcher-label { display: none; }
.switcher-btn { gap: 6px; padding: 0 9px; }
}
@media (max-width: 520px) {
.topbar-inner { gap: 8px; padding: 0 12px; }
.brand-tag { display: none; }
.topbar-switchers { gap: 6px; }
.switcher-icon { display: none; }
.switcher-value { font-size: 12.5px; }
.switcher-menu { min-width: 220px; position: fixed; right: 12px; left: 12px; }
.article h1 { font-size: 27px; }
.lede { font-size: 16px; }
.content { padding: 22px 16px 56px; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
html { scroll-behavior: auto; }
}/* Aurora DB docs — version + language switcher
Accessible custom dropdowns, outdated-version banner, language demo string. */
(function () {
"use strict";
const LATEST = "v3.2";
/* ───────── toast helper ───────── */
const toastEl = document.getElementById("toast");
let toastTimer = null;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.hidden = false;
requestAnimationFrame(() => toastEl.classList.add("show"));
clearTimeout(toastTimer);
toastTimer = setTimeout(() => {
toastEl.classList.remove("show");
setTimeout(() => (toastEl.hidden = true), 240);
}, 2200);
}
/* ───────── generic accessible dropdown ───────── */
function Dropdown(rootId, btnId, menuId, onSelect) {
const root = document.getElementById(rootId);
const btn = document.getElementById(btnId);
const menu = document.getElementById(menuId);
if (!root || !btn || !menu) return null;
const opts = () => Array.from(menu.querySelectorAll('[role="option"]'));
let activeIndex = -1;
function setActive(i) {
const list = opts();
if (!list.length) return;
activeIndex = (i + list.length) % list.length;
list.forEach((o, idx) => {
const on = idx === activeIndex;
o.classList.toggle("is-active", on);
if (on) {
o.focus();
o.scrollIntoView({ block: "nearest" });
}
});
}
function isOpen() {
return btn.getAttribute("aria-expanded") === "true";
}
function open() {
if (isOpen()) return;
closeAll(root);
btn.setAttribute("aria-expanded", "true");
menu.hidden = false;
const sel = opts().findIndex((o) => o.getAttribute("aria-selected") === "true");
setActive(sel >= 0 ? sel : 0);
}
function close(focusBtn) {
if (!isOpen()) return;
btn.setAttribute("aria-expanded", "false");
menu.hidden = true;
opts().forEach((o) => o.classList.remove("is-active"));
activeIndex = -1;
if (focusBtn) btn.focus();
}
function choose(opt) {
opts().forEach((o) => {
o.classList.remove("is-selected");
o.setAttribute("aria-selected", "false");
});
opt.classList.add("is-selected");
opt.setAttribute("aria-selected", "true");
onSelect(opt);
close(true);
}
btn.addEventListener("click", () => (isOpen() ? close(true) : open()));
btn.addEventListener("keydown", (e) => {
if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
e.preventDefault();
open();
} else if (e.key === "ArrowUp") {
e.preventDefault();
open();
setActive(opts().length - 1);
}
});
menu.addEventListener("keydown", (e) => {
switch (e.key) {
case "ArrowDown":
e.preventDefault();
setActive(activeIndex + 1);
break;
case "ArrowUp":
e.preventDefault();
setActive(activeIndex - 1);
break;
case "Home":
e.preventDefault();
setActive(0);
break;
case "End":
e.preventDefault();
setActive(opts().length - 1);
break;
case "Enter":
case " ":
e.preventDefault();
if (activeIndex >= 0) choose(opts()[activeIndex]);
break;
case "Escape":
e.preventDefault();
close(true);
break;
case "Tab":
close(false);
break;
}
});
menu.addEventListener("click", (e) => {
const opt = e.target.closest('[role="option"]');
if (opt) choose(opt);
});
menu.addEventListener("mousemove", (e) => {
const opt = e.target.closest('[role="option"]');
if (!opt) return;
const i = opts().indexOf(opt);
if (i !== activeIndex) {
activeIndex = i;
opts().forEach((o, idx) => o.classList.toggle("is-active", idx === i));
}
});
return { root, close };
}
const dropdowns = [];
function closeAll(except) {
dropdowns.forEach((d) => {
if (d && d.root !== except) d.close(false);
});
}
/* ───────── version switcher ───────── */
const versionValue = document.getElementById("versionValue");
const versionBadge = document.getElementById("versionBadge");
const banner = document.getElementById("outdatedBanner");
const bannerVersion = document.getElementById("bannerVersion");
function applyBadge(el, type) {
el.classList.remove("badge-latest", "badge-eol");
if (type === "latest") {
el.textContent = "latest";
el.classList.add("badge-latest");
el.hidden = false;
} else if (type === "eol") {
el.textContent = "EOL";
el.classList.add("badge-eol");
el.hidden = false;
} else {
el.hidden = true;
}
}
function selectVersion(opt) {
const v = opt.dataset.value;
const badge = opt.dataset.badge || "";
versionValue.textContent = v;
applyBadge(versionBadge, badge);
if (v === LATEST) {
banner.hidden = true;
toast("Now viewing " + v + " (latest)");
} else {
bannerVersion.textContent = v;
banner.hidden = false;
toast("Switched to " + v + (badge === "eol" ? " — end of life" : ""));
}
}
const versionDd = Dropdown("versionSwitcher", "versionBtn", "versionMenu", selectVersion);
dropdowns.push(versionDd);
/* ───────── language switcher ───────── */
const langValue = document.getElementById("langValue");
const updatedString = document.getElementById("updatedString");
const LANG_NAMES = { en: "English", es: "Español", ja: "日本語", de: "Deutsch", pt: "Português" };
function selectLanguage(opt) {
const code = opt.dataset.value;
langValue.textContent = LANG_NAMES[code] || opt.querySelector(".opt-main").textContent;
updatedString.textContent = opt.dataset.string;
document.documentElement.lang = code;
toast("Language: " + langValue.textContent);
}
const langDd = Dropdown("langSwitcher", "langBtn", "langMenu", selectLanguage);
dropdowns.push(langDd);
/* close on outside click */
document.addEventListener("click", (e) => {
if (!e.target.closest(".switcher")) closeAll(null);
});
/* banner controls */
const bannerClose = document.getElementById("bannerClose");
if (bannerClose) {
bannerClose.addEventListener("click", () => {
banner.hidden = true;
toast("Notice dismissed");
});
}
const bannerLatest = document.getElementById("bannerLatest");
if (bannerLatest) {
bannerLatest.addEventListener("click", (e) => {
e.preventDefault();
const latestOpt = document.querySelector('#versionMenu [data-value="' + LATEST + '"]');
if (latestOpt) latestOpt.click();
});
}
/* ───────── mobile sidebar drawer ───────── */
const navToggle = document.getElementById("navToggle");
const scrim = document.getElementById("scrim");
function setNav(open) {
document.body.classList.toggle("nav-open", open);
navToggle.setAttribute("aria-expanded", String(open));
if (open) scrim.hidden = false;
else setTimeout(() => (scrim.hidden = true), 220);
}
if (navToggle) {
navToggle.addEventListener("click", () => setNav(!document.body.classList.contains("nav-open")));
}
if (scrim) scrim.addEventListener("click", () => setNav(false));
document.querySelectorAll(".sidebar a").forEach((a) =>
a.addEventListener("click", () => {
if (window.matchMedia("(max-width: 820px)").matches) setNav(false);
})
);
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && document.body.classList.contains("nav-open")) setNav(false);
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Aurora DB Docs — Version & Language Switcher</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Source+Serif+4:opsz,[email protected],400;8..60,500;8..60,600&family=JetBrains+Mono:wght@400;500;600&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<!-- Topbar -->
<header class="topbar">
<div class="topbar-inner">
<button class="nav-toggle" id="navToggle" aria-label="Toggle navigation" aria-expanded="false" aria-controls="sidebar">
<span></span><span></span><span></span>
</button>
<a class="brand" href="#main" aria-label="Aurora DB home">
<span class="brand-mark" aria-hidden="true">◆</span>
<span class="brand-name">Aurora DB</span>
<span class="brand-tag">docs</span>
</a>
<div class="topbar-switchers">
<!-- Version switcher -->
<div class="switcher" id="versionSwitcher">
<button
type="button"
class="switcher-btn"
id="versionBtn"
aria-haspopup="listbox"
aria-expanded="false"
aria-controls="versionMenu"
aria-labelledby="versionLabel versionValue"
>
<span class="switcher-icon" aria-hidden="true">⌥</span>
<span class="switcher-text">
<span class="switcher-label" id="versionLabel">Version</span>
<span class="switcher-value" id="versionValue">v3.2</span>
</span>
<span class="badge badge-latest" id="versionBadge" data-badge="latest">latest</span>
<span class="chev" aria-hidden="true">▾</span>
</button>
<ul class="switcher-menu" id="versionMenu" role="listbox" aria-label="Documentation version" tabindex="-1" hidden>
<li role="option" class="switcher-opt is-selected" data-value="v3.2" data-badge="latest" aria-selected="true" tabindex="-1">
<span class="opt-main">v3.2</span>
<span class="opt-meta">Aurora 2026.1 · current release</span>
<span class="badge badge-latest">latest</span>
<span class="opt-check" aria-hidden="true">✓</span>
</li>
<li role="option" class="switcher-opt" data-value="v3.1" data-badge="" aria-selected="false" tabindex="-1">
<span class="opt-main">v3.1</span>
<span class="opt-meta">Aurora 2025.3 · maintenance</span>
<span class="opt-check" aria-hidden="true">✓</span>
</li>
<li role="option" class="switcher-opt" data-value="v3.0" data-badge="" aria-selected="false" tabindex="-1">
<span class="opt-main">v3.0</span>
<span class="opt-meta">Aurora 2025.1 · maintenance</span>
<span class="opt-check" aria-hidden="true">✓</span>
</li>
<li role="option" class="switcher-opt" data-value="v2.x" data-badge="eol" aria-selected="false" tabindex="-1">
<span class="opt-main">v2.x</span>
<span class="opt-meta">Aurora 2024 · legacy line</span>
<span class="badge badge-eol">EOL</span>
<span class="opt-check" aria-hidden="true">✓</span>
</li>
</ul>
</div>
<!-- Language switcher -->
<div class="switcher" id="langSwitcher">
<button
type="button"
class="switcher-btn"
id="langBtn"
aria-haspopup="listbox"
aria-expanded="false"
aria-controls="langMenu"
aria-labelledby="langLabel langValue"
>
<span class="switcher-icon" aria-hidden="true">🌐</span>
<span class="switcher-text">
<span class="switcher-label" id="langLabel">Language</span>
<span class="switcher-value" id="langValue">English</span>
</span>
<span class="chev" aria-hidden="true">▾</span>
</button>
<ul class="switcher-menu" id="langMenu" role="listbox" aria-label="Documentation language" tabindex="-1" hidden>
<li role="option" class="switcher-opt is-selected" data-value="en" data-string="Last updated 3 days ago" aria-selected="true" tabindex="-1">
<span class="opt-flag" aria-hidden="true">🇬🇧</span>
<span class="opt-main">English</span>
<span class="opt-sub">English</span>
<span class="opt-check" aria-hidden="true">✓</span>
</li>
<li role="option" class="switcher-opt" data-value="es" data-string="Actualizado hace 3 días" aria-selected="false" tabindex="-1">
<span class="opt-flag" aria-hidden="true">🇪🇸</span>
<span class="opt-main">Español</span>
<span class="opt-sub">Spanish</span>
<span class="opt-check" aria-hidden="true">✓</span>
</li>
<li role="option" class="switcher-opt" data-value="ja" data-string="3日前に更新" aria-selected="false" tabindex="-1">
<span class="opt-flag" aria-hidden="true">🇯🇵</span>
<span class="opt-main">日本語</span>
<span class="opt-sub">Japanese</span>
<span class="opt-check" aria-hidden="true">✓</span>
</li>
<li role="option" class="switcher-opt" data-value="de" data-string="Zuletzt vor 3 Tagen aktualisiert" aria-selected="false" tabindex="-1">
<span class="opt-flag" aria-hidden="true">🇩🇪</span>
<span class="opt-main">Deutsch</span>
<span class="opt-sub">German</span>
<span class="opt-check" aria-hidden="true">✓</span>
</li>
<li role="option" class="switcher-opt" data-value="pt" data-string="Atualizado há 3 dias" aria-selected="false" tabindex="-1">
<span class="opt-flag" aria-hidden="true">🇧🇷</span>
<span class="opt-main">Português</span>
<span class="opt-sub">Portuguese</span>
<span class="opt-check" aria-hidden="true">✓</span>
</li>
</ul>
</div>
</div>
</div>
</header>
<div class="layout">
<!-- Sidebar -->
<nav class="sidebar" id="sidebar" aria-label="Documentation sections">
<div class="sidebar-scroll">
<div class="nav-group">
<p class="nav-title">Getting started</p>
<ul>
<li><a href="#main">Introduction</a></li>
<li><a href="#install">Installation</a></li>
<li><a href="#quickstart">Quickstart</a></li>
</ul>
</div>
<div class="nav-group">
<p class="nav-title">Core concepts</p>
<ul>
<li><a href="#cluster" class="is-current" aria-current="page">Cluster topology</a></li>
<li><a href="#replication">Replication</a></li>
<li><a href="#consistency">Consistency model</a></li>
<li><a href="#sharding">Sharding</a></li>
</ul>
</div>
<div class="nav-group">
<p class="nav-title">Operations</p>
<ul>
<li><a href="#backups">Backups & restore</a></li>
<li><a href="#upgrades">Rolling upgrades</a></li>
<li><a href="#metrics">Metrics & alerts</a></li>
</ul>
</div>
</div>
</nav>
<div class="scrim" id="scrim" hidden></div>
<!-- Main article -->
<main class="content" id="main">
<!-- Outdated banner -->
<div class="banner" id="outdatedBanner" role="alert" hidden>
<span class="banner-icon" aria-hidden="true">⚠</span>
<p class="banner-text">
You are viewing docs for <strong id="bannerVersion">v3.1</strong>, not the latest release.
Some APIs may differ. <a href="#main" id="bannerLatest">Switch to v3.2 (latest)</a>.
</p>
<button class="banner-x" id="bannerClose" aria-label="Dismiss notice">✕</button>
</div>
<article class="article">
<nav class="crumbs" aria-label="Breadcrumb">
<a href="#main">Docs</a><span aria-hidden="true">/</span>
<a href="#cluster">Core concepts</a><span aria-hidden="true">/</span>
<span aria-current="page">Cluster topology</span>
</nav>
<header class="doc-head" id="cluster">
<div class="doc-meta">
<span class="pill">Core concepts</span>
<span class="updated" id="updatedString">Last updated 3 days ago</span>
</div>
<h1>Cluster topology</h1>
<p class="lede">
Aurora DB organizes nodes into a single logical cluster split across regions. This page
explains how the control plane, coordinator nodes, and storage replicas fit together, and
how the topology changes when you scale Project Nimbus deployments.
</p>
</header>
<p>
A cluster is the unit of failure isolation in Aurora DB. Every cluster runs an odd number
of <strong>coordinator</strong> nodes that participate in the Raft-based control loop, plus
any number of stateless <strong>gateway</strong> nodes and durable <strong>storage</strong>
replicas. The coordinators elect a leader; the leader owns the routing table.
</p>
<h2 id="replication">Replication factor</h2>
<p>
By default each shard is replicated <code>RF=3</code> across distinct availability zones.
Writes are acknowledged once a quorum of replicas has persisted the entry to its write-ahead
log. You can raise the replication factor per keyspace for hot data:
</p>
<pre class="code"><code>ALTER KEYSPACE verdant
WITH replication = { 'class': 'ZoneAware', 'rf': 5 };</code></pre>
<blockquote class="callout callout-note">
<p><strong>Note —</strong> Increasing the replication factor improves durability but raises
write amplification. Plan storage for roughly <code>rf × dataset</code> before enabling.</p>
</blockquote>
<h2 id="consistency">Consistency model</h2>
<p>
Aurora DB offers tunable consistency. The table below summarizes the read/write level pairs
that guarantee read-your-writes within the Verdant Empire region set.
</p>
<div class="table-wrap">
<table>
<thead>
<tr><th>Write level</th><th>Read level</th><th>Guarantee</th></tr>
</thead>
<tbody>
<tr><td><code>QUORUM</code></td><td><code>QUORUM</code></td><td>Strong, read-your-writes</td></tr>
<tr><td><code>ALL</code></td><td><code>ONE</code></td><td>Strong, slow writes</td></tr>
<tr><td><code>ONE</code></td><td><code>ONE</code></td><td>Eventual, lowest latency</td></tr>
</tbody>
</table>
</div>
<blockquote class="callout callout-warn">
<p><strong>Caution —</strong> Mixing <code>ONE/ONE</code> with multi-region clusters can
surface stale reads for up to one gossip interval. Pin latency-sensitive paths to
<code>QUORUM</code>.</p>
</blockquote>
<h2 id="upgrades">Rolling upgrades</h2>
<p>
To move a cluster from one minor version to the next, drain one coordinator at a time using
<kbd>aurora</kbd> <kbd>node</kbd> <kbd>drain</kbd>, wait for the leader to re-stabilize, then
rejoin. The control plane keeps the cluster writable throughout.
</p>
<blockquote class="callout callout-tip">
<p><strong>Tip —</strong> Use the version switcher above to compare these instructions
against an older release line — the drain command changed in v3.0.</p>
</blockquote>
<p>
When all coordinators report the new build hash, the upgrade is complete and the
compatibility window closes automatically. See the operations runbook for the full
checklist and rollback procedure.
</p>
</article>
<footer class="content-foot">
<p>Aurora DB Documentation · fictional reference build · <a href="#main">Edit this page</a></p>
</footer>
</main>
</div>
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
<script src="script.js"></script>
</body>
</html>Version / Language Switcher
A documentation header for the fictional Aurora DB knowledge base, built around two
accessible custom dropdowns. The version switcher lists release lines — v3.2 (carrying a
green latest badge), the maintenance builds v3.1 and v3.0, and the legacy v2.x line
marked EOL. The language switcher offers English, Español, 日本語, Deutsch and Português,
each with a flag and native name. Both sit in a real sample doc header, complete with a sidebar,
breadcrumb, and an article on cluster topology.
Choosing any release other than v3.2 raises an amber “you are viewing an old version” banner
that names the selected version and offers a one-click jump back to latest; the banner is also
dismissible. Switching languages updates a small localized “last updated” string and the
document lang attribute, and a toast confirms each change.
The dropdowns are keyboard-first. They open with Enter, Space, or the arrow
keys, move the active option with ↑/↓/Home/End, commit
with Enter, and close with Esc — restoring focus to the trigger. They use a
listbox/option ARIA pattern, close on outside click or Tab, and the layout collapses
the sidebar to a drawer and condenses the switchers below 820px and 520px.
Illustrative UI only — fictional articles, products, and data.