DEMO
桜色と若葉色で、やわらかく明るい印象にまとめます。
空色と白を中心に、強い日差しに負けない爽やかさを出します。
紅葉の橙と栗色で、落ち着いた温かみを足します。
藍色と銀色で、澄んだ空気と静けさを表します。
CODE
<div class="demo-stage dat-stage">
<div class="dat-box">
<div class="dat-tabs" role="tablist" aria-label="季節の配色">
<button type="button" role="tab" class="dat-tab" id="dat-t1" aria-selected="true" aria-controls="dat-p1">春</button>
<button type="button" role="tab" class="dat-tab" id="dat-t2" aria-selected="false" aria-controls="dat-p2" tabindex="-1">夏</button>
<button type="button" role="tab" class="dat-tab" id="dat-t3" aria-selected="false" aria-controls="dat-p3" tabindex="-1">秋</button>
<button type="button" role="tab" class="dat-tab" id="dat-t4" aria-selected="false" aria-controls="dat-p4" tabindex="-1">冬</button>
<span class="dat-ink" id="dat-ink" aria-hidden="true"></span>
</div>
<div class="dat-panels">
<div class="dat-panel" role="tabpanel" id="dat-p1" aria-labelledby="dat-t1" tabindex="0"><h3>春の配色</h3><p>桜色と若葉色で、やわらかく明るい印象にまとめます。</p><div class="dat-strip dat-s1" aria-hidden="true"></div></div>
<div class="dat-panel" role="tabpanel" id="dat-p2" aria-labelledby="dat-t2" tabindex="0" hidden><h3>夏の配色</h3><p>空色と白を中心に、強い日差しに負けない爽やかさを出します。</p><div class="dat-strip dat-s2" aria-hidden="true"></div></div>
<div class="dat-panel" role="tabpanel" id="dat-p3" aria-labelledby="dat-t3" tabindex="0" hidden><h3>秋の配色</h3><p>紅葉の橙と栗色で、落ち着いた温かみを足します。</p><div class="dat-strip dat-s3" aria-hidden="true"></div></div>
<div class="dat-panel" role="tabpanel" id="dat-p4" aria-labelledby="dat-t4" tabindex="0" hidden><h3>冬の配色</h3><p>藍色と銀色で、澄んだ空気と静けさを表します。</p><div class="dat-strip dat-s4" aria-hidden="true"></div></div>
</div>
</div>
</div>
.dat-stage { display:flex; align-items:center; justify-content:center; padding:1.5rem; background:#0B0B10; min-height:300px; }
.dat-box { width:100%; max-width:360px; }
.dat-tabs { position:relative; display:grid; grid-template-columns:repeat(4, 1fr); border-bottom:1px solid rgba(255,255,255,.14); }
.dat-tab { min-height:44px; border:0; background:transparent; color:#A1A1AA; font-size:.9rem; font-weight:600; cursor:pointer; }
.dat-tab[aria-selected="true"] { color:#fff; }
.dat-tab:focus-visible { outline:2px solid #E91B89; outline-offset:-2px; border-radius:6px; }
.dat-ink { position:absolute; left:0; bottom:-1px; width:25%; height:2px; background:#E91B89; transform:translateX(calc(var(--i, 0) * 100%)); transition: transform .35s cubic-bezier(.22,1,.36,1); }
.dat-panels { overflow:hidden; padding-top:1rem; }
.dat-panel { padding:1rem; border-radius:12px; background:#15151B; color:#C9C9D1; font-size:.84rem; line-height:1.7; outline:none; }
.dat-panel:focus-visible { box-shadow:inset 0 0 0 2px #E91B89; }
.dat-panel h3 { margin:0 0 .3rem; color:#fff; font-size:1rem; }
.dat-panel p { margin:0; }
.dat-strip { height:28px; margin-top:.8rem; border-radius:8px; }
.dat-s1 { background:linear-gradient(90deg,#F9A8D4,#BBF7D0); }
.dat-s2 { background:linear-gradient(90deg,#38BDF8,#F0F9FF); }
.dat-s3 { background:linear-gradient(90deg,#EA580C,#78350F); }
.dat-s4 { background:linear-gradient(90deg,#1E3A8A,#CBD5E1); }
@media (prefers-reduced-motion: reduce) {
.dat-ink { transition-duration:.01ms; }
}
(function(){
var tabs = Array.prototype.slice.call(document.querySelectorAll('.dat-tab'));
var ink = document.getElementById('dat-ink');
if (!tabs.length || !ink) return;
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)');
var cur = 0;
function select(i, focus){
if (focus) tabs[i].focus();
if (i === cur) return;
var dir = i > cur ? 1 : -1;
var oldP = document.getElementById(tabs[cur].getAttribute('aria-controls'));
var newP = document.getElementById(tabs[i].getAttribute('aria-controls'));
tabs[cur].setAttribute('aria-selected', 'false'); tabs[cur].tabIndex = -1;
tabs[i].setAttribute('aria-selected', 'true'); tabs[i].tabIndex = 0;
oldP.hidden = true; newP.hidden = false;
ink.style.setProperty('--i', i);
if (!reduce.matches && newP.animate) {
newP.animate([{ transform: 'translateX(' + (dir * 40) + 'px)', opacity: 0 }, { transform: 'none', opacity: 1 }],
{ duration: 320, easing: 'cubic-bezier(.22,1,.36,1)' });
}
cur = i;
}
tabs.forEach(function(t, i){
t.addEventListener('click', function(){ select(i, false); });
t.addEventListener('keydown', function(e){
var n = tabs.length, k = e.key, to = null;
if (k === 'ArrowRight') to = (i + 1) % n;
else if (k === 'ArrowLeft') to = (i - 1 + n) % n;
else if (k === 'Home') to = 0;
else if (k === 'End') to = n - 1;
if (to === null) return;
e.preventDefault();
select(to, true);
});
});
})();
AI PROMPT
右のタブを選ぶと中身が右から、左のタブを選ぶと左からすべり込むタブ(Direction-aware Tabs)を作って。WAI-ARIA のタブの形で、role="tablist"(aria-label 付き)の中に role="tab" のボタンを4つ置き、aria-selected と aria-controls を付け、選ばれていないタブは tabindex="-1" にする。パネルは role="tabpanel"・aria-labelledby・tabindex="0" で、選ばれていないパネルは hidden にする。切り替える時は、新しいタブの番号が今のタブより大きければ +1、小さければ −1 を向きとし、新しいパネルに element.animate で translateX(向き × 40px)・透明 から transform: none・不透明 へ320ミリ秒・cubic-bezier(.22,1,.36,1) の動きを付ける(古いパネルはすぐ hidden にして、高さの変化と重なりを避ける)。タブの下には幅25%の下線を置き、カスタムプロパティ --i(タブの番号)で transform: translateX(calc(var(--i) × 100%)) を指定して .35秒で移動させる(left や width は動かさない)。キーボードは左右の矢印キーで前後のタブへ移動して選択し(端では反対側へ回り込む)、Home で最初、End で最後のタブを選ぶ。どちらもタブにフォーカスを移し、既定のスクロールは preventDefault で止める。prefers-reduced-motion: reduce の時は、パネルのすべりと下線の移動を止めて即座に切り替える。 Acceptance criteria: works with prefers-reduced-motion (animation disabled), zero console errors, zero layout shift
AIエージェントでの実装ガイド → Claude Code でWebアニメーションを実装する
BOOKS · PR · AMAZON
※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。
学びを止めない · AMAZONサブスク
※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。
CREATOR TOOLS · スクール(PR)
※本サイトはアフィリエイトリンクを含みます。リンク経由のご購入・お申し込みで運営者が紹介料を受け取る場合があります。
タブを切り替える時、選んだタブが今のタブより右なら中身を右から、左なら左からすべり込ませる演出です。タブの並びと中身の動く向きがそろうため、どのタブへ移ったのかが動きからも分かります。
当サイトの Slide Panel Transition はタブを押すとパネルが横スライドで切り替わるトランジション、Elastic Tab Indicator はインジケーターが一度伸びてから縮んで着地する弾性アニメーション、Segmented Control は iOS 風のセグメント切替です。本ページは切り替えの向きを番号から決め、WAI-ARIA のタブの形とキーボード操作までをまとめた別テーマです。
タブは見た目を作るだけでなく、role・aria-selected・キーボード操作をそろえることで、読み上げやキーボードの利用者にも同じように使えます。動きは中身が入ってくる一瞬だけにとどめ、切り替えの操作そのものを待たせないようにしてください。
すべり込む向きはどう決めていますか?
新しく選んだタブの番号と、それまで選ばれていたタブの番号を比べています。右側(番号が大きい)のタブなら中身を右から、左側なら左から入れると、タブの並びと中身の動きが一致し、「隣のページへ移った」ことが伝わります。矢印キーで端から反対側へ回り込んだ時も番号で比べるので、最後のタブから最初のタブへ移る時は左から入ります。
キーボードではどう操作しますか?
WAI-ARIA Authoring Practices Guide のタブのパターンに合わせ、左右の矢印キーで前後のタブへ移り、端では反対側へ回り込むようにしています。本実装はフォーカスしたタブをすぐに表示する「自動で切り替わる」方式で、Home と End で最初と最後のタブへ移れます。タブの並びの中では、選ばれている1つだけを Tab キーで止まる場所にし、ほかは tabindex="-1" にしています。
コードは無料で使えますか?
はい。Motion LabのコードスニペットとAIプロンプトは、クレジット表記なしで自由に使えます。コピーしてそのままプロジェクトに利用できます。