🌊

プレースホルダーをlabelで実装する with Vue.js

2022/10/30に公開

ラベルを表示させるのがユーザビリティ/アクセシビリティ観点でベストという話をこちらの記事でまとめました。

https://zenn.dev/nako_110/articles/ce7e9a453dfd07

しかし人生そう甘くはありません.....

「ラベルは無しでプレースホルダーだけ表示したい」という場合もあるかと思います。

ですがそんな時もアクセシビリティ確保の観点から<label>要素は必須ものです。
(そちらの理由も上記と同じ記事にまとめてあります。)

「<label>要素は残しつつ、プレースホルダーのみ表示する」を実現する方法に下記の2つがあるかと思います。

  1. <label>要素を実装しつつ、CSSで見た目上は表示させない
  2. <label>要素をプレースホルダーとして表示する

今回はこの2つめの実装をご紹介します。

<label>要素をプレースホルダーとして表示する実装

全体像

<template>
  <label class="container">
    <span :class="['label', { '-visible': !isInputFocused && !input }]">
      {{ label }}
    </span>
    <input
      v-model="input"
      @focus="isInputFocused = true"
      @blur="isInputFocused = false"
    />
  </label>
</template>


... 
setup(){
 const input = ref('')
 const isInputFocused = ref(false)
 
 return { input, isInputFocused }
}

<style lang="scss" scoped>
.container {
  position: relative;
}

.label {
  position: absolute;
  left: 2.5em;
  top: 50%;
  transform: translateY(-50%);
  color: gray; 
  cursor: text;

  &.-visible {
    z-index: 1;
  }
}

</style>

1つ1つ見ていきます。

↓まずは入力欄を用意します。

<template>
    <input
      v-model="input"
    />
</template>


... 
setup(){
 const input = ref('')
 
 return { input }
}

↓そして問題のlabel追加

<template>
  <label>
     ラベル // ラベルに入れたい文字
     <input
	v-model="input"
      />
  </ label>
</template>


... 
setup(){
 const input = ref('')
 
 return { input }
}

これだとラベルが入力欄の上の位置に表示されているので、CSSでプレースホルダーの位置に表示されるようにします。

<template>
  <label class="container">
    <span :class="label">
      {{ label }}
    </span>
    <input
      v-model="input"
    />
  </label>
</template>


... 
setup(){
 const input = ref('')
 
 return { input }
}

<style lang="scss" scoped>
.container {
  position: relative;
}

.label {
  position: absolute;
  left: 2.5em;
  top: 50%;
  transform: translateY(-50%);
  color: gray; // プレースホルダーとして表示したい文字の色を指定
}

</style>

↑これでプレースホルダーの位置に表示されました🎉

ただし、これだと常に表示されてしまっているので、
フォーカスが当たった時にプレースホルダーが消え、フォーカスが外れた時に再度表示されるようにします。

<template>
  <label class="container">
  // isInputFocusedがfalse=フォーカスが当たっていない、かつ、inputの値がない時に`-visible`を適用しlabelを表示させる
    <span :class="['label', { '-visible': !isInputFocused && !input }]">
      {{ label }}
    </span>
    <input
      v-model="input"
      @focus="isInputFocused = true" // フォーカスが当たるとisInputFocusedがtrue
      @blur="isInputFocused = false" // フォーカスが外れるとisInputFocusedがfalse
    />
  </label>
</template>


... 
setup(){
 const input = ref('')
 const isInputFocused = ref(false) // focusを管理する変数
 
 return { input, isInputFocused }
}

<style lang="scss" scoped>
.container {
  position: relative;
}

.label {
  position: absolute;
  left: 2.5em;
  top: 50%;
  transform: translateY(-50%);
  color: $textdisable-gray;

  &.-visible {
    z-index: 1; // input要素の上にspanで囲んだ値を持っていく
  }
}

</style>


isInputFocusedによって、フォーカスの変化を管理させます。そしてこれをlabelを表示させるかどうかを管理する-visibleの条件に加えます。
ただし、これだけだと、
入力をして、その値を消去せずにフォーカスを外した場合にプレースホルダーが表示されてしまうので、inputに値があるかどうかもlabelを表示させるかどうかの条件に加えます。

最後に、現状ではplaceholderの位置にカーソルを当てるとデフォルトの矢印に戻ってしまうので、テキストとして認識するように変えます。↓

<template>
  <label class="container">
    <span :class="['label', { '-visible': !isInputFocused && !input }]">
      {{ label }}
    </span>
    <input
      v-model="input"
      @focus="isInputFocused = true"
      @blur="isInputFocused = false"
    />
  </label>
</template>


... 
setup(){
 const input = ref('')
 const isInputFocused = ref(false)
 
 return { input, isInputFocused }
}

<style lang="scss" scoped>
.container {
  position: relative;
}

.label {
  position: absolute;
  left: 2.5em;
  top: 50%;
  transform: translateY(-50%);
  color: gray; 
  cursor: text; // textを指定

  &.-visible {
    z-index: 1;
  }
}

</style>

これで完成です🌸

さいごに

今回、<label>要素をプレースホルダーとして表示する方法をお伝えしましたが、
大前提として、「ラベルの代わりにプレースホルダーを使用する」という考え方は良くありません。
(詳細は下記の記事をご参照ください)
https://zenn.dev/nako_110/articles/ce7e9a453dfd07

この前提を踏まえながら、
それでもプレースホルダーのみ表示したいとなった場合に、
アクセシビリティを保ちつつその仕様を実現できる方法として参考にしていただけたら幸いです!

参考文献

https://dev.to/piguicorn/labels-as-placeholders-labels-as-placeholders-10ei

Discussion