EQMonitor は、日本国内向けの地震・防災情報を扱う Flutter モバイルアプリです。UI は「緊急時でも読み取りやすいこと」を最優先にしつつ、Google 純正アプリのような落ち着いたダーク UI を基調にします。Material 3 をベースにしながらも Android に寄りすぎず、iOS でも違和感の少ない、静かで信頼感のある見た目を採用します。
このデザインシステムは、アプリ全体の設定画面、オンボーディング、権限導線、地図まわりのシート、空状態、情報カードに適用します。見た目のキーワードは、dark, calm, rounded, layered, readable, mobile-first です。
- ベースはダークテーマ。背景は黒に近いが完全な純黒にはしない。
- 面は 1 枚のフラットな黒ではなく、
background,surface,raised surface,card surfaceの階層で整理する。 - 大見出しは大胆に、本文は抑制的に、補助文は静かに見せる。
- カード、ボトムシート、入力面は強めの角丸を使い、親しみと可読性を両立する。
- 1 画面 1 目的を徹底し、主要操作は常に 1 つだけ強調する。
- 地震情報の色はブランド色より優先される。震度・警報・危険度を示す色はドメイン固有ルールに従い、通常 UI の装飾色として流用しない。
background.default:#0F141A- アプリ全体の基底面。長時間見ても眩しくない深い青みのある黒。
background.subtle:#131A21- 全画面系オンボーディングやイラスト面の背景。
surface.default:#171E26- 通常の
Scaffoldやシート本体。
- 通常の
surface.raised:#1D2630- セクションやグループ面。
surface.card:#232D38- 設定カード、リストアイテム、アカウントカード。
surface.emphasis:#2B3744- 選択中・強調中のカード背景。
outline.soft:#3A4654- 区切り線やカード境界。
outline.strong:#506073- フォーカス、強めの境界、選択輪郭。
brand.primary:#4D8DFF- アプリの主要アクセント。CTA、選択状態、主要トグルに使う。
brand.primaryContainer:#24344A- トーナルボタン、選択済みカード、補助ラベル背景。
brand.secondary:#8FB7FF- 情報補助、軽いアクセント。
brand.tertiary:#91D4C8- 穏やかな補助色。成功や正常系の近傍で使う。
status.success:#63D39Bstatus.warning:#F4C75Estatus.danger:#FF7A7Astatus.info:#78B8FF
text.primary:#F3F6FA- 見出し、主要本文。
text.secondary:#C4CCD7- 補助説明、サブタイトル。
text.tertiary:#98A5B5- ラベル、プレースホルダ、補足。
text.inverse:#0F141A- 明るいボタン面上の文字。
- 主要アクションは 1 画面につき 1 つだけ
brand.primary系で強調する。 surface.cardは設定カードやグループボックスの標準とし、背景との差を明確にする。- 危険・警告を示す
status.dangerとstatus.warningは、通常の装飾用途には使わない。 - 地震の震度色、津波、EEW 警報色などの防災ドメイン色は、ここで定義するブランド色より優先して維持する。
- 地図上のオーバーレイやライブ情報では、可読性を優先し、半透明色を多用しすぎない。
- Primary UI font:
Google Sans Flex- fallback:
Noto Sans JP
- Monospace / code / numeric emphasis:
Google Sans Code- fallback:
Noto Sans JP
- アプリ内のすべての見出し、本文、ラベル、ボタン文言、設定項目名は
Google Sans Flexを第一選択とする。 - 日本語グリフが必要な箇所では
Noto Sans JPをフォールバックとして利用する。 - コード、ビルド番号、観測値、時刻、緯度経度、ID、ログ、固定幅で揃えたい数値には
Google Sans Codeを利用する。 - 日本語本文では極端な letter spacing を使わない。標準は
0.0、英字主体のラベルのみわずかに正方向を許容する。 - 緊急時の読みやすさを優先し、過剰な light weight は使わない。基本は
400,500,600,700の範囲で構成する。 - 行間は詰めすぎない。日本語を含む本文は十分な line height を確保する。
displayLarge- use: フルスクリーンの歓迎画面、重大導線の大見出し
- size:
40 - line height:
48 - weight:
600
displayMedium- use: オンボーディング上部、印象的な 2 行見出し
- size:
36 - line height:
44 - weight:
600
headlineLarge- use: ページタイトル、シートの大見出し
- size:
32 - line height:
40 - weight:
600
headlineMedium- use: セクション主見出し
- size:
28 - line height:
36 - weight:
600
headlineSmall- use: ボトムシートやカード群のタイトル
- size:
24 - line height:
30 - weight:
600
titleLarge- use: 画面上部タイトル、重要カードのタイトル
- size:
22 - line height:
28 - weight:
600
titleMedium- use: セクションカードの見出し、リストグループタイトル
- size:
18 - line height:
24 - weight:
600
titleSmall- use: ListTile タイトル、フォーム見出し
- size:
16 - line height:
22 - weight:
600
bodyLarge- use: 標準本文、説明文
- size:
16 - line height:
24 - weight:
400
bodyMedium- use: 補助本文、カード内説明
- size:
14 - line height:
20 - weight:
400
bodySmall- use: 注記、サブコピー、メタ情報
- size:
13 - line height:
18 - weight:
400
labelLarge- use: ボタン、セグメント、主要ラベル
- size:
14 - line height:
20 - weight:
500
labelMedium- use: バッジ、補助ラベル、フィルタ
- size:
12 - line height:
16 - weight:
500
labelSmall- use: キャプション、極小メタ情報
- size:
11 - line height:
14 - weight:
500
monoLarge- family:
Google Sans Code - size:
16 - line height:
22 - weight:
500 - use: 緯度経度、ID、設定値、観測数値
- family:
monoMedium- family:
Google Sans Code - size:
14 - line height:
20 - weight:
500 - use: ログ、時刻、バージョン、診断情報
- family:
monoSmall- family:
Google Sans Code - size:
12 - line height:
16 - weight:
500 - use: チップ内メタ情報、小さな数値ラベル
- family:
- 日本語の長文を
all caps的に扱わない。 - 重要情報はサイズだけでなく weight とコントラストで見せる。
- 情報密度が高い画面でも、
bodyMedium未満の本文を主テキストにしない。 - 震度、時刻、座標など比較対象になる情報は
Google Sans Codeで揃える。 - 1 つの画面内で font family をむやみに混在させない。通常は
Google Sans Flex、数値強調だけGoogle Sans Codeに切り替える。
- ベースグリッドは
4dp。 - 標準余白スケール:
xs:4sm:8md:12lg:16xl:202xl:243xl:284xl:32
- スマートフォン画面の左右パディングは
16を標準、没入感を出したい画面は20を使う。 - 設定カードやシート内コンテンツの内部余白は
20を基本とする。 - セクション間の縦間隔は
16。大きな画面ブロックの分離は24以上を使う。 - タイトルと本文の間は
4から8。 - カード内の操作群と説明文の間は
12から16。 - 画面下部の主要 CTA の上には
24以上の余白を確保する。
- 標準カード半径:
24 - 強調カード半径:
28 - ボタン半径:
20から24 - シート上端半径:
28 - 小さなバッジ、トグル背景、補助ピルは
999の完全ピルを許容する
- 画面上部は詰め込みすぎず、
戻る or 閉じる,タイトル,必要なら 1 個の補助アクションに留める。 - ボトムシートの先頭にはドラッグハンドルを置いてよい。
- ボトムシート見出しは、大見出し + 1 行から 2 行の説明 + 右上の閉じるボタン、の構成を標準にする。
- ヘッダー内の補助ラベルは
labelMediumを使い、brand.primaryContainerまたはsurface.emphasis上に置く。
- 設定、情報グループ、オンボーディング下部アカウント面などは、面ごとに独立したカードとして見せる。
- カード背景は
surface.cardとし、選択状態はsurface.emphasisで示す。 - カード境界は濃すぎない 1px 相当の
outline.softを使う。 - 影は最小限。ダークテーマでは elevation より面の色差で階層を出す。
- 設定行は
title + subtitle + trailing controlを基本構成とする。 - タイトルは
titleSmall、補助文はbodySmallを基本とする。 - 行全体をタップ可能にしつつ、スイッチやナビゲーションアイコンも明確に見せる。
- 1 行に情報を詰め込みすぎない。2 行説明は許容する。
- ブール設定のトグル行は、統一見た目の
AppSwitchListTile(末尾にAppSwitch)を標準とする。ListTile+AppSwitchの組み合わせも許容する。
- 実装の参照先:
app/lib/core/component/widget/app_switch.dart。 - アプリ内の ON/OFF は、ブランドに沿ったピル型トラックと円形サム(ON はチェック、OFF はクローズアイコン)を
AppSwitchで表現する。 - モーションは
cueを用い、ON/OFF の切り替えやサム移動はスムーズ/スナップ系のプリセットで宣言的に定義する。 - 押下中はサムをわずかに拡大し、タッチ位置がスイッチ上にあることが分かるフィードバックを付ける(
Listenerで押下を追跡し、Cue.onToggle+Act.scaleで表現する)。 - Material の
Switchを設定画面で直接使うのは新規では避け、上記コンポーネントに寄せる。
- 大きな設定群はアコーディオンを標準にする。
- 同時に展開するのは原則 1 セクションのみ。新しいセクションを開いたら他を閉じる。
- セクションヘッダーは
48dp以上のタップ可能領域を確保する。 - 開閉アイコンは回転で状態変化を示し、開閉本体はサイズ変化で表現する。
- 開閉状態が重要な画面では、将来的に状態保持を検討してよい。
- 主要 CTA は
FilledButton相当の明るい面で、画面内に 1 つを原則とする。 - 二次 CTA は
FilledButton.tonalまたはOutlinedButtonを使う。 - 破壊的操作は赤い塗りボタンを乱用せず、通常はトーナルまたはテキストボタンで確認導線を挟む。
- ボタン内文字は
labelLargeを使う。 - ボタン部品は Material 3 の
FilledButton/FilledButton.tonal/OutlinedButton/TextButtonを素直に使い、プラットフォーム別のラッパは導入しない。
- モード切り替え、表示種別切り替え、フィルタ切り替えにはセグメント UI を使う。
- 最大 3 から 4 選択肢までを推奨する。それ以上はメニューや別画面に分離する。
- 選択中は背景と文字色の両方で状態を示す。
- セグメント UI は Material 3 の
SegmentedButtonを標準として用い、プラットフォーム別の実装を切り替えるラッパは導入しない。
- 確認ダイアログは Material 3 の
AlertDialog(showDialog経由)を素直に用いる。iOS スタイルのCupertinoAlertDialogをプラットフォーム別に出し分けることはしない。 - 一時的な通知は
SnackBarをScaffoldMessenger経由で表示する。重要なフィードバックはダイアログや画面内表示に格上げし、SnackBarに依存しすぎない。 - 長押しメニューや文脈依存アクションは Material の
MenuAnchor/PopupMenuButtonを基本とする。 - 日付選択は
showDatePicker、時刻選択はshowTimePickerを標準とする。 - チェックボックス、スライダー、ラジオは Material 3 の
Checkbox,Slider,Radioを素直に使う。セグメントは前節の通りSegmentedButtonを用いる。 - スイッチは上記「Toggles (AppSwitch)」に従い、原則として
AppSwitchを使う。 - テキスト入力は
TextField/TextFormFieldを直接使う。プラットフォーム別の入力ウィジェット切り替えは行わない。 - フォームのグルーピングは、見出し付きの
Cardやセクション区切りで Material 的なまとまりとして見せる。
- 1 画面 1 メッセージを徹底する。
- 上部は大見出し、中央はイラストまたは象徴的なビジュアル、下部はアカウント面または主要 CTA の構成を基本とする。
- 説明文は短く、読み切れる長さに抑える。長文の規約説明は CTA 付近に二次情報として置く。
- アカウント選択面や許諾カードは、ヒーローセクションとは別のカードに分離して読みやすくする。
- 権限要求は OS ダイアログの前に、なぜ必要かを人間向けの文で説明する。
- 防災・位置情報・通知のような重要権限では、ベネフィットを先に示す。
- 主要 CTA は 1 つ、副次的な「あとで」導線は控えめに置く。
- 読み込み中の表示には
skeletonizerを利用する。 - ローディング中は、最終的に表示されるレイアウトに近い skeleton を出し、読み込み完了後のレイアウトジャンプを最小限に抑える。
- カード、設定行、リスト、情報ブロックは、可能な限り実 UI をそのまま skeletonize する。
- 画面全体を単一の
CircularProgressIndicatorだけで済ませるのは、初期起動や極めて短い待機を除き避ける。 - 空状態とローディング状態を混同しない。データが未取得なのか、取得結果が空なのかを UI で明確に分ける。
- 画像、地図、ネットワークリソースなど fake data で崩れやすい要素は、
Skeleton.replaceなどの annotation を使って安全に置き換える。 - Skeleton の色と shimmer はダークテーマの階層を壊さないよう控えめにし、情報色や警告色と競合させない。
- 空状態は冷たい無表示にしない。1 つの説明文と、可能なら次の行動を用意する。
- イラストやシンボルを使う場合も、過剰に賑やかにしない。
- 地図やライブデータの文脈では、空状態より「現在データなし」の説明を優先する。
- 地図上の補助シートや設定シートは、地図を邪魔しすぎない短い情報単位で構成する。
- 数値、時刻、座標、状態コードは
Google Sans Codeを優先する。 - 地図レイヤー設定は、ボトムシートのモーダルに閉じ込めず、
go_router経由の専用フルページ(例: ホーム配下のマップレイヤールート)として開く。戻る操作は AppBar の戻るで足りる想定とする。 - ページ先頭には、地図のレイヤー構造を抽象的に示すヒーロービジュアル(イラストやレイヤー風グラフィック)を置いてよい。動きは控えめで、情報を奪わない。
- 強震モニタに関する詳細設定(表示種別、マーカー、フィルタなど)は、レイヤー設定と同じページ内のセクションとしてまとめ、別モーダルに分断しない。
- 地図レイヤー周りの設定はセクション化し、関連するトグルやセグメントをカード単位で束ねる。トグルは
AppSwitchを用いる。
- 基本アニメーション時間:
- micro interaction:
150ms - accordion / content reveal:
200ms - sheet / panel transition:
240msから280ms
- micro interaction:
- 標準カーブは
easeOutCubicまたは Material 3 に近い減速系カーブを使う。 - 状態変化は派手な演出ではなく、位置、サイズ、透明度、回転の最小限で伝える。
- 緊急情報を扱う画面では、注意を奪う不要なアニメーションを入れない。
- カスタムトグル(
AppSwitch)ではcueのモーション(例:.smooth()/.snappy())で値の変化とサムの押下スケールを分け、操作感を一定に保つ。
- Flutter 実装では、通常テキストを
Google Sans Flex、日本語フォールバックをNoto Sans JPとする前提でTextThemeを構成する。 - 数値・コード系スタイルは
Google Sans Codeを明示的に適用する。 - タイポグラフィ、色、角丸、余白は
ThemeDataとThemeExtensionで一元化する。 - iPhone の Safe Area、Android のシステム UI、テキストスケール拡大を前提に、固定高さに依存しない。
- 設定行、カード、シートは
ListTile,Card,BottomSheet,SegmentedButtonをベースにしてもよいが、余白と shape はこのドキュメントに合わせて調整する。 - 読み込み中 UI は
skeletonizerを標準採用とし、Skeletonizer,Skeletonizer.sliver, annotation 群を用いて既存レイアウトから skeleton を生成する。 - Skeleton 用の fake data が必要なケースでは、本番レイアウトを崩さない最小限のダミーデータを用意し、
NetworkImageなどはSkeleton.replaceや条件分岐で安全に扱う。
- 大見出しで画面目的を明確にする
- ダーク面の階層差で情報構造を見せる
- カード単位で情報を束ねる
- 主要 CTA を 1 つに絞る
- 日本語の読みやすさを優先して十分な行間を取る
- 数値や時刻は
Google Sans Codeで揃えて比較しやすくする - 防災情報の色はブランド装飾より優先する
- 画面内に複数の強いアクセント色を混在させない
- 純黒背景と純白文字だけで構成しない
- 1 行に情報を詰め込みすぎない
- 日本語本文を小さすぎる文字で長く表示しない
- Android 専用の見た目に寄せすぎて iOS で浮く UI にしない
- 危険色を通常の装飾や選択状態に使わない
- 防災ドメイン色をブランドカラーへ無理に寄せない