私の似顔絵

Miko

働いたり働かなかったり、畑を耕したりしています。

このブログについて

WordPressカスタムブロックでアコーディオンを作成する

以前、マーカー機能を追加する方法【プラグイン不要/JSX不要で実装】を紹介しましたが、今回はサイトで必ず実装されるであろうアコーディオン機能に挑戦しました。

アコーディオンはFAQ(よくある質問)で実装されます。FAQはSEOの観点からも重要な要素で、サイト制作では必須になります。

WordPressのGutenbergエディターでアコーディオン機能を実装したくなり、カスタムブロックを作成してみました。Advanced Custom Fields(ACF)などとの比較も含め、開発手順を詳しく記録として残しておきます。

完成イメージ

完成したアコーディオンブロックの特徴:

  • エディター内編集: タイトルとコンテンツを直接編集可能
  • フロントエンド動作: 公開ページでのクリックによる滑らかな開閉機能
  • 直感的UI: シンプルで使いやすいユーザーインターフェース
  • リッチテキスト対応: 編集画面で太字、色変更、フォントサイズ変更などの編集機能
  • アクセシビリティ対応: SEO対策に、スクリーンリーダーやキーボード操作に配慮



アコーディオン実装方法比較表

項目ACF(通常)ACF BlocksGutenberg カスタムブロックショートコード
開発コスト低(GUI設定メイン)中(GUI設定+簡単なテンプレ)高(コーディング必要)低(短い関数で実装可能)
学習コスト中(PHP+ACF Block設定理解)高(React知識必要)低(PHPの基礎で可)
UI/UX管理画面ベース(本文と分離)エディター内統合(インライン編集)エディター内で編集完結プレーンテキストベース
プレビュー下書き画面で可能編集画面で可能(静的)のようです編集画面で可能(静的)下書き画面で可能
再利用性テンプレート依存ブロック単位で完結ブロック単位で完結コードをコピペ
保守性ACF依存ACF依存(ただしブロック化)WordPress標準機能テーマ/プラグインに依存
カスタマイズ性制限あり制限あり(PHPベース)完全自由完全自由
パフォーマンス軽量軽量JS読み込み必要軽量
HTML構造の自由度制約あり制約あり自由自由
適合ケース既存でACF多用のサイトACFを使い慣れていてブロック化したい場合クライアント案件や長期運用サイト自分用・短納期案件

ACF

  • ACFプラグインでアコーディオン用カスタムフィールドを登録し、テンプレート(single.php や functions.php など)に表示処理を記述する必要がある。
  • 入力エリアは投稿編集画面の下部に設置されるため、本文とフィールド欄を行き来する必要があり、編集UIに一体感がなく、初心者にはやや直感的でない。

ACF Blocks

  • ACFの柔軟なフィールド定義を活かしつつ、ブロックとしてGutenberg内に表示可能で、本文とフィールドが同一画面で完結。
  • HTML構造やレイアウトはテンプレートPHPで制御。React知識は不要だが、ACF Proが必須。

カスタムブロック

  • 高いカスタマイズ性と将来性があるが、React/JSの知識が必須。
  • エディタ内から自作ブロックを呼び出せるため、同じ編集画面で完結。
  • テンプレート、プラグインに依存しない。

ショートコード

  • 数行のPHP関数で動くため、実装が早い。
  • 標準WordPress機能のみで使える
  • プラグインなしでも動作可能
  • 必要な処理だけを出力するため、オーバーヘッドが小さい
  • 編集画面ではただの[accordion title=”○○”]内容[/accordion]のようなテキスト表示
  • コードをコピペする形になる
  • 誤って属性名を間違えると表示が崩れる
  • Gutenberg標準の方向性(ビジュアルブロック化)に沿わない
  • 複雑になるとカスタムブロックに書き換えた方が早くなる

以上を踏まえ、WordPressの方向性に合致(Gutenbergファースト)という将来性:と、React/JavaScriptスキル向上の観点から、今回はカスタムブロック開発を選択しました。

開発環境の準備

まずはWordPressのブロック開発環境を整えます。

他のサイトでも簡単に使えるように、プラグインで開発していきます。

私の環境はMacで、Dockerアプリを立ち上げるとhttp://localhost:8081/にアクセスするとサイトが立ち上がるようになっています。参考記事:DockerでローカルPCにWordPress環境を構築する

ノーコードでWordPressを更新するのはこれだけで良いのですが、テーマを開発するにはnpm startを実行します。

# WordPressを開発しているディレクトリルートで実行
npm start

# ターミナルを並行して立ち上げて実行
cd wordpress/wp-content/plugins/

# 公式ツールでブロックプロジェクト作成
npx @wordpress/create-block my-accordion

# カスタムブロックディレクトリに移動して開発開始
cd my-accordion
npm run start

すると、pluginsディレクトリ配下にmy-accordionディレクトリの中に複数のファイルが生成されているのが確認できます。それらを好みの関数名などに変更していきます(初期の関数名のままでもOKです)。

ディレクトリ内構造

my-accordion/
├── build/                    # ビルド後のファイル(自動生成)
├── node_modules/            # npm依存関係(自動生成)
├── src/                     # ソースファイル
│   ├── block.json          # ブロック設定
│   ├── edit.js             # エディター用コンポーネント
│   ├── editor.scss         # エディター用CSS
│   ├── index.js            # メインエントリーポイント
│   ├── save.js             # 保存用コンポーネント
│   ├── style.scss          # フロント+エディター共通CSS
│   └── view.js             # フロントエンド用JavaScript
├── .editorconfig           # エディター設定
├── .gitignore              # Git除外設定
├── my-accordion.php        # メインプラグインファイル
├── package.json            # npm設定
├── package-lock.json       # npm依存関係ロック
└── readme.txt              # WordPress.org用説明

注意:開発をしている最中にも、バージョンが新しくなるとディレクトリ構造が異なっています。

現在(2025/08/11)では、srcの下にmy-accordionディレクトリがあり、その下にblock.json などが配置されていて、buildディレクトリの構造と合わせた内容になっているようです。

カスタムブロックの開発

Step 1: プラグインの基本構造を作成

パス: wordpress/wp-content/plugins/my-accordion/my-accordion.php

<?php
/**
 * Plugin Name:       My accordion
 * Description:       アコーディオンブロック(カスタム版)
 * Version:           0.1.0
 * Requires at least: 5.8 //WPのバージョンによっては有効化できないので、バージョン数を変更するなどして対応してください。
 * Requires PHP:      7.4
 * Author:            Your Name
 * License:           GPL-2.0-or-later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       my-accordion
 * Update URI:        false
 *
 * @package CreateBlock
 */

// 直接アクセスを禁止
if (!defined('ABSPATH')) {
    exit;
}

/**
 * ブロックを登録
 */
function create_block_my_accordion_block_init() {
    // register_block_type(__DIR__ . '/build/');
	 register_block_type(__DIR__ . '/build/my-accordion');
}
add_action('init', 'create_block_my_accordion_block_init');

/**
 * 自動更新を無効化
 */
function my_accordion_disable_updates($transient) {
    $plugin_file = plugin_basename(__FILE__);

    if (isset($transient->response[$plugin_file])) {
        unset($transient->response[$plugin_file]);
    }

    if (isset($transient->checked[$plugin_file])) {
        unset($transient->checked[$plugin_file]);
    }

    return $transient;
}
add_filter('pre_set_site_transient_update_plugins', 'my_accordion_disable_updates');
add_filter('pre_set_transient_update_plugins', 'my_accordion_disable_updates');

このファイルはプラグインのエントリーポイントです。WordPress起動時にブロックを登録する役割を持ちます。

WordPressのバージョンが古いと有効化できません。バージョンを指定し、自動で更新したくない時の設定も含まれています。

下記のように初期の設定だと、ブロックエディタにアコーディオンのアイコンが出てこないので、my-accordionまでパスと通します。

 //buildディレクトリ配下にmy-accordionディレクトリがある場合は、以下の場合はパスが通らない
function create_block_my_accordion_block_init() {
     register_block_type(__DIR__ . '/build/');
}

package.json

パス: wordpress/wp-content/plugins/my-accordion/package.json

{
	"name": "my-accordion",
	"version": "0.1.0",
	"description": "アコーディオンブロック",
	"author": "The WordPress Contributors",
	"license": "GPL-2.0-or-later",
	"main": "build/index.js",
	"scripts": {
		"build": "wp-scripts build",
		"format": "wp-scripts format",
		"lint:css": "wp-scripts lint-style",
		"lint:js": "wp-scripts lint-js",
		"packages-update": "wp-scripts packages-update",
		"plugin-zip": "wp-scripts plugin-zip",
		"start": "wp-scripts start"
	},
	"devDependencies": {
		"@wordpress/scripts": "^30.20.0"
	}
}

@wordpress/scriptsを使用することで、複雑なWebpackの設定なしにブロック開発が可能になります。

Step 2: ブロックの設定定義

block.json

パス: wordpress/wp-content/plugins/my-accordion/src/block.json

{
	// WordPress Block JSON Schema(ブロック定義の標準形式)
	"$schema": "https://schemas.wp.org/trunk/block.json",

	// Block API のバージョン(3が最新)
	"apiVersion": 3,

	// ブロックの一意な識別子(namespace/block-name形式)
	"name": "create-block/my-accordion",

	// ブロックのバージョン番号
	"version": "0.1.0",

	// エディター内で表示されるブロック名
	"title": "My Accordion",

	// ブロックが属するカテゴリー(widgets, text, media など)
	"category": "widgets",

	// ブロック選択時に表示されるアイコン(Dashicons名)
	"icon": "editor-ul",

	// ブロックの説明文(ブロック選択画面で表示)
	"description": "アコーディオンブロック",

	// ブロック選択時のプレビュー例(空オブジェクトでデフォルト表示)
	"example": {},
	"attributes": {
	   "title": {
	     "type": "string",
	     "default": "アコーディオンタイトル"
	   },
	   "content": {
	    "type": "string",
	    "default": "アコーディオンコンテンツ"
	   }
	 },
	// ブロックでサポートする機能の設定
	"supports": {
		// HTML編集モードを無効化(ユーザーが直接HTMLを編集できない)
		"html": false
	},

	// 翻訳ドメイン(多言語対応用)
	"textdomain": "my-accordion",

	// === アセットファイルの指定 ===

	// エディター用JavaScript(ブロックの登録・編集機能)
	"editorScript": "file:./index.js",

	// フロントエンド+エディター共通CSS
	"style": "file:./style-index.css",

	// エディター専用CSS(編集画面でのみ読み込み)
	"editorStyle": "file:./index.css",

	// フロントエンド用JavaScript(サイト表示時の動作)
	"viewScript": "file:./view.js"
}

block.jsonはブロックのメタデータを定義します。WordPress 5.8以降で推奨される方法で、ブロックの基本情報やアセットファイルを指定できます。

上記のコメント部分は削除しないとビルドエラーになりますので、実装の際には削除してください。

Step 3: ブロックの登録

index.js(メインエントリーポイント)

パス: wordpress/wp-content/plugins/my-accordion/src/index.js

import "./style.scss"; // フロント + エディター共通
import "./editor.scss"; // エディター専用

import { registerBlockType } from "@wordpress/blocks";
import { __ } from "@wordpress/i18n";
import Edit from "./edit";
import Save from "./save";

registerBlockType("create-block/my-accordion", {
	title: __("Accordion", "my-accordion"),
	icon: "editor-ul",
	category: "widgets",
	edit: Edit,
	save: Save,
});

重要なポイント

  • attributes: block.jsonで定義するので、こちらでは記述しません。
  • edit: エディター内での編集コンポーネント(edit.jsファイルで定義)
  • save: フロントエンドでの表示コンポーネント(save.jsファイルで定義)

Step 4: エディター用コンポーネント

edit.js

パス: wordpress/wp-content/plugins/my-accordion/src/edit.js

import { useBlockProps, RichText } from "@wordpress/block-editor";

export default function Edit({ attributes, setAttributes }) {
	const blockProps = useBlockProps();

	return (
		<div {...blockProps}>
			<div className="accordion-editor">
				<RichText
					tagName="h3"
					className="accordion-title-editor"
					value={attributes.title}
					onChange={(value) => setAttributes({ title: value })}
					placeholder="アコーディオンのタイトルを入力"
				/>
				<RichText
					tagName="div"
					className="accordion-content-editor"
					value={attributes.content}
					onChange={(value) => setAttributes({ content: value })}
					placeholder="アコーディオンの内容を入力"
				/>
			</div>
		</div>
	);
}

<RichText>を使うことで、

✅ リッチテキストツールバー表示(太字、イタリック、リンクなど)

✅ フォーマット機能(見出し、段落など)

✅ キーボードショートカット対応

が簡単に行えます。

Edit関数の引数について

{ attributes, setAttributes }の役割:

  • attributes: ブロックに保存されているデータ(読み取り専用)
  • setAttributes: ブロックのデータを更新する関数
// 使用例
const currentTitle = attributes.title; // データを読み取り
setAttributes({ title: '新しいタイトル' }); // データを更新

// 実際の流れ
onChange={(title) => setAttributes({ title })}
// ↓
// 1. ユーザーがテキストを入力
// 2. onChangeイベント発火
// 3. setAttributesでattributes.titleを更新
// 4. WordPressがデータベースに保存

Step 5: 保存用コンポーネント

save.js

パス: wordpress/wp-content/plugins/my-accordion/src/save.js

import { useBlockProps, RichText } from "@wordpress/block-editor";

export default function save({ attributes }) {
	const blockProps = useBlockProps.save();

	return (
		<div {...blockProps}>
			<div className="my-accordion">
				<RichText.Content
					tagName="h3"
					value={attributes.title}
					className="accordion-title"
				/>
				<RichText.Content
					tagName="div"
					value={attributes.content}
					className="accordion-content"
				/>
			</div>
		</div>
	);
}

保存時はRichText.Contentを使用して、静的なHTMLを出力します。

Step 6: フロントエンド用のJavaScript

view.js

フロントエンドでの動的機能(アコーディオン開閉)を実装します。

viewScriptを使用することを推奨しており、以下の利点があります:

  • ✅ WordPress公式推奨
  • ✅ 軽量(プレーンJS)
  • ✅ 自動最適化
  • ✅ 保存されたHTMLをそのまま活用
  • ✅ ブロックが使用されているページでのみ自動読み込み

パス: wordpress/wp-content/plugins/my-accordion/src/view.js

document.addEventListener("DOMContentLoaded", function () {
	// アコーディオンブロックをすべて取得
	const accordions = document.querySelectorAll(
		".wp-block-create-block-my-accordion",
	);

	accordions.forEach((accordion) => {
		const accordionContainer = accordion.querySelector(".my-accordion");
		if (!accordionContainer) return;

		const title = accordionContainer.querySelector(".accordion-title");
		const content = accordionContainer.querySelector(".accordion-content");

		if (!title || !content) return;

		// ボタン要素を作成
		const button = document.createElement("button");
		button.className = "accordion-header";
		button.setAttribute("aria-expanded", "false");
		button.setAttribute("type", "button");

		// ユニークIDを生成
		const uniqueId =
			"accordion-content-" +
			Date.now() +
			"-" +
			Math.random().toString(36).substr(2, 9);
		button.setAttribute("aria-controls", uniqueId);
		button.id = "accordion-button-" + uniqueId;

		// ボタンの中身を設定
		button.innerHTML = `
            <span class="accordion-title-text">${title.innerHTML}</span>
            <span class="accordion-icon" aria-hidden="true">▼</span>
        `;

		// アクセシビリティ属性を設定
		content.id = uniqueId;
		content.setAttribute("role", "region");
		content.setAttribute("aria-labelledby", button.id);
		content.setAttribute("aria-hidden", "true");

		// クリックイベントを追加
// クリックイベント内
button.addEventListener("click", function () {
    const isOpen = button.getAttribute("aria-expanded") === "true";
    const icon = button.querySelector(".accordion-icon");
    button.setAttribute("aria-expanded", !isOpen);
    content.setAttribute("aria-hidden", isOpen);

    if (isOpen) {
        // 閉じる時
        content.style.maxHeight = null; // max-heightをリセット
        content.classList.remove("open");
        icon.style.transform = "rotate(0deg)";
    } else {
        // 開く時
        content.classList.add("open");
        // content.scrollHeight で実際の高さを取得して設定
        content.style.maxHeight = content.scrollHeight + "px";
        icon.style.transform = "rotate(180deg)";
    }
});

		// 既存のタイトルをボタンに置き換え
		title.parentNode.replaceChild(button, title);
	});
});

Step 7: CSSの作成

style.scss:フロント + エディター共通のCSS

パス: wordpress/wp-content/plugins/my-accordion/src/style.scss

/**
 * 共通スタイル(フロント・エディター両方で使用)
 */
.wp-block-create-block-my-accordion {
	/* 共通のベーススタイル */
	margin-bottom: 20px;

	/* 共通のタイトルスタイル */
	.accordion-title,
	.accordion-title-editor {
		font-size: 16px;
		font-weight: 600;
		margin-bottom: 0;
	}

	/* 共通のコンテンツスタイル */
	.accordion-content,
	.accordion-content-editor {
		p:last-child {
			margin-bottom: 0;
		}
	}
}

/**
 * フロントエンド専用スタイル
 */
.wp-block-create-block-my-accordion {
	.my-accordion {
		border: 1px solid #ddd;
		border-radius: 4px;

		.accordion-header {
			width: 100%;
			background-color: #f8f9fa;
			border: none;
			padding: 15px 20px;
			text-align: left;
			cursor: pointer;
			display: flex;
			justify-content: space-between;
			align-items: center;
			transition: background-color 0.3s ease;

			&:hover {
				background-color: #e9ecef;
			}

			&:focus {
				outline: 2px solid #007cba;
				outline-offset: 2px;
			}

			.accordion-title-text {
				flex: 1;
			}

			.accordion-icon {
				transition: transform 0.3s ease;
				font-size: 12px;
				margin-left: 10px;
			}
		}

		.accordion-content {
			padding: 20px;
			border-top: 1px solid #eee;
			background-color: #fff;
		}
	}
}

/**
 * フロントエンド用アニメーション
 */
.accordion-content {
	max-height: 0;
	overflow: hidden;
	transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}

/* .open自体はJSの判定で使うので残しても良い */
// .accordion-content.open {
// }


editor.scss:エディター用のCSS

パス: wordpress/wp-content/plugins/my-accordion/src/editor.scss

/**
 * エディター専用スタイル
 */
.wp-block-create-block-my-accordion {
	.accordion-editor {
		border: 1px solid #ddd;
		border-radius: 4px;
		padding: 20px;
		background-color: #fff;

		.accordion-title-editor {
			padding: 10px;
			background-color: #f8f9fa;
			border-radius: 4px;
			border: 1px solid #e9ecef;
			margin-bottom: 15px;

			&:focus {
				outline: 2px solid #007cba;
				outline-offset: 2px;
			}

			/* プレースホルダーのスタイル */
			&:empty::before {
				content: attr(data-placeholder);
				color: #757575;
			}
		}

		.accordion-content-editor {
			padding: 15px;
			border: 1px solid #e9ecef;
			border-radius: 4px;
			background-color: #fafbfc;
			min-height: 100px;

			&:focus {
				outline: 2px solid #007cba;
				outline-offset: 2px;
			}

			/* プレースホルダーのスタイル */
			&:empty::before {
				content: attr(data-placeholder);
				color: #757575;
			}
		}
	}
}

ビルドと実装

カスタムブロック開発中のディレクトリ(wordpress/wp-content/plugins/my-accordion/)で、ビルドを行います。

# 開発中のビルドを一旦停止
# Ctrl + C

# 本番用ビルド
npm run build

WordPress管理画面のプラグイン画面を開くと、「My Accordion」プラグインが表示されているはずです。

注意: 「新バージョンの更新」を押すと有効化が表示されますが、更新処理により作成したファイルが削除される場合があります。

my-accordion.php のバージョンがWPと相性が悪いのが原因です。

この場合は、ファイルをバックアップしてからバージョンをWPに合わせたのち、再度ビルドし有効化してください。

複数アコーディオンブロックを作り、保存したのちにフロントで(下書き、公開ページ)で挙動を確認して完了です。

編集画面

フロント

まとめ

WordPressのカスタムブロック作成は、Reactの知識があれば比較的スムーズに進められます。ACFと比較すると開発コストは高くなりますが、比較表に表した通り多くのメリットがあります。

学んだこと

  • WordPress標準に沿った開発: block.jsonやWordPress公式ツールの活用
  • @wordpress/scriptsによる効率的な開発環境構築: 複雑なWebpack設定不要
  • RichTextコンポーネントによるリッチな編集体験: WordPressらしいUI/UX
  • edit.jssave.jsの役割分担: エディター用と表示用の明確な分離
  • アクセシビリティを考慮したReactコンポーネント設計: WAI-ARIAガイドライン準拠
  • viewScriptの活用: パフォーマンスを意識したフロントエンド実装

今回のアコーディオンブロックは基本的な機能に留まりましたが、これをベースにさらなる機能拡張も可能です。実際のプロジェクトでの使用時は、パフォーマンスやアクセシビリティも考慮した実装を検討していきたいと思います。

この記事が同じようにWordPressカスタムブロック開発に取り組む方の参考になれば幸いです。質問や改善点があれば、ぜひコメントでお聞かせください。

PAGE TOP