TECH · Bootstrap
決まった型に流し込んで速く出す
Bootstrap 5のサンプル集。12分割グリッドとブレークポイント、カードとユーティリティ、標準検証を使ったフォーム、JavaScriptが要るナビバーとモーダルを、CDNに頼らず本体を持ち込んだ状態で収録します。
サンプルとコード
container / row / col の3階層と、6段階のブレークポイント。すべて min-width で効き、指定しない画面幅は直前の指定を引き継ぎます。だからクラスは小さい画面から順に書きます。
HTML
<!-- container が左右の余白と最大幅、row が横並び、col-* が幅を担当する。
この3階層は崩さない。row の直下は必ず col-* にする。 -->
<div class="container">
<!-- g-3 は列同士の隙間(gutter)。row 側に付ける。 -->
<div class="row g-3 demo-col">
<!-- 既定(col-12)は縦積み。md(768px)以上で 6/12 = 半分、
lg(992px)以上で 4/12 = 3列。指定しない画面幅は
直前の指定を引き継ぐので、小さいほうから書く。 -->
<div class="col-12 col-md-6 col-lg-4"><div>col-12 / md-6 / lg-4</div></div>
<div class="col-12 col-md-6 col-lg-4"><div>col-12 / md-6 / lg-4</div></div>
<div class="col-12 col-md-12 col-lg-4"><div>col-12 / md-12 / lg-4</div></div>
</div>
<!-- 数字を書かない col は「残りを均等に分け合う」。
3つ並べれば自動で 4:4:4 になる。 -->
<div class="row g-3 demo-col mt-3">
<div class="col"><div>col</div></div>
<div class="col"><div>col</div></div>
<div class="col"><div>col</div></div>
</div>
<!-- col-auto は中身の幅ぶんだけ。残りを col が吸う。
ラベルと入力欄のように「片方だけ固定」したいときに使う。 -->
<div class="row g-3 demo-col mt-3 align-items-center">
<div class="col-auto"><div>col-auto</div></div>
<div class="col"><div>col(残り全部)</div></div>
</div>
<!-- offset で空きを作る。offset-md-3 + col-md-6 で中央寄せ。 -->
<div class="row g-3 demo-col mt-3">
<div class="col-md-6 offset-md-3"><div>col-md-6 offset-md-3</div></div>
</div>
<!-- order で見た目の順序だけ入れ替える。HTMLの順序(=読み上げ順)は
変えないので、重要な内容を先に書いたまま右に置ける。 -->
<div class="row g-3 demo-col mt-3">
<div class="col-md-8 order-md-2"><div>HTMLでは1番目 / 表示は2番目</div></div>
<div class="col-md-4 order-md-1"><div>HTMLでは2番目 / 表示は1番目</div></div>
</div>
</div> AIへの指示文
Bootstrap 5 のグリッドの使い方が一目で分かるページを作ってください。
要件:
- container / row / col-* の3階層を崩さない。row の直下は必ず col-*
にする、という制約をコメントで書く。
- col-12 / col-md-6 / col-lg-4 のように、小さい画面から順に指定する。
すべて min-width で効くので大きいほうへ引き継がれる、と書く。
- 数字なしの col(残りを均等に分ける)、col-auto(中身の幅だけ)、
offset-md-3(左に空きを作る)、order-md-*(表示順だけ入れ替える)
の4パターンを並べる。
- order は HTML の順序を変えずに見た目だけ入れ替えられるので、
読み上げ順を保ったまま右に置ける、という利点をコメントで書く。
- 列同士の隙間は row 側に g-3 を付ける。
- Bootstrap は CDN ではなく相対パスで読み込む。
- 日本語の本文なので line-height は 1.85、letter-spacing は 0 にする。
h-100 で高さを揃え、d-flex flex-column と mt-auto でボタンを下端に寄せる。最頻出の組み合わせです。あわせて、和文向けに行間を直すCSSも載せました。
HTML
<!-- row-cols-* は「1行に何列並べるか」を直接指定する書き方。
col-md-4 と同じ結果になるが、列数のほうが意図が読みやすい。 -->
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
<div class="col">
<!-- h-100 で高さを揃える。カードの中身の量が違っても、
並んだカードの下端が揃う。Bootstrapで最も使う小技。 -->
<div class="card h-100 shadow-sm border-0">
<div class="shot"></div>
<div class="card-body d-flex flex-column">
<!-- d-flex + gap-2 で横並び。mb-2 は下の余白(0.5rem)。
余白は 0〜5 の6段階しかない。刻みを決めなくていいのが利点。 -->
<div class="d-flex gap-2 mb-2">
<span class="badge text-bg-primary">新着</span>
<span class="badge text-bg-light border">無料</span>
</div>
<h2 class="card-title h6">クラス名だけで形が決まる</h2>
<p class="card-text small text-body-secondary">
カードの枠線・角丸・内側の余白は、あらかじめ決められています。設計せずに済むぶん速く出せます。
</p>
<!-- mt-auto で、中身の量に関わらずボタンを下端に寄せる。
親に d-flex flex-column が要る。 -->
<a href="#" class="btn btn-primary btn-sm mt-auto align-self-start">詳しく見る</a>
</div>
</div>
</div>
<div class="col">
<div class="card h-100 shadow-sm border-0">
<div class="shot shot-b"></div>
<div class="card-body d-flex flex-column">
<div class="d-flex gap-2 mb-2">
<span class="badge text-bg-danger">残りわずか</span>
</div>
<h2 class="card-title h6">中身の量が違っても下端が揃う</h2>
<p class="card-text small text-body-secondary">
このカードは説明文が短いですが、h-100 と mt-auto のおかげでボタンの位置は隣と揃います。
</p>
<a href="#" class="btn btn-primary btn-sm mt-auto align-self-start">詳しく見る</a>
</div>
</div>
</div>
<div class="col">
<div class="card h-100 shadow-sm border-0">
<div class="shot shot-c"></div>
<div class="card-body d-flex flex-column">
<div class="d-flex gap-2 mb-2">
<span class="badge text-bg-success">対応済み</span>
<span class="badge text-bg-light border">CC0</span>
</div>
<h2 class="card-title h6">色の名前は6種類に決まっている</h2>
<p class="card-text small text-body-secondary">
primary / secondary / success / danger / warning / info。この語彙で色を指定するので、配色を毎回決め直す必要がありません。
</p>
<a href="#" class="btn btn-primary btn-sm mt-auto align-self-start">詳しく見る</a>
</div>
</div>
</div>
</div>
<!-- list-group は区切り線つきの縦並び。
list-group-flush で外枠を消すと、カードの中に自然に収まる。 -->
<div class="card mt-4 shadow-sm border-0">
<div class="card-header bg-transparent fw-semibold">直近の更新</div>
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-center">
<span class="small">サンプルを4件追加</span>
<span class="badge text-bg-secondary rounded-pill">09/01</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
<span class="small">フォームの検証表示を修正</span>
<span class="badge text-bg-secondary rounded-pill">08/31</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
<span class="small">ナビバーの折りたたみを調整</span>
<span class="badge text-bg-secondary rounded-pill">08/28</span>
</li>
</ul>
</div> CSS
/* Bootstrap の既定は欧文前提で、見出しの line-height が 1.2 しかない。
和文をこの行間で組むと、2行目以降が前の行に食い込んで読みにくい。
5.3 は主要な値を CSS変数で持っているので、クラスを上書きせず
変数を差し替えるだけで直る。詳細度の戦いにならない。 */
:root {
--bs-body-font-family: "Inter", "Helvetica Neue", Arial,
"Hiragino Kaku Gothic ProN", "Hiragino Sans", "Yu Gothic Medium",
"Yu Gothic", "Meiryo", sans-serif;
--bs-body-line-height: 1.8;
/* 和文では字間を詰めない。0 が既定だが、意図を残すために明示する。 */
letter-spacing: 0;
}
/* 見出しの行間には変数が用意されていないので直接指定する。
Bootstrap は h1〜h6 だけでなく .h1〜.h6 のユーティリティクラスにも
同じ 1.2 を当てている。要素セレクタ(詳細度 0,0,1)だけでは
クラス(0,1,0)に負けるので、両方を並べて書く必要がある。
詳細度が同じなら、あとに書いたこちらが勝つ。 */
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 { line-height: 1.5; } AIへの指示文
Bootstrap 5 でカードを3枚並べたページを作ってください。
要件:
- row-cols-1 row-cols-md-2 row-cols-lg-3 で「1行に何列並べるか」を指定する。
col-md-4 と同じ結果だが列数のほうが意図が読みやすい、と書く。
- カードには h-100 を付けて高さを揃える。card-body に
d-flex flex-column、ボタンに mt-auto を付けて下端に寄せる。
説明文の長さが違っても下端が揃うことをコメントで書く。
- badge は text-bg-primary / text-bg-danger / text-bg-success を使い分ける。
色の語彙が6種類に決まっているので配色を毎回決めなくてよい、と書く。
- 余白は m/p + 方向 + 0〜5 の刻みだけで指定し、独自の値を書かない。
- list-group-flush を使った更新履歴のリストも1つ置く。
- Bootstrap の既定は見出しの line-height が 1.2 で和文だと詰まりすぎる。
本文側は CSS変数(--bs-body-font-family / --bs-body-line-height)で直す。
- 見出しには変数が無いので直接指定する。ただし Bootstrap は
h1〜h6 だけでなく .h1〜.h6 のクラスにも同じ値を当てているため、
要素セレクタだけでは詳細度で負ける。両方を並べて書くこと。
この理由をコメントで書く。
- Bootstrap は CDN ではなく相対パスで読み込む。
判定はブラウザの標準検証、見せ方だけがBootstrapです。was-validated を付けるかどうかで表示が切り替わるので、自前の正規表現は1つも書きません。
HTML
<!-- novalidate でブラウザ既定の吹き出しを止め、表示は Bootstrap に任せる。
判定そのものは checkValidity() で標準の検証をそのまま使う。
required / type / pattern を書くだけで済み、自前の判定を書かない。 -->
<form class="card shadow-sm border-0 p-4 needs-validation" novalidate>
<div class="mb-3">
<!-- label は for と id で必ず結ぶ。これを省くと
ラベルを押しても入力欄に飛ばず、読み上げも壊れる。 -->
<label for="name" class="form-label">お名前</label>
<input type="text" class="form-control" id="name" name="name"
autocomplete="name" required />
<!-- invalid-feedback は既定で非表示。親フォームに was-validated が
付き、かつこの入力が :invalid のときだけ現れる。 -->
<div class="invalid-feedback">お名前を入力してください。</div>
</div>
<div class="mb-3">
<label for="email" class="form-label">メールアドレス</label>
<input type="email" class="form-control" id="email" name="email"
autocomplete="email" required />
<div class="form-text">確認のご連絡にのみ使います。</div>
<div class="invalid-feedback">メールアドレスの形式で入力してください。</div>
</div>
<div class="mb-3">
<label for="plan" class="form-label">ご検討中のプラン</label>
<select class="form-select" id="plan" name="plan" required>
<option value="">選択してください</option>
<option>フリー</option>
<option>スタンダード</option>
<option>エンタープライズ</option>
</select>
<div class="invalid-feedback">プランを選択してください。</div>
</div>
<div class="mb-3">
<label for="body" class="form-label">お問い合わせ内容</label>
<textarea class="form-control" id="body" name="body" rows="4" required></textarea>
<div class="invalid-feedback">内容を入力してください。</div>
</div>
<div class="form-check mb-4">
<input class="form-check-input" type="checkbox" id="agree" required />
<label class="form-check-label" for="agree">
利用規約に同意します
</label>
<div class="invalid-feedback">同意が必要です。</div>
</div>
<button class="btn btn-primary" type="submit">送信する</button>
<div class="alert alert-success mt-3 mb-0 d-none" id="done" role="status">
送信しました。折り返しご連絡します。
</div>
</form> JavaScript
// Bootstrap の検証表示は「was-validated を付けるかどうか」だけで切り替わる。
// 判定は checkValidity()、つまりブラウザ標準。自前の正規表現は書かない。
const form = document.querySelector(".needs-validation");
const done = document.getElementById("done");
form.addEventListener("submit", (e) => {
e.preventDefault();
// was-validated を付けた時点で、:valid / :invalid に応じた
// 緑・赤の表示と invalid-feedback の出し分けが始まる。
form.classList.add("was-validated");
if (!form.checkValidity()) {
// 最初の不正な入力へフォーカスを移す。これが無いと、
// 長いフォームでどこが悪いのか探すことになる。
form.querySelector(":invalid")?.focus();
done.classList.add("d-none");
return;
}
done.classList.remove("d-none");
});
// やり直すときは was-validated を外す。付けっぱなしにすると、
// 入力し直している最中ずっと赤いままになる。
form.addEventListener("reset", () => {
form.classList.remove("was-validated");
done.classList.add("d-none");
}); AIへの指示文
Bootstrap 5 の検証つきお問い合わせフォームを作ってください。
要件:
- form に novalidate を付けてブラウザ既定の吹き出しを止め、
表示は Bootstrap に任せる。判定は checkValidity()、つまり標準検証を
そのまま使う。自前の正規表現は書かない、とコメントで書く。
- submit 時に was-validated を付ける。これを付けた時点で
:valid / :invalid の色分けと invalid-feedback の出し分けが始まる、と書く。
- 不正な入力があれば最初の :invalid にフォーカスを移す。
これが無いと長いフォームでどこが悪いか探すことになる、と書く。
- reset 時には was-validated を外す。付けっぱなしだと入力し直している
最中ずっと赤いままになる、と書く。
- すべての入力に label を for と id で結び、autocomplete を指定する。
- 送信成功のメッセージは role="status" で伝える。
- Bootstrap は CDN ではなく相対パスで読み込む。
折りたたみもドロップダウンもモーダルも、書くのは data-bs-* 属性だけです。ここだけはCSSに加えてJavaScript本体(Popper同梱のbundle)が要ります。
HTML
<!-- navbar-expand-md は「md 以上で横並び、それ未満はハンバーガー」。
この1クラスで折りたたみの境界が決まる。 -->
<nav class="navbar navbar-expand-md bg-body-tertiary border-bottom">
<div class="container">
<a class="navbar-brand fw-semibold" href="#">Vibehaus</a>
<!-- data-bs-toggle と data-bs-target で開閉を宣言する。
JavaScript は本体を読み込むだけで、自分では1行も書かない。
aria-controls / aria-expanded / aria-label は必須。
省くとキーボードと読み上げで何が起きたのか伝わらない。 -->
<button class="navbar-toggler" type="button"
data-bs-toggle="collapse" data-bs-target="#nav"
aria-controls="nav" aria-expanded="false" aria-label="メニューを開閉">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="nav">
<ul class="navbar-nav me-auto mb-2 mb-md-0">
<li class="nav-item">
<!-- aria-current="page" が現在地。active クラスは見た目だけなので、
これが無いと読み上げ利用者に現在地が伝わらない。 -->
<a class="nav-link active" aria-current="page" href="#">ホーム</a>
</li>
<li class="nav-item"><a class="nav-link" href="#">デザイン例</a></li>
<!-- ドロップダウンは Popper が要る。だから bootstrap.js ではなく
bootstrap.bundle.js(Popper 同梱)を読む。 -->
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button"
data-bs-toggle="dropdown" aria-expanded="false">技術別</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Bootstrap</a></li>
<li><a class="dropdown-item" href="#">Tailwind CSS</a></li>
<li><hr class="dropdown-divider" /></li>
<li><a class="dropdown-item" href="#">すべて見る</a></li>
</ul>
</li>
</ul>
<!-- モーダルも同じ宣言だけで開く。閉じる操作・背面の不活性化・
フォーカスの閉じ込めは Bootstrap 側が面倒を見る。 -->
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#ask">
お問い合わせ
</button>
</div>
</div>
</nav>
<div class="modal fade" id="ask" tabindex="-1" aria-labelledby="ask-title" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title fs-5" id="ask-title">お問い合わせ</h2>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="閉じる"></button>
</div>
<div class="modal-body">
<p class="mb-0 small text-body-secondary">
このモーダルもクラスと data 属性だけで動いています。Escキーでも背景クリックでも閉じます。
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">閉じる</button>
</div>
</div>
</div>
</div> AIへの指示文
Bootstrap 5 のナビバーを作ってください。
要件:
- navbar-expand-md で、md 以上は横並び、それ未満はハンバーガーにする。
この1クラスで折りたたみの境界が決まる、とコメントで書く。
- 開閉ボタンは data-bs-toggle="collapse" と data-bs-target で宣言する。
JavaScript は本体を読み込むだけで自分では書かない、と書く。
- aria-controls / aria-expanded / aria-label を必ず付ける。
省くとキーボードと読み上げで何が起きたか伝わらない、と書く。
- 現在地のリンクには active だけでなく aria-current="page" を付ける。
active は見た目だけなので、これが無いと現在地が伝わらない、と書く。
- ドロップダウンと、data-bs-toggle="modal" で開くモーダルを1つずつ置く。
- ドロップダウンは Popper が要るので bootstrap.js ではなく
bootstrap.bundle.js を読む、という理由をコメントで書く。
- Bootstrap は CDN ではなく相対パスで読み込む。
この技術について
Bootstrapの価値は、機能ではなくすでに決まっていることにあります。
グリッドは12分割。ブレークポイントは6段階。余白の刻みは0から5の6段階。色の語彙は primary / secondary / success / danger / warning / info の6つ。ボタンの角丸も、カードの内側の余白も、影の濃さも決まっています。
だから議論が要りません。「ここの余白どうする?」が発生しない。動くものが速く出ます。
決まっていることの裏返し
同じ理由で、その決定を覆したい場面ではずっと戦うことになります。
既定の見た目のまま出せば「Bootstrapのサイトだ」と一目で分かります。それを消そうとしてクラスを上書きし始めると、詳細度との戦いが始まる。Bootstrapを入れてからデザインをゼロから作るのが、いちばん遅い道です。
判断はここで分かれます。作るものが管理画面や社内ツールのように「整っていれば十分」なら、いまでも最短距離。独自のデザインがあるなら、見た目を決めてこないTailwindのような道具のほうが素直です。
グリッドは3階層を崩さない
container が左右の余白と最大幅、row が横並び、col-* が幅。この順番を崩すと余白が二重になったり、はみ出したりします。row の直下は必ず col-* にしてください。
col-12 col-md-6 col-lg-4 のように、小さい画面から順に書きます。すべて min-width で効くので、指定しなかった画面幅は直前の指定を引き継ぐからです。逆順に書くと、意図しない幅で意図しない列数になります。
隙間は col ではなく row 側に g-3 を付けます。
h-100 と mt-auto は覚えておく
カードを並べたとき、説明文の長さが違うとボタンの位置がばらつきます。
card に h-100、card-body に d-flex flex-column、ボタンに mt-auto。この3点セットで下端が揃います。Bootstrapで最も使う組み合わせで、知らないと position: absolute で無理やり寄せることになります。
検証はブラウザに任せる
フォームで Bootstrap が担当するのは見せ方だけです。判定は checkValidity()、つまりブラウザの標準検証をそのまま使います。
やることは1つ。送信時にフォームへ was-validated を付ける。それだけで :valid / :invalid に応じた色分けと、invalid-feedback の出し分けが始まります。自前の正規表現は1つも書きません。
2つだけ補ってください。不正な入力があったら最初の :invalid にフォーカスを移すこと(長いフォームでどこが悪いか探させない)。そしてやり直すときは was-validated を外すこと(付けっぱなしだと入力中ずっと赤いままになります)。
JavaScriptが要るのはここだけ
折りたたみ、ドロップダウン、モーダル、タブ、トースト。これらは data-bs-* 属性を書くだけで動きますが、本体の読み込みは必要です。CSSだけでは動きません。
読むファイルは2種類あります。bootstrap.bundle.js は Popper を同梱した版で、ドロップダウン・ツールチップ・ポップオーバーの位置計算に使われます。これらを使わないと確実に言えないなら bundle を選ぶのが安全です。
属性を書くだけ、では足りないもの
data-bs-toggle で動くことと、正しく使えることは別です。
開閉ボタンには aria-controls aria-expanded aria-label を付けてください。現在地のリンクには active だけでなく aria-current="page" を付けてください。active は色を変えるだけのクラスなので、それだけでは読み上げ利用者に現在地が伝わりません。
Bootstrapは見た目を用意してくれますが、意味は用意してくれません。そこは書く側の仕事です。
和文で使うなら、行間だけは直す
Bootstrapの既定値は欧文前提です。とくに見出しの line-height が 1.2 しかなく、日本語の見出しが2行になると前の行に食い込みます。このサイトのビルドゲートも、サンプルを作った時点でここを弾きました。
本文側は変数で直ります。
:root {
--bs-body-font-family: "Inter", "Hiragino Kaku Gothic ProN",
"Yu Gothic Medium", "Meiryo", sans-serif;
--bs-body-line-height: 1.8;
}
見出しには変数が用意されていないので直接指定します。ここに一つ罠があって、要素セレクタだけでは勝てません。
/* h1〜h6(詳細度 0,0,1)だけでは、Bootstrap が .h1〜.h6 に
当てている 1.2(詳細度 0,1,0)に負ける。両方を並べて書く。 */
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 { line-height: 1.5; }
最初は要素セレクタだけで書いて、ビルドゲートに二度弾かれました。<h2 class="card-title h6"> のように見出しの大きさをクラスで指定するのがBootstrapの流儀なので、クラス側を書き忘れると効きません。
letter-spacing は触らないでください。和文で字間を詰めると読みにくくなるだけです。
色を変えるなら変数から
既定の青を変えたくなったとき、.btn-primary を直接上書きするのは最後の手段です。
Bootstrap 5.3 は色・角丸・影を CSS変数で持っています。--bs-primary、--bs-border-radius、--bs-body-font-family あたりを差し替えるだけで印象は変わります。詳細度の戦いを始める前に、変数で足りないかを確認してください。
使いどころとつまずきどころ
向いている場面
- 管理画面や社内ツールを短時間で形にする
- デザイナーがいない案件の見た目を底上げする
- 既存のBootstrap製サイトに機能を足す
- プロトタイプを1日で組んで議論の土台にする
つまずきやすい点
- CSSが232KB。使うのが数クラスでも全部読み込まれる
- 見た目が既定のままだと「Bootstrapのサイト」だと一目で分かる
- 上書きは詳細度との戦いになりやすい
- ドロップダウンやモーダルはJavaScript本体が要る
- 見出しの行間が1.2。欧文前提の値で、和文だと詰まりすぎる
よくある質問
いまから新規で作るならBootstrapとTailwindのどちらですか。
「見た目を決めたいか、決めたくないか」で選んでください。Bootstrapは見た目まで決まっているので、決めたくない場合に速い。管理画面や社内ツール、デザイナーのいない案件が典型です。Tailwindは見た目を決めないので、独自のデザインがある場合に向きます。BootstrapでデザインをゼロからやるとCSSの上書きと詳細度の戦いになり、かえって遅くなります。
CSSが232KBもあるのは重くないですか。
重いです。ただし gzip で約30KB、しかもページをまたいでキャッシュが効くので、多くの案件では許容できます。気になる場合は Sass から必要なコンポーネントだけを import してビルドすると小さくできます。それでも「使う数クラスのために全部読む」構造は変わらないので、ここが本当に問題になる規模なら別の選択肢を検討してください。
bootstrap.js と bootstrap.bundle.js の違いは何ですか。
bundle のほうは Popper を同梱しています。ドロップダウン、ツールチップ、ポップオーバーは位置の計算に Popper を使うため、bootstrap.js だけを読むと動きません。これらを使わないと確実に言えないなら bundle を選んでおくのが安全です。逆に折りたたみとモーダルしか使わないなら bootstrap.js で足ります。
見た目が「Bootstrapのサイト」だと分かってしまいます。
既定の青、既定の角丸、既定のフォントをそのまま使うとそうなります。CSS変数を上書きするのが最も軽い対処です。Bootstrap 5.3 は色や角丸を CSS変数で持っているので、--bs-primary-bg-subtle などを差し替えるだけで印象が変わります。クラスを直接上書きすると詳細度の戦いになるので、変数から手を付けてください。
サンプルがCDNではなく相対パスで読み込んでいるのはなぜですか。
このサイトのサンプルは、1枚のHTMLとして単独で完結し、ダウンロードして file:// で開いても動くことを条件にしているためです。CDNを参照すると、オフラインでは動かず、将来CDNが消えたときにサンプルが壊れます。実務でCDNを使うのは問題ありませんが、その場合も integrity 属性を付けて配信内容が差し替わっていないことを確認してください。
Bootstrap 4 から 5 へ上げるとき、何が変わりますか。
大きいのは jQuery 依存が無くなったこと、方向を表すクラスが left/right から start/end になったこと、そしてデータ属性が data-toggle から data-bs-toggle に変わったことです。この3つだけでも既存コードはほぼ全面的に触ることになります。段階的な移行は難しいので、ページ単位で切り替えるほうが現実的です。