はじめに
プリザンターの分類項目では、選択肢をラジオボタンで表示できます。また、チェック項目では標準のチェックボックスが使われます。これらの見た目は素朴で実用的ですが、もう少しモダンな印象にしたいと感じることもあるのではないでしょうか。
この記事では、拡張スタイルと拡張サーバスクリプトを使って、ラジオボタンとチェックボックスを Bootstrap 風のボタングループに変換する方法を紹介します。プリザンターのテーマカスタムプロパティを活用するため、テーマを切り替えても自動的に配色が追従します。
仕組みを整理する
プリザンターの HTML 構造
分類項目をラジオボタン表示にすると、次のような HTML が出力されます。
<div class="field-normal" id="ClassAField">
<label class="field-label">分類A</label>
<div class="field-control">
<div class="container-normal">
<span class="radio-value">
<input type="radio" name="ClassA" value="1" id="ClassA-1">
<label for="ClassA-1">選択肢1</label>
</span>
<span class="radio-value">
<input type="radio" name="ClassA" value="2" id="ClassA-2">
<label for="ClassA-2">選択肢2</label>
</span>
</div>
</div>
</div>
チェック項目は次のような構造です。
<div class="field-normal" id="CheckAField">
<label class="field-label" for="CheckA">チェックA</label>
<div class="field-control">
<div class="container-normal">
<input type="checkbox" id="CheckA" class="control-checkbox">
</div>
</div>
</div>
Bootstrap のボタングループ
Bootstrap 5 では、ラジオボタンやチェックボックスをボタンのように並べて表示する「ボタングループ」が提供されています。ネイティブの <input> を視覚的に隠し、隣接する <label> をボタンとしてスタイリングする仕組みです。
今回はこの考え方をプリザンターに適用します。
テーマカスタムプロパティの活用
プリザンター v1 のテーマ CSS には、ボタンやコントロールの配色がカスタムプロパティとして定義されています。たとえば cerulean テーマの場合は次のとおりです。
| カスタムプロパティ | 用途 | cerulean での値 |
|---|---|---|
--btn-normal-bg |
通常ボタン背景 | #fff |
--btn-normal-label |
通常ボタン文字色 | #222 |
--btn-normal-border |
通常ボタン枠線 | #cecece |
--btn-positive-bg |
選択ボタン背景 | #106ebe |
--btn-positive-label |
選択ボタン文字色 | #fff |
--btn-positive-hover |
ホバー時背景 | #005a9e |
--control-border |
コントロール枠線 | #cecece |
これらを使うことで、テーマに合わせたスタイリングが可能です。
実装の構成
今回は拡張スタイルと拡張サーバスクリプトの 2 ファイルで構成します。
| 拡張機能 | 役割 |
|---|---|
| 拡張スタイル | ボタングループの見た目を定義 |
| 拡張サーバスクリプト |
context.AddResponse でチェック項目の DOM を補正(Ajax 再読込にも対応) |
実装してみよう
ボタングループのスタイル(拡張スタイル)
拡張スタイルとして App_Data/Parameters/ExtendedStyles/ に配置します。
/* ================================================================
Bootstrap 風ボタングループ(ラジオボタン・チェックボックス共通)
================================================================ */
/* --- ラジオボタン:ボタングループ化 --- */
/* コンテナを横並びに */
.btn-group .container-normal {
display: inline-flex;
flex-wrap: wrap;
}
/* ラジオボタン間のマージンを消す */
.btn-group .radio-value {
margin: 0;
}
/* ネイティブのラジオボタンを非表示(アクセシビリティは維持) */
.btn-group .radio-value input[type="radio"] {
position: absolute;
clip: rect(0, 0, 0, 0);
pointer-events: none;
}
/* ラベルをボタン化 */
.btn-group .radio-value label {
display: inline-block;
padding: 6px 16px;
border: 1px solid var(--control-border);
background: var(--btn-normal-bg);
color: var(--btn-normal-label);
cursor: pointer;
line-height: 1.5;
margin: 0;
user-select: none;
transition: background-color 0.15s ease,
color 0.15s ease,
border-color 0.15s ease;
}
/* 隣接ボタンのボーダー重複を排除 */
.btn-group .radio-value + .radio-value label {
border-left: none;
}
/* 先頭だけ左角丸 */
.btn-group .radio-value:first-child label {
border-radius: 4px 0 0 4px;
}
/* 末尾だけ右角丸 */
.btn-group .radio-value:last-child label {
border-radius: 0 4px 4px 0;
}
/* 選択肢が 1 つだけの場合は両端角丸 */
.btn-group .radio-value:only-child label {
border-radius: 4px;
}
/* 選択状態 */
.btn-group .radio-value input[type="radio"]:checked + label {
background: var(--btn-positive-bg);
color: var(--btn-positive-label);
border-color: var(--btn-positive-bg);
}
/* 隣接する選択ボタンのボーダー補正 */
.btn-group .radio-value:has(input:checked) + .radio-value label {
border-left: 1px solid var(--control-border);
}
/* ホバー(未選択時) */
.btn-group .radio-value:not(:has(input:checked)) label:hover {
background: var(--btn-positive-hover);
color: var(--btn-positive-label);
border-color: var(--btn-positive-hover);
}
/* フォーカス */
.btn-group .radio-value input[type="radio"]:focus-visible + label {
outline: 2px solid var(--btn-positive-bg);
outline-offset: -2px;
}
/* --- チェックボックス:トグルボタン化 --- */
/* ネイティブのチェックボックスを非表示 */
.btn-toggle .control-checkbox {
position: absolute;
clip: rect(0, 0, 0, 0);
pointer-events: none;
}
/* field-control 領域を非表示(チェックボックスは label の for 経由で操作) */
.btn-toggle .field-control {
display: none;
}
/* フィールドラベルをボタン化 */
.btn-toggle .field-label {
display: inline-block;
padding: 6px 16px;
border: 1px solid var(--control-border);
border-radius: 4px;
background: var(--btn-normal-bg);
color: var(--btn-normal-label);
cursor: pointer;
line-height: 1.5;
user-select: none;
transition: background-color 0.15s ease,
color 0.15s ease,
border-color 0.15s ease;
}
/* チェック ON 時 */
.btn-toggle:has(.control-checkbox:checked) .field-label {
background: var(--btn-positive-bg);
color: var(--btn-positive-label);
border-color: var(--btn-positive-bg);
}
/* ホバー(未チェック時) */
.btn-toggle:not(:has(.control-checkbox:checked)) .field-label:hover {
background: var(--btn-positive-hover);
color: var(--btn-positive-label);
border-color: var(--btn-positive-hover);
}
/* フォーカス */
.btn-toggle:has(.control-checkbox:focus-visible) .field-label {
outline: 2px solid var(--btn-positive-bg);
outline-offset: -2px;
}
DOM の補正(拡張サーバスクリプト)
ラジオボタンは CSS だけで機能しますが、チェック項目の <label class="field-label"> に for 属性がない場合、ラベルクリックでチェックボックスを切り替えられません。拡張サーバスクリプトの context.AddResponse で <script> タグを注入し、DOM を補正します。
自動ポストバックなどの Ajax 通信で要素が再描画されると for 属性の設定が失われるため、$(document).ajaxComplete() で再適用しています。
App_Data/Parameters/ExtendedServerScripts/ に配置してください。
{
"Name": "BtnGroup",
"WhenViewProcessing": true
}
var js = [
'(function(){',
' if($p.action!=="edit"&&$p.action!=="new")return;',
' function applyBtnToggle(){',
' document.querySelectorAll(".field-normal.btn-toggle,.field-wide.btn-toggle")',
' .forEach(function(f){',
' var c=f.querySelector(".control-checkbox");',
' var l=f.querySelector(".field-label");',
' if(!c||!l)return;',
' if(!l.getAttribute("for"))l.setAttribute("for",c.id);',
' });',
' }',
' applyBtnToggle();',
' $(document).ajaxComplete(function(){applyBtnToggle();});',
'})()'
].join('\n');
context.AddResponse('Append', 'body', '<script>' + js + '</script>');
context.AddResponse の Append メソッドは $(target).append(value) として実行されます。<body> に <script> タグを注入することで、初回表示時の DOM 補正と ajaxComplete による再描画対応の両方を実現できます。
設定方法
ラジオボタン(分類項目)
分類項目の「項目の詳細設定」でコントロール種別を「ラジオボタン」に設定したうえで、フィールド CSS に btn-group を入力します。
| 設定箇所 | 値 |
|---|---|
| コントロール種別 | ラジオボタン |
| フィールド CSS | btn-group |
チェックボックス(チェック項目)
チェック項目の「項目の詳細設定」でフィールド CSS に btn-toggle を入力します。
| 設定箇所 | 値 |
|---|---|
| フィールド CSS | btn-toggle |
フィールド CSS はサイト管理 → エディタ → 対象項目の詳細設定から入力できます。
適用範囲の設定
ここまでの実装では、フィールド CSS にクラスを設定した項目だけがボタングループ化されます。用途に応じて、次の 3 パターンで適用範囲を切り替えられます。
| パターン | 説明 | フィールド CSS |
|---|---|---|
| 指定した項目だけ変更 | 対象項目のフィールド CSS にクラスを追加 |
btn-group / btn-toggle
|
| すべて変更 | 全ラジオボタン・チェック項目を一括変換 | 設定不要 |
| 指定した項目だけ除外 | 全項目を変換しつつ、除外クラスで特定項目をスキップ |
no-btn-group / no-btn-toggle
|
パターン 1:指定した項目だけ変更する(opt-in)
ここまでの実装がこのパターンです。対象の項目にだけフィールド CSS を設定するため、既存の画面に影響を与えません。
- ラジオボタン → フィールド CSS に
btn-group - チェック項目 → フィールド CSS に
btn-toggle
パターン 2:すべて変更する
すべてのラジオボタン・チェック項目をボタングループ化したい場合は、CSS セレクタをフィールドクラスに依存しない形に変更します。
拡張スタイルのセレクタを次のように書き換えてください。ラジオボタンは .radio-value を持つフィールドすべてに、チェック項目は .control-checkbox を持つフィールドすべてに適用されます。
/* --- ラジオボタン:すべてのラジオボタンをボタングループ化 --- */
.field-normal:has(.radio-value) .container-normal,
.field-wide:has(.radio-value) .container-normal {
display: inline-flex;
flex-wrap: wrap;
}
.field-normal:has(.radio-value) .radio-value,
.field-wide:has(.radio-value) .radio-value {
margin: 0;
}
.field-normal:has(.radio-value) .radio-value input[type="radio"],
.field-wide:has(.radio-value) .radio-value input[type="radio"] {
position: absolute;
clip: rect(0, 0, 0, 0);
pointer-events: none;
}
.field-normal:has(.radio-value) .radio-value label,
.field-wide:has(.radio-value) .radio-value label {
display: inline-block;
padding: 6px 16px;
border: 1px solid var(--control-border);
background: var(--btn-normal-bg);
color: var(--btn-normal-label);
cursor: pointer;
line-height: 1.5;
margin: 0;
user-select: none;
}
/* 以降の :checked, :hover, border-radius ルールも同様に
.btn-group → .field-normal:has(.radio-value), .field-wide:has(.radio-value)
に置き換えてください */
/* --- チェックボックス:すべてのチェック項目をトグルボタン化 --- */
.field-normal:has(.control-checkbox) .control-checkbox,
.field-wide:has(.control-checkbox) .control-checkbox {
position: absolute;
clip: rect(0, 0, 0, 0);
pointer-events: none;
}
.field-normal:has(.control-checkbox) .field-control,
.field-wide:has(.control-checkbox) .field-control {
display: none;
}
.field-normal:has(.control-checkbox) .field-label,
.field-wide:has(.control-checkbox) .field-label {
display: inline-block;
padding: 6px 16px;
border: 1px solid var(--control-border);
border-radius: 4px;
background: var(--btn-normal-bg);
color: var(--btn-normal-label);
cursor: pointer;
line-height: 1.5;
user-select: none;
}
/* 以降の :checked, :hover ルールも同様に
.btn-toggle → .field-normal:has(.control-checkbox), .field-wide:has(.control-checkbox)
に置き換えてください */
拡張サーバスクリプトの DOM 補正も、クラスに依存せずすべてのチェック項目を対象にします。
var js = [
'(function(){',
' if($p.action!=="edit"&&$p.action!=="new")return;',
' function applyBtnToggle(){',
' document.querySelectorAll(".field-normal:has(.control-checkbox),.field-wide:has(.control-checkbox)")',
' .forEach(function(f){',
' var c=f.querySelector(".control-checkbox");',
' var l=f.querySelector(".field-label");',
' if(!c||!l)return;',
' if(!l.getAttribute("for"))l.setAttribute("for",c.id);',
' });',
' }',
' applyBtnToggle();',
' $(document).ajaxComplete(function(){applyBtnToggle();});',
'})()'
].join('\n');
context.AddResponse('Append', 'body', '<script>' + js + '</script>');
パターン 3:指定した項目だけ除外する(opt-out)
全項目をボタングループ化しつつ、特定の項目だけ標準表示に残したい場合は、パターン 2 のセレクタに :not() で除外クラスを追加します。
除外したい項目のフィールド CSS に no-btn-group(ラジオボタン)または no-btn-toggle(チェック項目)を設定してください。
/* --- ラジオボタン:除外クラス付きの項目をスキップ --- */
.field-normal:has(.radio-value):not(.no-btn-group) .container-normal,
.field-wide:has(.radio-value):not(.no-btn-group) .container-normal {
display: inline-flex;
flex-wrap: wrap;
}
.field-normal:has(.radio-value):not(.no-btn-group) .radio-value,
.field-wide:has(.radio-value):not(.no-btn-group) .radio-value {
margin: 0;
}
.field-normal:has(.radio-value):not(.no-btn-group) .radio-value input[type="radio"],
.field-wide:has(.radio-value):not(.no-btn-group) .radio-value input[type="radio"] {
position: absolute;
clip: rect(0, 0, 0, 0);
pointer-events: none;
}
.field-normal:has(.radio-value):not(.no-btn-group) .radio-value label,
.field-wide:has(.radio-value):not(.no-btn-group) .radio-value label {
display: inline-block;
padding: 6px 16px;
border: 1px solid var(--control-border);
background: var(--btn-normal-bg);
color: var(--btn-normal-label);
cursor: pointer;
line-height: 1.5;
margin: 0;
user-select: none;
}
/* 以降の :checked, :hover, border-radius ルールも同様に
:not(.no-btn-group) を付与してください */
/* --- チェックボックス:除外クラス付きの項目をスキップ --- */
.field-normal:has(.control-checkbox):not(.no-btn-toggle) .control-checkbox,
.field-wide:has(.control-checkbox):not(.no-btn-toggle) .control-checkbox {
position: absolute;
clip: rect(0, 0, 0, 0);
pointer-events: none;
}
.field-normal:has(.control-checkbox):not(.no-btn-toggle) .field-control,
.field-wide:has(.control-checkbox):not(.no-btn-toggle) .field-control {
display: none;
}
.field-normal:has(.control-checkbox):not(.no-btn-toggle) .field-label,
.field-wide:has(.control-checkbox):not(.no-btn-toggle) .field-label {
display: inline-block;
padding: 6px 16px;
border: 1px solid var(--control-border);
border-radius: 4px;
background: var(--btn-normal-bg);
color: var(--btn-normal-label);
cursor: pointer;
line-height: 1.5;
user-select: none;
}
/* 以降の :checked, :hover ルールも同様に
:not(.no-btn-toggle) を付与してください */
拡張サーバスクリプトでも除外クラスを考慮します。
var js = [
'(function(){',
' if($p.action!=="edit"&&$p.action!=="new")return;',
' function applyBtnToggle(){',
' document.querySelectorAll(',
' ".field-normal:has(.control-checkbox):not(.no-btn-toggle),"',
' +".field-wide:has(.control-checkbox):not(.no-btn-toggle)"',
' ).forEach(function(f){',
' var c=f.querySelector(".control-checkbox");',
' var l=f.querySelector(".field-label");',
' if(!c||!l)return;',
' if(!l.getAttribute("for"))l.setAttribute("for",c.id);',
' });',
' }',
' applyBtnToggle();',
' $(document).ajaxComplete(function(){applyBtnToggle();});',
'})()'
].join('\n');
context.AddResponse('Append', 'body', '<script>' + js + '</script>');
カスタマイズ
縦並びにする
ラジオボタンを縦並びにしたい場合は、コンテナの方向を変更します。拡張スタイルに次のルールを追記してください。
/* 縦並びボタングループ */
.btn-group-vertical .container-normal {
display: inline-flex;
flex-direction: column;
}
.btn-group-vertical .radio-value + .radio-value label {
border-left: 1px solid var(--control-border);
border-top: none;
}
.btn-group-vertical .radio-value:first-child label {
border-radius: 4px 4px 0 0;
}
.btn-group-vertical .radio-value:last-child label {
border-radius: 0 0 4px 4px;
}
.btn-group-vertical .radio-value:only-child label {
border-radius: 4px;
}
フィールド CSS を btn-group-vertical に変更するだけで切り替えられます。縦並び時にも同じ選択状態・ホバーのスタイルが適用されるように、共通の btn-group ルールに加えて上記を拡張スタイルに追記してください。
アウトラインスタイル
選択時にべた塗りではなくアウトラインだけにしたい場合は、選択状態のスタイルを次のように変更します。
/* アウトラインスタイル */
.btn-group-outline .radio-value input[type="radio"]:checked + label {
background: var(--btn-normal-bg);
color: var(--btn-positive-bg);
border-color: var(--btn-positive-bg);
box-shadow: inset 0 0 0 1px var(--btn-positive-bg);
}
ネガティブカラー
削除系の操作など、警告的な意味合いを持たせたい場合は --btn-negative-* 系の変数を使います。
/* ネガティブカラー */
.btn-group-danger .radio-value input[type="radio"]:checked + label {
background: var(--btn-negative-bg);
color: var(--btn-negative-label);
border-color: var(--btn-negative-bg);
}
.btn-group-danger .radio-value:not(:has(input:checked)) label:hover {
background: var(--btn-negative-hover);
color: var(--btn-negative-label);
border-color: var(--btn-negative-hover);
}
まとめ
プリザンターのラジオボタンとチェックボックスを Bootstrap 風のボタングループに変換する方法を紹介しました。
| 項目 | 方法 |
|---|---|
| ラジオボタン | フィールド CSS に btn-group を設定、拡張スタイルのみで実現 |
| チェックボックス | フィールド CSS に btn-toggle を設定、拡張スタイル+ context.AddResponse で DOM 補正 |
| テーマ追従 |
--btn-positive-* などのカスタムプロパティを使用 |
| Ajax 再描画 |
$(document).ajaxComplete() で DOM 補正を再適用 |
| 適用範囲 | opt-in(指定のみ変更)/ 全変更 / opt-out(指定のみ除外)の 3 パターン |
拡張スタイルでボタングループの見た目を定義し、チェック項目の DOM 補正は context.AddResponse で <script> を注入しています。テーマのカスタムプロパティを活用しているため、cerulean 以外のテーマに切り替えてもスタイルが自動的に追従します。適用範囲もフィールド CSS の設定だけで柔軟に切り替えられるため、段階的な導入にも一括導入にも対応できます。