F-011 Form medium

Rotary Dial Plan Selector ロータリーダイヤル式プランセレクター

Subscription tier selector with a mechanical rotary dial gauge, dynamic pricing update, and detailed feature breakdown. 装飾回転ダイヤルと連動して価格とプラン内容が切り替わるUI。

ライブデモ Live Demo

Pro Tier

$29/mo

Optimal power for growing teams with priority cloud builds.

概要・用途・特徴Overview, Usage & Features

何ができるかWhat it does

Subscription tier selector with a mechanical rotary dial gauge, dynamic pricing update, and detailed feature breakdown.

装飾回転ダイヤルと連動して価格とプラン内容が切り替わるUI。

どこで使うかWhere to use

Portfolio, product showcase, landing page.

ポートフォリオ、製品紹介、ランディングページ。

特徴Key features

Subscription tier selector with a mechanical rotary dial gauge, dynamic pricing update, and detailed feature breakdown.

装飾回転ダイヤルと連動して価格とプラン内容が切り替わるUI。

調整可能パラメータ Adjustable Parameters

Parameter Default Description
--demo-accent#38bdf8Accent color for the panel border and keyboard focus ring.

実装コード Implementation Code

<div class="demo-stage rs-root" role="region" aria-label="Subscription Plan Selector"><div class="rs-container"><div class="rs-dial-display" aria-hidden="true"><div class="rs-dial-wheel"><div class="rs-dial-marker"></div><div class="rs-dial-center"><span class="rs-dial-badge">TIER</span></div></div></div><div class="rs-content" aria-live="polite" aria-atomic="true"><div class="rs-plan-header"><h3 class="rs-plan-title">Pro Tier</h3><div class="rs-price-tag"><span class="rs-currency">$</span><span class="rs-amount">29</span><span class="rs-period">/mo</span></div></div><p class="rs-plan-desc">Optimal power for growing teams with priority cloud builds.</p></div><div class="rs-nav" role="group" aria-label="Subscription tiers"><button type="button" class="rs-btn" data-price="9" data-angle="-45deg" data-title="Starter Tier" data-desc="Essential tools for hobbyists, indie hackers, and micro projects." aria-pressed="false" aria-label="Select Starter Plan">Starter</button><button type="button" class="rs-btn rs-active" data-price="29" data-angle="0deg" data-title="Pro Tier" data-desc="Optimal power for growing teams with priority cloud builds." aria-pressed="true" aria-label="Select Pro Plan">Pro</button><button type="button" class="rs-btn" data-price="89" data-angle="45deg" data-title="Enterprise Tier" data-desc="Dedicated high-throughput nodes, SLA, and customized support." aria-pressed="false" aria-label="Select Enterprise Plan">Enterprise</button></div></div></div>
<script>
document.querySelectorAll('.rs-root').forEach(root => {
  const buttons = root.querySelectorAll('.rs-btn');
  const amountEl = root.querySelector('.rs-amount');
  const titleEl = root.querySelector('.rs-plan-title');
  const descEl = root.querySelector('.rs-plan-desc');
  const dialWheel = root.querySelector('.rs-dial-wheel');
  if (!buttons.length || !amountEl || !titleEl || !descEl || !dialWheel) return;
  buttons.forEach(btn => {
    btn.addEventListener('click', () => {
      buttons.forEach(b => {
        b.classList.remove('rs-active');
        b.setAttribute('aria-pressed', 'false');
      });
      btn.classList.add('rs-active');
      btn.setAttribute('aria-pressed', 'true');
      amountEl.textContent = btn.dataset.price;
      titleEl.textContent = btn.dataset.title;
      descEl.textContent = btn.dataset.desc;
      dialWheel.style.setProperty('--dial-angle', btn.dataset.angle);
    });
  });
});
</script>
.rs-root {
  box-sizing: border-box;
  width: 100%;
  max-width: 560px;
  max-height: 420px;
  min-width: 0;
  margin: 0 auto;
  padding: 18px;
  background: linear-gradient(145deg, #181c24, #0f1217);
  border-radius: 16px;
  border: 1px solid #2b3240;
  box-shadow: 0 16px 36px rgba(0, 0, 0, 0.55);
  font-family: system-ui, -apple-system, sans-serif;
  color: #e2e8f0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.rs-root *, .rs-root *::before, .rs-root *::after {
  box-sizing: border-box;
  min-width: 0;
}
.rs-root .rs-container {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}
.rs-root .rs-dial-display {
  position: relative;
  width: 110px;
  height: 110px;
  border-radius: 50%;
  background: radial-gradient(circle, #252d3a, #13171e);
  border: 2px solid #3b4556;
  box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.8), 0 4px 14px rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
}
.rs-root .rs-dial-wheel {
  position: relative;
  width: 86px;
  height: 86px;
  border-radius: 50%;
  background: conic-gradient(from 180deg, #3b82f6, #06b6d4, #8b5cf6, #3b82f6);
  display: flex;
  align-items: center;
  justify-content: center;
  transform: rotate(var(--dial-angle, 0deg));
}
.rs-root .rs-dial-marker {
  position: absolute;
  top: 4px;
  width: 4px;
  height: 14px;
  background: #ffffff;
  border-radius: 2px;
  box-shadow: 0 0 6px rgba(255, 255, 255, 0.9);
}
.rs-root .rs-dial-center {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: #11141a;
  border: 1px solid #333d4e;
  display: flex;
  align-items: center;
  justify-content: center;
}
.rs-root .rs-dial-badge {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1px;
  color: #94a3b8;
}
.rs-root .rs-content {
  width: 100%;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.rs-root .rs-plan-header {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 12px;
  flex-wrap: wrap;
}
.rs-root .rs-plan-title {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
  color: #f8fafc;
}
.rs-root .rs-price-tag {
  font-size: 26px;
  font-weight: 800;
  color: #38bdf8;
  display: flex;
  align-items: baseline;
}
.rs-root .rs-currency {
  font-size: 16px;
  margin-right: 2px;
}
.rs-root .rs-period {
  font-size: 13px;
  color: #94a3b8;
  font-weight: 500;
}
.rs-root .rs-plan-desc {
  margin: 0 auto;
  max-width: 420px;
  font-size: 12px;
  color: #94a3b8;
  line-height: 1.4;
}
.rs-root .rs-nav {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
}
.rs-root .rs-btn {
  appearance: none;
  border: 1px solid #334155;
  background: #1e293b;
  color: #cbd5e1;
  padding: 8px 18px;
  border-radius: 8px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.rs-root .rs-btn:hover {
  background: #273549;
  color: #ffffff;
}
.rs-root .rs-btn.rs-active {
  background: #0284c7;
  border-color: #38bdf8;
  color: #ffffff;
  box-shadow: 0 0 12px rgba(56, 189, 248, 0.4);
}
.rs-root .rs-btn:focus-visible {
  outline: 2px solid #38bdf8;
  outline-offset: 2px;
}
@media (prefers-reduced-motion: no-preference) {
  .rs-root .rs-dial-wheel {
    transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
  }
  .rs-root .rs-btn {
    transition: background 0.2s ease, border-color 0.2s ease, transform 0.1s ease;
  }
  .rs-root .rs-btn:active {
    transform: scale(0.96);
  }
  .rs-root .rs-price-tag {
    transition: color 0.3s ease;
  }
}
.rs-root{max-height:none;--demo-accent:#38bdf8;border-color:var(--demo-accent)}.rs-root button:focus-visible{outline-color:var(--demo-accent)}
import { useState } from "react";
import "./F-011.css";
const plans = [{ "price": "9", "angle": "-45deg", "title": "Starter Tier", "desc": "Essential tools for hobbyists, indie hackers, and micro projects." }, { "price": "29", "angle": "0deg", "title": "Pro Tier", "desc": "Optimal power for growing teams with priority cloud builds." }, { "price": "89", "angle": "45deg", "title": "Enterprise Tier", "desc": "Dedicated high-throughput nodes, SLA, and customized support." }];
function RotaryPlanSelector() {
  const [selected, setSelected] = useState(1);
  return <div className="demo-stage rs-root" role="region" aria-label="Subscription Plan Selector"><div className="rs-container"><div className="rs-dial-display" aria-hidden="true"><div className="rs-dial-wheel" style={{ "--dial-angle": plans[selected].angle }}><div className="rs-dial-marker" /><div className="rs-dial-center"><span className="rs-dial-badge">{"TIER"}</span></div></div></div><div className="rs-content" aria-live="polite" aria-atomic="true"><div className="rs-plan-header"><h3 className="rs-plan-title">{plans[selected].title}</h3><div className="rs-price-tag"><span className="rs-currency">{"$"}</span><span className="rs-amount">{plans[selected].price}</span><span className="rs-period">{"/mo"}</span></div></div><p className="rs-plan-desc">{plans[selected].desc}</p></div><div className="rs-nav" role="group" aria-label="Subscription tiers"><button type="button" data-price="9" data-angle="-45deg" data-title="Starter Tier" data-desc="Essential tools for hobbyists, indie hackers, and micro projects." aria-label="Select Starter Plan" className={"rs-btn" + (selected === 0 ? " rs-active" : "")} aria-pressed={selected === 0} onClick={() => setSelected(0)}>{"Starter"}</button><button type="button" data-price="29" data-angle="0deg" data-title="Pro Tier" data-desc="Optimal power for growing teams with priority cloud builds." aria-label="Select Pro Plan" className={"rs-btn" + (selected === 1 ? " rs-active" : "")} aria-pressed={selected === 1} onClick={() => setSelected(1)}>{"Pro"}</button><button type="button" data-price="89" data-angle="45deg" data-title="Enterprise Tier" data-desc="Dedicated high-throughput nodes, SLA, and customized support." aria-label="Select Enterprise Plan" className={"rs-btn" + (selected === 2 ? " rs-active" : "")} aria-pressed={selected === 2} onClick={() => setSelected(2)}>{"Enterprise"}</button></div></div></div>;
}
export {
  RotaryPlanSelector as default
};
.rs-root {
  box-sizing: border-box;
  width: 100%;
  max-width: 560px;
  max-height: 420px;
  min-width: 0;
  margin: 0 auto;
  padding: 18px;
  background: linear-gradient(145deg, #181c24, #0f1217);
  border-radius: 16px;
  border: 1px solid #2b3240;
  box-shadow: 0 16px 36px rgba(0, 0, 0, 0.55);
  font-family: system-ui, -apple-system, sans-serif;
  color: #e2e8f0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.rs-root *, .rs-root *::before, .rs-root *::after {
  box-sizing: border-box;
  min-width: 0;
}
.rs-root .rs-container {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}
.rs-root .rs-dial-display {
  position: relative;
  width: 110px;
  height: 110px;
  border-radius: 50%;
  background: radial-gradient(circle, #252d3a, #13171e);
  border: 2px solid #3b4556;
  box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.8), 0 4px 14px rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
}
.rs-root .rs-dial-wheel {
  position: relative;
  width: 86px;
  height: 86px;
  border-radius: 50%;
  background: conic-gradient(from 180deg, #3b82f6, #06b6d4, #8b5cf6, #3b82f6);
  display: flex;
  align-items: center;
  justify-content: center;
  transform: rotate(var(--dial-angle, 0deg));
}
.rs-root .rs-dial-marker {
  position: absolute;
  top: 4px;
  width: 4px;
  height: 14px;
  background: #ffffff;
  border-radius: 2px;
  box-shadow: 0 0 6px rgba(255, 255, 255, 0.9);
}
.rs-root .rs-dial-center {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: #11141a;
  border: 1px solid #333d4e;
  display: flex;
  align-items: center;
  justify-content: center;
}
.rs-root .rs-dial-badge {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1px;
  color: #94a3b8;
}
.rs-root .rs-content {
  width: 100%;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.rs-root .rs-plan-header {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 12px;
  flex-wrap: wrap;
}
.rs-root .rs-plan-title {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
  color: #f8fafc;
}
.rs-root .rs-price-tag {
  font-size: 26px;
  font-weight: 800;
  color: #38bdf8;
  display: flex;
  align-items: baseline;
}
.rs-root .rs-currency {
  font-size: 16px;
  margin-right: 2px;
}
.rs-root .rs-period {
  font-size: 13px;
  color: #94a3b8;
  font-weight: 500;
}
.rs-root .rs-plan-desc {
  margin: 0 auto;
  max-width: 420px;
  font-size: 12px;
  color: #94a3b8;
  line-height: 1.4;
}
.rs-root .rs-nav {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
}
.rs-root .rs-btn {
  appearance: none;
  border: 1px solid #334155;
  background: #1e293b;
  color: #cbd5e1;
  padding: 8px 18px;
  border-radius: 8px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.rs-root .rs-btn:hover {
  background: #273549;
  color: #ffffff;
}
.rs-root .rs-btn.rs-active {
  background: #0284c7;
  border-color: #38bdf8;
  color: #ffffff;
  box-shadow: 0 0 12px rgba(56, 189, 248, 0.4);
}
.rs-root .rs-btn:focus-visible {
  outline: 2px solid #38bdf8;
  outline-offset: 2px;
}
@media (prefers-reduced-motion: no-preference) {
  .rs-root .rs-dial-wheel {
    transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
  }
  .rs-root .rs-btn {
    transition: background 0.2s ease, border-color 0.2s ease, transform 0.1s ease;
  }
  .rs-root .rs-btn:active {
    transform: scale(0.96);
  }
  .rs-root .rs-price-tag {
    transition: color 0.3s ease;
  }
}
.rs-root{max-height:none;--demo-accent:#38bdf8;border-color:var(--demo-accent)}.rs-root button:focus-visible{outline-color:var(--demo-accent)}

仕組みとカスタマイズHow It Works & Customization

仕組みHow it works

Scoped CSS creates the visual design; small vanilla JavaScript handlers update the local demo state.

スコープ化したCSSで描画し、Vanilla JavaScriptでデモ内の状態だけを更新します。

カスタマイズ方法Customization

Accent color for the panel border and keyboard focus ring.

パネルの枠線とキーボードフォーカスの色を変更します。

注意点Caveats

The sample content is fictional. No external assets or libraries are required. Motion follows the operating system preference. Demo labels are English; surrounding documentation supports Japanese and English.

デモ内容は架空です。外部素材・ライブラリは不要。OSの動きを減らす設定に対応します。デモ内のラベルは英語、解説は日英対応です。

よくある質問 Frequently Asked Questions

Does this demo need external libraries? 外部ライブラリは必要ですか?

The sample content is fictional. No external assets or libraries are required. Motion follows the operating system preference. Demo labels are English; surrounding documentation supports Japanese and English.

デモ内容は架空です。外部素材・ライブラリは不要。OSの動きを減らす設定に対応します。デモ内のラベルは英語、解説は日英対応です。

AIへの指示テンプレート AI Prompt Template

以下をAIに貼り付けると実装を依頼できます。 Paste the following into your AI assistant to request implementation.