ブロックエディターでブロックを選ぶと、そのブロックの上に小さなツールバーが出てきます。段落ブロックなら太字や配置、画像ブロックならトリミングなどが並ぶ、あの帯の部分です。自作ブロックにもここへボタンを足すことができ、その入口になるのが BlockControls と ToolbarButton です。この記事では、ツールバーとサイドバーの使い分けから、基本の書き方、属性をトグルするボタンの完成コード、ボタンが出てこないときの原因までを順に解説します。
目次
ツールバーに置くか、サイドバーに置くか
ブロックの設定 UI を差し込める場所は、大きく2か所あります。ブロックのすぐ上に出るツールバーと、画面右側に開く設定サイドバーです。前者を担当するのが BlockControls、後者を担当するのが InspectorControls で、どちらも @wordpress/block-editor から読み込みます。
この2つは機能が重なっているわけではなく、置くべきものが違います。ツールバーは常に目に入る場所にあり、クリック1回で結果が変わるので、執筆中に何度も切り替える操作――配置の変更、強調のオン・オフ、表示モードの切り替えなど――に向いています。一方サイドバーは、開かないと見えない代わりに広い面積を使えるので、一度決めたらあまり触らない細かい設定――色、余白、リンクの開き方、表示件数といった数値入力――を置く場所です。
ツールバーは横一列しかなく、ボタンを増やすほど窮屈になります。目安として、ツールバーに置くのはそのブロックで「よく使う操作」を2〜3個までにとどめ、残りはサイドバーへ回すと使いやすい UI になります。
BlockControls は edit の返り値に入れるだけでよい
BlockControls の使い方で最初に戸惑いやすいのが、書く場所と表示される場所が一致しない点です。BlockControls はブロックの edit が返す JSX の中に書きますが、実際に描画されるのはブロックの中ではなく、エディター上部のツールバー領域です。
これは Slot/Fill と呼ばれる仕組みによるものです。エディター側があらかじめ「ここに差し込める」という受け口(Slot)をツールバーに用意していて、BlockControls はそこへ中身を送り込む差込口(Fill)として働きます。そのため、開発者は座標や DOM の位置を気にする必要がなく、edit の返り値に BlockControls を含めるだけで、選択中のブロックのツールバーに内容が現れます。
差し込まれるのは、そのブロックが選択されている間だけです。選択を外せば自動的に消えるので、表示・非表示を自分で制御する必要はありません。
ToolbarGroup と ToolbarButton で1つボタンを置く
最小の構成は「BlockControls の中に ToolbarGroup、その中に ToolbarButton」の3階層です。ToolbarGroup はボタンをまとめる箱で、これで包むとボタンの間に区切り線が入り、コアのブロックと同じ見た目に揃います。ToolbarGroup と ToolbarButton は @wordpress/components のコンポーネントなので、BlockControls とは import 元が違う点に注意してください。
import { __ } from '@wordpress/i18n';
import { useBlockProps, BlockControls } from '@wordpress/block-editor';
import { ToolbarGroup, ToolbarButton } from '@wordpress/components';
import { starFilled } from '@wordpress/icons';
export default function Edit() {
const blockProps = useBlockProps();
return (
<>
<BlockControls>
<ToolbarGroup>
<ToolbarButton
icon={ starFilled }
label={ __( '強調する', 'webool' ) }
isPressed={ false }
onClick={ () => console.log( 'clicked' ) }
/>
</ToolbarGroup>
</BlockControls>
<p { ...blockProps }>ブロックの中身</p>
</>
);
}
ポイントは、edit が返す JSX の一番外側をフラグメント(<>...</>)にしていることです。BlockControls とブロック本体という2つの要素を並べて返す必要があるため、どちらか一方だけを返す形になっていると、ツールバー側かブロック側のどちらかが消えてしまいます。
アイコンは @wordpress/icons パッケージから読み込むのが手軽です。starFilled や chevronDown のように名前付きでエクスポートされているほか、icon="star-filled" のように Dashicons の名前を文字列で渡すこともできます。label は目には見えませんが、スクリーンリーダー向けの aria-label とマウスオーバー時のツールチップに使われるので、必ず付けておきましょう。
ToolbarButton に渡せる主なプロパティ
ToolbarButton は内部で Button コンポーネントを使っており、Button と同じプロパティをそのまま受け取れます。よく使うものは次のとおりです。
| プロパティ | 説明 |
|---|---|
icon | ボタンに表示するアイコン。@wordpress/icons のアイコンや Dashicons の名前(文字列)を渡す |
label | ボタンの説明文。aria-label とツールチップの文言になる |
title | label と同じ役割の別名。既存のブロックのコードではこちらもよく使われている |
isPressed | true のとき「押されている」見た目になる。isActive を渡しても同じ扱いになる |
disabled | true でボタンを押せなくする。古い isDisabled は内部で disabled に読み替えられる |
onClick | クリックされたときに実行する関数 |
shortcut | ツールチップに併記するキーボードショートカットの表示文字列 |
children | 子要素にテキストを渡すと、アイコンではなく文字ラベルのボタンになる |
containerClassName | ボタンを包む要素に追加するクラス名 |
文字だけのボタンにしたいときは icon を省略し、子要素にテキストを書きます。ただしツールバーは横幅が限られるので、文字ラベルは短い語に絞るか、アイコン+label の組み合わせにしておくのが無難です。
<ToolbarButton onClick={ handleReset }>リセット</ToolbarButton>
group プロパティで表示される位置が変わる
BlockControls には group というプロパティがあり、ツールバーのどのエリアに差し込むかを指定できます。省略した場合は default です。
| group の値 | 差し込まれる場所 |
|---|---|
default | 既定値。そのブロック固有のコントロールが並ぶエリア |
block | ブロックの種類を切り替えるブロックスイッチャーの隣。配置など、ブロック共通の操作が並ぶエリア |
inline | 太字やリンクなど、選択した文字列に対する書式ボタンが並ぶエリア |
other | 上のどれにも当てはまらないコントロール用のエリア |
見た目の違いだけでなく、扱いにも差があります。group を省略した default のときは中身が自動的に ToolbarGroup で包まれますが、block などを指定したときは包まれません。group="block" を使うのは配置コントロールのような既製のコンポーネントを置くときが多く、それらは自前でグループを持っているため、この挙動で困ることはほとんどありません。自作のボタンを default 以外のグループに置く場合だけ、自分で ToolbarGroup を書く必要があると覚えておけば十分です。
配置コントロールなど既製のコンポーネントを載せる
ツールバーに置くものは、自作のボタンだけとは限りません。コアが用意しているコントロールをそのまま差し込めば、コアのブロックとまったく同じ操作感を数行で用意できます。代表的なのがテキストの配置を切り替える AlignmentControl です(以前からある AlignmentToolbar も同じ用途のコンポーネントで、どちらも @wordpress/block-editor から読み込めます)。
import {
useBlockProps,
BlockControls,
AlignmentControl,
} from '@wordpress/block-editor';
export default function Edit( { attributes, setAttributes } ) {
const { textAlign } = attributes;
// 配置に応じてコア互換のクラスを付ける
const blockProps = useBlockProps( {
className: textAlign ? `has-text-align-${ textAlign }` : undefined,
} );
return (
<>
<BlockControls group="block">
<AlignmentControl
value={ textAlign }
onChange={ ( nextAlign ) =>
setAttributes( { textAlign: nextAlign } )
}
/>
</BlockControls>
<p { ...blockProps }>ブロックの中身</p>
</>
);
}
value に現在の値、onChange に更新用の関数を渡すだけで、左・中央・右の切り替え UI が手に入ります。textAlign は文字列型の属性として block.json に定義しておき、未選択のときは値なし(undefined)になる点に注意してください。なお、ブロック全体を「幅広」「全幅」にする配置は別のコントロールで、そちらは BlockAlignmentControl が担当します。
実践例:ハイライトを切り替えるボタンを作る
ここまでの内容を使って、テキストを強調表示するかどうかをツールバーから切り替えるブロックを作ってみます。真偽値の属性 isHighlighted を用意し、ボタンで反転させ、その状態を isPressed で見せて、保存する HTML のクラスにも反映させる、という流れです。
属性を block.json に定義する
まず、切り替えの状態をどこに保存するかを決めます。ブロックが持つ値は block.json の attributes に書き、真偽値なので "type": "boolean"、初期値は false にしておきます。
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "webool/notice",
"title": "お知らせ",
"category": "text",
"icon": "megaphone",
"textdomain": "webool",
"attributes": {
"content": {
"type": "string",
"source": "html",
"selector": "p",
"default": ""
},
"isHighlighted": {
"type": "boolean",
"default": false
}
},
"editorScript": "file:./index.js",
"style": "file:./style-index.css"
}
edit.js でボタンと状態をつなぐ
編集画面側では、attributes から現在の値を取り出してボタンの isPressed に渡し、onClick で setAttributes を呼んで反転させます。この「読む」と「書く」が両方そろって、はじめて押下状態が正しく動きます。
import { __ } from '@wordpress/i18n';
import {
useBlockProps,
BlockControls,
RichText,
} from '@wordpress/block-editor';
import { ToolbarGroup, ToolbarButton } from '@wordpress/components';
import { starFilled } from '@wordpress/icons';
export default function Edit( { attributes, setAttributes } ) {
const { content, isHighlighted } = attributes;
// 編集画面でも保存後と同じクラスを付けて見た目を合わせる
const blockProps = useBlockProps( {
className: isHighlighted ? 'is-highlighted' : undefined,
} );
return (
<>
<BlockControls>
<ToolbarGroup>
<ToolbarButton
icon={ starFilled }
label={ __( 'ハイライト', 'webool' ) }
isPressed={ isHighlighted }
onClick={ () =>
setAttributes( { isHighlighted: ! isHighlighted } )
}
/>
</ToolbarGroup>
</BlockControls>
<div { ...blockProps }>
<RichText
tagName="p"
value={ content }
onChange={ ( value ) => setAttributes( { content: value } ) }
placeholder={ __( 'テキストを入力', 'webool' ) }
/>
</div>
</>
);
}
isPressed={ isHighlighted } と書いているので、属性が true の間はボタンが押し込まれた表示になり、いま強調が有効かどうかが一目で分かります。ここを固定値のままにしたり、コンポーネント内の useState で持ったりすると、見た目は変わっても保存内容とずれてしまうので、必ず属性と結び付けてください。
save.js でクラスを出し分ける
保存側では useBlockProps.save() に同じ条件でクラス名を渡します。編集画面と保存後で同じクラスが付くので、CSS も1つで済みます。
import { useBlockProps, RichText } from '@wordpress/block-editor';
export default function save( { attributes } ) {
const { content, isHighlighted } = attributes;
const blockProps = useBlockProps.save( {
className: isHighlighted ? 'is-highlighted' : undefined,
} );
return (
<div { ...blockProps }>
<RichText.Content tagName="p" value={ content } />
</div>
);
}
あとは .is-highlighted に CSS を当てれば完成です。block.json の style で指定したスタイルシートは編集画面とフロントの両方で読み込まれるため、切り替えた結果が編集中にもそのまま見えます。
.wp-block-webool-notice.is-highlighted {
background-color: #fff7d6;
border-left: 4px solid #f0b429;
padding: 16px;
}
1つだけ注意点があります。すでに記事内で使われているブロックの save を書き換えると、保存済みの HTML と新しい save の出力が食い違い、「このブロックには、予期しないか無効なコンテンツが含まれています」という検証エラーになります。公開済みのブロックに後からこの仕組みを足すときは、deprecated に旧バージョンの save を登録してから変更してください。
選択肢が多いときは ToolbarDropdownMenu にまとめる
切り替えたい候補が3つ4つとある場合、ボタンを横に並べるとツールバーがすぐ一杯になります。そんなときは ToolbarDropdownMenu を使い、1つのアイコンを押すと候補が縦に開く形にまとめます。controls に候補を配列で渡すだけで、メニューが組み立てられます。
import { __ } from '@wordpress/i18n';
import { BlockControls } from '@wordpress/block-editor';
import { ToolbarGroup, ToolbarDropdownMenu } from '@wordpress/components';
import { info, warning, chevronDown } from '@wordpress/icons';
// noticeType は文字列型の属性
<BlockControls>
<ToolbarGroup>
<ToolbarDropdownMenu
icon={ chevronDown }
label={ __( '種類を選ぶ', 'webool' ) }
controls={ [
{
title: __( 'お知らせ', 'webool' ),
icon: info,
isActive: noticeType === 'info',
onClick: () => setAttributes( { noticeType: 'info' } ),
},
{
title: __( '注意', 'webool' ),
icon: warning,
isActive: noticeType === 'warning',
onClick: () => setAttributes( { noticeType: 'warning' } ),
},
] }
/>
</ToolbarGroup>
</BlockControls>
各項目の isActive に「いま選ばれているか」を渡しておくと、メニューを開いたときに現在の選択肢にチェックが付きます。ToolbarButton の isPressed と同じ考え方で、状態を属性から読んで渡すのがコツです。
ボタンがツールバーに表示されないとき
コードを書いたのにツールバーに何も増えない、という場合、原因はだいたい次の3つのどれかです。
edit の返り値に含まれていない
BlockControls は「返して初めて差し込まれる」コンポーネントです。edit の中で変数に代入しただけだったり、条件分岐の都合で返らない経路に入っていたりすると、当然ながらツールバーには何も出ません。return の中に BlockControls が入っているかをまず確認してください。
次に多いのが、フラグメントを使わずに要素を返そうとしているケースです。edit は1つの要素しか返せないため、BlockControls とブロック本体を並べるときは全体を <>...</> で包む必要があります。ここを忘れて BlockControls をブロック本体の外に書くと、JSX の構文エラーになるか、片方だけを返して見た目が壊れます。
import 元のパッケージを取り違えている
この記事で扱ったコンポーネントは、パッケージが2つに分かれています。BlockControls や AlignmentControl は @wordpress/block-editor、ToolbarGroup / ToolbarButton / ToolbarDropdownMenu は @wordpress/components です。逆に書くと undefined が読み込まれ、ブロック全体が描画されなくなります。ブラウザのコンソールに「Element type is invalid」のようなエラーが出ていたら、まず import 行を疑ってください。
あわせて、ビルド時の依存指定も確認します。@wordpress/scripts でビルドしていれば、これらの import は自動的に wp-block-editor / wp-components への依存として index.asset.php に書き出され、block.json の editorScript 経由で正しく読み込まれます。自前でビルド設定を組んでいる場合は、依存が抜けていないかを見直しましょう。
古いブロックの JS が残っている
ビルドを回し忘れている、あるいはブラウザやキャッシュ系プラグインが古い JavaScript を配信している、という単純な原因もよくあります。開発中は npm run start でウォッチしておき、それでも変わらないときはスーパーリロードでキャッシュを飛ばして確認してください。
押しても状態が変わって見えないとき
ボタンは出ているのに、クリックしても押されている感じがしない――この症状はほぼ isPressed の渡し方が原因です。
isPressed に属性ではない値を渡していると、クリックで属性は変わっても見た目が追従しません。isPressed={ isHighlighted } のように、表示に使う値と保存する値を同じものにするのが原則です。コンポーネント内の useState で状態を持つのも避けてください。見た目はそれらしく動きますが、その値は保存されないので、ページを再読み込みすると元に戻ってしまいます。
逆に、押下状態は変わるのに保存されない場合は、onClick の中で setAttributes を呼べているかを確認します。setAttributes は変更したいキーだけを含むオブジェクトを渡す関数なので、setAttributes( { isHighlighted: ! isHighlighted } ) のように書きます。属性名のつづりが block.json の定義と1文字でも違うと、未定義の属性として扱われ保存されません。
まとめ
BlockControls は、ブロック選択時に上へ出るツールバーへ独自のコントロールを差し込むためのコンポーネントです。edit の返り値に含めるだけで、Slot/Fill の仕組みによってツールバー上に描画されます。中身は ToolbarGroup でまとめ、ToolbarButton に icon / label / isPressed / onClick を渡すのが基本形です。isPressed には必ず属性の値を渡し、onClick で setAttributes を呼んで更新する、という往復を作れば、押下状態と保存内容がずれません。選択肢が増えたら ToolbarDropdownMenu に、細かい設定は InspectorControls のサイドバーに逃がす――この使い分けができれば、コアのブロックと違和感のない操作感の自作ブロックになります。
参考ページ
- @wordpress/block-editor – Block Editor Handbook | WordPress Developer Resources
- ToolbarButton – Block Editor Handbook | WordPress Developer Resources
- ToolbarDropdownMenu – Block Editor Handbook | WordPress Developer Resources
- The block in the Editor – Block Editor Handbook | WordPress Developer Resources