Ionic 7で刷新されたフォームシンタックスを学ぼう
簡素化されたフォームシンタックス
Ionic 7では、InputやSelectなどのコンポーネントのシンタックスが大幅に簡素化されました。これまで、ion-input
や ion-select
というフォームコントロールを目的としたコンポーネントは必ず ion-item
と ion-label
と組み合わせて使う必要がありました。しかし、Ionic 7では、これらのコンポーネントと組み合わす必要がなくなりました。
v6:
<ion-item fill="outline">
<ion-label>Email:</ion-label>
<ion-input placeholder="hi@ionic.io"></ion-input>
<div slot="helper">Please enter a valid email address</div>
</ion-item>
v7:
<ion-input
label="Email:"
placeholder="hi@ionic.io"
fill="outline"
helper-text="Please enter a valid email address"
></ion-input>
マークアップが簡素になった以外に、以下のメリットがあります。
- フォームコントロールがラベルに関連付けられるようになったことで、アクセシビリティが向上します
- APIの意図が明確になったことにより、開発者体験を向上させます。
- (Ionic本体の開発がシンプルになって将来的に不具合が減ります)。
基本的なフォームシンタックスの変更例
いくつかのフォームコントロールのシンタックスの変更例を紹介します。 ion-item
は未使用でも実装できるというだけで、v7の実装でも ion-item
を利用することも可能です。 ion-list
の内部で利用する場合は、 ion-item
を使うことをお勧めします。
ion-input
v6:
<ion-item>
<ion-label>Enter your name</ion-label>
<ion-input></ion-input>
<span slot="error">Please enter a name</span>
</ion-item>
v7:
<ion-input label="Enter your name" error="Please enter a name"></ion-input>
ion-textarea
v6:
<ion-item fill="solid" counter="true">
<ion-label>Comments</ion-label>
<ion-textarea maxlength="500"></ion-textarea>
</ion-item>
v7:
<ion-textarea fill="solid" counter="true" maxlength="500" label="Comments"></ion-textarea>
ion-select
v6:
<ion-item>
<ion-label>Pick a color</ion-label>
<ion-select>
<ion-select-option value="red">Red</ion-select-option>
<ion-select-option value="blue">Blue</ion-select-option>
<ion-select-option value="green">Green</ion-select-option>
</ion-select>
</ion-item>
v7:
<ion-select label="Pick a color">
<ion-select-option value="red">Red</ion-select-option>
<ion-select-option value="blue">Blue</ion-select-option>
<ion-select-option value="green">Green</ion-select-option>
</ion-select>
ion-checkbox
v6:
<ion-item>
<ion-checkbox slot="start"></ion-checkbox>
<ion-label>I agree to the terms and conditions</ion-label>
</ion-item>
v7:
<ion-checkbox>I agree to the terms and conditions</ion-checkbox>
ion-radio
v6:
<ion-item>
<ion-label>Grapes</ion-label>
<ion-radio slot="end" value="grapes"></ion-radio>
</ion-item>
v7:
<ion-radio value="grapes">Grapes</ion-radio>
実装の応用
ただ実装するフォームがこういった基本的な構造でない場合は多々あります。応用として、いろいろな配置のフォームコントロールをみていきましょう。
ラベルが存在しないフォームコントロール
ラベルが存在しない場合、 aria-label
を使ってその要素が何のフォームコントロールを示してるかを提示します。
<!--この場合はラベルが表示される-->
<ion-input label="Enter your name"></ion-input>
<!--この場合はラベルが表示されない-->
<ion-input aria-label="Enter your name"></ion-input>
デザインの関係でラベルを表示したくない場合は、 label
を使わずに aria-label
を使うことができます。
ラベルが隣接していないフォームコントロール
続いてラベルが隣接していないフォームコントロールです。以下を実装してみましょう。
v6までのマークアップは以下のようになります(クリティカルでないコードは割愛しています)
<ion-item class="add-remove" lines="none">
<ion-label slot="start">移動本数</ion-label>
<ion-buttons slot="start" tabindex="1">
<ion-button slot="icon-only" (click)="remove()">
<ion-icon name="remove-circle-outline"></ion-icon>
</ion-button></ion-buttons>
<ion-input required tabindex="2"></ion-input>
<ion-buttons slot="end" tabindex="1">
<ion-button slot="icon-only" (click)="add()"><ion-icon name="add-circle-outline"></ion-icon></ion-button>
</ion-buttons>
<ion-text slot="end" i18n>本 / 在庫数1本</ion-text>
</ion-item>
ただこれを単純にv7のフォームシンタックスにあわせて ion-input
に label
属性を追加すると、Inputのすぐ左にラベルがきてしまうためデザインがずれます。そこで、以下のように aria-labelledby
を使って、隣接していないラベルを ion-input
に紐つけます。
<ion-item class="add-remove" lines="none">
- <ion-label slot="start">移動本数</ion-label>
+ <ion-label id="label-amount" slot="start">移動本数</ion-label>
<ion-buttons slot="start" tabindex="1">
<ion-button slot="icon-only" (click)="remove()">
<ion-icon name="remove-circle-outline"></ion-icon>
</ion-button></ion-buttons>
- <ion-input required tabindex="2"></ion-input>
+ <ion-input required tabindex="2" aria-labelledby="label-amount"></ion-input>
<ion-buttons slot="end" tabindex="1">
<ion-button slot="icon-only" (click)="add()"><ion-icon name="add-circle-outline"></ion-icon></ion-button>
</ion-buttons>
<ion-text slot="end" i18n>本 / 在庫数1本</ion-text>
</ion-item>
WAI-ARIA属性をつけるだけです。簡単ですね。
ShadowPartsとなるコンポーネントも組み合わせる
ただ、 ion-input
、 ion-textarea
はこれでいいのですが、 それ以外の ion-select
、 ion-toggle
などのShadowDOMとして展開されるフォームコントロールコンポーネントは、現時点で aria-labelledby
を使うことができません。(参考: https://github.com/ionic-team/ionic-framework/issues/26829#issuecomment-1438844711 )
そこでこれらの場合は代わりに aria-label
を用います。以下の例をみてみましょう。
<ion-item>
<ion-input label="購入額" labelPlacement="fixed"></ion-input>
<ion-select
slot="end"
aria-label="currency"
placeholder="通貨単位"
>
<ion-select-option *ngFor="let item of currencyCodes" [value]="item.code">{{ item.label }}</ion-select-option>
</ion-select>
</ion-item>
ion-input
は左側にラベルを表示するので、 ion-input
に label
属性を指定しています。今回重要なのは ion-select
で、これはShadowDOMとして展開されるため、 aria-labelledby
ではなく aria-label
を使います。
ですので、以下のようなマークアップは間違いとなります。お気をつけください。
<ion-item>
<ion-label position="fixed" id="label-buy">購入額</ion-label>
<ion-button>何かしらのコンポーネント</ion-button>
<ion-select
slot="end"
aria-labelledby="label-buy"
placeholder="通貨単位"
>
<ion-select-option *ngFor="let item of currencyCodes" [value]="item.code">{{ item.label }}</ion-select-option>
</ion-select>
</ion-item>
ion-select
の真横にラベルが存在しないので、 ion-select[label]
でラベル名を指定することができません(順序が入れ替わります)。また、 ion-select
はShadowDOMとして展開されるため、 aria-labelledby
でラベル要素を指定することはできません。できればこのようなデザインは避けるべきですが、避けることができない場合は ion-select
には aria-label
を指定してください。
テンプレートの条件分岐を更に組み合わせる
ちょっとAngularの構文が入って読みづらいですが *ngIf
は条件構文で、条件を満たす場合は当該DOMが描画されます。
<ion-item>
<ion-label position="fixed" id="label-year">生産年</ion-label>
<ng-container *ngIf="year !== 0">
<ion-input aria-labelledby="label-year"></ion-input>
<ion-text slot="end">年</ion-text>
</ng-container>
<ion-text *ngIf="year === 0">生産年なし(Non Vintage)</ion-text>
<ion-checkbox aria-label="Non Vintage" slot="end"></ion-checkbox>
</ion-item>
まず、 ion-input
が存在する場合も存在しない場合も、ラベルを表示したいので、ラベルは ion-input[label]
ではなく、実体のある ion-label
を使って表示します。そのため、 ion-input
ではそのラベルを参照するため、 aria-labelledby="label-year"
を指定します。
続いて ion-checkbox
ですが、これはラベルがShadowDOMとなっているため、 aria-labelledby
ではなく aria-label
を使います。 aria-label
はラベルの内容を直接指定する属性です。
対応を後回しにする
とはいえ、「この表現は新シンタックスでは難しいので、デザイン自体を変えたい。後回しだ!!」という場合があると思います。その場合、 legacy=true
をつけることによって、新シンタックスに対応していないことを明示することができます。
<ion-item fill="outline">
<ion-label>Email:</ion-label>
<ion-input placeholder="hi@ionic.io" legacy="true"></ion-input>
<div slot="helper">Please enter a valid email address</div>
</ion-item>
一時的な回避策ですが、覚えておくと便利です。
まとめ
基本パターンから外れると少しややこしくなりますが、考える順序としては
- フォームコントロールコンポーネントの真左にラベルがくる → label
- 真横にラベルがこない場合 && ShadowDOMとして展開されない → aria-labelledby
- ラベルが存在しない or ShadowDOMとして展開される → aria-label
- それでもわからん → legacy=true
あたりでいいかと思います。基本パターンの実装がしやすくなったことはいいことです。
それではまた。
Discussion