1. ホーム
  2. WordPress

【WordPress】FontSizePicker の使い方|カスタムブロックに文字サイズ設定を追加する方法

Share

カスタムブロックに「文字サイズを変えられる設定」を付けたいとき、RangeControl で数値を入力させる作りにしていませんか。それでも動きはしますが、テーマが theme.json で用意している「小・中・大」といったプリセットとは無関係の値になってしまい、サイトの中で文字サイズがバラバラになります。@wordpress/components の FontSizePicker を使えば、コアのブロックとまったく同じ見た目・同じ選択肢の文字サイズ UI を、自分のブロックにも置けます。この記事では、そもそも自前で使うべきかの判断から、onChange が受け取る値の正体、save 側での出力までを順に解説します。

文字サイズはテーマのプリセットに合わせる

ブロックテーマでは、使える文字サイズを theme.json の settings.typography.fontSizes に定義します。ここに書いた1つ1つが「プリセット」で、それぞれ表示名(name)・識別子(slug)・実際の値(size)を持ちます。

theme.json
{
  "version": 3,
  "settings": {
    "typography": {
      "customFontSize": true,
      "fontSizes": [
        { "name": "小",   "slug": "small",  "size": "0.875rem" },
        { "name": "標準", "slug": "medium", "size": "1rem" },
        { "name": "大",   "slug": "large",  "size": "1.5rem" }
      ]
    }
  }
}

WordPress はこの定義から、--wp--preset--font-size--small のような CSS カスタムプロパティと、has-small-font-size のようなユーティリティクラスを自動で出力します。つまりプリセットを選んでもらう作りにしておけば、投稿本文には「どのサイズか」という意味だけが残り、実際の値はテーマ側で一元管理できるわけです。あとからテーマの「大」を 1.5rem から 1.75rem に変えれば、過去の記事もまとめて追従します。

逆に、自作の数値入力で fontSize: 24 のような値を直接保存してしまうと、投稿ごとに数字が散らばり、あとから一括で変えることができません。FontSizePicker はこのプリセットを選ばせるための専用コンポーネントで、必要なら任意の値も入力できる、という二段構えになっています。

まず supports.typography.fontSize で足りないか確かめる

実装に入る前に確認しておきたいことがあります。block.json の supports に typography.fontSize を書くだけで、コアがまったく同じ文字サイズ UI を自動で表示してくれます。自分で FontSizePicker を書く必要はありません。

src/notice/block.json
{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "my-blocks/notice",
  "title": "Notice",
  "category": "design",
  "textdomain": "my-blocks",
  "supports": {
    "typography": {
      "fontSize": true
    }
  },
  "editorScript": "file:./index.js"
}

これを書くと、WordPress が fontSize という型 string の attribute をブロックに自動で追加します。ここに入るのはプリセットの slug("large" など)です。ユーザーが任意の値を入力した場合は slug が無いので、代わりに style.typography.fontSize に "22px" のような値が保存されます。プリセットなら fontSize、任意の値なら style という使い分けだと覚えてください。

出力側も自動です。save で useBlockProps.save() を展開していれば、コアが has-large-font-size というクラスや font-size のインラインスタイルを付けてくれます。ここまで自動なので、ブロックのルート要素の文字サイズを変えたいだけなら supports で終わりです。

それでも FontSizePicker を自分で使う場面はあります。ひとつは、ブロックの中の特定の要素だけにサイズを当てたいとき。引用ブロックの引用元表示や、カードブロックのキャプションなど、ルート要素とは別の場所を対象にしたい場合です。もうひとつは、1つのブロックに複数の文字サイズ設定を持たせたいとき。supports が用意してくれる attribute は1組だけなので、見出し用と本文用を別々に持ちたければ自前で書くしかありません。以降ではこのケースを前提に進めます。

FontSizePicker の基本の使い方

コンポーネントは @wordpress/components から読み込みます。最低限必要なのは、選択肢の配列 fontSizes、現在の値 value、変更を受け取る onChange の3つです。

最小の例
import { FontSizePicker } from '@wordpress/components';

const fontSizes = [
    { name: '小', slug: 'small', size: '0.875rem' },
    { name: '大', slug: 'large', size: '1.5rem' },
];

<FontSizePicker
    fontSizes={ fontSizes }
    value={ value }
    onChange={ ( newValue ) => setValue( newValue ) }
/>

選択肢が5個以下ならボタンが横に並ぶトグルグループ、6個以上ならドロップダウンに切り替わります。これは自動判定なので、こちらで指定する項目はありません。またプリセットの一覧とは別に、任意の値を入力する欄も表示されます(disableCustomFontSizes で消せます)。

主な props

prop型(既定値)説明
valuenumber / string現在の文字サイズ。単位を効かせたいときは "18px" のように単位付きの文字列で渡す。数値で渡すと単位なしモードになり units が無視される
onChangefunction(必須)値が変わったときに呼ばれる。引数なし(undefined)で呼ばれたらリセットを意味する
fontSizesarray([])プリセットの配列。各要素は { name, slug, size }。default と custom は予約済みの slug なので使わない
disableCustomFontSizesboolean(false)true にすると任意の値を入力できなくなり、プリセットからしか選べなくなる
withSliderboolean(false)true にすると、任意の値の入力欄にスライダーを併設する
withResetboolean(true)任意の値が入っているときにリセットボタンを表示する
fallbackFontSizenumber値が無いときのスライダーの初期位置。withSlider が true のときだけ意味を持つ
unitsstring[]([ 'px', 'em', 'rem', 'vw', 'vh' ])任意の値で選べる単位
valueMode‘literal’ / ‘slug’('literal')value をサイズの値として解釈するか、プリセットの slug として解釈するか。比較的新しく追加された prop なので、古い環境では使えない

disableCustomFontSizes が true のとき、withSlider と withReset はどちらも効きません。これらは任意の値の入力欄に付属する UI だからです。

onChange が受け取る値

ここが FontSizePicker でいちばん誤解されやすいところです。onChange に渡ってくるのは slug ではなく、CSS の値そのものです。プリセットの「大」を選んだ場合も、渡ってくるのは "large" ではなく "1.5rem"(その size の値)になります。

ユーザーの操作第1引数第2引数
プリセットを選ぶそのプリセットの size(例: "1.5rem")選ばれたプリセットのオブジェクト
任意の値を入力する数値+単位の文字列(例: "22px")。単位なしモードなら数値なし(undefined)
スライダーを動かす数値+単位の文字列。単位が未設定なら px が補われるなし(undefined)
リセットを押すundefinedなし(undefined)

プリセットを選んだときだけ第2引数にそのオブジェクトが渡る、というのが実装上の鍵になります。「第2引数があればプリセット、なければ任意の値」と判定できるので、保存先を fontSize(slug)と style(値)で振り分けられるわけです。第1引数が undefined なら、どちらの attribute も消してリセット扱いにします。

テーマのフォントサイズプリセットを取得する

fontSizes に渡す配列を自分でハードコードしてしまうと、テーマのプリセットと食い違います。エディター側の設定から読み取るには @wordpress/block-editor の useSettings を使います。

src/quote-card/edit.js(抜粋)
import { useSettings } from '@wordpress/block-editor';

// 戻り値は配列。パスを複数渡せば複数まとめて受け取れる
const [ fontSizes ] = useSettings( 'typography.fontSizes' );

// 複数のパスをまとめて取得する場合
const [ sizes, allowCustom ] = useSettings(
    'typography.fontSizes',
    'typography.customFontSize'
);

useSettings は渡したパスの数だけ値が入った配列を返します。設定を1つしか取らない場合でも分割代入が必要なので、括弧を忘れないでください。返ってくる fontSizes は { name, slug, size } を持つオブジェクトの配列です。

この typography.fontSizes は、テーマ(theme.json)・コアの既定値・ユーザーがサイトエディターで追加したもの、という複数の出どころを持ちますが、useSettings は優先順位の高いものを1つ選んで、フラットな配列にして返してくれます。そのまま fontSizes prop に渡せる形なので、こちらでマージ処理を書く必要はありません。

なお、WordPress 6.5 より前は useSetting(単数形)という、パスを1つだけ受け取って値をそのまま返すフックが使われていました。現在は非推奨で、呼ぶとブラウザーのコンソールに「useSettings を使ってください」という警告が出ます。動作自体は続いていますが、新しく書くコードでは useSettings を使ってください。

block-editor 版の FontSizePicker という近道

実は FontSizePicker という名前のコンポーネントは @wordpress/block-editor にもあります。こちらは中身で useSettings を呼び、fontSizes と disableCustomFontSizes をテーマの設定から自動で埋めてくれるラッパーです。

block-editor 版を使う場合
// components ではなく block-editor から読み込む
import { FontSizePicker } from '@wordpress/block-editor';

// fontSizes / disableCustomFontSizes は渡さなくてよい
<FontSizePicker
    value={ value }
    onChange={ onChangeFontSize }
    withSlider
/>

ブロックのエディター内で使うならこちらのほうが短く書けます。ただし fontSizes を渡しても内部の値で上書きされるため、テーマのプリセットとは違う独自の選択肢を出したいときは @wordpress/components 版を使ってください。以下の実践例では、中で何が起きているかが分かるように components 版と useSettings の組み合わせで書きます。

実践:引用元の文字サイズだけを設定できるブロック

本文と引用元(cite)を持つ引用カードを作り、引用元の文字サイズだけを設定できるようにします。ルート要素ではなく中の特定の要素が対象なので、supports では実現できないケースです。

block.json:slug 用と任意の値用の2つを持つ

コアの supports と同じ考え方で、attribute を2つ用意します。プリセットが選ばれたときは slug を、任意の値が入力されたときは値そのものを保存します。どちらも文字列なので型は string です。

src/quote-card/block.json
{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "my-blocks/quote-card",
  "title": "Quote Card",
  "category": "design",
  "textdomain": "my-blocks",
  "attributes": {
    "content": {
      "type": "string",
      "source": "html",
      "selector": "p"
    },
    "citation": {
      "type": "string",
      "source": "html",
      "selector": "cite"
    },
    "citationFontSize": {
      "type": "string"
    },
    "citationCustomFontSize": {
      "type": "string"
    }
  },
  "editorScript": "file:./index.js",
  "style": "file:./style-index.css"
}

2つに分ける理由は出力の仕方が違うからです。プリセットは has-large-font-size というクラスで出し、任意の値は style="font-size:22px" というインラインスタイルで出します。クラスで出しておけば、前述のとおりテーマ側で値を変えたときに追従できます。

保存する内容attribute入る値の例出力
プリセットcitationFontSize"large"class="has-large-font-size"
任意の値citationCustomFontSize"22px"style="font-size:22px"

edit.js:InspectorControls に組み込む

設定サイドバーに置くので InspectorControls と PanelBody で囲みます。保存済みの2つの attribute から現在値を組み立てる getFontSize() と、値から逆にプリセットを探す getFontSizeObjectByValue()、クラス名を作る getFontSizeClass() は、どれも @wordpress/block-editor が用意しているヘルパーです。

src/quote-card/edit.js
import { __ } from '@wordpress/i18n';
import {
    useBlockProps,
    RichText,
    InspectorControls,
    useSettings,
    getFontSize,
    getFontSizeClass,
    getFontSizeObjectByValue,
} from '@wordpress/block-editor';
import { PanelBody, FontSizePicker } from '@wordpress/components';

export default function Edit( { attributes, setAttributes } ) {
    const { content, citation, citationFontSize, citationCustomFontSize } =
        attributes;

    // テーマのプリセットを取得する
    const [ fontSizes ] = useSettings( 'typography.fontSizes' );

    // 2つの attribute から、いま選ばれているサイズのオブジェクトを作る
    // slug が見つかればそのプリセット、見つからなければ { size: 任意の値 }
    const activeFontSize = getFontSize(
        fontSizes,
        citationFontSize,
        citationCustomFontSize
    );

    const onChangeFontSize = ( newValue, selectedItem ) => {
        // プリセット選択時のみ selectedItem が渡る。
        // 念のため値からも slug を探して、どちらでも拾えるようにしておく
        const slug =
            selectedItem?.slug ||
            getFontSizeObjectByValue( fontSizes, newValue ).slug;

        setAttributes( {
            // プリセットなら slug を、そうでなければ空にする
            citationFontSize: slug,
            // 任意の値のときだけ値を保存する(リセット時は undefined)
            citationCustomFontSize: slug ? undefined : newValue,
        } );
    };

    // プリセットならクラス、任意の値ならインラインスタイル
    const citationClass = getFontSizeClass( citationFontSize );
    const citationStyle = citationClass
        ? undefined
        : { fontSize: citationCustomFontSize };

    return (
        <>
            <InspectorControls>
                <PanelBody title={ __( 'Citation', 'my-blocks' ) }>
                    <FontSizePicker
                        fontSizes={ fontSizes }
                        value={ activeFontSize?.size }
                        onChange={ onChangeFontSize }
                        units={ [ 'px', 'rem' ] }
                        fallbackFontSize={ 16 }
                        withSlider
                    />
                </PanelBody>
            </InspectorControls>

            <blockquote { ...useBlockProps() }>
                <RichText
                    tagName="p"
                    value={ content }
                    onChange={ ( value ) =>
                        setAttributes( { content: value } )
                    }
                    placeholder={ __( 'Quote…', 'my-blocks' ) }
                />
                <RichText
                    tagName="cite"
                    className={ citationClass }
                    style={ citationStyle }
                    value={ citation }
                    onChange={ ( value ) =>
                        setAttributes( { citation: value } )
                    }
                    placeholder={ __( 'Source…', 'my-blocks' ) }
                />
            </blockquote>
        </>
    );
}

getFontSize( fontSizes, slug, customValue ) は、第2引数の slug が配列の中に見つかればそのプリセットのオブジェクトを、見つからなければ { size: customValue } というオブジェクトを返します。どちらの場合も .size を見れば現在の CSS 値が取れるので、これを value に渡せば FontSizePicker が該当するプリセットを選択状態にしてくれます。

getFontSizeObjectByValue( fontSizes, value ) のほうは逆引きで、size が一致するプリセットを探します。見つからなければ { size: value } を返すので .slug は undefined になり、結果として「任意の値」として扱われます。リセットで newValue が undefined になったときも一致するプリセットは無いため、slug は undefined、citationCustomFontSize も undefined となって両方きれいに消えます。

save.js:クラスとスタイルを出力する

フロント側に反映させるには、save でも同じ計算をして出力します。edit でしか書いていないと、エディター上では変わるのに公開ページでは変わらない、という状態になります。

src/quote-card/save.js
import {
    useBlockProps,
    RichText,
    getFontSizeClass,
} from '@wordpress/block-editor';

export default function save( { attributes } ) {
    const { content, citation, citationFontSize, citationCustomFontSize } =
        attributes;

    // 'large' → 'has-large-font-size'(slug が無ければ undefined)
    const citationClass = getFontSizeClass( citationFontSize );
    const citationStyle = citationClass
        ? undefined
        : { fontSize: citationCustomFontSize };

    return (
        <blockquote { ...useBlockProps.save() }>
            <RichText.Content tagName="p" value={ content } />
            <RichText.Content
                tagName="cite"
                className={ citationClass }
                style={ citationStyle }
                value={ citation }
            />
        </blockquote>
    );
}

getFontSizeClass() は slug をケバブケースに変換して has- と -font-size で挟むだけの関数です。"large" なら has-large-font-size、"xLarge" なら has-x-large-font-size になります。slug が空なら undefined を返すので、そのまま className に渡しても余計なクラスは付きません。

クラス名を自分で文字列連結せず、この関数を通すのが大事です。WordPress がプリセットから CSS を生成するときのクラス名も同じルールで作られているため、手書きで has-xLarge-font-size のように書いてしまうと、どこにも当たらないクラスになります。

なお、edit と save で同じ計算をする都合上、片方だけ直すと保存済みの投稿で「ブロック検証エラー(Block validation failed)」が起きます。ロジックを変えるときは必ず両方をそろえ、公開後に出力を変える場合は deprecated を用意してください。

選んだサイズが反映されないとき

「サイドバーでは選べるのに文字が変わらない」「そもそも UI が出てこない」といったときは、次の順で見ていくと原因にたどり着きやすいです。

save で className も style も出していない

いちばん多いパターンです。supports を使った場合はコアが useBlockProps.save() 経由でクラスを差し込んでくれますが、自前の attribute はどこまでいっても自分で出力しないと何も起きません。値は正しく保存されているので、エディターを再読み込みすると設定だけは残っていて、見た目だけ変わらない、という分かりにくい状態になります。

切り分けるには、投稿のコードエディター表示(エディター右上のメニューから切り替えられます)でブロックの HTML を直接見るのが早いです。<!-- wp:my-blocks/quote-card {"citationFontSize":"large"} --> のように attribute は入っているのに <cite> にクラスが無ければ、save 側の問題だと確定できます。

fontSizes が空でコンポーネントごと消えている

FontSizePicker は、fontSizes が空配列で、かつ disableCustomFontSizes が true のとき、何もレンダリングせず null を返します。選べるものが1つも無いので当然の挙動ですが、エラーも警告も出ないため「パネルの中が空っぽ」という形で現れます。

theme.json を持たないクラシックテーマや、settings.typography.fontSizes を空配列で上書きしているテーマでは、useSettings( 'typography.fontSizes' ) が空配列や undefined を返します。@wordpress/block-editor 版の FontSizePicker を使っている場合は disableCustomFontSizes もテーマの typography.customFontSize から自動で決まるため、テーマがこれを false にしていると条件がそろって丸ごと消えます。まずは console.log( fontSizes ) で中身を確認してください。

attribute の型と保存される値が食い違っている

block.json で "type": "number" と宣言したのに、onChange から "22px" という文字列が渡ってくる、というずれです。FontSizePicker が単位付きの文字列を返すか数値を返すかは、value に何を渡したかで決まります。単位付きの文字列を渡せば単位ありモードになり、返り値も単位付きの文字列になります。数値を渡すと単位なしモードになり、units の指定自体が無視されます。

theme.json のプリセットは "1.5rem" のように文字列で書くのが一般的なので、テーマのプリセットをそのまま使うなら attribute の型は string にしておくのが無難です。型が合っていないと、保存はされても再読み込み時に値が落ちたり、save の出力が保存済み HTML と一致せず検証エラーになったりします。

プリセットのクラスに対応する CSS が無い

has-large-font-size というクラスは出ているのに文字サイズが変わらない場合、そのクラスを定義する CSS が読み込まれていません。ブロックテーマなら theme.json の fontSizes から WordPress が自動でこのクラスを生成しますが、theme.json を持たないクラシックテーマでは生成されません。

その場合は、テーマ側で add_theme_support( 'editor-font-sizes', ... ) を使ってプリセットを登録するか、ブロックの style(style-index.css)に自分でクラスを定義してください。ブラウザーの開発者ツールで cite 要素を選び、クラスが付いているのに font-size の指定がどこにも無ければこの原因です。

親要素の指定に負けている

クラスは当たっていて font-size も効いているのに見た目が変わらない場合は、より詳細度の高いセレクターに上書きされている可能性があります。テーマが .wp-block-quote cite { font-size: 0.8em; } のように書いていると、単独のクラスである has-large-font-size より強くなります。開発者ツールの Styles パネルで打ち消し線が付いている指定を探してください。

まとめ

文字サイズ設定を付けたくなったら、まず block.json の supports.typography.fontSize で足りないかを確認します。ブロックのルート要素が対象なら、これだけで attribute の登録から save のクラス出力までコアが面倒を見てくれます。FontSizePicker を自分で書くのは、ブロック内の特定の要素に当てたいときや、複数の文字サイズ設定を持たせたいときです。

自前で使うときのポイントは3つです。選択肢は useSettings( 'typography.fontSizes' ) でテーマから取ること。onChange に渡ってくるのは slug ではなく CSS の値で、プリセットを選んだときだけ第2引数にそのオブジェクトが渡ること。そしてリセット時は undefined が渡ること。この3つを押さえれば、プリセットは getFontSizeClass() でクラスに、任意の値はインラインスタイルに、と振り分けるだけで完成します。edit と save の出力をそろえるのを忘れなければ、コアのブロックと遜色ない文字サイズ設定が作れます。

参考ページ