Novi UI

Tactile

タッチファースト。指で触る前提で寸法を決め、面を持ち上げて階層を作ります。 下から出るシート、大きなタップ領域、押した手応え。 画面あたりの情報量は Raster より意図的に少なくしてあります。

Raster とどう違うか

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

部位RasterTactile
Modal中央のダイアログ・閉じるは右上の ✕下から出るシート・閉じるはフッターのフルワイド
Select / Menuトリガーの隣に出る画面下端のシート
Tabs下線1本セグメンテッドコントロール
Toast右下上端(下端はシートとキーボードが占有する)
高さ32 / 40 / 48px40 / 48 / 56px
本文16px17px

デスクトップで情報密度が要るなら Raster を選んでください。 寸法はテーマの美学に属するもので、どちらが正しいという話ではありません。

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

「タッチファースト」を感覚で運用すると必ずブレます。目分量で近い値を書かず、 この値を使ってください。

controlHeights
sm: 40 / md: 48 / lg: 56
radii
none: 0px / sm: 8px / md: 14px / lg: 20px / full: 9999px
text
xs: 13px / sm: 15px / base: 17px / lg: 21px / xl: 26px / 2xl: 32px / 3xl: 40px
motion
duration-fast: 120ms / duration-base: 200ms / duration-slow: 260ms / ease-standard: cubic-bezier(0.2, 0, 0, 1) / ease-emphasized: cubic-bezier(0.05, 0.7, 0.1, 1)

指が届くことを実測しています

対話要素の実効タップ領域は 44×44px 以上です(WCAG 2.2 AAA / Apple HIG)。Checkbox の箱のように小さく見せたい部品は、 擬似要素で当たり判定だけを広げてあります — 視覚寸法とタップ寸法は別物です。

クラスが付いているかではなく、実際のブラウザで計算済みスタイルを測って検査しています。 擬似要素で広げた領域は要素の矩形に現れないため、それ以外の方法では確かめられません。

入力の文字は 16px を下回りません

iOS Safari は 16px 未満の入力欄にフォーカスするとページごと拡大し、以後戻りません。size="sm" でも入力の文字は小さくせず、視覚的な軽さは高さと余白で作ります。

Tactile の文字サイズトークン
CSS 変数値
--novi-text-xs13px
--novi-text-sm15px
--novi-text-base17px
--novi-text-lg21px
--novi-text-xl26px
--novi-text-2xl32px
--novi-text-3xl40px

影は面にも使います

階層は持ち上がりで作ります (Raster は線で切ります)。境界線は補助的にしか使いません。lg だけ影が上向きなのは、 浮く面が画面の下端から来るためです。

Tactile の影トークン
CSS 変数値
--novi-shadow-none0 0 #0000
--novi-shadow-sm0 1px 3px oklch(20% 0.01 255 / 0.10), 0 1px 2px oklch(20% 0.01 255 / 0.06)
--novi-shadow-md0 6px 16px oklch(20% 0.01 255 / 0.14), 0 2px 4px oklch(20% 0.01 255 / 0.08)
--novi-shadow-lg0 -8px 32px oklch(20% 0.01 255 / 0.18), 0 -2px 8px oklch(20% 0.01 255 / 0.08)

中立色まで選んだ色に染まります

Tactile は染まる生地です。data-novi-color で染料を選ぶと、primary だけでなく背景・境界線・文字までその色相を帯びます。 Raster(染まらない紙・中立色の彩度は 0)との最も分かりやすい違いがここに出ます。

色は --novi-color-* の CSS 変数経由でのみ指定する。data-novi-color で選ばれた染料に中立色まで追従するため、リテラル値を書くとテーマの外に取り残される
bg
subtle
surface
border
muted
fg

size は幅ではなく最大高です

全幅のシートに幅の段階は存在しないので、Modal の size はシートの最大高として解釈します (sm 40dvh / md 60dvh / lg 80dvh / full 100dvh)。 Select と Menu ではトリガーの寸法と行の密度になります。

語彙は core が共通で固定し、解釈はテーマの自由という設計です。 同じコードがテーマを跨いでそのまま動くのはこのためで、不具合ではありません。

書いてはいけないクラス

テーマを触る場合の規律です。CI が機械的に検査しているため、違反するとビルドが落ちます。 この一覧は検査スクリプトと同じ定義から作られています。

  • トークンを経由しない角丸(rounded-sm / rounded-full / rounded-t-xl など)角丸はトークン経由。Tactile は sm=8 / md=14 / lg=20px で、none 以外は 8px 以上
  • トークン外の影(shadow-md / shadow-[0_1px…] など)影はトークン経由。値は α ≤ 0.24 に縛られている
  • border-2 以上境界線は補助。使う場合も 1px のみ。面の分割は影と背景色差で行う
  • 押下状態以外の scale-*装飾目的の scale は使わない。押下フィードバック(data-[pressed]:scale-*)のみ許可
  • rotate-* / animate-spin回転は Spinner と Accordion のシェブロンのみ例外(ADR-T4)
  • リテラルの色値(#fff / rgb() / hsl() / oklch())色は必ず --novi-color-* を経由する。リテラル値を書かない
  • 8px 以上の生の余白ユーティリティ(p/px/py/pt/pb/pl/pr/gap-2 以上)余白はトークン経由(--novi-pad-* / --novi-gap-*)。余白がテーマの所有物でないと、各コンポーネントが自分の判断で数値を書き、3モデルが同じ密度に潰れる。8px 未満の微小インセット(アイコンの位置合わせ)だけ生値を許す
  • duration-*(--novi-duration-* の任意値を除く)モーションの時間はトークン経由にする

例外

  • tactile-tokens.ts は literal-color, duration, radius, shadow-literal のみ許可 — トークンの値そのものを定義する唯一の場所。ここだけは「使用」ではなく「定義」
  • color-set.ts は literal-color のみ許可 — カラーセット Textile Dyes の値を定義する場所(specs/06-tones-and-colors)。tactile-tokens.ts と同じ「定義」側
  • spinner.styles.ts は rotate のみ許可 — ローディング表現の代替(点滅・バー往復)は視認性か情報量で劣る(ADR-R2 と同じ判断)
  • accordion.styles.ts は rotate のみ許可 — シェブロンの回転は開閉方向そのものを示す。+/− への置き換えは向きの情報を失う(ADR-T4)

押すと沈みます

対話要素は押下中に 0.97 倍に縮み、solid では影が消えます。持ち上がっていた面が沈むという一連の動きで手応えを返します。scale を使うのはここだけで、装飾目的では使いません。prefers-reduced-motion が有効なときは沈み込みもシートのスライドも起きません。

Tactile のモーショントークン
CSS 変数値
--novi-duration-fast120ms
--novi-duration-base200ms
--novi-duration-slow260ms
--novi-ease-standardcubic-bezier(0.2, 0, 0, 1)
--novi-ease-emphasizedcubic-bezier(0.05, 0.7, 0.1, 1)

値を変えたいとき

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