はじめに
「Reactを使っているチームとVueを使っているチームが混在していて、UIコンポーネントを共有できない」——複数のフロントエンドフレームワークが社内に乱立していると、こうした悩みに直面することは少なくありません。デザインシステムをせっかく作っても、フレームワークごとに実装を分けるのはコストが高すぎます。
そこで注目したいのが、Microsoftが開発するFluent UI Web Componentsです。ブラウザ標準のWeb Components技術で作られているため、React・Vue・Angular・素のHTMLなど、どんな環境でも同じコンポーネントをそのまま使い回せます。この記事では、その特徴からインストール方法、実際のコード例までを順を追って紹介します。
とはいえ、読むより触った方が早いと思います。<fluent-text-input>と<fluent-button>が実際にブラウザ上で動く様子を先に見てみましょう。
Fluent UI Web Componentsとは
Fluent UI Web Componentsは、MicrosoftのデザインシステムであるFluent Design Systemを、Web Components標準で実装したコンポーネントライブラリです。Microsoft Edgeなどの実プロダクトでも採用されており、@fluentui/web-componentsというnpmパッケージとして公開されています。
MicrosoftはFluent UIとして「React v9」「React v8」「Web Components」という3つの実装を提供していますが、Web Components版はフレームワークに依存しない点が最大の特徴です。ライブラリはMITライセンスで公開されており、GitHub上(microsoft/fluentui)で活発に開発が続けられています。
主な特徴
- フレームワーク非依存 - カスタムエレメントとして実装されているため、React・Vue・Angular・Svelte・素のJavaScriptなど、どの環境からも同じタグ(
<fluent-button>など)で呼び出せます - CSSカスタムプロパティによるテーマ対応 -
@fluentui/tokensのデザイントークンをsetThemeで適用するだけで、ライト・ダークテーマの切り替えが簡単に行えます - コンポーネント単位のインポート - 使うコンポーネントだけを個別importできる設計になっており、バンドルサイズを最小限に抑えられます
インストール
お使いのパッケージマネージャーでインストールします。
# npm
npm install @fluentui/web-components
# yarn
yarn add @fluentui/web-components
# pnpm
pnpm add @fluentui/web-components
ビルド環境を用意しない場合は、CDN経由で読み込むことも可能です。
<script type="module" src="https://unpkg.com/@fluentui/web-components/web-components.min.js"></script>
Fluent UI Web Componentsのボタンとテキスト入力を動かす
Fluent UI Web Componentsは、使いたいコンポーネントを@fluentui/web-componentsのサブパスから個別にimportし、カスタムエレメントとして登録するだけで使い始められます。ここでは<fluent-text-input>と<fluent-button>を組み合わせて、タスク名を入力してボタンを押すと一覧に追加される、簡易的なToDo入力フォームを動かしてみましょう。入力欄を空にしてから追加ボタンを押すと、どう反応するかも確認してみてください。
要点は次の数行です。fluent-text-inputはinputイベントのたびに.valueプロパティへ最新の入力値を反映するため、通常の<input>と同じ感覚でイベントを扱えます。
import { setTheme } from '@fluentui/web-components'
import { webLightTheme } from '@fluentui/tokens'
import '@fluentui/web-components/text-input/define.js'
import '@fluentui/web-components/button/define.js'
setTheme(webLightTheme)
document.body.innerHTML = `
<fluent-text-input id="task" placeholder="タスク名を入力"></fluent-text-input>
<fluent-button id="add" appearance="primary">追加</fluent-button>
`
document.getElementById('add').addEventListener('click', () => {
const value = document.getElementById('task').value
console.log(value)
})
実際に動かせるサンプルが下です。タスク名を入力して「追加」を押すと一覧にチェック付きで追加され、空のまま押すとエラーメッセージが表示されます。
appearance="primary"の部分をappearance="outline"やappearance="subtle"に書き換えると、Fluent Design Systemに沿ったボタンの見た目がその場で切り替わります。fluent-text-input側もappearance属性にunderlineやfilled-darkerを指定すると、下線スタイルや塗りつぶしスタイルの入力欄に変わるので、あわせて試してみてください。
基本的な使い方
まずはテーマを設定し、使いたいコンポーネントを個別にインポートします。ここではボタンコンポーネントを例にします。
// main.js
import { setTheme } from "@fluentui/web-components";
import { webLightTheme } from "@fluentui/tokens";
import "@fluentui/web-components/button/define.js";
// アプリ全体にライトテーマを適用
setTheme(webLightTheme);
HTML側では、通常のHTMLタグと同じ感覚でカスタムエレメントを配置するだけです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<script type="module" src="./main.js"></script>
</head>
<body>
<fluent-button appearance="accent">送信する</fluent-button>
<fluent-button appearance="outline">キャンセル</fluent-button>
</body>
</html>
appearance属性を変えるだけで、アクセントカラーのボタンやアウトラインボタンなど、Fluent Design Systemに準拠したバリエーションを簡単に表示できます。
実践的なユースケース
Reactアプリへの組み込み
Web Componentsであるという特性を活かし、Reactアプリの中に組み込んでみましょう。フォーム入力欄とボタンを組み合わせた、シンプルな入力フォームの例です。
// FeedbackForm.jsx
import { useRef } from "react";
import { setTheme } from "@fluentui/web-components";
import { webLightTheme } from "@fluentui/tokens";
import "@fluentui/web-components/text-input/define.js";
import "@fluentui/web-components/button/define.js";
setTheme(webLightTheme);
export function FeedbackForm({ onSubmit }) {
const inputRef = useRef(null);
const handleClick = () => {
const value = inputRef.current?.value ?? "";
if (value.trim().length === 0) return;
onSubmit(value);
};
return (
<div>
<fluent-text-input
ref={inputRef}
placeholder="ご意見をお聞かせください"
></fluent-text-input>
<fluent-button appearance="accent" onClick={handleClick}>
送信
</fluent-button>
</div>
);
}
Reactではカスタムエレメントの属性やイベントの扱いに癖がある場合があるため、値の取得にはref経由でDOMプロパティにアクセスするのがポイントです。同じコンポーネント定義は、Vueであればv-modelのバインディング先として、Angularであれば[value]のプロパティバインディングとして、それぞれのテンプレート構文にそのまま馴染ませることができます。
社内に複数のフレームワークが混在している組織であっても、Fluent UI Web Componentsを共通のデザインシステム層として採用すれば、ボタンやフォーム部品を作り直すことなく横断的に再利用できます。
チェックボックス・スイッチで選択肢をまとめる
<fluent-checkbox>と<fluent-switch>はどちらもcheckedプロパティとchangeイベントを持つ、フォーム向けのコンポーネントです。興味のある技術を複数選択できるチェックボックス群と、通知のON/OFFを切り替えるスイッチを組み合わせて、選択結果をリアルタイムに表示してみましょう。
import '@fluentui/web-components/checkbox/define.js'
import '@fluentui/web-components/switch/define.js'
document.querySelectorAll('fluent-checkbox').forEach((cb) => {
cb.addEventListener('change', () => {
console.log(cb.getAttribute('name'), cb.checked)
})
})
document.querySelector('fluent-switch').addEventListener('change', (e) => {
console.log('notify:', e.target.checked)
})
下のサンプルでは、チェックボックスやスイッチを切り替えるたびに、選択中の項目と通知設定が下部のサマリーに反映されます。
fluent-checkbox・fluent-switchはどちらもcheckedという真偽値のプロパティで状態を表すため、通常の<input type="checkbox">とほぼ同じ感覚でロジックを組めます。checked属性を外した状態から始めれば初期状態を未選択にできますし、<label>でカスタムエレメントを囲むだけでラベルクリックによるトグルにも対応する点も便利です。
setThemeでテーマをリアルタイムに切り替える
Fluent UI Web ComponentsはsetTheme()にテーマオブジェクトを渡すだけで、CSSカスタムプロパティ経由で配色を一括変更できます。@fluentui/tokensが提供するwebLightThemeとwebDarkThemeを、<fluent-switch>のON/OFFに合わせて切り替えるサンプルです。
import { setTheme } from '@fluentui/web-components'
import { webLightTheme, webDarkTheme } from '@fluentui/tokens'
function applyTheme(isDark) {
setTheme(isDark ? webDarkTheme : webLightTheme)
}
toggleSwitch.addEventListener('change', (e) => {
applyTheme(e.target.checked)
})
下のサンプルでは、スイッチを切り替えるたびにページ全体の背景色・文字色・ボタンの配色が瞬時に変わります。
setThemeは既定ではdocument全体にテーマを適用しますが、第2引数にDOM要素を渡せば特定のコンポーネントだけをスコープを絞って別テーマにすることもできます。webDarkThemeの部分を自社ブランドカラーで組んだ独自のテーマオブジェクトに差し替えれば、Fluent Design Systemの構造を保ったまま配色だけを自社仕様に変えることも可能です。
まとめ
Fluent UI Web Componentsは、Microsoft品質のデザインシステムをフレームワークの垣根を越えて利用できる、実用性の高いライブラリです。テーマ設定とコンポーネント単位のインポートというシンプルな仕組みながら、React・Vue・Angularなど既存のプロジェクトに違和感なく組み込める点が大きな魅力といえます。
複数チームで異なるフレームワークを使っている、あるいはこれから長く使えるUI基盤を選定したいという方は、ぜひ一度公式リポジトリ(microsoft/fluentui)を覗いて、実際にコンポーネントを触ってみてください。
