aria-current属性と一般兄弟結合子で作るステップUI
UI開発者 寒川この記事はミツエーリンクス Advent Calendar 2020 - Adventarの21日目の記事です。
今回はCSSのお話です。
CSSをうまく使うことができると
- 必要だと思っていたJavaScriptが不要になる
- HTMLが簡潔に書ける
場合があります。
そのことから私は日々CSSを書きながら「まだ気がついていない使い方があるのではないか」とその可能性に魅力を感じています。
特に静的に実装するのであれば、万人が触れる機会が多いHTMLの編集しやすさは意識したいところですね。
ステップUIをお題として一般兄弟結合子(~
)の実用例をご紹介します。
加えてaria-current
属性の使い方も少しだけご紹介できればと思います。
例えば、以下のような見た目のステップUIをスタイリングする場合は
一般兄弟結合子を意識しないと、次のようにclass
属性をたくさん付与するマークアップになりそうです...。
<div class="ui-step">
<ol>
<li aria-current="step"><em class="label">入力</em></li>
<li><span class="label">確認</span></li>
<li><span class="label">完了</span></li>
</ol>
<!-- /.ui-step --></div>
<div class="ui-step">
<ol>
<li class="is-done"><span class="label">入力</span></li>
<li aria-current="step"><em class="label">確認</em></li>
<li><span class="label">完了</span></li>
</ol>
<!-- /.ui-step --></div>
<div class="ui-step">
<ol>
<li class="is-done"><span class="label">入力</span></li>
<li class="is-done"><span class="label">確認</span></li>
<li aria-current="step"><em class="label">完了</em></li>
</ol>
<!-- /.ui-step --></div>
記事自体の見やすさも考慮してステップの数は最小限にしてあります。ステップの数が多いケースをご想像ください...。🤔
マークアップ
一般兄弟結合子を活用することを前提に改めたマークアップが以下になります。
<div class="ui-step">
<ol>
<li aria-current="step"><em class="label">入力</em></li>
<li><span class="label">確認</span></li>
<li><span class="label">完了</span></li>
</ol>
<!-- /.ui-step --></div>
<div class="ui-step">
<ol>
<li><span class="label">入力</span></li>
<li aria-current="step"><em class="label">確認</em></li>
<li><span class="label">完了</span></li>
</ol>
<!-- /.ui-step --></div>
<div class="ui-step">
<ol>
<li><span class="label">入力</span></li>
<li><span class="label">確認</span></li>
<li aria-current="step"><em class="label">完了</em></li>
</ol>
<!-- /.ui-step --></div>
たくさん付与されていたclass
属性がなくなりとてもシンプルになりました。
簡単にマークアップの説明をしていきます。
- 順序を意識して
ol
要素を採用しています。 - ステップごとに
li
要素で分け、テキストはclass
属性付きのspan
要素に入れておきます。 - 現在地の設定は
class
属性ではなくaria-current
属性をstep
で設定しています。aria-current="step"
を設定すると、例えばGoogle ChromeとNVDAの組み合わせの場合は「現在のステップ 入力」のように読み上げてくれるようになり、他のブラウザやスクリーンリーダーでも読み上げの向上が期待できます。- 見た目の表現のみで実際に現在地を伝える必要がない場合は
aria-current
属性の代わりに.is-current
などのclass
属性を設定しましょう。
- CSSが読み込まれない場合を考慮して、現在地の見た目が変わるように
span
要素ではなくem
要素でマークアップしています。
aria-current
属性についてより詳しく知りたい場合は以下を参照してください。
- Accessible Rich Internet Applications (WAI-ARIA) 1.1 (aria-current)
- Accessible Rich Internet Applications (WAI-ARIA) 1.2 (aria-current)
スタイル
先程のマークアップに対しデザイン通りスタイリングすると以下のようになります。
.ui-step {
margin: 50px 0;
}
.ui-step ol {
display: flex;
position: relative;
padding: 0 0 26px;
}
.ui-step li {
display: block;
position: relative;
padding-bottom: 6px;
flex-basis: 100%;
font-size: 1.25em;
text-align: center;
}
.ui-step li::before,
.ui-step li::after {
content: "";
display: block;
position: absolute;
bottom: -16px;
margin: auto;
height: 5px;
}
.ui-step li::before {
left: 0;
width: 100%;
}
.ui-step li::after {
right: 0;
width: 100%;
}
.ui-step li:first-child::before {
left: 50%;
width: 100%;
}
.ui-step li:last-child::after {
right: 50%;
width: 100%;
}
.ui-step li:first-child::after,
.ui-step li:last-child::before {
width: 0;
}
.ui-step li[aria-current="step"]::before,
.ui-step li[aria-current="step"]:not(:first-child)::after,
.ui-step li[aria-current="step"] ~ li::before,
.ui-step li[aria-current="step"] ~ li::after {
width: 50%;
}
.ui-step li::before,
.ui-step li::after,
.ui-step li[aria-current="step"]:last-child::after {
z-index: 2;
background-color: #004ea2;
}
.ui-step li[aria-current="step"] ~ li::before,
.ui-step li[aria-current="step"] ~ li::after,
.ui-step li + li[aria-current="step"]::after,
.ui-step li[aria-current="step"]:first-child::before,
.ui-step li[aria-current="step"]:first-child ~ li::before {
z-index: 1;
background-color: #ddd;
}
.ui-step li .label {
color: #004ea2;
}
.ui-step li[aria-current="step"] .label {
font-style: normal;
font-weight: bold;
}
.ui-step li[aria-current="step"] ~ li .label{
font-weight: normal;
color: inherit;
}
.ui-step li .label::before,
.ui-step li .label::after {
content: "";
display: block;
position: absolute;
left: 50%;
margin: auto;
}
.ui-step li .label::before {
bottom: -18px;
z-index: 4;
border: 3px solid #fff;
border-top: none;
border-left: none;
width: 8px;
height: 13px;
transform: translateX(-4px) rotate(43deg);
}
.ui-step li .label::after {
bottom: -26px;
z-index: 3;
width: 26px;
height: 26px;
background-color: #004ea2;
transform: translateX(-50%) rotate(0deg);
}
.ui-step li[aria-current="step"] .label::before,
.ui-step li[aria-current="step"] ~ li .label::before {
top: auto;
left: 50%;
bottom: -19px;
border: none;
width: 12px;
height: 12px;
background-color: #fff;
transform: translateX(-50%) rotate(0deg);
}
.ui-step li[aria-current="step"] ~ li .label::after {
background-color: #ddd;
}
.ui-step li:last-child .label::before,
.ui-step li:last-child .label::after {
left: auto;
right: 50%;
transform: translateX(50%) rotate(0deg);
}
/* ※記事用にわかりやすくする目的で冗長になっている部分があります。 */
簡単にスタイルの説明をしていきます。
li
には共通のスタイルと「済み」状態のスタイルを設定li[aria-current="step"]
には「現在地」状態のスタイルを設定li[aria-current="step"] ~ li
のように記述し、aria-current
属性をstep
で設定したli
要素に続く全てのli
要素に対し「未到達の状態」のスタイルを設定- その他、先頭や末尾に位置する場合の調整
このように記述することでaria-current
属性を持つli
要素の位置関係だけで見た目を表現できるようになります。
以下は実際にスタイリングした場合のサンプル動画ですが、aria-current
属性が設定されているli
要素の位置順を変更するだけでスタイルの変更が実現できています。
テキストは自分で更新する必要がありますが、HTMLが見やすく編集も楽になりましたね。🙌
まとめ
- 今回のように限られた範囲で限られた使い方をする場面では、一般兄弟結合子を活用することで、使いやすく機能的なスタイルでHTMLを簡潔に書くことが可能になり、HTMLの編集作業の効率化を期待できます。
- 可能であれば、意味的にマークアップすることでより多くのユーザーに明確な情報を提供することが期待できるでしょう。
以上になります。
早いもので、今年も残すところあと半月になりましたね。それではよい年の瀬をお過ごしください。