DEMO
Pro
¥1,200/ 月
いつでも解約できます
CODE
<div class="demo-stage prc-stage">
<div class="prc-box">
<div class="prc-switch" role="radiogroup" aria-label="請求の周期">
<span class="prc-pill" aria-hidden="true"></span>
<button type="button" role="radio" class="prc-opt" data-cycle="m" aria-checked="true">月額</button>
<button type="button" role="radio" class="prc-opt" data-cycle="y" aria-checked="false">年額 <span class="prc-save">2か月分お得</span></button>
</div>
<div class="prc-card">
<p class="prc-plan">Pro</p>
<p class="prc-price"><span class="prc-yen">¥</span><span class="prc-num" id="prc-num" aria-live="polite">1,200</span><span class="prc-per" id="prc-per">/ 月</span></p>
<p class="prc-note" id="prc-note">いつでも解約できます</p>
</div>
</div>
</div>
.prc-stage { display:flex; align-items:center; justify-content:center; padding:2rem; background:#0B0B10; min-height:300px; }
.prc-box { width:100%; max-width:320px; display:flex; flex-direction:column; gap:1rem; }
.prc-switch { position:relative; display:grid; grid-template-columns:1fr 1fr; padding:4px; border-radius:999px; background:#17171C; border:1px solid rgba(255,255,255,.1); }
.prc-pill { position:absolute; top:4px; bottom:4px; left:4px; width:calc(50% - 4px); border-radius:999px; background:#E91B89; transition: transform .35s cubic-bezier(.22,1,.36,1); }
.prc-switch.is-y .prc-pill { transform:translateX(100%); }
.prc-opt { position:relative; z-index:1; min-height:44px; border:0; border-radius:999px; background:transparent; color:#9A9AA6; font-size:.85rem; font-weight:600; cursor:pointer; transition: color .25s ease; }
.prc-opt[aria-checked="true"] { color:#fff; }
.prc-opt:focus-visible { outline:2px solid #fff; outline-offset:2px; }
.prc-save { display:block; font-size:.62rem; font-weight:400; opacity:.85; }
.prc-card { padding:1.4rem; border-radius:16px; background:#15151B; border:1px solid rgba(255,255,255,.1); text-align:center; }
.prc-plan { margin:0 0 .3rem; font-family:'Oswald',sans-serif; letter-spacing:.2em; color:#C9C9D1; }
.prc-price { margin:0; color:#fff; overflow:hidden; }
.prc-yen { font-size:1.2rem; vertical-align:top; }
.prc-num { display:inline-block; font-family:'Inter',sans-serif; font-size:2.6rem; font-weight:700; font-variant-numeric:tabular-nums; }
.prc-num.is-swap { animation: prc-swap .35s cubic-bezier(.22,1,.36,1); }
.prc-per { color:#8A8A96; font-size:.85rem; margin-left:.2rem; }
.prc-note { margin:.4rem 0 0; color:#8A8A96; font-size:.76rem; }
@keyframes prc-swap { from { opacity:0; transform:translateY(40%); } to { opacity:1; transform:none; } }
@media (prefers-reduced-motion: reduce) {
.prc-pill, .prc-opt { transition-duration:.01ms; }
.prc-num.is-swap { animation:none; }
}
(function(){
var sw = document.querySelector('.prc-switch');
var num = document.getElementById('prc-num');
var per = document.getElementById('prc-per');
var note = document.getElementById('prc-note');
if (!sw || !num) return;
var opts = sw.querySelectorAll('.prc-opt');
var PRICE = { m: [1200, '/ 月', 'いつでも解約できます'], y: [12000, '/ 年', '月あたり1,000円(2か月分お得)'] };
function select(btn, focus){
var y = btn.dataset.cycle === 'y';
opts.forEach(function(o){ var on = o === btn; o.setAttribute('aria-checked', on ? 'true' : 'false'); o.tabIndex = on ? 0 : -1; });
sw.classList.toggle('is-y', y);
var p = PRICE[btn.dataset.cycle];
num.textContent = p[0].toLocaleString('ja-JP'); per.textContent = p[1]; note.textContent = p[2];
num.classList.remove('is-swap'); void num.offsetWidth; num.classList.add('is-swap');
if (focus) btn.focus();
}
opts.forEach(function(o, i){
o.addEventListener('click', function(){ select(o, false); });
o.addEventListener('keydown', function(e){
if (e.key === 'ArrowRight' || e.key === 'ArrowLeft' || e.key === 'ArrowUp' || e.key === 'ArrowDown') {
e.preventDefault(); select(opts[(i + 1) % opts.length], true);
}
});
});
opts[0].tabIndex = 0; opts[1].tabIndex = -1;
})();
AI PROMPT
月額と年額を切り替えるスイッチと、切り替えに合わせて数字が入れ替わる料金カードを作って。スイッチは2つの button を grid で並べ、後ろに選択中を示す丸い背景(pill)を絶対配置し、年額を選んだら transform: translateX(100%) で .35秒すべらせる(left や width を動かさない)。スイッチは role="radiogroup"、各ボタンは role="radio" と aria-checked で状態を示し、選択中だけ tabindex=0・もう一方は -1 にして、矢印キーで選択を切り替える(ラジオボタンと同じ操作)。料金は数字部分だけを translateY(40%)・透明 から元の位置へ .35秒で入れ替え、font-variant-numeric: tabular-nums で桁幅をそろえる。数字は toLocaleString('ja-JP') で3桁区切りにし、aria-live="polite" で変わった値を読み上げに伝える。年額のボタンには「2か月分お得」の補足を添え、カードの注記も「月あたり1,000円」のように換算を示す。押しやすいよう高さは44px以上。@media (prefers-reduced-motion: reduce) で transition と animation を無効化する。
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アソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。
SaaSやサブスクリプションの料金ページで、月払いと年払いの価格を切り替えて見比べられるようにするスイッチとカードの組み合わせです。年払いの割引を分かりやすく見せることで、年額プランを選んでもらうきっかけを作ります。
当サイトの Segmented Control は選択肢を切り替える汎用の部品、Toggle Switch はオン・オフの切り替え、Number Count Up は数字が増えていく演出です。本ページは料金の周期の切り替えと価格の入れ替えを1つにまとめた別テーマで、料金ページにそのまま置けます。
価格は利用者が最も注意して読む数字です。入れ替えの動きは短く控えめにし、変わった値を読み上げにも伝え、割引は月額と比べられる言葉で示してください。デモの価格は例なので、実際の料金に合わせて必ず書き換えてください。
スイッチを role="radio" にするのはなぜですか?
月額と年額はどちらか一方だけを選ぶ選択肢なので、支援技術にはラジオボタンの集まりとして伝えるのが正確です。role="radiogroup" と role="radio"、aria-checked を付けると「年額、選択中、2つ中2つ目」のように読み上げられます。選択中の項目だけを Tab で止まる対象にし、矢印キーで切り替える操作も標準のラジオボタンに合わせています。
年額の割引はどう表示するのが分かりやすいですか?
「◯%オフ」よりも「2か月分お得」「月あたり1,000円」のように、月額と比べられる形で示すと得の大きさが伝わりやすくなります。本デモは月額1,200円・年額12,000円(月あたり1,000円)を例にしており、12か月分の14,400円と比べて2,400円=2か月分安い計算です。実際の価格と割引率に合わせて文言を必ず書き換えてください。
コードは無料で使えますか?
はい。Motion LabのコードスニペットとAIプロンプトは、クレジット表記なしで自由に使えます。コピーしてそのままプロジェクトに利用できます。