📚

Ionic 7で刷新されたフォームシンタックスを学ぼう

2023/04/10に公開

簡素化されたフォームシンタックス

Ionic 7では、InputやSelectなどのコンポーネントのシンタックスが大幅に簡素化されました。これまで、ion-inpution-select というフォームコントロールを目的としたコンポーネントは必ず ion-itemion-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 を使うことができます。

ラベルが隣接していないフォームコントロール

続いてラベルが隣接していないフォームコントロールです。以下を実装してみましょう。

Image from Gyazo

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-inputlabel 属性を追加すると、Inputのすぐ左にラベルがきてしまうためデザインがずれます。そこで、以下のように aria-labelledby を使って、隣接していないラベルを ion-input に紐つけます。

html
  <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-selection-toggle などのShadowDOMとして展開されるフォームコントロールコンポーネントは、現時点で aria-labelledby を使うことができません。(参考: https://github.com/ionic-team/ionic-framework/issues/26829#issuecomment-1438844711

そこでこれらの場合は代わりに aria-label を用います。以下の例をみてみましょう。

Image from Gyazo

<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-inputlabel 属性を指定しています。今回重要なのは 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 を指定してください。

テンプレートの条件分岐を更に組み合わせる

Image from Gyazo

ちょっと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>

一時的な回避策ですが、覚えておくと便利です。

まとめ

基本パターンから外れると少しややこしくなりますが、考える順序としては

  1. フォームコントロールコンポーネントの真左にラベルがくる → label
  2. 真横にラベルがこない場合 && ShadowDOMとして展開されない → aria-labelledby
  3. ラベルが存在しない or ShadowDOMとして展開される → aria-label
  4. それでもわからん → legacy=true

あたりでいいかと思います。基本パターンの実装がしやすくなったことはいいことです。

それではまた。

Discussion