ヘッダーの切替ボタンも、このページの選択肢も、同じ useColorScheme() です。「システム」に戻すと保存行が消え、OS の設定に追従します。
lightsystemルートレイアウトに Provider を 1 つ、切替に hook を 1 つ。それ以外に書くものはありません。
<ColorSchemeProvider> が先頭にインラインスクリプトを描く。Provider が書く行と同じ行を読み、React が読み込まれる前に dark を付けるので、ダークのページが一瞬ライトで光らない。<head> に置くものは無い。defaultPreference(既定は system)に従い、prefers-color-scheme の変化にその場で追従する。初回に見たシステムの値に固定されない。defineLocalState('color-scheme') の 1 行が保存先。キーも JSON の形もここには書かれず、タブ間の同期も古い行のサルベージも state のもの。useColorScheme() は { scheme, preference, setPreference } を読むだけの hook なので、切替ボタンとプレビューがずれようがない。設計ガイドは npm パッケージに同梱されています。AIコーディングエージェントは node_modules/@k8ordo/color-scheme/docs/ からインストールした版そのものを読みます。
// routes/layout.tsx — Provider を body の中で全部に被せる。
// 先頭にインラインスクリプトを描くので、最初の描画から dark が付いている
import { ColorSchemeProvider } from '@k8ordo/color-scheme';
<html suppressHydrationWarning>
<body>
<ColorSchemeProvider>{children}</ColorSchemeProvider>
</body>
</html>
// components/scheme-switcher.tsx
'use client';
import { useColorScheme } from '@k8ordo/color-scheme';
const { scheme, preference, setPreference } = useColorScheme();
setPreference(scheme === 'dark' ? 'light' : 'dark'); // 切り替える
setPreference('system'); // 保存行を消してシステムに追従する
// 訪問者が選ぶまでダークで始めたいなら、1 回だけ言う
<ColorSchemeProvider defaultPreference="dark">