Spring ’26 で一般公開された ExperiencePropertyTypeBundle を使用したコンポーネント作成ガイド記事を併せてご覧ください。LRWサイトを構築しているのなら、ExperiencePropertyTypeBundle 版をお勧めいたします。
LWRサイトを細部までカスタマイズしようとすると、必然的に設定項目は増えていきます。 過去の記事で紹介した「Dark Mode Toggle」は、テキストや背景だけでなく、リストビューのヘッダー、ボタンの全ステート(Hover/Focus)、フォームの枠線など、60以上のCSS変数を制御する高機能コンポーネントです。

これを標準の js-meta.xml で定義すると、設定画面は無限スクロールとなり、運用者は「どこを変更したかわからない」「元に戻せない」というストレスを抱えます。
今回は、以下の技術を組み合わせて、この問題を根本から解決する「究極の管理画面」を構築します。
- JSON一元管理: 60個の値を1つのプロパティに集約する。
- UIの部品化: カラーピッカーとリセットボタンをセットにした「設定部品」を作る。
- 個別リセット: 項目ごとに「未設定(デフォルト)」に戻せる機能を提供する。

これらをCustom Property Editor (CPE) の技術をベースに実装しています。
CPEについて詳しくは以下をご覧ください。
今回の記事で作成した「ダークモード切り替えコンポーネント」の全ソースコードをGitHubで公開しています。 ぜひ Clone して、あなたの組織で動かしてみてください。
ステップ1: 設定部品コンポーネント (c-removable-color-picker)
60回も同じHTMLを書くのは非効率です。まずは、「カラーピッカー」と「状態表示バッジ」、そして「リセットボタン」がセットになった子コンポーネントを作成します。
HTML (removableColorPicker.html)
<template>
<div class="slds-form-element slds-m-bottom_x-small">
<label class="slds-form-element__label">{label}</label>
<div class="slds-form-element__control">
<div class="slds-grid slds-gutters_x-small slds-grid_vertical-align-center">
<div class="slds-col slds-grow-none">
<input
type="color"
class="slds-input slds-p-around_none"
style="height: 32px; width: 50px;"
value={internalValue}
onchange={handleChange}>
</div>
<div class="slds-col slds-grow">
<lightning-input
type="text"
variant="label-hidden"
value={internalValue}
pattern="^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"
onchange={handleChange}
placeholder="#RRGGBB">
</lightning-input>
</div>
</div>
<div class="slds-grid slds-gutters_x-small slds-grid_vertical-align-center">
<div class="slds-m-around_x-small">
<template if:true={hasValue}>
<span class="slds-badge slds-theme_success">設定中: {value}</span>
</template>
<template if:false={hasValue}>
<span class="slds-badge">デフォルト</span>
</template>
</div>
<template if:true={hasValue}>
<div class="slds-m-around_x-small">
<lightning-button-icon
icon-name="utility:undo"
variant="border-filled"
alternative-text="リセット"
onclick={handleClear}>
</lightning-button-icon>
</div>
</template>
</div>
</div>
</div>
</template>
JavaScript (removableColorPicker.js)
import { LightningElement, api } from 'lwc';
export default class RemovableColorPicker extends LightningElement {
@api label;
@api name;
@api value;
/**
* 値が設定されているか(バッジ表示用)
* null, undefined, 空文字以外なら true
*/
get hasValue() {
return this.value != null && this.value !== '';
}
/**
* カラーピッカーに渡す値
* 余計なデフォルト値を設定せず、nullならnullを渡します。
* (lightning-input type="color" は null を受け取るとブラウザ標準の色を表示しますが、
* hasValueがfalseなので「デフォルト」バッジが表示され、区別がつきます)
*/
get internalValue() {
return this.value;
}
handleChange(event) {
// 値が空文字の場合は null として扱う
const val = event.target.value;
this.dispatchChange(val === '' ? null : val);
}
handleClear() {
this.dispatchChange(null);
}
dispatchChange(val) {
this.dispatchEvent(new CustomEvent('colorchange', {
detail: {
name: this.name,
value: val
}
}));
}
}
ステップ2: 親エディタコンポーネント (c-dark-mode-configurator)
作成した部品を使い、アコーディオンの中に並べていきます。 部品化したおかげで、HTMLが劇的に読みやすくなります。
HTML (darkModeConfigurator.html)
<template>
<div class="slds-p-around_x-small">
<div class="slds-grid slds-grid_align-spread slds-m-bottom_small" style="align-items: center;">
<span class="slds-text-heading_small">ダークモード設定</span>
<template if:true={isModified}>
<lightning-button-icon icon-name="utility:delete" variant="border-filled"
alternative-text="全リセット" onclick={handleResetAll} title="全項目を未設定に戻す">
</lightning-button-icon>
</template>
</div>
<div class="slds-text-body_small slds-text-color_weak slds-m-bottom_medium">
※ 未設定の項目は、ライトモード(標準)の色がそのまま使用されます。
</div>
<lightning-accordion allow-multiple-sections-open active-section-name="basic">
// ここに各種設定を記述します(詳細はGitHubをご覧ください)
</lightning-accordion>
</div>
</template>
JavaScript (darkModeConfigurator.js)
import { LightningElement, api, track } from 'lwc';
export default class DarkModeConfigurator extends LightningElement {
// 内部保持用の変数
_value;
// 設定オブジェクト(画面表示用)
@track settings = {};
/**
* @api value の Getter
* Builderが値を取得する際に呼ばれます
*/
@api
get value() {
return this._value;
}
/**
* @api value の Setter
* Builderから保存されたデータが渡された瞬間に実行されます
* connectedCallbackよりも確実です
*/
set value(v) {
this._value = v;
if (v) {
try {
// 保存されたJSON文字列をオブジェクトに戻す
this.settings = JSON.parse(v);
} catch (e) {
console.error('JSON Parse Error', e);
this.settings = {};
}
} else {
// 値がない場合は初期化
this.settings = {};
}
}
/**
* 何か一つでも設定されているか判定
*/
get isModified() {
return Object.keys(this.settings).length > 0;
}
/**
* 子コンポーネントからの変更通知を一括処理
*/
handleColorChange(event) {
const { name, value } = event.detail;
// null または 空文字なら設定から削除(リセット)
if (value === null || value === '') {
// リアクティブ性を保つため、オブジェクトを複製して削除
const newSettings = { ...this.settings };
delete newSettings[name];
this.settings = newSettings;
} else {
// 値があれば更新
this.settings = { ...this.settings, [name]: value };
}
this.notifyChange();
}
/**
* 全リセットボタン
*/
handleResetAll() {
this.settings = {};
this.notifyChange();
}
/**
* Builderに変更を通知
*/
notifyChange() {
// 設定オブジェクトをJSON文字列に変換して渡す
const newValue = JSON.stringify(this.settings);
// 自身の_valueも更新しておく(ループ防止のため本来はチェックが必要だが、今回は簡易実装)
this._value = newValue;
this.dispatchEvent(new CustomEvent('valuechange', {
detail: { value: newValue }
}));
}
}
ステップ3: メタデータ (ultimateDarkModeSwitcher.js-meta.xml)
60行あったプロパティ定義は、この1つの定義で完結します。
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>65.0</apiVersion>
<isExposed>true</isExposed>
<masterLabel>Dark Mode Switcher Ultimate</masterLabel>
<targets>
<target>lightningCommunity__Page</target>
<target>lightningCommunity__Default</target>
</targets>
<targetConfigs>
<targetConfig targets="lightningCommunity__Default">
<property
name="themeSettings"
type="String"
label="テーマ設定 (JSON)"
editor="c/darkModeConfigurator"
/>
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
ステップ4: メインロジック (ultimateDarkModeSwitcher.js)
最後に、受け取ったJSONをパースして適用します。 以前は60個の @api 変数がありましたが、それらはすべてJSONのキーとして扱います。
import { LightningElement, api } from 'lwc';
export default class DarkModeSwitcherUltimate extends LightningElement {
@api themeSettings;
isDark = false;
// ...
getSettings() {
try {
return this.themeSettings ? JSON.parse(this.themeSettings) : {};
} catch (e) {
return {};
}
}
applyTheme() {
const style = document.documentElement.style;
const settings = this.getSettings(); // JSONから設定を取得
const darkVars = this.getCssVariableMap(settings); // 設定を渡してマップ生成
const darkKeys = Object.keys(darkVars);
if (this.isDark) {
// ダークモード適用: 設定値があるものだけ書き換える
darkKeys.forEach(key => {
if (darkVars[key]) style.setProperty(key, darkVars[key]);
});
// 強制スタイル注入
this.injectOverrideStyles(settings);
} else {
// ダークモード解除
darkKeys.forEach(key => {
style.removeProperty(key);
});
// ライトモード設定など(必要に応じて)
// ...
}
}
// ... injectOverrideStyles も同様に settings引数 を使うように修正 ...
/**
* settingsオブジェクトを受け取ってマップを生成
* ※ これまでは this.colorRoot でしたが s.colorRoot に変わります
*/
getCssVariableMap(s) {
return {
// --- 基本 ---
'--dxp-g-root': s.colorRoot,
'--dxp-g-root-contrast': s.colorText,
'--dxp-g-brand': s.colorBrand,
'--dxp-g-brand-contrast': s.colorBrandContrast,
// ... 他60項目すべて同様にマッピング ...
};
}
}
まとめ
このアーキテクチャを採用することで、以下の「UX革命」が起きました。
- 個別リセットが可能に: 「あ、ボタンの色だけデフォルトに戻したい」という時も、横の×ボタンを押すだけです。
- 管理画面がスッキリ: 60個の項目が初期状態ではコンパクトに収まっています。
- 安全な運用: 設定していない項目(null)は、サイト本来のデザイン(ライトモード)を維持します。
「多機能」と「使いやすさ」はトレードオフではありません。Custom Property Editor の力を引き出せば、その両方を満たす素晴らしいコンポーネントが作成可能です。






読者の声