Kibana UIのコンポーネント化、パート1: スケーラブルなCSS

スケーラブルなCSSを目指す

数万行ものCSS。8レベルにもネストされたセレクタ。オーバーライドにオーバーライドを重ね続けて目指すのは…(ぜいぜい)… 今度は !important が現れた! こうした怪獣の話を聞いただけで立ちすくみ、震え上がるものもいるが、Kibanaチームはコーヒーをがぶ飲みし、Webインスペクタを手に、正面から敵に立ち向かうのだ。

少しやりすぎですかね。でも、スケーラブルなCSSを記述するための奮闘は 現実です。Kibanaチームでは、次のように形式化したCSS記述方法でこの問題に取り組んでいます。

  • UIについてコンポーネントの観点から考える。
  • クラスを使用してCSSをシンプルにする。
  • BEM命名規則を使用してマークアップとCSSの予測可能性読みやすさを高める。

UIをコンポーネントから作成する

コンポーネント とは、ボタン、フォームフィールド、モーダルウィンドウ、その他の視覚要素など、ユーザーインターフェイスのビルディングブロックです。エンジニアはこれらを組み合わせて機能を構築していきます。

コンポーネントの形式でCSSを記述することで、エンジニアは既存のCSSとマークアップを利用でき、いちから記述しなくてすむため、作業負担を軽減できます。

コンポーネントとして記述されたCSSは保守も容易です。これは、コンポーネントでは自然とスタイルの範囲が制限されるため、変更時に意図しない悪影響が回避されるためです。

コンポーネントの例を1つお見せしましょう。Panelという名前のコンポーネントです。

Our Panel component

コンポーネントには、視覚的表現とコード表現の両方があり、どちらも同じ名前で識別されます。これにより、UIについて話すときに共通の認識を持つことができます。設計者、エンジニア、または製品マネージャが「Panel」と言えば、会話に加わっている他の全員も何を指しているかを正確に把握できます。

次は、CSSを シンプルに保つ方法と、Panelコンポーネントの背後にあるCSSを見ていきましょう。

シンプルさはスケーラブルなCSSの基礎である

シンプルなコードには可動部分が少なく、意図が明確に表現されています。コード内のロジックの単位は互いに分離され、やりとりは明示的であいまいさがなく、境界が適切に定義されています。

シンプルなCSSにもこれと同じ特質がありますが、CSSをシンプルにする方法は、JavaScriptやPythonのような命令型言語で記述するコードの場合とは異なります。Kibanaチームは、シンプル化のために最も強力なのはCSS*クラス*であることに気付きました。

クラスを使用してCSSをシンプルにする方法には、次の原則があります。

クラスを使用して明示的にマークアップをレンダリングされるUIにマッピングする。 コンポーネントを念頭に置いてクラスを命名すると、クラス名に意味が補足されます。これらのクラス名は、マークアップにおいてランドマークや交通標識のように機能するため、マークアップの操作や要素間の関係の把握に役立ち、頻繁にブラウザをチェックしなくてもUIのイメージを描きやすくなります。

セレクタを1つのクラスに制限する。 セレクタに複数のクラスが含まれると、コンテキストに依存するようになります。つまり、スタイルが有効になるには要素間に特定の関係が必要になり、マークアップを解読するときにこの関係を特定するのが難しくなることがあります。各セレクタを1つのクラスに制限することで、要素間の関係をシンプルにし、マークアップを読みやすくして、コンテキストの変更時に誤って関係を断絶する可能性を低くすることができます。

継承されたプロパティは「リーフ」ノードでのみ使用する。 CSSで「font-size」や「color」などのプロパティを要素に適用すると、それらは継承を介してその要素の子にも適用されます。こうした副次作用は予期できないことがあり、不適切な結果になることさえあります。私たちは解決策として、継承されたプロパティは子が含まれないUIの部分(フォームラベルやボタンなど)でのみ使用することで、継承によってUIの外観に影響が及ぶのを防止しています。

それでは、Panelコンポーネントでこれらの原則がどのようにコードに適用されているかを確認しましょう。Panelを作成するためのマークアップは次のようになります。

<div class="panel">
  <div class="panelHeader">
    <div class="panelHeader__title">
      Panel title
    </div>
  </div>

  <div class="panelBody">
    <!-- Content goes here -->
  </div>
</div>

UIは、マークアップでクラス名を使用することで明確に識別されています。これがヘッダーとボディを含むPanelコンポーネントであることは明らかです。Kibanaチームでは、マークアップを読みやすくすることで、UI作成の迅速化と信頼性の向上という効果もあることが判明しました。

このマークアップの外観を定義するCSSは次のようになります。

.panel {
  border-left: 2px solid #e4e4e4;
  border-right: 2px solid #e4e4e4;
  border-bottom: 2px solid #e4e4e4;
}

.panelHeader {
  display: flex;
  align-items: center;
  padding: 10px;
  height: 50px;
  background-color: #e4e4e4;
}

  /*
   * FYI, we indent child classes like this to emphasize its role
   * in the markup as a tightly-coupled child of the .panel class.
   */
  .panelHeader__title {
    font-size: 18px;
    line-height: 1.5;
  }

.panelBody {
  padding: 10px;
}

各セレクタを1つのクラスで宣言する方法を確認してみましょう。これにより、CSSがマークアップに対してどのように機能し、最終的にどのような視覚的UIコンポーネントが作成されるかを理解するのが非常に容易になります。

最後に、継承されたプロパティを持つセレクタは.panelHeader__title クラスのみです。このセレクタが適用される要素にはPanelのタイトルにあるテキスト以外に子は決して含まれないことがわかっています。これは、CSSの継承モデルによって発生しかねない問題を回避する方法の一例です。

BEM命名規則

BEM命名規則は、2007年にYandexが初めて開発して以来、広く普及しています。このブログ記事ではBEMの意味合いは説明しませんが、関心のある方は、こちらの Harry Roberts氏による記事か、こちらのCSSのコツに関する記事で詳細を参照してください。

KibanaチームがBEMをCSSの命名規則として選んだのは、マークアップを解読するときにコンポーネントを構成するさまざまなクラス間のまとまりを識別する方法が必要であったからです。同時に、マークアップ内で各クラスが果たす役割を区別する方法が必要でした。クラスには、構造的な役割を果たすもの、他のクラスを変更するもの、JavaScriptで動的に適用されることを意図したものなどがあります。BEMでは、パターンを使用してこうしたさまざまなクラスを見分けることができます。

BEMのしくみについては、前述のリンクを参照してください。

次回の予告: コンポーネント化プロセス

これはシリーズのパート1です。次回の記事では、再利用性や構成可能性を考慮したコンポーネントの設計方法など、コンポーネント化プロセスから学んだことを共有します。お楽しみに!

Kibanaチームでは 常時、優れた人材を募集しています。ご関心のある方はぜひご連絡ください。