Novi UI

Flatlay

帳票・文具 / z 軸なし。浮く層が無いので、開くものは場所を取ります。 展開はその場を押し下げ、Modal は画面を占め、階層は罫線と地色と面積だけで作ります。 机に広げた書類を真上から見た状態が、このテーマの名前の由来です。

他の2つとどう違うか

公開 API は3テーマとも完全に同一です。import 元を差し替えるだけで、見た目だけでなく DOM の組み立て方ごと切り替わります。違うのは次の構造です。

部位RasterTactileFlatlay
階層の作り方線で切る面を持ち上げる罫線・地色・面積だけ
影浮く層に使う面にも使う全段が透明(存在しない)
Select / Menu / Popover隣に浮く下から出るシートその場を押し下げる
Modal中央のダイアログ下から出るシート全画面テイクオーバー
Toast右下に浮く上端に浮くフローに挿入される帯
押下の表現色0.97 倍に沈む面と文字が入れ替わる
高さ32 / 40 / 48px40 / 48 / 56px28 / 32 / 40px
中立色染まらない紙染まる生地地は染まらず罫線だけが染まる

指で触る画面なら Tactile を選んでください。 Flatlay はタッチの下限(44px)を意図的に満たしていません。帳票の密度と両立しないためです。

すべて数値で決まっています

「z 軸を持たない」を感覚で運用すると必ず崩れます。目分量で近い値を書かず、 この値を使ってください。

controlHeights
sm: 28 / md: 32 / lg: 40
radii
none: 0px / sm: 2px / md: 2px / lg: 4px / full: 9999px
text
xs: 11px / sm: 13px / base: 16px / lg: 18px / xl: 22px / 2xl: 26px / 3xl: 32px
motion
duration-fast: 100ms / duration-base: 100ms / duration-slow: 100ms / ease-standard: cubic-bezier(0.2, 0, 0, 1) / ease-emphasized: cubic-bezier(0.2, 0, 0, 1)

影は全段が透明です

sm から lg まで、値はすべて 0 0 #0000 です。 トークンを消さずに透明で残しているのは、他テーマ向けに書いたコードがそのまま動くようにするためと、フォーカスリングが box-shadow に合成されるためです(段を削ると合成先が消えます)。

Flatlay の影トークン
CSS 変数値
--novi-shadow-none0 0 #0000
--novi-shadow-sm0 0 #0000
--novi-shadow-md0 0 #0000
--novi-shadow-lg0 0 #0000

角は書類の直角です

sm と md は同じ 2px です。 ボタンと小さな部品に差を付けないのは、紙を裁った断面に段階が無いからです。 円になるのは Radio と Avatar の2つだけで、これは形の弁別が角丸の規律より優先する場所です(Radio が Checkbox と同じ形になると、 複数選べるのか1つだけなのかが形から読めなくなります)。

Flatlay の角丸トークン
CSS 変数値
--novi-radius-none0px
--novi-radius-sm2px
--novi-radius-md2px
--novi-radius-lg4px
--novi-radius-full9999px

染まるのは罫線だけです

Raster は染まらない紙、Tactile は染まる生地でした。Flatlay は第3の方式で、 地も文字も無彩(chroma 0)のまま、罫線2本だけが選んだ色の色相を帯びます (chroma 0.02〜0.03)。唯一の階層表現である罫線に色を持たせないと、 色を選んだ実感がどこにも出ないからです。

色は --novi-color-* の CSS 変数経由でのみ指定する。data-novi-color で染まるのは罫線だけだが、地の色もトークン経由でなければテーマの外に取り残される
bg
subtle
surface
muted
fg
border(染まる)
border-strong(染まる)

border-strong は全 hue で地に対し 3:1 以上を実測しています。色を替えても罫線が消えないことを、目視ではなく数値で担保しています。

赤がありません

カラーセット Stationery の8色に赤は入っていません。校正の朱書きはエラーを示す道具なので、赤は danger に予約しています。primary が danger と紛れる事故を、規則ではなく世界観で防ぐ設計です。 eraser(hue 352)は ピンクの消しゴムであって朱ではありません。

既定が fieldbook(測量野帳の緑) なのは、Raster の ink・Tactile の indigo に続く3代連続の藍を避けるためです。

差し色が組で付いてきます

色を1つ選ぶと、--novi-color-secondary に相方が入ります。複式簿記になぞらえてダブルエントリーと呼んでいます。 片側だけを決めると差し色は「余った色」になりますが、組で決めれば対照そのものが設計の対象になります。8色のうち6色は相互に指し合う3組で、残り2色(fieldbook と ribbon)だけが片方向です。

色hue出自相方
FieldbookDEFAULT172測量野帳の表紙の緑。既定色Eraser
Blueprint215青焼き図面の青Manila
Carbon288カーボン複写紙の紫Legalpad
Ribbon330タイプライターのインクリボンFieldbook
Eraser352ピンクの消しゴムPencil
Manila58マニラ封筒の黄土Blueprint
Legalpad98黄色いリーガルパッドCarbon
Pencil240鉛筆の芯。無彩枠Eraser

トーンはテーマが持ちます。どの色を選んでも light は L47% / C0.070、dark は L75% / C0.060 です。 実物は Lookbookで見開きにしてあります。

書いてはいけないクラス

テーマを触る場合の規律です。CI が機械的に検査しているため、違反するとビルドが落ちます。 この一覧は検査スクリプトと同じ定義から作られています。z-index の禁止に例外はありません。 重なりの順序は DOM 順だけで表します。

  • z-index の指定(z-10 / z-[999] / zIndex)Flatlay は z 軸を持たない。重なりの順序は DOM 順だけで表す(例外なし・FR-02)
  • fixed / absolute浮く面を作らない。例外は modal.styles.ts(テイクオーバー)と tooltip.styles.ts の2つだけ(FR-03)
  • sticky滞留も重なり。スクロール中にコンテンツへ被る時点で z 軸の語彙になる(ADR-F4)
  • トークン外の影(shadow-md / shadow-[0_1px…] など)影は嘘(浮く層が存在しない)。トークンは全段 0 0 #0000 で、書いても何も出ない
  • 高さ・スライドのアニメーション展開・格納は即時。押し下げに transition を付けると後続が滑り続けて読めなくなる(FR-12 / ADR-F1)
  • scale-* / translate-* / rotate-* / animate-spin押下は反転(スタンプ)で示す。動きで飾らない。例外は spinner.styles.ts のみ(FR-11)
  • トークンを経由しない角丸(rounded-sm / rounded-full / rounded-t-xl など)角丸はトークン経由。Flatlay は書類の直角で sm=md=2 / lg=4px
  • リテラルの色値(#fff / rgb() / hsl() / oklch())色は必ず --novi-color-* を経由する。リテラル値を書かない
  • duration-*(--novi-duration-* の任意値を除く)モーションの時間はトークン経由にする(Flatlay は 100ms の1本しかない)
  • 8px 以上の生の余白ユーティリティ(p/px/py/pt/pb/pl/pr/gap-2 以上)余白はトークン経由(--novi-pad-* / --novi-gap-*)。生値で書くと3モデルが同じ密度に潰れ、「余白がテーマの所有物」でなくなる。8px 未満の微小インセットだけが例外(字やアイコンの当たり合わせ)

例外

浮くものは Modal(全画面)と Tooltip(付箋)の2つで凍結しています。 Tooltip だけが例外なのは、フローに入れた瞬間にレイアウトが動いてポインタが トリガーから外れ、出た瞬間に閉じてしまうためです。

  • flatlay-tokens.ts は literal-color, duration, radius, shadow-literal のみ許可 — トークンの値そのものを定義する唯一の場所。ここだけは「使用」ではなく「定義」
  • color-set.ts は literal-color のみ許可 — カラーセット Stationery の値を定義する場所。flatlay-tokens.ts と同じ「定義」側
  • spinner.styles.ts は transform のみ許可 — ローディング表現の代替(点滅・バー往復)は視認性か情報量で劣る(両テーマと同じ判断)
  • modal.styles.ts は position のみ許可 — 全画面テイクオーバーの例外1号。viewport 全面を占めるには fixed が要るが、z-index は使わず DOM 順で最前に置く(ADR-F2 / FR-06)
  • tooltip.styles.ts は position のみ許可 — 例外2号。ポインタ追従の一時表示はフローに入れられない(レイアウトが動くと hover が外れる)ため、唯一の浮きとして absolute を許す(ADR-F6)
  • accordion.styles.ts は raw-spacing のみ許可 — panel の pl-6/8/10 は見出しの字下げに揃える階段で、階層の表現であって余白の設計ではない。pr / pb はその階段と対で決まり、trigger の上下は行の高さそのもの。左右はトークン化済み

動きは1本しかありません

速度は 100ms の1本で、 イージングも1本です。段階を持たないのは、速度差が「遠くから来た」「近くで動いた」 という奥行きの言い方だからです。展開と格納はアニメーションしません。開いた瞬間に後続が下がり、閉じた瞬間に戻ります。高さを補間すると、 その間だけ紙が伸び縮みする物体に見えます。

押下は反転です。面の色と文字の色が 入れ替わり、離すと戻ります。スタンプを押した跡の見え方で、要素が 1px も動かなくても押下が伝わります。 沈む(Tactile)にも色の変化だけ(Raster)にも 寄せていない第3の形です。

transform を使えるのは Spinner の回転だけです。代替(点滅・バーの往復)は処理中であることの視認性か情報量で必ず劣り、 待たされている人にとっては世界観よりも「動いていると分かること」が先だと判断しました。prefers-reduced-motion が有効なときはその回転も止まります。

Flatlay のモーショントークン
CSS 変数値
--novi-duration-fast100ms
--novi-duration-base100ms
--novi-duration-slow100ms
--novi-ease-standardcubic-bezier(0.2, 0, 0, 1)
--novi-ease-emphasizedcubic-bezier(0.2, 0, 0, 1)

印刷しても欠けません

z 軸を捨てたことの配当がここに出ます。浮く overlay は印刷すると紙の外に落ちるか、下の内容に重なって両方読めなくなります。Flatlay の展開はフローの一部なので、開いたまま印刷しても後続を押し下げた状態で紙に載ります。 これは副産物ではなく、印刷される画面(帳票・伝票・見積)を想定した設計上の狙いです。

値を変えたいとき

ここにある値はすべて CSS 変数です。上書きの方法は テーマの調整にあります。