aria-current属性と一般兄弟結合子で作るステップUI

UI開発者 寒川

この記事はミツエーリンクス Advent Calendar 2020 - Adventarの21日目の記事です。

今回はCSSのお話です。

CSSをうまく使うことができると

  • 必要だと思っていたJavaScriptが不要になる
  • HTMLが簡潔に書ける

場合があります。

そのことから私は日々CSSを書きながら「まだ気がついていない使い方があるのではないか」とその可能性に魅力を感じています。

特に静的に実装するのであれば、万人が触れる機会が多いHTMLの編集しやすさは意識したいところですね。

ステップUIをお題として一般兄弟結合子(~の実用例をご紹介します。

加えてaria-current属性の使い方も少しだけご紹介できればと思います。

例えば、以下のような見た目のステップUIをスタイリングする場合は

ステップ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属性についてより詳しく知りたい場合は以下を参照してください。

スタイル

先程のマークアップに対しデザイン通りスタイリングすると以下のようになります。

.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の編集作業の効率化を期待できます。
  • 可能であれば、意味的にマークアップすることでより多くのユーザーに明確な情報を提供することが期待できるでしょう。

以上になります。

早いもので、今年も残すところあと半月になりましたね。それではよい年の瀬をお過ごしください。