Skip to content
fandhe-frontendcore v0.4.3
GitHub

fandhe-frontend-pre-styled-ui API

1. 目的とトレーサビリティ

本ドキュメントは fandhe-frontend-pre-styled-ui(chakra-ui 参考の pre-styled UI コンポーネント層)の公開 API 表面をまとめる。fandhe-frontend-headless-ui(ark-ui 相当の下層、docs/api/headless-ui-api.md)の上に、テーマトークン・variant API・静的 CSS 生成を重ね、styled 部品を実装する 2 層構造の上層を担う。

2. モジュール一覧(repo main 時点。crates.io 公開状況は §2a 参照)

本クレートは 121 の公開モジュール(grep -c '^pub mod ' crates/pre-styled-ui/src/lib.rs の実測。collapsible はイシュー #1682/#1683、field はイシュー #1684、fieldset はイシュー #1686、input_group はイシュー #2063、item はイシュー #2066、button_group はイシュー #2060、command はイシュー #2070、sidebar はイシュー #2073、message はイシュー #2106、bubble はイシュー #2109、attachment はイシュー #2112、marker はイシュー #2115、questionnaire はイシュー #2119、message_scroller はイシュー #2123 で追加)+ charts サブモジュール群を持つ(charts::bar_chart/charts::bar_list/charts::bar_segment/ charts::scatter_chart/charts::radar_chart/charts::axis/charts::grid/ charts::legend/charts::tooltip/charts::pie/charts::data/ charts::scale/charts::svg は既存の pub mod charts; 配下のサブモジュールであり、grep -E '^pub mod ' によるトップレベル公開モジュール集計には計上されない)。118 は grep -c '^pub mod ' crates/pre-styled-ui/src/lib.rs の実測値である。モジュール一覧・本数の正は下表と上記実測値・各モジュール冒頭 rustdoc とする。部品ごとの詳細(anatomy・Demo・Examples・キーボード操作)は本表に複製せず、各部品ページ(/themes/<kebab>/)へ委譲する。

2a. crates.io 公開状況とドキュメント対象バージョン(イシュー #1118)

本ドキュメントは repo main(開発中の最新コード)を対象として記述しています。crates.io への公開はリリース運用(.github/workflows/release.yml の workflow_dispatch による手動実行、mode: publish の明示選択)を経るため、repo main の到達点から遅延することがあります。利用者が Cargo.toml に指定した実際のバージョンに何のモジュールが含まれるかは、本ドキュメントではなく https://docs.rs/fandhe-frontend-pre-styled-ui/<version>(例: https://docs.rs/fandhe-frontend-pre-styled-ui/0.5.0)で確認してください。

本節は執筆時点(イシュー #1118 対応時)のスナップショットとして、乖離の実例を記録します。crates.io 最新公開バージョンは fandhe-frontend-pre-styled-ui 0.5.0(依存する fandhe-frontend-headless-ui は 0.4.0)であり、本節冒頭の「106 モジュール」(repo main、v0.37.0 時点)に対し、0.5.0 時点では typography・navigation 系モジュール(heading / text / link / breadcrumb / nav_list / separator / list 等)を含む多くのモジュールが未収録です(約 15 モジュール、イシューフィードバックの実測)。この遅延を解消する crates.io への追加公開は、本イシューのコード変更とは別の運用アクションとして実施します(承認境界は .claude/rules/ci.md の release ワークフロー節を参照。本ドキュメントの自動更新は行わないため、公開が進み次第この節を追随更新してください)。

分類モジュール部品ページ
基盤theme—
基盤css—
基盤recipe(詳細は pre-styled-recipe-api.md)—
基盤stylesheet(CSS 集約・配布ヘルパ、§4a 参照)—
単純 styled 部品button / badge / spinner / alert / callout / card(button は icon_button/close_button を icon-only 修飾 variant として提供。独立部品ではなく button recipe の非公開 variant であり data-scope="button" を共有する。icon_size_for がボタン size からアイコン size を決定的に写像し(イシュー #1674)、close_button はこれを内蔵する。callout は本文中の補足情報向け静的部品で、alert と異なり live region ではないため role を付与しない、イシュー #994。spinner の spinner_decorative(aria-hidden の装飾用、#2051 で pub(crate) から公開化)は Button 末尾配置・Badge・Empty state 等の合成に使う。button/badge は共通 shape 軸(recipe::Shape、ButtonProps/BadgeProps の shape: Option<Shape>、既定 None)を持つ。Pill/Circle とも新規トークンなしで既存 --fandhe-radius-full を参照する、イシュー #3117)button / badge / spinner / alert / callout / card
単純 styled 部品skeleton(ローディングプレースホルダー。形状軸 variant(text/circle/rect)+ アニメーション軸 animation(pulse(既定)/shine/none、イシュー #1566)、常時 aria-hidden="true"、color-palette/size 軸は非提供、prefers-reduced-motion: reduce でアニメーション停止)skeleton
単純 styled 部品image(写真等の静的コンテンツを表示する <img>。ImageFit(object-fit)/AspectRatio(Auto/Square/Landscape/Portrait/Video)/ImageShape(角丸、Square/Rounded/Circle)の 3 軸 variant、alt 必須引数。base は height: auto を持つ(イシュー #1562)。headless-ui avatar の ImageStatus 状態機械とは独立。中立的な表示部品のため color-palette 軸は非提供)image
単純 styled 部品icon(インライン SVG の寸法を統一する <svg> ラッパー。size variant のみ(Xs〜Xl の 5 段、既定 Md。イシュー #1561 で chakra-ui 同名段の実寸へ是正)、color: currentColor 継承のため color-palette 軸は非提供。SVG 本体(path 等)は呼び出し側がノード木 API で構築し、外部リソース(href/xlink:href)は本モジュール自身が参照しない)icon
単純 styled 部品separator(区切り線、<hr>。orientation(horizontal/vertical)・variant(solid/dashed/dotted、イシュー #1585 で dotted 追加)の 2 軸、常時 role="separator"/aria-orientation/data-orientation を出力、罫線の太さは --fandhe-separator-thickness(既定 1px、イシュー #1585)の上書きで変更、color-palette/size 軸は非提供。group/label(pre-styled-only、イシュー #2053、shadcn/ui 突合で補完したラベル付き区切り線パート)。group_with(SeparatorLabelPosition: center〔既定〕/start、イシュー #3137)でラベル位置を opt-in で切り替え可能)separator
単純 styled 部品highlight(テキスト中の一致語句を <mark> で強調する <span> + <mark>。query(複数可)・ignore_case(ASCII 限定)・match_all の 3 プロパティ。一致判定は正規表現不使用の決定的な部分文字列検索のみ(ReDoS 非該当)。color-palette/size 軸は非提供)highlight
単純 styled 部品visually_hidden(視覚的には隠すが支援技術には読ませ続けるテキストコンテナ。variant 軸を持たず clip 手法の CSS のみ。aria-hidden を一切出力しない)visually-hidden
単純 styled 部品skip_nav(WCAG 2.1 SC 2.4.1 Bypass Blocks 対応の「本文へスキップ」リンク。link/content の 2 slot recipe。link は visually_hidden の clip 手法を base に持ち :focus-visible でのみ視覚的に復元する。docs-site の全ページレイアウトへ実適用済み)skip-nav
headless ラッパーdialog / tabs / accordion / menu / select(dialog はイシュー #2193 で close-trigger に [data-variant="text"] state 規則を追加し、close_trigger_with_variant で footer 内の平文ボタンとしても再利用可能。アイコン専用契約(data-variant 非出力の既存 close_trigger)は不変。menu はイシュー #2203 で item の data-danger(destructive 項目、呼び出し側が attrs 経由で付与する pre-styled-only の存在属性)が data-highlighted と同時に立つ場合の背景色合成を StateCondition::AttrAll(値なし存在属性同士の AND)で実装済み)dialog / tabs / accordion / menu / select
headless ラッパーpopover / tooltippopover / tooltip
headless ラッパーswitchswitch
headless ラッパーradio_group(§4c 参照)radio-group
headless ラッパーavatar(§4b 参照)avatar
headless ラッパーcheckbox(§4e 参照)checkbox
静的フォーム部品input / textarea / native_select(§4f 参照)。input は共通 shape 軸(InputProps.shape: Option<Shape>、既定 None)を持ち Pill のみ登録(Circle は未登録、イシュー #3117)input / textarea / native-select
静的フォーム部品field(§4f-1 参照。ラベル・補助テキスト・エラーテキストの型階層、orientation 軸のみ)field
静的フォーム部品fieldset(§4f-2 参照。<fieldset>/<legend> グループコンテナ、size 軸のみ)fieldset
静的フォーム部品input_group(§4f-3 参照。入力欄の前後 addon、軸なし)input-group
headless ラッパーitem(§4f-4 参照。media + title/description + actions からなる汎用リスト行。variant/size は headless の data-variant/data-size を AttrEq 参照するのみで class ベース軸を持たない)item
headless ラッパーcommand(§4f-5 参照。cmdk 由来のコマンドパレット。headless 10 パーツ + pre-styled-only footer の計 11 パーツ構成、軸なし)command
headless ラッパーmessage(§4f-6 参照。会話 1 発言。6 パーツ構成、軸なし。data-role/data-align/data-loading/data-error を AttrEq/Attr 参照するのみ)message
headless ラッパーbubble(§4f-7 参照。チャット吹き出し 1 個。6 パーツ構成、軸なし。data-variant/data-align/data-group-position/data-selected/data-state を AttrEq/Attr/AttrEqAll 参照するのみ)bubble
headless ラッパーattachment(§4f-8 参照。添付ファイル 1 件。8 パーツ構成、軸なし。data-variant/data-state/data-disabled を AttrEq/Attr 参照するのみ)attachment
headless ラッパーmarker(§4f-9 参照。会話中の注記行。3 パーツ構成、軸なし。data-variant/data-tone を AttrEq 参照するのみ)marker
headless ラッパーquestionnaire(§4f-10 参照。多段質問 UI。11 パーツ構成、軸なし。data-state/data-answered/data-skipped/data-invalid/data-disabled/data-complete を AttrEq/Attr 参照するのみ)questionnaire
headless ラッパーmessage_scroller(§4f-11 参照。会話ログのスクロールコンテナ。6 パーツ構成、軸なし。data-stuck/data-has-new/data-visible/hidden/data-loading/data-disabled を AttrEq/Attr 参照するのみ)message-scroller
headless ラッパーdata_table(§4f-12 参照。表の操作 UI。8 パーツ構成、軸なし。data-loading/data-empty/data-sort/data-state/data-hidden を Attr/AttrEq 参照するのみ)data-table
headless ラッパーsidebar(§4m 参照。アプリシェル用サイドバー。22 パーツ構成、variant/collapsible/side は headless の data-variant/data-collapsible/data-side を AttrEq 参照するのみで class ベース軸を持たない)sidebar
headless ラッパーnumber_input(§4d 参照、size variant のみ・color-palette 軸は非提供)number-input
headless ラッパーpin_input(size variant のみ)pin-input
headless ラッパーpassword_input(src/password_input.rs 冒頭 rustdoc 参照)password-input
headless ラッパーslider(size/color-palette 両軸提供。動的値は --fandhe-slider-percent custom property の 1 点のみで伝搬)slider
headless ラッパーrating_group(size/color-palette 両軸、星形 indicator は clip-path インライン表現)rating-group
headless ラッパーsegment_group(§4d 参照、size variant のみ・color-palette 軸は非提供。状態機械は radio_group へ全委譲)segment-group
headless ラッパーtags_input(size variant のみ。フォーム入力部品のため color-palette 軸は非提供)tags-input
headless ラッパーeditable(size variant のみ・color-palette 軸は非提供)editable
headless ラッパーlistbox(size variant のみ・color-palette 軸は非提供。常時展開(trigger/positioner なし)で select とは責務境界が異なる。詳細は src/listbox.rs 参照)listbox
headless ラッパーtoggle / toggle_group(実フォーカスをネイティブ <button> 自身が受けるため data-focus-visible 配線ではなく FocusVisible state condition で対応。size/color-palette 両軸提供)toggle / toggle-group
カード型選択 UI(styled バリエーション)checkbox_card / radio_card(§4g 参照。headless-ui は変更なし、pre-styled 層で新規 anatomy checkbox-card/radio-card を定義。状態機械は headless Checkbox/RadioGroup を再利用)checkbox-card / radio-card
headless ラッパーcombobox(select と同型の size variant のみ・color-palette 軸は非提供。状態機械は state::Disclosure + state::SingleSelect + state::TextInput の合成。フォーカスは input が保持するため :focus-visible を input へ、:focus-within を control へ登録する)combobox
headless ラッパーtree_view(イシュー #1578 で root へのみクラスを付与する size variant(xs〜xl、既定 md。行密度・文字サイズを切り替える)を新設。color-palette 軸は非提供(popover/tooltip と同型の判断)。branch のインデントは CSS custom property(--fandhe-tree-view-indent)で表現し、DOM ネストにより深さ分が自然に累積する(size では上書きしない))tree-view
headless ラッパー(tree_view の派生)json_tree_view(構造部は tree_view の既存 recipe をそのまま再利用するため tree_view の size variant を継承する。JSON 固有の key/colon/value(data-scope="json-tree-view")3 パーツのうち key/value の型別配色のみを追加する。colon はイシュー #1661 で headless 側に新設されたが本モジュールでは意図的に未スタイル。value の data-kind へ型別配色(string/number/boolean/null の 4 種、object/array は既定色のまま)を適用。本モジュール固有の size/color-palette 軸は非提供)json-tree-view
headless ラッパーpagination(size/color-palette 両軸提供。root_with 経由で連結表示 variant(PaginationVariant、既定 Separated、イシュー #3136)を opt-in 提供)pagination
headless ラッパーsteps(size/color-palette 両軸。fandhe_frontend_headless_ui::steps が自由関数を持たず全パーツが Steps の inherent メソッドのため、本モジュールの全パーツ関数が state: &Steps を受け取る点が他コンポーネントと異なる。PR #1814 codex-review 対応で追加した body(data-scope="steps" data-part="body")のみ headless に対応物を持たない pre-styled-ui 専用のグルーピングパーツで、state: &Steps を取らずプレーンな <div> を直接構築する。縦向き(Orientation::Vertical)で root が flex-direction: row へ切り替わる際、list 以外を body でまとめて root 直下を list/body の 2 要素に保つ契約〔steps::root rustdoc 参照〕。イシュー #3139 で StepsVariant(Circle 既定/Bar/Panel/Dot)を純追加し、root_with/list_with/item_with/indicator_with/separator_with が Circle 以外のとき該当 slot へ単独クラスを付与する。Circle は既存関数とバイト一致)steps
headless ラッパーbreadcrumb(状態機械なし。size/BreadcrumbVariant(link の下線表示切り替え)の 2 軸 variant を root のみへ付与し、link への伝搬は root スコープ CSS custom property の継承で行う)breadcrumb
headless ラッパーcarousel(size variant のみ・color-palette 軸は非提供(選択・チェック状態を示す部品ではないため)。item-group の transform は --fandhe-carousel-index CSS カスタムプロパティ 1 点のみで伝搬し、data-orientation="vertical" で translateX/translateY を切り替える。autoplay は初期実装スコープ外)carousel
headless ラッパーdrawer(dialog の変種。状態機械は headless の dialog::Dialog をそのまま再利用し新規状態機械は作らない。size(drawer の占有幅/高さ)variant のみを root へ付与し color-palette 軸は非提供。placement(start/end/top/bottom)は variant ではなく headless 層が出力する data-placement に連動する CSS で表現する。dialog と対称の close-trigger text variant(イシュー #2193)も持つが、fandhe-frontend-wasm-full が drawer scope を未配線のため現状 inert。イシュー #3128 で dialog::body/dialog::footer と同型の pre-styled-only body/footer パートを追加した。headless anatomy は変更せず、content の attrs へ data-has-body を渡したときのみ opt-in で flex column 化し body が残り高さを埋めてスクロールする。イシュー #3129 で pre-styled-only header パート(全幅アクセント帯のレイアウト)と、header/description/close_trigger への data-tone="accent" state・content/close_trigger への data-close-outside(外側 close-trigger 配置)を追加した。いずれも headless anatomy 無変更の opt-in 属性で、既存呼び出しには影響しない)drawer
headless ラッパーlink / link_overlay / nav_list(状態機械なし。link_overlay は ::before 疑似要素の代わりに overlay 自身を position: absolute; inset: 0; で展開する。イシュー #1580 で overlay に :focus-visible の共通フォーカスリング・cursor: pointer を追加し、root/overlay 双方へ border-radius: inherit を連鎖させて root の角丸へフォーカスリングが追従するようにした。nav_list は fandhe-frontend-docs-site::nav.rs::sidebar が直接使う想定のため、root 以外(heading/list/item/link)は headless 自由関数をそのまま選択的に再エクスポートする)link / link-overlay / nav-list
headless ラッパーaction_bar(size/color-palette 軸は非提供。positioner の position: fixed; bottom: ...; left: 50%; transform: translateX(-50%) による画面下部固定配置と data-state 連動の見た目切り替えのみを提供する。z-index: 900(menu/select の dropdown positioner(10)より上、dialog backdrop(1000)より下))action-bar
headless ラッパーtoolbar(イシュー #991。size/color-palette 軸は非提供。root の data-orientation="vertical" で flex-direction: column へ切り替え、separator は aria-orientation の値(toolbar 自身と直交)で向き別の太さを出し分ける。押下状態の管理は独自 CSS を持たず既存の toggle_group recipe と同型の data-state="on" 強調のみ提供する)toolbar
headless ラッパーbutton_group(§4d-1 参照。size/color-palette 軸は非提供、root の data-orientation で横並び/縦積み切替、直接の子の角丸連結・境界線二重描画解消は raw CSS 追記で行う。イシュー #2060)button-group
headless ラッパーmenubar(イシュー #992。size/color-palette 軸は非提供。複数 menu を水平(または垂直)に並べるコンテナ。root の data-orientation="vertical" で flex-direction: column へ切り替え、per-menu ラッパーである menu パーツが position: relative(positioner の containing block)を担う。開いている trigger/sub-trigger の視覚強調・virtual focus の highlight 表示は menu recipe と同型)menubar
headless ラッパーnavigation_menu(イシュー #993。size/color-palette 軸は非提供。トリガー起点で開閉するナビゲーションパネル。item に position: relative、content に position: absolute; top: 100%; left: 0; を宣言する一般的なナビゲーションドロップダウン構成。list の align-items は center ではなく flex-start を既定にし、content 展開時に他項目が縦ずれする回帰を構造的に防ぐ。開いている trigger の視覚強調は data-state="open"、アクティブリンクの強調は data-current で行う。イシュー #2187: root へ position: relative を追加し、ルートレベル indicator の絶対配置基準点とする。indicator は --fandhe-navigation-menu-indicator-x/-width(horizontal)・-y/-height(vertical)の CSS 変数契約を既定 0px フォールバック付きで宣言し、実座標が書き込まれない間は不可視(pointer-events: none)。実座標の書き込みは fandhe-frontend-wasm-full の責務)navigation-menu
pre-styled-ui 単独定義(headless-ui 変更なし、checkbox_card/radio_card §4g と同型の判断)tab_nav(イシュー #996。新規 anatomy data-scope="tab-nav" を定義し role="tablist"/role="tab" を一切出力しない。見た目は自前の宣言列から生成する(イシュー #1541 で tabs.rs の pub(crate) ヘルパ共有を解消)。現在ページは aria-current="page" で示す。size 軸を持ち、root_with 経由で pill/bar variant(TabNavVariant、イシュー #3125/#3126)・color-palette 軸を opt-in 提供)tab-nav
headless ラッパーcheckbox_group(イシュー #997。単一選択版 radio_group と対称の構造。item-hidden-input slot を持たず、利用時は checkbox::stylesheet() も併せて読み込む必要がある。size/color-palette 両軸提供)checkbox-group
headless ラッパーtoast(placement(group slot)/status(root slot、ColorPalette 6 役割束ね〔palette_scale_declarations〕による淡色面配色。イシュー #1544)の 2 軸 variant を持つが、各軸が別 slot へ付与されるため variant_class(単一軸専用 API)をスロットごとに個別に呼ぶ。Toaster 状態機械は再エクスポートしない。#1545: action-trigger/close-trigger の hover/focus/disabled・root の mount 時 enter 遷移(@keyframes fd-toast-enter)・group/root のスタック配置固定幅化を追加。close-trigger はアイコン専用契約への破壊的変更(0.64.0)。タイマー自動 dismiss・ActionTrigger の動作配線は wasm-full 後続のスコープ外。イシュー #2543: toast_motion(motion feature 配下)で stack 表示(積層・hover/focus-within 展開)を opt-in 追加。STACK_ATTR/stack_group_keyed/Theme::to_css_with_toast_motion。イシュー #3142: pre-styled-only content/actions パート + ACTIONS_COLUMN_ATTR で右端アクション列(全高 1 ボタン、または縦 2 分割)を opt-in 追加)toast
headless ラッパーhover_card(popover/tooltip と同型の判断で variant は非提供。構造上最も近い先行例は tooltip。content の開閉連動・--fandhe-reference-width 非消費・focus-visible リングを継承する)hover-card
headless ラッパーtoggle_tip(popover/tooltip と同型の判断で size/color-palette のいずれも非提供。「見た目は Tooltip・挙動は Popover」の変種であり、content の視覚系は tooltip と同一値。状態機械は state::Disclosure)toggle-tip
headless ラッパーcollapsible(glob 再エクスポート。参照 3 サイト(chakra-ui/Ark UI/Radix Primitives)のいずれも size/variant/color-palette を持たないため variant 軸は非提供。開閉時の高さトランジションは @starting-style/transition-behavior: allow-discrete + fandhe-frontend-wasm-full の実測高さ CSS 変数で JS 有効時のみ適用(イシュー #2192、案 C)。イシュー #1682/#1683)collapsible
headless ラッパーprogress(イシュー #1564。headless の値状態機械 Progress が持つ Track/Range(linear)と Circle/CircleTrack/CircleRange(SVG、circular)の両方へ CSS を追加提供。Progress 型はあえて再エクスポートせず、ProgressProps(size/variant/color-palette の 3 軸)を付与する styled root と、determinate 時のみ --fandhe-progress-percent を付与する styled range の 2 つを新設する。track/circle 系は headless の inherent メソッドをそのまま呼ばせる(クラス不要)。indeterminate 時のアニメーション(linear は横スライド・circular は回転)は [data-part="..."][data-state="indeterminate"] セレクタ + @keyframes で提供し、prefers-reduced-motion: reduce で停止する。イシュー #1688: circular indeterminate は circle 全体の回転に加え、circle-range へ固定長の弧〔stroke-dasharray〕を与えて塗り色の完全リングと complete 状態を視覚的に区別できるようにする。イシュー #2049: shadcn/ui と突合し ProgressVariant::Plain(枠線なし中立トラック)を純追加。イシュー #3140: headless-ui の anatomy には存在しない pre-styled-only パート marker_group/marker(マイルストーンの目盛りラベル)を純追加。marker は value と現在値の比較で data-state(under-value/at-value/over-value)を自前算出し、配置は値に比例した絶対配置ではなく CSS Grid の等間隔列が担う)progress
単純 styled 部品(静的)tag / kbd / code(tag は variant(Solid/Subtle(既定)/Outline/Surface の 4 値、イシュー #1573 で Surface を追加)/size/color-palette の 3 軸 variant を持つ root/label/close-trigger の 3 パーツ。badge と同型の判断。close-trigger は状態機械を持たず data-action 属性の出力のみを担う。code は variant 軸を持たない単一 slot。kbd は variant(raised(既定)/subtle/outline の 3 値、イシュー #1436)/size/color-palette の 3 軸 variant を持つ root slot に加え、group〔shadcn/ui KbdGroup 相当、イシュー #2048〕の pre-styled-only 2 slot 構成)tag / kbd / code
状態機械を要しない静的部品status / empty_state(§4h 参照。status は size/color-palette の 2 軸、empty_state は card と同型の中立コンテナで color-palette 軸は非提供)status / empty-state
headless ラッパーclipboard(hover_card/toggle_tip と同型の判断で variant は非提供。Indicator の可視性切り替えは avatar の image/fallback と同型の data-state 多層防御パターン。navigator.clipboard.writeText 実配線は fandhe-frontend-wasm-full::headless_clipboard が提供)clipboard
タイポグラフィ静的部品heading / text / em / mark / blockquote / list / quote / strong(§4i 参照。素の HTML 意味論(h1〜h6/p/em/mark/blockquote/ul・ol・li/q/strong)をそのまま styled 化。headless 状態機械は要しない。quote/strong はイシュー #995 で追加。quote は短いインライン引用〔blockquote とは役割が異なる〕、strong は重要性の強調〔em の文法的強勢とは役割が異なる〕)heading / text / em / mark / blockquote / list / quote / strong
headless ラッパーqr_code(headless の外部依存ゼロ QR Model 2 エンコーダ(crates/headless-ui/src/qr_code.rs)へ CSS のみ追加提供。size variant のみ・color-palette 軸は非提供(前景/背景色は固定トークンに閉じ、低コントラスト組み合わせを誘発しないための意図的判断)。Frame/Pattern/Overlay は headless 自由関数をそのまま選択的に再エクスポートする。overlay は root の --fandhe-qr-code-overlay-size(--fandhe-qr-code-size の 1/3、chakra-ui 準拠)custom property により中央固定表示される、イシュー #1565)qr-code
headless ラッパー(Button recipe 流用)download_trigger(a[download] 属性による静的ダウンロードトリガー。独自 CSS 宣言を持たず crate::button::recipe_with_scope("download-trigger") へ委譲し、variant/size/color-palette の宣言・既定値を Button と共有する。disabled/loading は a 要素の意味論に存在しないため非提供)download-trigger
状態機械を持たない静的表示部品table / data_list(card と同型。headless-ui 側に対応する anatomy を持たず本クレートで新規 anatomy table/data-list を定義する。table は 10 パーツ(root/header/body/footer/row/column-header/cell/row-header〔<th scope="row">、イシュー #2825〕/caption/scroll-area)で、variant(Line/Outline)/size(Size 5 段、既定 Md)/striped/sticky_header/interactive の 5 軸 variant を持ち、striped は新設の StateCondition::NthChildEven で表現、sticky_header(イシュー #1571)は column-header を position: sticky にする。interactive(イシュー #2052、shadcn/ui 突合)は row に bg-muted の hover 背景を付ける opt-in variant。呼び出し側が付与する共有語彙 data-selected(行選択、accent-subtle/accent-fg-subtle)・data-align(start/center/end、headless positioning.rs と共有)の消費側規則も持つ。scroll_area(イシュー #1572、chakra Table.ScrollArea 相当)は状態を持たない専用パーツで root を包むスクロール枠を提供する。data_list は orientation(Vertical/Horizontal)/variant(Subtle/Bold)/size(Size 5 段、既定 Md)の 3 軸を持つ、イシュー #1559)table / data-list
静的部品(新規 anatomy)stat / timeline(ark-ui に対応する headless anatomy が存在しないため pre-styled-ui 層で新規 anatomy data-scope="stat"/"timeline" を定義。stat は <dl>/<dt>/<dd> を使い size variant のみ・color-palette 軸は非提供、増減 indicator は rating_group と同型の clip-path インライン三角形(イシュー #1568 で chakra-ui v3 基準(xs=lg〜xl=4xl の等差進行・letter-spacing/line-height・indicator 余白)へ調整済み)。stat はイシュー #3138 で delta パート(矢印なし・tone(StatDeltaTone: neutral/success/danger)軸で意味色のみを示す増減率表示)を純追加した。tone 軸は delta パートのみへ付与し default_variant を登録しないため root の class 出力には影響しない。timeline は <ol>/<li> を使い variant(TimelineVariant: solid/subtle/outline/plain)/size/color-palette の 3 軸を root のみへ付与し indicator/separator へは CSS custom property の継承で伝搬)stat / timeline
headless ラッパーfloating_panel(fandhe_frontend_headless_ui::floating_panel の Root/Trigger/Positioner/Content/Header/Title/Control/StageTrigger/CloseTrigger/Body 10 anatomy パーツと FloatingPanel 状態機械をそのまま再エクスポートし CSS のみ追加提供する薄いラッパー。variant(size/color-palette)は非提供。content の開閉 data-state に加え body の data-stage="minimized"(折り畳み)・positioner の data-stage="maximized"(ビューポート全面表示)を CSS で切り替える。positioner は position: fixed を基点に headless 側の --fandhe-x/--fandhe-y を transform: translate3d(...) で反映し、z-index は dialog モーダル層(1000/1001)未満・menu/popover の dropdown 層(10)超の専用 tier(900)を割り当てる)floating-panel
headless ラッパーscroll_area(状態機械なし。variant は非提供(イシュー #1584 で再確認)。viewport へ overflow: auto + scrollbar-width/scrollbar-color(標準プロパティ)を付与し、stylesheet() が recipe().css() に続けて ::-webkit-scrollbar 系規則を固定文字列として追記する。scrollbar/thumb/corner は JS によるスクロール位置追従がスコープ外のため初期実装では display: none にしてネイティブスクロールバーの装飾で代替する。thumb 色は custom property --fandhe-scroll-area-thumb-bg(既定 fg-subtle → border-emphasized → border のフォールバック連鎖、hover/focus 時は --fandhe-scroll-area-thumb-hover-bg = fg → 同じ fg-subtle → border-emphasized → border の連鎖へ強調)で一元化されており、利用側が root へ再定義するだけで chakra variant="hover" 相当の見た目を variant 軸を新設せず再現できる。フォーカスリングは focus_ring_declarations(Token/Inset)による canonical 表現、イシュー #1584。イシュー #2054(shadcn/ui 突合)で viewport の opt-in 属性 2 個を補完: data-orientation="horizontal"(横スクロール、headless data_attrs::data_orientation と共有する既存語彙、content を display: flex; width: max-content; にする子結合子規則)・data-fade(端フェード、mask-image の linear-gradient。animation-timeline: scroll() 対応ブラウザでは @supports 配下でスクロール量連動、非対応時は両端固定フェード。custom property --fandhe-scroll-area-fade-size(既定 min(12%, 2.5rem))/--fandhe-scroll-area-fade-reveal(既定 2rem)/--fandhe-scroll-area-fade-start/--fandhe-scroll-area-fade-end を持つ))scroll-area
headless ラッパーsplitter(size variant のみを root へ持ち resize-trigger の厚みと panel の余白(--fandhe-splitter-panel-padding)へ継承、color-palette はセパレータの強調色にのみ使う。動的値は panel の --fandhe-splitter-size(flex-basis 経由)の 1 点のみ。resize-trigger はネイティブ <div tabindex> が実フォーカスを受けるため FocusVisible state condition で足りる。root は 1px 外枠・角丸・背景を持つ)splitter
単純 styled 部品marquee(ark-ui の Root/Viewport/Content/Item/Edge anatomy を root/content/item の 3 パーツへ縮約(Viewport は root が兼ね、Edge は呼び出し側 CSS で代替可能なため非提供。イシュー #1582 で root の mask-image による opt-in フェード(--fandhe-marquee-fade、既定 0px)を追加提供。イシュー #1583 で root の内側余白 opt-in フック(--fandhe-marquee-padding、既定 0)を追加提供)。content を内部で 2 回複製しシームレスループを実現し、2 個目は常時 aria-hidden。direction(Start/End)の 1 軸 variant のみを root へ付与し content への伝搬は --fandhe-marquee-direction custom property の継承で行う。color-palette/size 軸は非提供。gap は既定で --fandhe-space-4 へフォールバックする。CSS のみ(JS ゼロ)・hover/focus-within での常時一時停止・prefers-reduced-motion: reduce でのアニメーション停止に加え複製 content の除去・可視コピーの折り返し全文表示・両端フェード解除(イシュー #1583)という決定的設計)marquee
headless ラッパーdate_input(fandhe_frontend_headless_ui::date_input の Label/Control/SegmentGroup/Segment/HiddenInput を選択的再エクスポートし、状態機械 DateInput はあえて再エクスポートしない。size variant のみを root へ持ち --fandhe-date-input-* custom property 経由で segment/segment-group へ継承、color-palette は非提供。segment はネイティブ <input> ではなく div role="spinbutton" のため FocusVisible state condition で足りる)date-input
単純 styled 部品(静的)color_swatch(headless-ui に対応する anatomy を新設しない(headless 列は「—」のまま)。色値は fandhe_frontend_headless_ui::color::Color 型のみを受け取り(本モジュールが再エクスポート)、任意文字列を受け取る API は持たない。size(Xs〜Xl の 5 段、既定 Md。イシュー #1558 で chakra-ui 同名段の実寸へ是正)/shape(Square/Circle/Rounded、既定)の 2 軸 variant。color-palette 軸は非提供(表示する色そのものが value で決まるため)。透過色は background-image の 2 レイヤー(前面に色レイヤー、背面に固定チェッカーボード模様)で表現する。内側 1px の輪郭リング(box-shadow: inset、--fandhe-color-border-muted トークン参照)を持ち、淡色・低アルファ色でも外形が判別できる(イシュー #1558)。base は inline-flex 中央寄せ)color-swatch
headless ラッパー(canvas 非依存)color_picker(fandhe_frontend_headless_ui::color_picker::ColorPicker(HSV + アルファ + Disclosure)はあえて再エクスポートしない(headless-ui から直接 import する)。動的値は custom property の注入 5 箇所のみ: area_background の --fandhe-color-picker-hue-color・area_thumb の --fandhe-color-picker-x/-y・channel_slider_track(Channel::Alpha のみ)の --fandhe-color-picker-alpha-color・channel_slider_thumb の --fandhe-color-picker-thumb-percent・trigger の --fandhe-color-picker-preview。size/color-palette variant・saturation-slider/value-slider 専用スタイル(2 次元 area が代替)は非提供(最小サブセット、スコープ外は crates/pre-styled-ui/src/color_picker.rs rustdoc 参照))color-picker
headless ラッパーfile_upload(size variant のみ・color-palette 軸は非提供(フォーム入力部品)。実操作対象の dropzone へ :focus-visible を登録(ネイティブ <input type="file"> は視覚的に非表示にするため)。hidden-input slot は CSS 非登録。FileUpload 状態機械はあえて再エクスポートしない。styled root は (size, &FileUploadProps, dragging, attrs, children) へシグネチャ変更し、headless 側の破壊的変更(FileUploadProps/ItemType 新設、参照突合イシュー #1609)へ追随した)file-upload
headless ラッパーcalendar(root_with 経由で variant(outline / plain)・cell-size(compact / large)を opt-in 提供(#3132)。color-palette 軸は非提供。day-trigger の data-selected/data-today/data-outside-month/data-disabled を CSS で切り替える。Calendar 状態機械はあえて再エクスポートしない。day_trigger の date 引数向けに PlainDate/Weekday(fandhe_frontend_headless_ui::date)も再エクスポートする)calendar
headless ラッパーdate_picker(popover 基盤(state::Disclosure)を再利用する crate::calendar と同型の判断。size variant のみ。content 内部に crate::calendar の styled パーツを合成する想定。DatePicker 状態機械はあえて再エクスポートしない。イシュー #1627 以降、styled root は &DatePickerProps を追加引数として headless root へ透過する)date-picker
headless ラッパーtimer(clipboard と同型の判断で variant は非提供。item-value に font-variant-numeric: tabular-nums を付与し桁の増減時のレイアウトシフトを防ぐ。root の data-state(completed / paused)に応じて --fandhe-timer-value-color 経由で item-value の色を切り替え、action-trigger は outline 風ボタン(hover / focus-visible リング / disabled / transition)を持つ。実 tick 駆動(setInterval)は fandhe-frontend-wasm-full::headless_timer が提供する)timer
静的部品(新規 anatomy、charts 基盤上層)charts::scatter_chart / charts::radar_chart(charts(ChartData/LinearScale/SVG ヘルパー)の上に実装。headless-ui は変更なし、pre-styled 層で新規 anatomy data-scope="scatter-chart"/"radar-chart" を定義。scatter_chart は ChartData では表現できない (x, y) 数値ペアの集合を独自の ScatterData/ScatterSeries で表現し、x/y 双方の LinearScale で 2 軸写像する。radar_chart は ChartData(カテゴリ = 軸、系列 = ポリゴン)をそのまま使い、軸 index i・軸数 n から頂点角度 θ_i = -π/2 + i・2π/n(12 時方向開始・時計回り)を算出する private ヘルパへ角度→座標変換を一元化する。軸数 3 未満は ChartError::TooFewAxes、負値は ChartError::NegativeValue、プロット領域が小さすぎる場合は ChartError::PlotAreaTooSmall として構築時に拒否する(fail-closed)。size/color-palette variant は非提供(色は系列インデックスからの series_color_var インライン fill 属性で決まる静的部品)。イシュー #2085 で radar_chart へ grid/fill/dots/axis-label/radius-axis の静的バリアントを純追加した(下記「RadarChart の shadcn/ui 突合バリアント」節参照))scatter-chart / radar-chart
headless ラッパーtour(fandhe_frontend_headless_ui::tour が自由関数を持たず全パーツが Tour の inherent メソッドのため、本モジュールの全パーツ関数が state: &Tour を受け取る点は steps と同型。color-palette 軸のみ提供、size 軸は初版スコープ外(overlay 系の寸法は呼び出し側の CSS カスタムプロパティ上書きに委ねる)。backdrop/spotlight/positioner は position: fixed の全面オーバーレイで、closed 時 [hidden] を明示規則で display: none に固定する。positioner は data-side/data-align に応じた静的フォールバック配置のみ(実座標追従は fandhe-frontend-wasm-full 後続の責務)。spotlight は位置・寸法の --fandhe-tour-spotlight-x/-y/-width/-height に加え、角丸・縁取り幅の --fandhe-tour-spotlight-radius/-ring-width(イシュー #1550 新設)を持つ計 6 CSS 変数(既定値つき var())で表現し、実測値の注入は同後続の責務。縁取り色は var(--fandhe-palette, var(--fandhe-color-accent, #3182ce))(Theme の accent light 既定値へのリテラルフォールバックを含む三段)に連動し、backdrop/spotlight の暗幕・z-index は --fandhe-color-bg-overlay/--fandhe-z-index-overlay、positioner は --fandhe-z-index-modal トークンへ揃えている(イシュー #1550)。イシュー #1551(0.65.0、破壊的変更)で content に border/radius-lg、scope 付き上書き変数 --fandhe-tour-content-padding/-content-max-width(既定値つき)を追加し、close-trigger を固定正方(--fandhe-space-8)+ overflow: hidden のアイコン専用ゴーストボタンへ変更した(呼び出し側は 1 グリフ + aria-label で渡す契約。テキスト children を渡す既存呼び出しは切り詰められる観測可能な破壊的変更)。action-trigger は solid palette 小ボタンへ是正し、文字色を --fandhe-palette-fg(未選択時 --fandhe-color-accent-fg)へ、hover(--fandhe-hover-bg 経由)・disabled([disabled]/[data-disabled])・transition_declarations・focus_ring_declarations(FocusRingColor::Palette, ...) を追加した。両トリガーの :focus-visible は canonical ヘルパ経由(生 outline 直書きは撤去)tour
基盤(外部依存ゼロ SVG 生成)charts(data/scale/svg。ChartData/Series・LinearScale・svg::{fmt_coord, ViewBox, svg_root, PathBuilder, circle, line, rect, group, svg_text} を提供する消費者向け基盤のみ。自身は UI コンポーネントを持たない。詳細は docs/design/charts-foundation-design.md 参照)charts
charts 基盤の消費者(新規 anatomy)line_chart / area_chart / sparkline(§4k 参照。charts 基盤の最初の消費者。area_chart はイシュー #2081 で、line_chart はイシュー #2083 で、いずれも charts::curve(曲線補間ジオメトリ、両者で共有)を用いた曲線/データ点/軸・グリッドの静的バリアントを追加済み(area_chart はさらに積み上げ/グラデーション、line_chart はさらに値/カテゴリラベル)。sparkline の曲線補間は引き続き別イシューのスコープ)line-chart / area-chart / sparkline
charts(SVG)charts::bar_chart(縦/横 orientation のグループ棒グラフ。値軸はベースライン 0 起点、カテゴリ軸はバンドレイアウト(両端 10% padding + 系列数で均等割り)。系列色は series_color_var(chart-1〜chart-6 循環)。イシュー #2082 で shadcn/ui Charts(bar)と突合し、BarStack(積み上げ Normal/Expand)・corner_radius(角丸)・BarLabel(値ラベル Outside/Inside)・active_index(強調表示)・color_by_category・highlight_negative・show_value_axis/show_grid/show_category_labels を BarChartProps へ純追加。凡例は引き続き呼び出し側が charts::legend を並べる。ツールチップは対象外(#2086)、詳細は bar_chart.rs rustdoc「shadcn/ui 突合(イシュー #2082)」節参照)bar-chart
charts(HTML)charts::bar_list(単一系列のランキング型バーリスト。バー幅は系列内最大値に対する比率(--fandhe-bar-list-percent custom property)。最大値 0 は全バー幅 0% を決定的に描画)bar-list
charts(HTML)charts::bar_segment(単一系列の構成比 100% 積み上げバー + 凡例。セグメント幅は系列合計に対する比率(--fandhe-bar-segment-percent custom property)、配色はカテゴリ index で series_color_var を循環。系列合計 0 は ChartError::ZeroTotal で構築時に拒否)bar-segment
単純 styled 部品(新規 anatomy、charts 基盤の初のチャート部品)pie_chart / donut_chart(charts 基盤(charts::pie の円弧ジオメトリ・charts::svg::PathBuilder::arc_to)を用いた円グラフ・ドーナツグラフ。ark-ui に対応する headless anatomy がないため新規 anatomy data-scope="pie-chart"/"donut-chart" を本クレートのみで定義する。stacked: false(既定)は系列 1 本専用(data.series().len() != 1 は PieChartError::MultiSeries で fail-closed 拒否)。size variant のみ、color-palette 軸は非提供(セグメント配色は charts::series_color_var の chart-1〜6 循環で決まるため)。donut_chart は追加で inner_ratio(既定 0.6、0.0 < ratio < 1.0 を検証)を持つ。イシュー #2084 で shadcn/ui Charts(pie)と突合したバリアント補完は下記「PieChart / DonutChart の shadcn/ui 突合バリアント」節参照)pie-chart / donut-chart
単純 styled 部品(新規 anatomy、イシュー #2079)radial_chart(shadcn/ui Charts Radial 相当の同心リング型グラフ。charts::pie の環状セクタジオメトリ(角丸端は本イシューで新設した charts::pie::annulus_sector_rounded_path)を再利用する。ark-ui に対応する headless anatomy がないため新規 anatomy data-scope="radial-chart" を本クレートのみで定義する。pie_chart/donut_chart と異なり複数系列の積み上げ(stacked)に対応。プロパティは度数法・12 時方向 0°・時計回り正(start_angle_deg/end_angle_deg、既定 0.0..360.0)。inner_ratio(既定 0.3)・corner_radius(既定 0.0)・show_track/show_labels/show_grid・center_text を持つ。size variant のみ、color-palette 軸は非提供。エラー型は既存 PieChartError/ChartError とは独立の RadialChartError(InvalidAngleRange/InvalidInnerRatio/InvalidCornerRadius/NegativeValue/ZeroTotal)。docs サイトページは #2080 で登録)radial-chart
headless ラッパー(非採用の再導入)angle_slider(size/palette variant のため styled root(slider と同型)を再定義し、pub use ...::* ではなく必要な識別子のみを選択的に再エクスポートする。動的な回転角は --fandhe-angle custom property の 1 点のみで伝搬し thumb_styled が一元的に組み立てる(headless 自由関数 thumb は事故防止のため意図的に非公開のまま内部委譲)。状態機械 AngleSlider は slider の Slider 非再エクスポートと同型の判断であえて再エクスポートしない)angle-slider
headless ラッパー(非採用の再導入)signature_pad(canvas を使わない決定的 SVG path 方式。root/control/segment/clear_trigger を本モジュールで再定義する qr_code と同型の選択的 re-export(label/segment_path/guide/hidden_input はそのまま再エクスポート)。raw_html() を使用せず、CSS 宣言値はすべてコンパイル時静的リテラル。wasm 配線済み)signature-pad
headless ラッパー(非採用の再導入)image_cropper(Root/Viewport/Image/Grid/Handle をそのまま再エクスポート(ImageCropperProps/GridAxis/KeyModifiers/action_for_key 等イシュー #1610 の新規公開項目も含む)し、crop 矩形(整数)のみの決定的状態機械。動的な位置・寸法は --fandhe-image-cropper-x/-y/-w/-h の 4 custom property のみで伝搬し selection が一元的に組み立てる(headless 自由関数 selection は意図的に非公開。イシュー #1610 で state/props 引数が増え、フォーカス可能な要素が handle から selection へ移ったため :focus-visible のフォーカスリングも selection へ移設)。状態機械 ImageCropper は slider と同型の判断であえて再エクスポートしない。canvas 実切り出し・pointer ドラッグ配線はスコープ外)image-cropper
headless 由来ユーティリティ(本クレートに固有モジュールなし)format / Locale本クレート自身は format モジュールを持たず、クレートルート再エクスポート pub use fandhe_frontend_headless_ui;(§3a)経由で fandhe_frontend_pre_styled_ui::fandhe_frontend_headless_ui::format::{format_byte, format_number, format_time, format_relative_time} および Locale(En/Ja)へ到達できる。API 詳細は本クレートで二重管理せず docs/api/headless-ui-api.md を正とする(部品ページなし)

各 headless ラッパーモジュールは対応する fandhe_frontend_headless_ui モジュールの anatomy パーツ・状態機械を薄く再エクスポートし、stylesheet()(モジュールにより css())で既定 CSS を追加提供する共通設計方針を採る。詳細・スコープ外事項は各モジュール冒頭の rustdoc または対応する部品ページを参照(例: switch は src/switch.rs、avatar/ radio_group は §4b/§4c、checkbox は §4e、input/textarea/ native_select は §4f)。switch/radio_group/checkbox の size/ color-palette variant 拡張の詳細は §4c・§4d・§4e を参照。

クレートルート再エクスポート(fandhe_frontend_headless_ui / fandhe_frontend_core / OpenState / Orientation ほか)は §3a を参照。

examples/headless-pre-styled-ui は本クレート v0.4.0(fandhe-frontend-pre-styled-ui = "0.4.0"、crates.io バージョン依存)へ統合済みである。旧来 headless-ui の data-scope/data-part/data-state セレクタへ手書きで当てていたコンポーネント CSS は撤去され、src/main.rs の build_stylesheet() が Theme/SlotRecipe から生成した CSS を stylesheet::StyleSheet で集約し dist/assets/ui.css へ書き出す方式へ切り替え済み。static/ui.css はショーケースページ固有の骨格レイアウトのみを保持する形で残存する。

3. 不変条件(実装済み・骨格に記載済み、src/lib.rs 参照)

  1. コンポーネントは fandhe_frontend_headless_ui 経由で fandhe_frontend_core::Node を返す通常の Rust 関数として実装する(REQ-5、マクロ DSL は採用しない)。
  2. 出力は fandhe_frontend_core::render の既定エスケープを必ず経由する。raw_html() の使用は stylesheet::StyleSheet::style_element 内のレビュー済み 1 箇所(#[expect(clippy::disallowed_methods, ...)] 付き)に限定する(§4a 参照)。新たなエスケープ迂回経路を作らない。
  3. #![forbid(unsafe_code)](REQ-2)によりクレート全体で unsafe を機械的に禁止する。
  4. 外部依存は fandhe-frontend-headless-ui(path)のみ。fandhe-frontend-core への直接依存は宣言しない(headless-ui 経由で間接的に利用する。fandhe-frontend-core はスモークテスト用の dev-dependency としてのみ許容する)。

これらの不変条件は実装済み各モジュール(§2 参照)でも維持されている(.claude/rules/coding-rust.md・docs/api/headless-ui-api.md §6 と同一の制約を上層でも維持する)。

3a. headless 型の再エクスポート契約

fandhe-frontend-headless-ui の 7 モジュール(tabs/accordion/dialog/ menu/select/popover/tooltip)を薄くラップする各 pre-styled-ui モジュールは、pre-styled-ui のみへの依存でラッパーを呼び出せることを保証する契約として、以下を明示 pub use で再エクスポートする(棚卸し表、crates/pre-styled-ui/src/{tabs,accordion,dialog,menu,select,popover,tooltip}.rs の各ファイル冒頭の pub use 直後のコメント参照)。tabs/accordion/ dialog/menu/select の 5 モジュールは size variant クラス付与のため styled root(tabs のみ tabs)を各モジュールで新設しており、headless 自由関数 root(tabs は tabs/tabs_with_root_attrs)との名前衝突を避けるため選択的 re-export とする(§4d 参照)。popover/tooltip を含め、styled パーツ関数を再定義しないモジュール 12 件は glob 再エクスポートを維持する(tree_view はイシュー #1578 で size variant 導入に伴い選択的 re-export(規約 A)へ移行したため本数から外れた。現況は popover/tooltip の 2 件に留まらない。一覧・維持条件は §3c 参照)。

pre-styled-ui モジュール再エクスポートする headless 型由来
tabsOrientationdata_attrs
accordionOpenState / SingleSelectAction / MultiSelectActionstate
dialogOpenState / DisclosureActionstate
menuOpenState / DisclosureAction / CheckableAction / SingleSelectActionstate
selectOpenStatestate
popoverOpenState / DisclosureActionstate
tooltipOpenState / DisclosureActionstate
comboboxOpenStatestate(select と同型の選択的 re-export)
tree_viewOpenState / MultiSelectAction / SingleSelectActionstate(イシュー #1578 で size variant 導入に伴い本体の再エクスポートは選択的(規約 A)へ移行したが、state 由来の型はこれまでどおり明示再エクスポートする)
toggle_tipOpenState / DisclosureActionstate(tooltip と同型の glob re-export)
collapsibleOpenState / DisclosureActionstate(toggle_tip と同型の glob re-export、イシュー #1682)

select は共通 shape 軸(recipe::Shape)・見た目 variant 軸(SelectVariant)・選択インジケータ位置軸(select::ItemIndicatorPlacement)を root_with(size, shape: Option<Shape>, variant: SelectVariant, item_indicator_placement: ItemIndicatorPlacement, state, props, attrs, children) で提供する(既存 root(size, state, props, attrs, children) はシグネチャ不変のまま shape: None + SelectVariant::Outline + ItemIndicatorPlacement::End で root_with へ委譲、shape はイシュー #3117、variant はイシュー #3121、item_indicator_placement はイシュー #3124)。Pill は root の CSS 変数 --fandhe-select-trigger-radius を上書きし、trigger の border-radius へ伝搬する。Circle は未登録(select の trigger は value-text + indicator を横並びにする構造で真円にする用途がないため)。SelectVariant(Outline〔既定〕/Subtle)は native_select:: NativeSelectVariant と同型の軸で、Subtle は root の CSS 変数 --fandhe-select-trigger-bg/--fandhe-select-trigger-border-color を上書きし trigger を淡色背景・枠線なしにする。Outline(既定)は default_variant を登録していないため class を一切出さず、root() の既存出力はバイト単位で不変。ItemIndicatorPlacement::Start(既定は End)は root の CSS 変数 3 本(--fandhe-select-item-indicator-order/ --fandhe-select-item-indicator-margin-left/ --fandhe-select-item-indicator-placeholder-display)を切り替える(order + 空き枠方式)。item-indicator は選択状態にかかわらず常に DOM へ出力され非選択時のみ hidden 属性が付く既存ノードをそのまま空き枠に転用する: state 規則([data-state])で order: -1・margin-left: 0 を全項目のインジケータへ適用し、[hidden] 規則で非選択項目のインジケータを visibility: hidden のまま同じ幅を占有させる。絶対配置・疑似要素は使わないため item の padding 等の既存宣言には一切触れず、既存フック --fandhe-select-item-padding による余白調整はそのまま効く。End(既定)は class を一切出さず既存出力はバイト不変。

ActivationMode/TabItem/TabsProps(tabs)・DialogRole/ContentIds(dialog)・SelectAction(select)は各 headless モジュール内定義のため既存の glob 再エクスポートで到達可能であり、追加の再エクスポートは不要(モジュール自身の impl Component の Action として使う場合を含む)。

加えて、クレートルート(crates/pre-styled-ui/src/lib.rs)から次を再エクスポートする。

  • pub use fandhe_frontend_headless_ui;: headless 層クレートそのもの。headless-ui が core に対して行う再エクスポートと同型のエスケープハッチであり、各ラッパーモジュールの glob では届かない headless API 全域(positioning/aria 等)への到達路を確保する。
  • pub use fandhe_frontend_headless_ui::fandhe_frontend_core;: Node を組み立てる core API(el/text/render 等)への推移的再エクスポート。fandhe_frontend_pre_styled_ui::fandhe_frontend_core::{el, text, render, Node} という単独依存パスを完結させる(Cargo.toml へ fandhe-frontend-core への直接依存を追加しない、不変条件 4 を維持)。
  • pub use fandhe_frontend_headless_ui::{OpenState, Orientation};: ラッパー呼び出しに頻出する状態値。pre-styled-ui 単独依存での import を可能にする。

セキュリティ上の注意(REQ-1、.claude/rules/security.md A03): fandhe_frontend_pre_styled_ui::fandhe_frontend_core 経由で raw_html() へ到達できる経路が増えるが、raw_html() 自体は既存の明示的オプトイン API であり、本契約は新たな迂回経路を作らない(headless-ui が確立した既存パターンの推移)。pre-styled-ui 内部の不変条件(raw_html() の使用は [stylesheet::StyleSheet::style_element] 内の 1 箇所限定)は「使用」に関する規約であり、pub use によるクレート到達性の追加はこれに抵触しない。

固定テストは crates/pre-styled-ui/tests/headless_reexports.rs(import を fandhe_frontend_pre_styled_ui:: パスのみに限定し、コンパイルと実行時アサーションの両方で契約を固定する)。

3b. interactive 層の再エクスポート契約

fandhe-frontend-headless-ui は pub use fandhe_frontend_interactive;(クレート再エクスポート)を持ち、fandhe-frontend-pre-styled-ui はそれを推移的に pub use fandhe_frontend_headless_ui::fandhe_frontend_interactive; で再エクスポートする。ルートへの個別型再エクスポート(Component 等をルート直下へ置く構成)は行わない。

棚卸し表(クレート再エクスポートにより全到達可能)

項目到達パス
Componentfandhe_frontend_pre_styled_ui::fandhe_frontend_interactive::Component
Hydrate同上 ::Hydrate
dispatch同上 ::dispatch
HydrateError同上 ::HydrateError
render_for_hydration同上 ::render_for_hydration
HYDRATE_ATTR_PREFIX同上 ::HYDRATE_ATTR_PREFIX
codec モジュール同上 ::codec
DirtyTracked同上 ::DirtyTracked

同型で fandhe_frontend_headless_ui::fandhe_frontend_interactive::{...}(headless-ui 単独依存経由)でも到達可能。

固定テスト

  • crates/headless-ui/tests/interactive_reexport.rs: headless-ui のクレート再エクスポート到達性を、styled Dialog 相当(headless の Dialog)の SSR → dispatch 往復と、改ざん属性による HydrateError 到達(panic しない)で固定する。
  • crates/pre-styled-ui/tests/interactive_reexports.rs: pre-styled-ui の推移的再エクスポート到達性を、styled Dialog/Accordion/Switch の SSR/hydration/dispatch 往復と HydrateError 到達で固定する。import は fandhe_frontend_pre_styled_ui:: パスのみに限定する。
  • crates/pre-styled-ui/tests/headless_reexports.rs は import を fandhe_frontend_pre_styled_ui::fandhe_frontend_interactive::{...}(再エクスポート経由)に限定し、契約テストとしての純度を保つ。

セキュリティ上の注意(REQ-1)

fandhe_frontend_interactive は raw_html() を公開せず、Component::view/render_for_hydration の戻り値は Node のみで既定エスケープを必ず経由する(interactive の不変条件 1)。本再エクスポートは新たな出力経路・エスケープ迂回を一切作らない。Hydrate::from_hydration_attrs は DOM 属性を改ざんされうる入力として扱い panic せず HydrateError を返す契約(interactive 不変条件 3)も、再エクスポートで弱まらないことを固定テストで検証している。

3c. 再エクスポートの形式規約(glob / 選択的 / shadowing、イシュー #1062)

各 styled モジュールが headless の対応モジュールを再エクスポートする形式(glob 併用か選択的個別かの選択、および暗黙 shadowing の禁止)の規約は crates/pre-styled-ui/src/lib.rs「headless 再エクスポートの形式規約(イシュー #1062)」節を正とし、本書では二重管理しない。要約:

  • 既定は選択的個別再エクスポート(規約 A)。styled パーツ関数を再定義するモジュールは、headless の同名自由関数・未スタイル inherent メソッドを持つ状態機械型を再エクスポートしない(fail-closed。avatar/breadcrumb 等の既存モジュールがこの運用実体)。
  • glob 再エクスポート(規約 B)は「トップレベル pub 項目が stylesheet()/css() のみ・variant 軸を提供しない・CSS 到達が [data-scope]/[data-part] 属性セレクタのみ」の 4 条件(マーカーコメント REEXPORT-GLOB-REVIEWED: を含む)を満たす場合のみ許可する。イシュー #1062 のレビューにより、現時点で action_bar / popover / hover_card / tooltip / toolbar / scroll_area / toggle_tip / menubar / json_tree_view / floating_panel / timer / navigation_menu / collapsible(イシュー #1682)の 13 モジュールが条件を満たすと判定済み(tree_view はイシュー #1578 で size variant を持つに至り規約 A(選択的)へ移行したため本一覧から外れた。レビュー来歴は docs/internal/pre-styled-ui-implementation-notes.md §3c 参照)。
  • glob 由来の名前をローカル定義・明示 pub use で上書きする暗黙 shadowing は禁止する(規約 C)。同名を styled 側で提供したい場合は規約 A(選択的)へ移行する。

機械検知は crates/pre-styled-ui/tests/reexport_policy.rs(glob 一覧の双方向一致・マーカーコメント有無・許可 pub 項目・root 再定義との併存禁止の 4 検査、いずれも fail-closed)が担う。

4. 設計方針

  • テーマトークン: 色・スペーシング等のデザイントークンとダークモード切り替えの基盤。chakra-ui の system/recipe 相当の設計を参考にしつつ、静的 SSR 出力(ビルド時に確定する CSS)を前提とする。詳細は theme モジュール rustdoc を参照。トークン API・既定値上書き(upsert_*)の詳細は §4l 参照。
  • variant API・静的 CSS 生成: chakra-ui の slot recipe 相当。コンポーネントの見た目バリエーション(size/variant/colorPalette 等)を型安全に選択し、対応する静的 CSS を生成する。詳細は pre-styled-recipe-api.md を参照。
  • styled 部品: 単純な部品(Button 等)に加え、headless-ui の Accordion/Dialog/Popover/Tooltip/Switch/RadioGroup/Avatar 等をラップした styled 版を提供する(一覧は §2 の表を参照)。

4a. stylesheet::StyleSheet(recipe / theme CSS の書き出し・埋め込みヘルパ)

SlotRecipe::css()・Theme::to_css()・各 styled 部品の css()/stylesheet() は決定的な CSS 文字列を返すのみで、その先の配布は呼び出し側任せだった(examples/headless-pre-styled-ui の手書き static/ui.css コピーが実例)。stylesheet::StyleSheet はこれを集約し、2 つの配布経路を提供する。

  • StyleSheet::new() / push_css(&mut self, css: &str) -> Result<(), StylesheetError>: 唯一の fallible な取り込み口。< を含む、または改行・タブ・復帰以外の制御文字を含む入力は Err(StylesheetError::CssRejected { .. }) になる(fail-closed)。
  • push_recipe(&mut self, recipe: &SlotRecipe) / push_theme(&mut self, theme: &Theme): 生成側 allowlist 検証(< を構成不能にする)に依拠した infallible な薄いラッパ。
  • as_css(&self) -> &str: 取り込んだ CSS 全量。
  • write_css_file(&self, path: &Path) -> std::io::Result<()>: 静的 .css ファイルへの書き出し(SSG・ビルドスクリプト向け。親ディレクトリを自動作成)。
  • style_element(&self) -> Node: SSR 用 <style> 要素ノード。本クレートで raw_html() を使用する唯一の箇所(§3 の不変条件 2 の例外)であり、呼び出し文に #[expect(clippy::disallowed_methods, reason = "ESCAPE-REVIEWED: ...")] を付与済み。StyleSheet は private フィールドのみで構成され、検証済み CSS 以外から構築する経路を公開しないため、呼び出し側へエスケープ迂回経路を公開しない。
Rust
use fandhe_frontend_pre_styled_ui::stylesheet::StyleSheet;
use fandhe_frontend_pre_styled_ui::theme::Theme;

let mut sheet = StyleSheet::new();
sheet.push_theme(&Theme::default());
sheet.push_css(&fandhe_frontend_pre_styled_ui::button::css()).unwrap();

// SSG: 静的ファイルとして配信する
sheet.write_css_file(std::path::Path::new("static/ui.css")).unwrap();

// SSR: <style> 要素として埋め込む(render() が既定エスケープを適用する
// 他のノードと同様に合成できる)
let _style_node = sheet.style_element();

4b. avatar(Avatar の styled ラッパー)

fandhe_frontend_headless_ui::avatar(Root/Image/Fallback の 3 anatomy パーツと Avatar 状態機械)を薄く再利用し、stylesheet() で既定 CSS を追加提供する(設計方針は crate::dialog/crate::tooltip と同じ、src/avatar.rs 冒頭の rustdoc 参照)。

  • 選択的 re-export(Avatar 型は再エクスポートしない): fallback/ image/AvatarAction/ImageStatus を headless 層からそのまま再エクスポートする。styled root は本モジュールで variant クラス付与のために再定義するため、pub use ...::* ではなく選択的 re-export とする(headless の自由関数 root との名前衝突を避けるため)。状態機械 Avatar はあえて再エクスポートしない: Avatar::root() は headless 自由関数 root へそのまま委譲するのみで size/shape variant クラスを一切付与しないため、再エクスポートすると呼び出し側が styled 層のつもりで Avatar::root() を呼びレイアウトが静かに崩れる事故を誘発する。Avatar による状態管理・hydration が必要な呼び出し側は fandhe_frontend_headless_ui::avatar::Avatar を直接 import すること。
  • root(&AvatarProps, attrs, children) -> Node: styled root パーツ(イシュー #1554 で root(size, shape, attrs, children) の位置引数 4 軸から Props 構造体へ破壊的変更。0.x の破壊的変更のためマイナーバンプ)。AvatarProps の size(Size::Xs〜Xl、既定 Md)・shape(AvatarShape::Circle/Rounded/Square、既定 Circle)・variant(AvatarVariant::Subtle/Solid/Outline、既定 Subtle)・palette(ColorPalette 6 値、既定 Neutral)の 4 軸 variant に応じたクラス(fd-avatar--size-<value> / fd-avatar--shape-<value> / fd-avatar--variant-<value> / fd-avatar--color-palette-<value>)を付与する。呼び出し側 attrs の class は除去してから合成するため class 属性は常に単一。実体は fandhe_frontend_headless_ui::avatar::root へ委譲する(呼び出し側 data-scope/data-part 偽装は headless 側で除去される)。
  • AvatarShape: recipe::VariantValue 実装 enum(Size と並ぶ本クレート 2 例目の variant 軸)。
  • AvatarVariant(イシュー #1554 で新設): Subtle(既定)/Solid/ Outline の 3 値。BadgeVariant/KbdVariant と同名の語彙(BadgeVariant はイシュー #1555 で Surface を追加し Solid/ Subtle(既定)/Outline/Surface の 4 値)。
  • AvatarProps(イシュー #1554 で新設): size/shape/variant/ palette の 4 フィールドを持つ root の設定構造体(KbdProps と同型)。Default は Md/Circle/Subtle/Neutral。イシュー #2044(shadcn/ui 突合)で stacked/with_badge(いずれも bool、既定 false)の 2 フィールドを追加した(0.x の破壊的変更のためマイナーバンプ。既定値のため AvatarProps::default() の class 出力・golden CSS は不変)。
  • group(attrs, children) -> Node(イシュー #2044 で新設): pre-styled-only group パート(<div>)。複数の root(stacked: true)を重ねて表示するレイアウト専用パートで、headless-ui の anatomy には存在しない(crate::dialog::footer と同型)。残数表示(+N)は独立パートを新設せず root(Subtle/Neutral, stacked: true) + fallback("+3") の組み合わせで表現する。
  • badge(&AvatarBadgeProps, attrs, children) -> Node(イシュー #2044 で新設): pre-styled-only badge パート(<span>)。root(with_badge: true)の右下に絶対配置される状態ドット。size(Size::Xs〜Xl、既定 Md)・palette(ColorPalette 6 値、既定 Accent)に応じたクラス(fd-avatar--size-<value> / fd-avatar--color-palette-<value>)のみを付与する(variant_classes を経由しないため fd-avatar--shape-/fd-avatar--variant- の既定値補完は付与されない)。
  • AvatarBadgeProps(イシュー #2044 で新設): size/palette/ placement(イシュー #3130 で追加)の 3 フィールドを持つ badge の設定構造体。Default は Md/Accent/BottomEnd。
  • AvatarBadgePlacement(イシュー #3130 で新設): BottomEnd(既定、従来の右下)/TopEnd(右上)の 2 値。badge() は BottomEnd 以外のときだけ fd-avatar--placement-<value> クラスを追加する(既定呼び出しの class 出力は変更前と完全一致)。
  • group_with(&AvatarGroupProps, attrs, children) -> Node(イシュー #3130 で新設): group の拡張版。props.stacking が LastOnTop(既定)のときは group(attrs, children) へそのまま委譲し出力が完全一致する。FirstOnTop のときは fd-avatar--stacking-first-on-top クラス(isolation: isolate)を付与し、直下の Node::Element 子へ先頭ほど大きい z-index を持つ固定クラス(fd-avatar--stack-order-pos<1..12>、AvatarStackOrder)を付与して先頭の root を最前面にする。厳格 CSP(style-src-attr 非許可)下でも無効化されないよう inline style="z-index: ..." は採らない(PR #3563 codex-review P1 指摘)。z-index の固定クラスは 1〜MAX_STACK_ORDER(12)の 12 段までしか持てないため、子の数 n が 12 を超える構成では、n - i(0 始まり位置 i)をそのまま使わず「先頭 12 件(i < min(n, 12))に min(n, 12) - i で一意な降順値を割り当て、それを超える末尾側(既に見た目上最背面に埋もれている子)だけを最背面側の固定値 pos1 へ合流させる」方式で先頭側の一意な重なり順を保つ(n <= 12 のときは従来どおり n - i と同じ値になる)。:nth-child(n) 等の子孫セレクタは SlotRecipe が生成できない(イシュー #708)ため、CSS だけでは実現できない。「先頭ほど前面」を全件で保証できる上限は公開定数 GROUP_FIRST_ON_TOP_MAX_CHILDREN(12)で示す。13 件以上では 12 件目以降が pos1 へ合流し DOM 順(後ろほど前面)で描画されるため、呼び出し側で表示件数をこの値以下に絞る(PR #3563 codex-review P2 指摘)。
  • AvatarGroupStacking(イシュー #3130 で新設): LastOnTop(既定、DOM 順のまま)/FirstOnTop(先頭が最前面)の 2 値。
  • AvatarGroupProps(イシュー #3130 で新設): stacking 1 フィールドを持つ group_with の設定構造体。Default は LastOnTop。
  • 大サイズ(56px 超)は新規 API なし: 共通 Size enum は 5 段方針のため拡張しない(イシュー #3130)。root の呼び出し側 attrs へ inline style(例: width: 4rem; height: 4rem; font-size: var(--fandhe-font-font-size-xl))を渡すとクラス規則より優先して上書きできる。厳格な CSP(style-src-attr に 'unsafe-inline' が無い環境)では利用者側のスタイルシートで外側の要素から上書きすること。
  • stylesheet() -> String: この styled Avatar の静的 CSS 全量を返す(決定的)。image/fallback の base 規則は display を宣言せず、headless 層が付与する hidden 存在属性(UA 既定 [hidden] { display: none })による JS なし SSR の表示制御を壊さない。data-state="hidden" 一致時の display: none は SlotRecipe::state 経由で多層防御として追加登録する(src/avatar.rs 冒頭の rustdoc 参照)。with_badge: true の root のみ既定 overflow: hidden を解除する(イシュー #2044)。

4c. styled RadioGroup ラッパー

radio_group モジュールは fandhe_frontend_headless_ui::radio_group の Label/Item/ItemControl/ItemText/ItemHiddenInput 5 anatomy パーツと RadioGroup 状態機械を選択的に再エクスポートし、stylesheet() で既定 CSS を追加提供する(設計方針は他 headless ラッパーと同じ、src/radio_group.rs 冒頭の rustdoc 参照)。

  • item-hidden-input の視覚的非表示化: headless 層はネイティブ <input type="radio"> に aria/data-* のみを設定し視覚的な非表示化を行わない契約のため、styled 層が visually-hidden パターン(position: absolute + 1px クリップ、select モジュールの hidden-select 規則と同一の 9 宣言)で覆い隠し、item-control をカスタムラジオ円として描画する。フォーム送信・キーボード操作・グループ内排他選択はネイティブ semantics のまま維持される。
  • StateCondition::FocusWithin の追加: item-hidden-input を視覚的に隠すと、ネイティブのフォーカスリングも見えなくなる。実フォーカスは隠された <input> にあり、item(<label>、input の祖先)へ :focus-within を当てるのが CSS 的に成立する唯一の経路のため、recipe::StateCondition へ FocusWithin(:focus-within 擬似クラス)を追加した(既存の Attr/AttrEq/FocusVisible に次ぐ 4 つ目の状態条件)。
  • root(size, palette, disabled, orientation, labelled_by, attrs, children) -> Node: styled root パーツ。size(Size::Sm/Md/Lg、既定 Md)・palette(ColorPalette 5 値、既定 Accent)の 2 軸 variant クラス(fd-radio-group--size-<value> / fd-radio-group--color-palette-<value>)を付与する。headless 自由関数 root との名前衝突を避けるため本モジュールで再定義し、pub use ...::* ではなく選択的 re-export とする。RadioGroup 状態機械は inherent root() を持たないため(item 系メソッドのみ)、avatar の Avatar と異なりそのまま再エクスポートを維持する。

4d. 複合部品の variant 統一方針・variant 表

単純部品(button/badge/spinner)・avatar に続き、headless 状態機械を持つ複合部品ラッパーへ size/color-palette variant を拡張する際の統一方針は crates/pre-styled-ui/src/lib.rs 冒頭の rustdoc「複合部品の variant 統一方針」節が正本。要旨:

  1. クラスは root slot のみに付与し、子孫パーツへの伝搬は root が登録する CSS custom property の通常の継承で行う(SlotRecipe へ子孫セレクタ機構は追加しない)。
  2. var() には Md/Accent 相当のフォールバック値を書き、headless 直接利用でも現行外観を維持する。
  3. size はフォーム操作部品・トリガー系へ、color-palette は選択・チェック状態を示す部品へ提供する。popover/tooltip は配置・寸法が positioning 起因のため提供しない。
部品sizecolor-palette備考
button/badge/spinner✓✓button は icon-only 修飾 variant(icon_button/close_button)を追加。専用の icon/close-button 行は設けない: data-scope="button" を共有する variant 拡張であり別部品ではないため
callout✓✓本文中の補足情報。alert と異なり live region ではない(イシュー #994)。size は root の --fandhe-callout-* へ一本化し text(attrs, children) から size 引数を廃止した(破壊的変更、イシュー #1556)
alert✓–(status)status(Info/Success/Warning/Error/Neutral、内部で ColorPalette へ写像)に加え variant(Subtle/Surface/Solid/Outline/AccentBorder、既定 Subtle)を持つ(イシュー #1553、AccentBorder はイシュー #3131)。action(pre-styled-only レイアウトパート、イシュー #2043)
avatar✓✓(既定 Neutral)variant(subtle/solid/outline)・shape の 2 軸も併せ持つ(イシュー #1554)
switch✓✓—
radio-group✓✓—
checkbox✓✓—
password-input✓✓—
input / textarea / native-select✓–フォーム入力は選択・チェック状態を示す部品ではないため color-palette は非提供
tabs✓✓(selected trigger の強調色)—
accordion / dialog / menu / select✓–—
number-input✓–フォーム入力部品のため color-palette は非提供
pin-input✓–palette は第 2 弾展開のフォローアップ
rating-group✓✓星形 indicator の寸法・点灯色に反映
toggle✓✓—
toggle-group✓✓root のみへクラス付与
segment-group✓–選択状態は indicator の移動 + 文字強調で表現するため color-palette は非提供
tags-input✓–フォーム入力部品のため color-palette は非提供
editable✓–フォーム操作部品のため color-palette は非提供
checkbox-card / radio-card✓✓カード外観・選択強調・ドット色に反映(§4g 参照)
pagination✓✓現在ページの強調色に反映。root scope の CSS custom property は --fandhe-pagination-item-size/-item-font-size
steps✓✓indicator の寸法・current/complete の強調色に反映。root scope の CSS custom property は --fandhe-steps-indicator-size(indicator の寸法)・--fandhe-steps-indicator-font-size(indicator 内ラベルの文字サイズ)・--fandhe-steps-font-size(イシュー #1540 で追加。trigger/prev-trigger/next-trigger のラベル文字サイズ)の 3 個
popover / tooltip提供しない提供しない配置・寸法が positioning 起因のため提供しない
tree-view✓提供しないイシュー #1578 で root のみへクラス付与する size variant(xs〜xl、既定 md)を新設。行密度・文字サイズに反映。color-palette は popover/tooltip と同型の判断で非提供
json-tree-viewtree-view の size を継承提供しない構造部は tree-view の recipe を再利用するため tree-view の size variant をそのまま継承する(key/value 固有の size 軸は持たない)
toggle-tip提供しない提供しないpopover/tooltip と同型の判断
collapsible提供しない提供しない参照 3 サイト(chakra-ui / Radix Primitives / ark-ui)いずれも size/variant/colorPalette を持たないため非提供(イシュー #1682)
breadcrumb✓– (BreadcrumbVariant: link の下線表示切り替え)アクセント色による選択・チェック状態を示す部品ではないため color-palette は非提供
drawer✓–dialog と同じく選択・チェック状態を示す部品ではないため color-palette は非提供。root scope の CSS custom property は --fandhe-drawer-size。placement(start/end/top/bottom)は variant 軸ではなく headless 層が出力する data-placement に連動する CSS で表現する
link提供しない提供しないLinkVariant(下線表示切り替え)のみの単軸 variant。インラインテキストリンクは寸法・強調色の variant 対象外
link-overlay / nav-list提供しない提供しない構造・意味論部品のため variant 軸を持たない
table✓提供しない選択・チェック状態を示す部品ではないため color-palette は非提供。TableVariant(Line/Outline)・striped(bool)・sticky_header(bool)・interactive(bool、イシュー #2052)の追加軸を持つ。striped は StateCondition::NthChildEven で表現、interactive は opt-in の行 hover
data-list✓(Size 5 段、既定 Md)提供しないorientation(Vertical/Horizontal)・DataListVariant(Subtle/Bold)の追加軸を持つ(イシュー #1559)
toast✓(placement、group slot)✓(status、root slot、ColorPalette 6 役割淡色面配色、イシュー #1544。alert と宣言の中身は共有しない)各軸が別 slot のため variant_class をスロットごとに個別呼び出し
tour提供しない✓(root slot)size は overlay 系の寸法を呼び出し側の CSS カスタムプロパティ上書きに委ねるため初版非提供。palette は action-trigger の背景・文字色(--fandhe-palette-fg)・両トリガーのフォーカスリング・スポットライト縁取りの強調色に反映(イシュー #1551 で action-trigger の文字色反映を追加)
file-upload✓–フォーム入力部品のため color-palette は非提供
toolbar提供しない提供しないボタン・セパレータ・ToggleGroup のグループ化コンテナであり寸法・強調色の variant 対象外(イシュー #991)
menubar提供しない提供しない複数 Menu のグループ化コンテナであり寸法・強調色の variant 対象外(イシュー #992)
navigation-menu提供しない提供しないトリガー起点のナビゲーションパネルであり寸法・強調色の variant 対象外(イシュー #993)
tab-nav✓提供しないsize 軸(xs〜xl、既定 md)を持つ(イシュー #1541)。palette は祖先の --fandhe-palette を継承する経路のみで軸としては非提供
checkbox-group✓✓radio-group と同型(イシュー #997)。item-control の寸法・palette 塗りに反映
card✓提供しない中立コンテナのため palette 非提供(イシュー #606)。size(xs〜xl、既定 md)は root の --fandhe-card-* へ一本化し padding/角丸/title の文字サイズへ連動する。size 軸新設に伴い root の第 1 引数を impl Into<CardProps> へ拡張した(イシュー #1557)。From<CardVariant> for CardProps により従来の root(CardVariant::Elevated, …) はそのまま有効で、size を指定する場合は root(CardProps { variant, size }, …) を渡す(後方互換の純追加)。イシュー #2046 で shadcn/ui と突合し、パーツ数を 6 → 8 へ拡張した(action/cover を純追加)。header/footer へ data-has-action/data-bordered を opt-in で渡すと、それぞれ action の grid 配置・1px 区切り線が有効になる(既定出力は不変)
button_group提供しない提供しない子の寸法に従属するグループ化コンテナ(イシュー #2060)

tabs/accordion/dialog/menu/select の実装詳細:

  • クラスは root slot のみに付与する。
  • root スコープの CSS custom property: tabs --fandhe-tabs-trigger-padding/-content-padding、accordion --fandhe-accordion-trigger-padding/-content-padding、dialog --fandhe-dialog-content-padding/-content-max-width/-title-font-size、menu --fandhe-menu-trigger-padding/-item-padding/-content-padding、select --fandhe-select-trigger-padding/-item-padding/-content-padding。menu/select の --fandhe-reference-width/--fandhe-x/--fandhe-y(wasm positioning 契約)には手を触れない。--fandhe-arrow-* はイシュー #2210 で menu(および popover/tooltip)の arrow/arrow-tip が消費するようになったため対象外(size variant はこれらへ影響しないという本節の趣旨は不変)。
  • tabs の color-palette は選択中 trigger の強調色(border-bottom-color: var(--fandhe-palette, var(--fandhe-color-accent)))にのみ反映する。
  • Dialog/Menu/Select(inherent root() を持つ状態機械型)は未スタイル root の静かな適用漏れ防止のため選択的 re-export へ切り替えている。Accordion/MultiAccordion(inherent root なし)は再エクスポート維持。
  • dialog::footer(pre-styled-only レイアウトパート、data-scope="dialog" 配下の headless-ui 非由来 9 番目の part。alert-dialog 構成のアクション列の横並び配置のみを担い、確認/キャンセルの強調色は既存の button の variant/palette 組み合わせで表現する。イシュー #1690)。
  • alert::action(pre-styled-only レイアウトパート、data-scope="alert" 配下の headless-ui 非由来 6 番目の part。shadcn/ui の AlertAction 相当を root 末尾の flex 兄弟として右寄せ(RTL では左寄せ)配置するのみを担い、アクションの機能配線は呼び出し側が行う。イシュー #2043)。

4d-1. button_group(連結ボタングループ、イシュー #2060、headless

anatomy は #2059)

  • 公開 API: root(orientation, label, attrs, children) / separator(group_orientation, attrs, children) / text(attrs, children) / stylesheet()。Orientation は fandhe_frontend_headless_ui::data_attrs::Orientation の選択的再エクスポート(規約 A)。
  • size/variant/color-palette いずれの軸も提供しない(上記 §4d 表参照、docs/design/pre-styled-ui-focus-ring-and-size-conventions.md §4 (d) 「子の寸法に従属するレイアウト部品」)。
  • 先頭・末尾以外の隣接要素の角丸・開始側境界線幅を [crate::css::serialize_rule] による raw CSS 追記で無効化する(SlotRecipe は子結合子を表現できないため)。対象は button / field-input / button-group-text / menu-trigger / select-trigger の 5 種を明示列挙し、> *:not(:first-child) のような汎用セレクタは特異度の衝突が呼び出し側の CSS 連結順に依存してしまうため採らない。
  • ネスト(グループの中にグループ)は親側 :has() による gap 付与を採らず(#2226)、角丸連結対象へ含めず先頭以外の内側グループへ margin のみ付与する代替表現とする(意図的非採用、docs/policy/intentional-non-adoption.md の評価軸に従う)。既定はこの margin 間隔のままだが、内側 root へ呼び出し側が値なし属性 data-attached を付与すると opt-in で間隔を打ち消し、接続辺の角丸・境界線を外側 root と共有する連結表示にする(イシュー #3135、縦横混在の入力欄連結に使う)。接続辺の判定規則(PR #3569): 外側 root の直接の子を通常の子 N・attached 内側 root A・間隔を保つ通常の内側 root D の 3 種に分け、隣接 2 兄弟の辺はどちらも D でないときに限り接続する(N–N / N–A / A–A は接続、D に面した辺は角丸を保ち、D 自身の先頭側 margin と D 直後の通常の子への margin で間隔を付ける)。末尾辺は後続兄弟を参照するため :not(:has(+ D))(Selectors Level 4)で判定する。表と根拠は crates/pre-styled-ui/src/button_group.rs モジュール doc「接続辺の判定規則」節を正とする。
  • バリデーション・送信処理等のアプリケーションロジックは内包しない(.claude/rules/coding-rust.md §3.25)。
  • docs サイト: button-group(Themes)/ button-group(Primitives)。

4d. data-focus-visible によるキーボード専用フォーカスリング

hidden-input パターン(実フォーカスが visually-hidden なネイティブ <input> にあり、リングを見せたい視覚パーツと分離している構成)は、擬似クラス(:focus-visible/:focus-within)だけでは表現しきれない。switch(root > control の兄弟配置。:focus-within すら不成立)と radio_group(item の :focus-within は成立するが、マウス操作でも発火する包括的なフォールバックでしかない)の 2 モジュールがこの補完を導入した。

  • fandhe-frontend-headless-ui の data_attrs::data_focus_visible が data-focus-visible 存在属性の SSR 静的表現(常に属性なし)を契約する(data_highlighted と同型、crates/headless-ui/src/switch.rs/ radio_group.rs/checkbox.rs のフォーカスリング契約 doc 参照)。
  • fandhe-frontend-wasm-full の focus 配線(focus_visible モジュール、keynav/events と同じ 2 層構成)が hidden-input の focusin/focusout と :focus-visible 判定に基づき、境界パーツ(switch: root、radio_group: item)とその配下で同一 data-scope を共有するパーツ(switch: control、radio_group: item-control)の双方へ付け外しする。
  • fandhe-frontend-pre-styled-ui は control/item-control slot へ StateCondition::Attr("data-focus-visible") の状態規則を登録し、select の trigger(StateCondition::FocusVisible)と同じ視覚言語(outline: 2px solid var(--fandhe-color-accent))でリングを表現する。RadioGroup の item :focus-within は wasm なしでも成立する no-JS フォールバックとして維持し、data-focus-visible はその補完(wasm 配線時のキーボード専用リング)として独立に共存する。
  • checkbox も switch の control と同型の StateCondition::Attr("data-focus-visible") 規則を実装済み(詳細は §4e)。

4e. styled Checkbox ラッパー

checkbox モジュールは fandhe_frontend_headless_ui::checkbox の root/control/indicator/label/hidden-input 5 anatomy パーツを選択的に再エクスポートし、stylesheet() で既定 CSS を追加提供する(設計方針は §4c/§4d と同型、src/checkbox.rs 冒頭の rustdoc 参照)。

  • root(size, palette, props, attrs, children) -> Node: styled root パーツ。size(Size::Sm/Md/Lg、既定 Md)・palette(ColorPalette 5 値、既定 Accent)の 2 軸 variant クラス(fd-checkbox--size-<value> / fd-checkbox--color-palette-<value>)を付与する。headless 自由関数 root はチェック状態を含む CheckboxProps を受け取るため(switch/radio_group の bool 個別引数と異なる形)、styled root も &CheckboxProps を第 3 引数に取る。headless Checkbox 状態機械(inherent root() を持つ)は switch::Switch と同じ理由(未スタイル root の静かな適用漏れ防止)で再エクスポートしない。
  • indicator の hidden 属性意味論の維持: headless indicator は unchecked 時に hidden 存在属性で非表示化する契約を持つ。styled recipe の indicator base 規則に display 宣言を一切含めないことで、UA stylesheet の [hidden] { display: none } を上書きしない(テスト indicator_base_has_no_display_declaration で固定)。checked/ indeterminate 時の見た目切り替えは border/transform/width/height の組み合わせで表現し、display を使わない。
  • data-focus-visible フォーカスリング: control slot へ switch の control と同一の宣言(outline: 2px solid var(--fandhe-color-accent); outline-offset: 2px;)を登録する。属性の付け外しは headless/wasm 層の責務(fandhe-frontend-wasm-full の focus 配線に ("checkbox", "hidden-input") => Some("root") のマッピングが登録済み)であり、本モジュールでの wasm 層変更は不要だった。

4f. 静的フォーム部品 input/textarea/native_select

input/textarea/native_select の 3 モジュールは状態機械を持たない(ブラウザネイティブ挙動をそのまま尊重する)。fandhe_frontend_headless_ui::field の input/textarea/select の 3 パーツへ variant/size variant クラスと既定 CSS を重ねる薄い委譲層で、アクセシビリティ配線(id・ネイティブ disabled/required/readonly・aria-invalid・aria-describedby・data-*)は headless field::* へ全面委譲する(詳細は src/input.rs 冒頭の rustdoc 参照)。

  • field scope を共有する recipe 設計: SlotRecipe が生成する CSS セレクタは [data-scope="<scope>"][data-part="<slot>"] 固定であり、headless field::* が実際にレンダリングする data-scope="field" と一致させる必要がある。そのため 3 モジュールは独自の scope を新設せず "field" を共有し、slot を "input"/"textarea"/"select" のみ個別に宣言する(slot が相互排他のためセレクタ・宣言は衝突しない)。
  • 各モジュールの API 形: input(&InputProps, &FieldProps<'_>, extra_attrs) のように、見た目 variant(InputProps)とアクセシビリティ props(headless から再エクスポートした FieldProps/FieldIds)を別引数として受け取る(ark-ui/chakra-ui が見た目 props とフォーム状態 props を分離する構成に合わせる)。
  • variant 軸: Outline(既定)/Subtle/Flushed/SubtleFlushed(淡色背景+下線のみ、input のみ、イシュー #3122)の 4 値(native_select のみ Flushed の代わりに枠なしの Plain)。
  • color-palette 軸を提供しない: §4d「複合部品の variant 統一方針」の基準 3(color-palette は選択・チェック状態を示す部品へ提供する)に従い、フォーム入力は該当しないため提供しない。フォーカスリングのアクセントは var(--fandhe-color-accent) の直接参照のみで表現する。
  • textarea の autoresize フックへの応答: headless field::textarea の autoresize: bool は SSR 時点で data-autoresize="" 存在属性のみを出力する宣言的フック(実際の高さ調整は CSR/wasm 層またはスタイルの責務)。textarea モジュールは [data-autoresize] 状態規則として field-sizing: content + resize: none を登録し、この宣言的フックへ styled 層として応答する。
  • native_select はネイティブ矢印を維持する: chakra-ui の NativeSelect はカスタム矢印アイコンを重ねるため appearance: none を使う構成が一般的だが、本モジュールは「ブラウザネイティブ挙動を尊重する」という設計原則に従い appearance 宣言を持たず、ネイティブの矢印・開閉挙動をそのまま残す最小サブセットとする。<select readonly> が HTML 仕様上無効なためネイティブ readonly を出力しない判断は headless 層に委譲済みで、本モジュールは再実装しない。
  • native_select は option/optgroup を system color で着色する(イシュー #2204): option/optgroup/optgroup > option の背景色を select と同じ --fandhe-color-bg トークン(Canvas フォールバック)で着色する。SlotRecipe が子孫セレクタを表現できないため recipe().css() の外側へ固定 CSS 文字列を追記する(field/status と同型のパターン)。文字色(color)は明示せず select の継承に委ねる(option:disabled の UA 既定減色を保つため)。

4f-1. field(ラベル・補助テキスト・エラーテキストの型階層、イシュー #1684)

field モジュールは fandhe_frontend_headless_ui::field の anatomy へ、ラベル・補助テキスト・エラーテキスト・必須マークの型階層と root の余白レイアウトを重ねる薄い委譲層である。input/textarea/native_select(§4f)と同じく "field" recipe scope を共有するが、宣言する slot は root/label/helper-text/error-text/required-indicator/group/ content/title/separator/separator-line/separator-content の 11 つ(末尾 6 つはイシュー #2185 で純追加。既存 5 slot の CSS 出力バイトは不変)のみで、input/textarea/select slot は宣言しない(§4f の 3 モジュールが引き続き所有する)。

  • 公開 API: root(&FieldRootProps, &FieldProps<'_>, attrs, children)(見た目 variant クラスを重ねて headless field::root へ委譲)、root_with_label_placement(&FieldRootProps, FieldLabelPlacement, &FieldProps<'_>, attrs, children)(イシュー #3134、下記参照。root は内部で root_with_label_placement(.., FieldLabelPlacement::Outside, ..) へ委譲する)、FieldLabelPlacement(label-placement 軸、Outside 既定/ Inset/Overlap)、FieldOrientation(orientation 軸、Vertical 既定 /Horizontal/ Responsive、イシュー #2199 で Responsive を追加)、FieldRootProps。label/helper_text/error_text/required_indicator/ group/content/title/separator/FieldIds/FieldProps は headless からの選択的再エクスポート(見た目は属性セレクタのみで到達するため styled 側の再定義は不要)。group/content/title/separator はイシュー #2185 で純追加した拡張パーツ(shadcn/ui FieldGroup/ FieldContent/FieldTitle/FieldSeparator 相当)。
  • イシュー #2185 の追加着装: group の間隔は space トークン --fandhe-space-6、separator の線描画は border-*(crate::separator の --fandhe-separator-thickness 上書き契約を共有)、title は label と同じ型階層(disabled 減光・invalid 配色も同一規則)を採用する(判断根拠は field.rs モジュール doc「採用したもの(イシュー #2185)」節参照)。
  • orientation="responsive"(イシュー #2199): group を crate::recipe::SlotRecipe::container_slot として宣言し、root へ crate::recipe::SlotRecipe::container_variant(ContainerBreakpoint:: Md = 448px、Horizontal と同一の宣言を共有)を登録した。group の inline サイズが 448px 以上のときのみ Horizontal と同じ配置へ切り替わり、group の外に置いた場合は常に縦積みのまま(mobile-first の安全な劣化)。受け入れ条件が述べる data-orientation="responsive" は実装しない: headless field::root は data-orientation を意図的に持たず、本モジュールも独自 data-* を出力しない契約(crates/pre-styled-ui/tests/data_attr_vocabulary.rs)のため、既存の horizontal と同じくクラス fd-field--orientation-responsive として語彙化した。判断根拠は field.rs モジュール doc「orientation="responsive"」節・docs/design/pre-styled-ui-scale-tokens.md §3.7 参照。
  • helper-text の text-wrap: balance(イシュー #2160): css() は error-text > ul と同型の静的追記を 2 種持つ。1 つは既存の error-text > ul(イシュー #2014)、もう 1 つは本イシューで追加した helper-text の horizontal/responsive 時 text-wrap: balance(shadcn/ui FieldDescription の group-has-[[data-orientation= horizontal]]/field 相当をクラス条件へ読み替え)。既存 variant の出力はバイト同一のまま末尾への純追加。判断根拠は field.rs モジュール doc「helper-text の text-wrap: balance」節参照。
  • orientation 軸のみ: size/color-palette 軸は持たない(子の寸法に従属するレイアウト部品の root は size 軸を持たないという規約、フォーム入力系は palette 非提供という §4f と同じ判断)。
  • 意図的非採用: hover(root/label は非インタラクティブ)・focus ring(実フォーカスはコントロール側。FieldLabelPlacement::Inset/ Overlap は input 自身の outline を消すため、代わりに root の :focus-within へ focus ring を付与する例外を持つ、下記参照)・transition(状態遷移する視覚がない)・data-readonly/data-invalid によるラベル色変更(chakra-ui v3 も持たない)。
  • FieldLabelPlacement(ラベル配置、イシュー #3134): orientation と独立な opt-in variant。Inset は root 自身を枠線・背景を持つ box とし、内側の label/input をリセットする(input のみ対象、textarea/select は対象外)。inset_stack(data-part="inset-stack" の wrapper)の直下に縦に並べた Inset の root 同士だけが +/:has(+ ...) セレクタで枠線を共有して連結する(+ は親の gap を区別できないため、暗黙の隣接ではなく wrapper を契約とし、group 内では連結しない)。Overlap はラベルを root の枠線の上へ絶対配置で重ね、--fandhe-field-label-bg(既定 var(--fandhe-color-bg))でラベル背景を地の色へ合わせられる。枠線を root が描く両 variant は枠線に結び付く状態表示(invalid の border-color・focus のリング)を root 側へ写し、input 側の outline を打ち消して二重表示を避ける(disabled は子パーツの既存減衰のみで root へは重ねない。readonly は input と同じく視覚宣言なし。4 状態 × 2 variant の対応表は field.rs モジュール doc「状態表示の対応表」節)。いずれも orientation = Vertical での使用のみを前提とし、Horizontal/Responsive との併用・forms_motion の floating label との併用は対象外。既定(Outside)はクラスを出力せず root() の既存出力をバイト単位で不変に保つ。判断根拠・CSS 宣言の詳細は field.rs モジュール doc「ラベル配置(イシュー #3134)」節参照。
  • バリデーション責務外: docs/policy/intentional-non-adoption.md §3.25 規則 1 のとおり、本モジュールは headless が出す data-invalid/data-disabled/data-required を CSS セレクタとして参照するだけで、値の妥当性判定・送信処理は実装しない。
  • docs サイト: field(イシュー #1685 で /themes/field/ ページ登録・showcase Demo・SPEC_TABLES 原稿を追加)。

4f-2. fieldset(<fieldset>/<legend> グループコンテナ、イシュー #1686)

fieldset モジュールは fandhe_frontend_headless_ui::fieldset の anatomy(root/legend/helper-text/error-text の 4 パーツ)へ、UA 既定の <fieldset>/<legend> 枠線・padding のリセットと size 軸による余白・文字サイズの段階化を重ねる薄い委譲層である。

  • 公開 API: root(&FieldsetRootProps, &FieldsetProps<'_>, attrs, children)(見た目 variant クラスを重ねて headless fieldset::root へ委譲)、FieldsetRootProps。legend/helper_text/error_text/ legend_with_variant/LegendVariant/FieldsetProps は headless からの選択的再エクスポート。
  • size 軸のみ: Sm/Md/Lg の 3 段のみ登録(chakra-ui v3 Fieldset.Root の size prop に対応、既定 Md)。Xs/Xl は未登録。orientation/color-palette 軸は持たない。
  • legend variant(イシュー #2214): shadcn/ui FieldLegend の variant(legend=大 / label=小)と突合し、legend_with_variant が出力する [data-variant="label"] を CSS セレクタとして参照して size 軸の 1 段下(sm→xs/md→sm/lg→md)のフォントサイズへ切り替える。--fandhe-fieldset-legend-label-font-size(root の size variant が設定する CSS カスタムプロパティ)を継承して値を受け取る。既存 legend(data-variant を出力しない)はバイト単位で不変で、[data-variant="legend"] の dead セレクタは持たない。
  • 意図的非採用: chakra-ui v3 の Content サブパートは headless anatomy に存在しないため実装しない(headless anatomy 変更はスコープ外)。root へのネイティブ disabled 二重表現防止のため disabled_declarations() を付与しない。legend への data-invalid 色反映なし(chakra-ui v3 も非対応)。Radix Primitives / Radix Themes のいずれにも Fieldset が存在しないため対応なし。
  • バリデーション責務外: docs/policy/intentional-non-adoption.md §3.25 規則 1 のとおり、本モジュールは headless が出す data-invalid/data-disabled を CSS セレクタとして参照するだけで、値の妥当性判定・送信処理は実装しない。
  • docs サイト: fieldset(イシュー #1687 でページ登録・showcase Demo・SPEC_TABLES 原稿を追加)。

4f-3. input_group(入力欄前後の addon、イシュー #2063、headless anatomy は #2062)

input_group モジュールは fandhe_frontend_headless_ui::input_group の anatomy(root/addon/text/button の 4 パーツ)へ、shadcn/ui の Input Group 相当の見た目(コンテナ側 1 本の枠線・角丸・:focus-within フォーカスリングと、内側 Input/ Textarea/ NativeSelect/ Select の枠線なしリセット)を重ねる薄い委譲層である(NativeSelect/Select は root の直接の子として配置した場合のみリセットが効く、イシュー #3123)。

  • 公開 API: root/addon/text/button の 4 関数はいずれも見た目クラスを付与せず(下記「軸なし」参照)、呼び出し側 class を drop_class_attr で除去してから headless 同名関数へそのまま委譲する(同名再定義、[crate::visually_hidden] と同型のパターン)。InputGroupAlign/InputGroupProps は headless からの選択的再エクスポート。stylesheet()(css() ではない)が静的 CSS 全量を返す。
  • 軸なし: size/variant/color-palette いずれも提供しない(docs/design/pre-styled-ui-focus-ring-and-size-conventions.md §4 (d)「子の寸法に従属するレイアウト部品」に該当。寸法・文字サイズは内側の Input/Textarea の size に従属する)。
  • raw CSS 追記: SlotRecipe は子孫セレクタを表現できないため、stylesheet() は root > field::input/root > field::textarea の枠線・角丸・背景リセットと :focus-visible の outline 無効化を serialize_rule で追記する(crate::toggle_group/crate::number_input と同型のパターン)。イシュー #3123 で root > field::select(native_select)・root > select::root(styled select、枠線リセット自体は子孫の trigger パーツへ適用)の追加リセットを同型のパターンで末尾へ純追加した。native_select/select は input/textarea と異なり flex: 1 1 0% を与えず内容幅のまま(flex: 0 1 auto)インライン配置する。
  • 意図的非採用: shadcn の :has(control:focus-visible) によるリング限定(SlotRecipe/raw CSS のいずれも :has() の先例を持たないため root 単純な :focus-within で代替)、InputGroupButton の variant/size 軸(ghost 相当の固定見た目のみ)、addon クリックで input へフォーカスを移す JS 配線(fandhe-frontend-wasm-full 側の関心、親 #2061 に該当 sub-issue なし)はいずれもスコープ外とする。
  • バリデーション責務外: docs/policy/intentional-non-adoption.md §3.25 規則 1 のとおり、本モジュールは headless が出す data-invalid/data-disabled/data-align を CSS セレクタとして参照するだけで、値の妥当性判定・送信処理は実装しない。
  • docs サイト: input-group(イシュー #2063 でページ登録・showcase Demo・SPEC_TABLES 原稿を追加)。

4f-4. item(media + title/description + actions の汎用リスト行、イシュー #2066、headless anatomy は #2065)

item モジュールは fandhe_frontend_headless_ui::item の anatomy(root/media/content/title/description/actions/header/ footer/group/separator の 10 パーツ)へ、shadcn/ui の Item 相当の見た目を重ねる薄い委譲層である。

  • 公開 API: 10 関数はいずれも見た目クラスを付与せず、呼び出し側 class を drop_class_attr で除去してから headless 同名関数へそのまま委譲する(同名再定義、crate::input_group と同型のパターン)。ItemRootProps/ItemVariant/ItemSize/ItemMediaVariant は headless からの選択的再エクスポート。stylesheet()(css() ではない)が静的 CSS 全量を返す。
  • variant/size は headless の data-* を AttrEq で参照するのみ(意図的差分その 1): headless item::root が固定出力する data-variant(default/outline/muted)/data-size(default/ sm)、item::media の data-variant(default/icon/image)を StateCondition::AttrEq で参照するのみで、class ベースの SlotRecipe::variant/SlotRecipe::size_variants は持たない(docs/design/pre-styled-ui-data-attr-vocabulary.md §2.2「役割 B: 参照のみ」)。docs/design/pre-styled-ui-focus-ring-and-size-conventions.md §4 (b) が container 部品に求める 5 段 Size 軸は、item の size が headless 層で shadcn 語彙(2 値)へ固定済みのため二重符号化を避けて新設しない。
  • リンク時 hover: [href] 状態 + custom property 間接参照(意図的差分その 2): SlotRecipe は [href]:hover の複合条件を表現できないため、crate::badge::link と同じ StateCondition::Attr("href") パターンを踏襲する。root base で --fandhe-item-bg/--fandhe-item-hover-bg の 2 個の custom property を定義し、hover 規則は background: var(--fandhe-item-hover-bg, var(--fandhe-item-bg)) のように fallback 付きで参照する(fallback を欠くと href を持たない div root で背景が透明化する回帰があり、golden テストで固定済み)。
  • raw CSS 追記: SlotRecipe は子孫セレクタを表現できないため、stylesheet() は media[data-variant="image"] > img への object-fit: cover リセットを serialize_rule で追記する(crate::input_group と同型のパターン)。
  • バリデーション責務外: docs/policy/intentional-non-adoption.md §3.25 規則 1 のとおり、本モジュールはアプリケーションロジックを実装しない。
  • docs サイト: item(イシュー #2066 でページ登録・showcase Demo・SPEC_TABLES 原稿を追加)。

4f-5. command(cmdk 由来のコマンドパレット、イシュー #2070、headless anatomy は #2068)

command モジュールは fandhe_frontend_headless_ui::command の anatomy(root/input/list/empty/group/group-heading/item/shortcut/ separator/dialog の 10 パーツ)へ、入力欄・リスト・group 見出し・選択行の背景・shortcut の右寄せ・dialog 型の幅という shadcn/ui Command 相当の意匠を重ねる薄い委譲層である。pre-styled-only の footer パート(イシュー #3143)を加えた計 11 パーツ構成。

  • 公開 API: headless 10 関数はいずれも見た目クラスを付与せず、呼び出し側 class を drop_class_attr で除去してから headless 同名関数へそのまま委譲する(同名再定義、crate::item と同型のパターン)。filter_items(純粋関数、combobox::filter_options へ全委譲)と OpenState(headless からの再エクスポート)のみを選択的に公開する。headless の状態機械 Command/CommandAction は再エクスポートしない(crate::combobox/crate::select/crate::menu と同じ判断)。stylesheet()(css() ではない)が静的 CSS 全量を返す。
  • 軸を持たない: size/variant/color-palette いずれの軸も提供しない(docs/design/pre-styled-ui-focus-ring-and-size-conventions.md §4 (d)「子の寸法に従属するレイアウト部品」相当。headless にも shadcn/ui にも軸が無い)。
  • empty の表示切替 CSS: headless command::empty は present が true のときのみ data-empty を出力し hidden は付与しない(SSR 決定性契約)。本モジュールは empty slot を既定 display: none にし、[data-empty] が付いたときのみ display: block へ切り替える。
  • item の選択表現とホバー除外: 選択行の背景は [data-selected](data-highlighted は使わない)で表す。StateCondition::HoverExceptAttr で選択行を hover 対象から除外し、選択色が hover の淡色背景で洗い流されないようにする(crate::combobox の item hover と同型の対策)。
  • dialog の [hidden]・幅トークン・backdrop 不在: closed 時の hidden を確実に非表示化するため .state("dialog", StateCondition::Attr("hidden"), [display: none]) を明示登録する(crate::dialog の positioner と同じ fail-closed)。幅は --fandhe-command-dialog-max-width(既定 32rem)。headless dialog パーツは単一要素のため独立した backdrop は持たず、本モジュールも追加しない。
  • shortcut 内への kbd 合成: API を増やさず、shortcut の children へ crate::kbd::kbd を渡す使い方で kbd を合成する。
  • pre-styled-only footer パート(イシュー #3143): headless-ui の anatomy には存在しないレイアウト専用パート。fandhe_frontend_headless_ui::anatomy::Anatomy::part を直接呼び出して組み立てる(crate::drawer の body/footer/header と同型、headless anatomy は不変)。本モジュールの「見た目クラスを一切付与せず class 属性自体が出力から消える」契約を踏襲するため、crate::drawer::footer と異なり呼び出し側 class も drop_class_attr で除去する。root の直接の子として list/empty の後ろに置く想定で、children へ crate::kbd::kbd とテキストを組んでキー操作ヒントを表す(shortcut と同じ合成方式)。キー操作そのものの配線は持たない。呼び出し側はヒント 1 件(kbd + 説明テキスト)を span 等 1 つの子要素へまとめてから渡す(footer 自体の flex-wrap: wrap は子要素単位でしか折り返さないため)。
  • raw CSS 追記: SlotRecipe は子結合子セレクタを表現できないため、stylesheet() は dialog 内 root の二重枠を解除する [data-scope="command"][data-part="dialog"] > [data-scope="command"][data-part="root"] 規則と、footer 直接の子(呼び出し側が組む 1 ヒント分の span 等)を inline-flex のまとまり(min-width: 0・max-width: 100%・overflow-wrap: anywhere)にする [data-scope="command"][data-part="footer"] > * 規則、その中の kbd だけを white-space: nowrap にする [data-scope="command"][data-part="footer"] > * kbd 規則を serialize_rule で追記する。kbd と説明文は同じ行にまとまったまま、ヒントがコンテナ幅を超えると説明文が折り返す(overflow-wrap は white-space: nowrap と同じ規則では効かないため分離する。#3143 Codex P2 再指摘・PR #3582 Codex P2 / Cursor Bugbot 指摘対応)。
  • バリデーション責務外: 絞り込み配線・Enter 実行・Cmd/Ctrl+K のグローバルショートカット・フォーカストラップは fandhe-frontend-wasm-full の責務として実装しない(docs/policy/intentional-non-adoption.md §3.25 規則 1)。
  • docs サイト: command(イシュー #2070 でページ登録・showcase Demo・SPEC_TABLES 原稿を追加)。

4f-6. message(AI チャット UI の会話 1 発言、イシュー #2106、headless anatomy は #2105)

message モジュールは fandhe_frontend_headless_ui::message の anatomy(root/avatar/header/content/footer/group の 6 パーツ)へ、発言者の役割・整列で背景色を切り替える shadcn/ui Message 相当の意匠を重ねる薄い委譲層である。

  • 公開 API: 6 関数はいずれも見た目クラスを付与せず、呼び出し側 class を drop_class_attr で除去してから headless 同名関数へそのまま委譲する(同名再定義、crate::item/crate::command と同型のパターン)。MessageRootProps/MessageRole/MessageAlign(headless からの再エクスポート)のみを選択的に公開する。stylesheet()(css() ではない)が静的 CSS 全量を返す。
  • 軸を持たない: role/align/loading/error は headless が固定出力する data-role/data-align/data-loading/data-error を StateCondition::AttrEq/Attr で参照するのみで、class ベースの SlotRecipe::variant は持たない(docs/design/pre-styled-ui-data-attr-vocabulary.md §2.2「役割 B: 参照のみ」、crate::item と同型の判断)。
  • role 別の意匠: data-role="user" は content の背景を accent subtle、"assistant" は muted、"system" は透明 + 斜体 + 控えめ文字色へ切り替える。data-align="end" は root を右寄せ + 行反転する(role から独立した軸、headless message.rs モジュール doc「会話系 4 部品の共通語彙」参照)。
  • loading/error の視覚差: data-loading は root を半透明化(opacity: 0.7)するのみでアニメーションは付けない。data-error は content の背景・文字色・枠線を危険色(--fandhe-color-danger-*)へ切り替える。いずれも見た目のみで、判定・再送はアプリ責務。
  • group 連続発言のまとめと raw CSS 追記: SlotRecipe は子結合子セレクタを表現できないため、stylesheet() は group 配下 2 件目以降の root の余白を詰める規則と、avatar を visibility: hidden(display: none ではなく幅を残す)で省略する規則を serialize_rule で追記する(crate::item の media[data-variant="image"] > img 追記と同型のパターン)。
  • aria-live/aria-busy は付与しない: headless 契約の継承(headless message.rs モジュール doc「aria-live/aria-busy を付けない理由」参照)。通知が必要な場合は呼び出し側が自前で aria-live リージョンを合成する。
  • バリデーション責務外: 送信処理・Markdown レンダリング・再送判定は実装しない(docs/policy/intentional-non-adoption.md §3.25 規則 1)。
  • docs サイト: message(イシュー #2106 でページ登録・showcase Demo・SPEC_TABLES 原稿を追加)。

4f-7. bubble(AI チャット UI の吹き出し 1 個、イシュー #2109、headless anatomy は #2108)

bubble モジュールは fandhe_frontend_headless_ui::bubble の anatomy(root/content/reactions/reaction/collapse-trigger/ collapse-content の 6 パーツ)へ、塗り・枠線・無装飾の 3 形態と連続発言の角丸連結を重ねる薄い委譲層である。

  • 公開 API: 6 関数はいずれも見た目クラスを付与せず、呼び出し側 class を drop_class_attr で除去してから headless 同名関数へそのまま委譲する(同名再定義、crate::message と同型のパターン)。BubbleRootProps/BubbleVariant/BubbleGroupPosition(headless からの再エクスポート)に加え、MessageAlign(会話系部品共通語彙)・OpenState(crate::collapsible と同じ再エクスポート方式)のみを選択的に公開する。stylesheet() が静的 CSS 全量を返す。
  • 軸を持たない: variant/align/group-position/selected/state は headless が固定出力する data-variant/data-align/ data-group-position/data-selected/data-state を StateCondition::AttrEq/Attr/AttrEqAll で参照するのみで、class ベースの SlotRecipe::variant は持たない(docs/design/pre-styled-ui-data-attr-vocabulary.md §2.2「役割 B: 参照のみ」、crate::message と同型の判断)。
  • variant 別の意匠: data-variant="solid" は accent 塗り(既定)、"outline" は枠線のみ、"plain" は無装飾(背景・枠線なし)。root が公開する --fandhe-bubble-bg/--fandhe-bubble-fg/ --fandhe-bubble-border の 3 custom property を切り替える形で実装し、呼び出し側の上書きフックとしても機能する。
  • 角丸連結: data-align(start/end)× data-group-position(first/middle/last。single は base の角丸のまま)の複合条件を StateCondition::AttrEqAll のみで表現し、crate::message と異なり raw CSS 追記は不要(root 自身の 2 属性の組み合わせのみで完結するため)。「何番目か」の算出は利用者責務のまま変わらない。
  • ColorPalette 軸は持たない: 実際の色調選択(アクセント・破壊的操作色等)は headless 側の判断では ColorPalette 軸の責務とされているが、本イシューのスコープには含めず、上記 3 custom property を上書きフックとして残した(軸追加は後続提案、.claude/rules/coding-rust.md §3.25 規則 2 参照)。
  • 高さトランジション(イシュー #2192 の共通機構を #2282 で適用): collapse-content の開閉は @starting-style/ transition-behavior: allow-discrete による高さトランジション(collapsible/accordion と同型)。calc-size() 対応ブラウザでは hidden の切り替えだけで遷移が成立し、fandhe-frontend-wasm-full の実測高さ CSS 変数(--fandhe-content-height)への書き込みは不要。calc-size() 未対応ブラウザでは共通 preset の @supports not (...) が常に遷移を無効化するため、実測値を同期しても hidden による即時切り替えのままになる(auto フォールバック)。加えて fandhe-frontend-wasm-full の MAPPING_TABLE に (bubble, collapse-trigger) の配線が無いため、wire_headless_component 経由のクリックでは hidden の切り替え自体が発火しない(呼び出し側が独自に切り替える経路を用意する必要がある)。トリガー未配線と CSS の遷移条件は独立した別々の前提である。
  • reactions/reaction は非インタラクティブ: 押下・集計・トグルは実装しない(docs/policy/intentional-non-adoption.md §3.25 規則 1)。
  • docs サイト: bubble(イシュー #2109 でページ登録・showcase Demo・SPEC_TABLES 原稿を追加)。

4f-8. attachment(添付ファイル 1 件の表示、イシュー #2112、headless anatomy は #2111)

attachment モジュールは fandhe_frontend_headless_ui::attachment の anatomy(root/media/content/name/meta/progress/actions/ action の 8 パーツ)へ、横並びの行カード(file 形態)・縦積みのサムネイルカード(image 形態)の 2 意匠とアップロード失敗時の枠色を重ねる薄い委譲層である。

  • 公開 API: 8 関数はいずれも見た目クラスを付与せず、呼び出し側 class を drop_class_attr で除去してから headless 同名関数へそのまま委譲する(同名再定義、crate::bubble と同型のパターン)。AttachmentRootProps/AttachmentVariant/AttachmentState(headless からの再エクスポート)のみを選択的に公開する。stylesheet() が静的 CSS 全量を返す。
  • 軸を持たない: variant/state/disabled は headless が固定出力する data-variant/data-state/data-disabled を StateCondition::AttrEq/Attr で参照するのみで、class ベースの SlotRecipe::variant は持たない(docs/design/pre-styled-ui-data-attr-vocabulary.md §2.2「役割 B: 参照のみ」、crate::bubble と同型の判断)。
  • file/image の意匠: file(既定)は横並びの行カード(media は固定サイズの角丸アイコン枠、actions は末尾へ常時表示)。image は縦積みのサムネイルカード(media を正方形で敷き詰め、子 img は object-fit: cover)。
  • actions の hover 表示とタッチ端末対策: image 形態の actions は既定で隠れ root:hover/root:focus-within で表示するが、この opacity: 0 の既定非表示規則そのものを @media (hover: hover) 配下へ限定する。hover 機構を持たない端末(タッチ端末等)では規則が適用されず CSS の初期値 opacity: 1(常時表示)のまま残るため、「見えないボタンで操作不能になる」到達不能を避ける(src/attachment.rs モジュール doc「actions の hover 表示とタッチ端末対策」節参照)。
  • data-state="error": root の枠色・meta の文字色を var(--fandhe-color-danger) へ切り替える(root/meta は別 slot のため raw CSS で追記)。
  • progress スロット: attachment scope の単純なスロットであり crate::progress::Progress を委譲しない。アップロード進捗を表示するときは呼び出し側がスタイル済み progress(root/track/range)を中身へ入れ子にする契約(headless 契約を継承)。
  • action: ゴーストボタン(type="button" 固定・label が空文字列でないときのみ aria-label)。disabled は root の disabled(opacity 0.5)とは独立し、二重減衰を避けるため cursor: not-allowed のみを適用する。
  • ColorPalette 軸は持たない: 本イシューのスコープに含まれない(軸追加は後続提案、.claude/rules/coding-rust.md §3.25 規則 2 参照)。
  • docs サイト: attachment(イシュー #2112 でページ登録・showcase Demo・SPEC_TABLES 原稿を追加)。

4f-9. marker(会話中の注記行、イシュー #2115、headless anatomy は #2114)

marker モジュールは fandhe_frontend_headless_ui::marker の anatomy(root/icon/content の 3 パーツ)へ、インライン注記表示(note 形態)・行下の境界線(divider 形態)・中央ラベル + 左右の線(label 形態)の 3 意匠と tone 別の文字色・線色を重ねる薄い委譲層である。

  • 公開 API: 3 関数はいずれも見た目クラスを付与せず、呼び出し側 class を drop_class_attr で除去してから headless 同名関数へそのまま委譲する(同名再定義、crate::attachment と同型のパターン)。MarkerRootProps/MarkerVariant/MarkerTone(headless からの再エクスポート)のみを選択的に公開する。stylesheet() が静的 CSS 全量を返す。
  • 軸を持たない: variant/tone は headless が固定出力する data-variant/data-tone を StateCondition::AttrEq で参照するのみで、class ベースの SlotRecipe::variant(ColorPalette 軸)は持たない(docs/design/pre-styled-ui-data-attr-vocabulary.md §2.2「役割 B: 参照のみ」、crate::attachment と同型の判断)。
  • 区切り線は疑似要素を使わない: divider 形態は DOM を増やさず root へ border-bottom の state 規則を付与するのみ。label 形態は呼び出し側 children を crate::separator::separator(horizontal・Solid、aria-hidden="true")2 個で挟んでから headless へ委譲する(区切り線を疑似要素・専用 DOM ではなく separator パート再利用で描画する設計、src/marker.rs モジュール doc「区切り線の描画方式」節参照)。
  • data-tone 別の色: neutral(既定・base のみ)/info/warning/ danger の 4 値それぞれに文字色(--fandhe-color-<tone>-fg-subtle)と線色用 custom property --fandhe-marker-line(--fandhe-color-<tone>-muted)を割り当てる。
  • ColorPalette 軸は持たない: 本イシューのスコープに含まれない(軸追加は後続提案、.claude/rules/coding-rust.md §3.25 規則 2 参照)。
  • docs サイト: marker(イシュー #2115 でページ登録・showcase Demo・SPEC_TABLES 原稿を追加)。

4f-10. questionnaire(多段質問 UI、イシュー #2119、headless anatomy は #2117、wasm-full 配線は #2118)

questionnaire モジュールは fandhe_frontend_headless_ui::questionnaire の anatomy(root/progress/question/prompt/description/options/ freeform/actions/back/next/skip の 11 パーツ)へ、質問カード・進捗ゲージ・選択肢の choice card 風表示・completed/upcoming の視覚差を重ねる薄い委譲層である。

  • 公開 API: 11 関数すべてが state: &Questionnaire(headless-ui の状態機械)を第 1 引数に取り、内部で state.<part>(...) へ委譲する(crate::steps と同型。headless 側に自由関数がないため)。見た目クラスは付与せず、呼び出し側 class を drop_class_attr で除去してから委譲する。QuestionProps/QuestionnaireAction(headless からの再エクスポート)のみを選択的に公開する。Questionnaire 状態機械自体は再エクスポートしない(呼び出し側は fandhe_frontend_headless_ui:: questionnaire::Questionnaire を直接 import する)。stylesheet() が静的 CSS 全量を返す。
  • 軸を持たない: size/colorPalette は持たず、headless が固定出力する data-state(active/completed/upcoming)・data-answered/ data-skipped/data-invalid/data-disabled/data-complete を StateCondition::Attr/AttrEq で参照するのみ。data-state="active" は base と同値のため state 規則を持たない。
  • progress の塗り: styled progress を入れ子にせず、step/count から百分率を計算した --fandhe-questionnaire-percent custom property を style へ設定し、CSS の linear-gradient で塗り幅を表現する(crate::progress::range の percent_style/drop_style_attr と同型のパターン)。既知の制約: fandhe-frontend-wasm-full(イシュー #2118)はクライアント側遷移で aria-valuenow/aria-valuetext を更新するが本 custom property は更新しないため、遷移直後はアプリ側再描画までゲージが古い値を示す。
  • 選択肢の choice card 風表示: options slot 配下の [data-scope="radio-group"][data-part="item"]/ [data-scope="checkbox-group"][data-part="item"] をカード状に整形する子孫セレクタを stylesheet() に含む(SlotRecipe が子孫セレクタを表現できないための raw CSS 追記、crate::marker と同型)。item 自体の基本規則は含まないため、crate::radio_group::stylesheet/ crate::checkbox_group::stylesheet の併用が必要。
  • ColorPalette 軸は持たない: 本イシューのスコープに含まれない(軸追加は後続提案、.claude/rules/coding-rust.md §3.25 規則 2 参照)。
  • docs サイト: questionnaire(イシュー #2119 でページ登録・showcase Demo・SPEC_TABLES 原稿を追加)。

4f-11. message_scroller(会話ログのスクロールコンテナ、イシュー #2123、headless anatomy は #2121、wasm-full 配線は #2122)

message_scroller モジュールは fandhe_frontend_headless_ui::message_scroller の anatomy(root/ viewport/content/anchor/jump-to-latest/load-more の 6 パーツ)へ、高さ確保・ネイティブスクロール・端フェード・浮遊 jump-to-latest ボタン・履歴読み込みトリガーの意匠を重ねる薄い委譲層である。

  • 公開 API: 6 関数はいずれも見た目クラスを付与せず、呼び出し側 class を drop_class_attr で除去してから headless 同名関数へそのまま委譲する(同名再定義、crate::message と同型のパターン)。MessageScrollerRootProps/MessageScrollerStuck(headless からの再エクスポート)のみを選択的に公開する。stylesheet() が静的 CSS 全量を返す。
  • 軸を持たない: data-stuck(bottom/free)・data-has-new・jump_to_latest の data-visible/hidden・load_more の data-loading/data-disabled はいずれも headless が固定出力するものを StateCondition::AttrEq/Attr で参照するのみで、class ベースの SlotRecipe::variant は持たない(docs/design/ pre-styled-ui-data-attr-vocabulary.md §2.2「役割 B: 参照のみ」、crate::message と同型の判断)。
  • 端フェードは既定 on: viewport は mask-image による両端フェードを base 宣言として持つ(crate::scroll_area の data-fade は opt-in だが、本部品は会話ログ専用の新規部品であり既定挙動を変える既存利用者が存在しないため既定 on にした)。--fandhe-message-scroller-fade-start/ -end を 0px へ上書きすれば片端無効化できる。root[data-stuck= "bottom"] > viewport の raw CSS 追記(子結合子)で末尾フェードを 0px へ上書きし、最下部に張り付いているときは最新メッセージを霞ませない。crate::scroll_area が持つ @supports (animation-timeline: scroll()) によるスクロール量連動アニメーションは採用しない(意図的非採用、src/message_scroller.rs モジュール doc「端フェードの採否」節参照)。
  • hidden 属性の上書き: jump-to-latest の base 宣言が display: inline-flex を持つため、UA スタイルシートの [hidden] { display: none } を詳細度で上書きしてしまう。.state("jump-to-latest", StateCondition::Attr("hidden"), [display: none]) を明示登録して回避する(crate::command/crate::bubble と同型の論法)。
  • root[data-has-new] > jump-to-latest の raw CSS 追記: SlotRecipe は別 slot の宣言を切り替える規則を組めないため(crate::message と同型の制約)、serialize_rule による子結合子規則で data-has-new 付き root 配下の jump-to-latest をアクセント色へ強調する。
  • load-more の spinner: SlotRecipe::pseudo_element(イシュー #2201)は状態条件と合成できないため [data-loading]::before は DSL で書けない。かわりに crate::button と同型で、styled load_more が loading == true のとき crate::spinner::spinner_decorative(Size::Sm・ColorPalette::Neutral、装飾的・aria-hidden のみ)を children 先頭へ埋め込む。
  • aria-live/aria-busy/aria-posinset/aria-setsize を付与しない理由: headless 判断の継承。通知タイミング・総数の伝達はアプリ固有の判断であり、本モジュールは見た目のみを担う。
  • ColorPalette 軸は持たない: 本イシューのスコープに含まれない(軸追加は後続提案、.claude/rules/coding-rust.md §3.25 規則 2 参照)。
  • docs サイト: message-scroller(イシュー #2123 でページ登録・showcase Demo・SPEC_TABLES 原稿を追加)。
  • スコープ外: wasm-full 配線(最下部追従・新着検知・履歴読み込み時の位置維持・data-* の実行時更新)は #2122。

4f-12. data_table(表の操作 UI、イシュー #2127、親 #2124、headless anatomy は #2125、wasm-full 配線は #2126)

data_table モジュールは fandhe_frontend_headless_ui::data_table の anatomy(root/toolbar/column-header/sort-trigger/select-all/ select-row/footer/selection-count の 8 パーツ)へ、shadcn/ui Data Table 相当(toolbar・ソート可能列ヘッダー・行選択・非表示列・footer のページング/件数表示)の意匠を重ねる薄い委譲層である。

  • 公開 API: 8 関数はいずれも見た目クラスを付与せず、呼び出し側 class を drop_class_attr で除去してから headless 同名関数へそのまま委譲する(同名再定義、crate::message_scroller と同型のパターン)。DataTable/DataTableAction/DataTableProps/ColumnProps/ ColumnHeaderProps/SortDirection/column_attrs/column_header_attrs/ row_attrs(headless からの再エクスポート)を選択的に公開する。stylesheet() が静的 CSS 全量を返す。
  • 軸を持たない: ColorPalette/Size 等の見た目 variant を公開しない(本イシューのスコープ外)。data-loading/data-empty/data-sort/ data-state/data-hidden はいずれも headless が固定出力するものを StateCondition::Attr/AttrEq で参照するのみで、class ベースの SlotRecipe::variant は持たない(docs/design/ pre-styled-ui-data-attr-vocabulary.md §2.2「役割 B: 参照のみ」)。
  • 表本体は table mod をそのまま使う: headless doc「イシュータイトルとの差分」節の契約どおり、<table>/<thead>/<tbody>/<tr> は本モジュールでは新規に作らず crate::table を使う。列ヘッダー/セル/行の表示状態(ソート・列非表示・行選択)は column_attrs/ column_header_attrs/row_attrs(node を作らない属性ヘルパ)を table::column_header/table::cell/table::row の attrs へ渡すことで同一要素に 2 つの data-scope を載せずに合成する。本モジュール自身が持つ column_header/sort_trigger(th/button ノードを実際に生成するパーツ)は Primitives 経路(自前 CSS を書く利用者・headless 単独利用)向けであり、Themes 経路(table::column_header へ委譲する側)の Demo には現れない。select_all/select_row は <table> セル階層に直接載る th/td であるため、Themes 経路でもそのまま使う。
  • ソート方向アイコンは raw CSS ::after で表現: SlotRecipe::state は状態条件付きの疑似要素を DSL で表現できない(§4f-11 節と同型の制約)ため、sort-trigger の [data-sort="ascending"|"descending"|"none"] ::after を serialize_rule による raw CSS 追記で stylesheet() へ連結する。content 値はすべて &'static str リテラル。
  • 選択行背景は自前で持たない: row_attrs が付与する data-selected は crate::table の row slot が既に持つ state 規則(イシュー #2052)がそのまま適用される。本モジュールは row slot を持たない。
  • select-all/select-row は --fandhe-table-* を参照しない: data-scope="data-table" の要素であり table scope ではないため、独自の --fandhe-data-table-* 変数のみを参照する(crates/docs-site/tests/css_var_scope_prefix.rs 契約)。
  • empty/skeleton の本体を持たない: empty-state/skeleton の本体は crate::empty_state/crate::skeleton が担う。本モジュールは root の data-empty/data-loading(+ aria-busy="true")という表示状態のみを持つ。
  • docs サイト: data-table(イシュー #2127 でページ登録・showcase Demo・SPEC_TABLES 原稿を追加)。
  • スコープ外: fandhe-frontend-wasm-full の DOM 配線(sort-trigger click → dispatch、indeterminate、data-hidden 反映、ページング data-disabled)は #2126。ColorPalette/Size 軸の追加、column-header slot と table::column-header の統合(同一要素 2 scope 禁止のため現設計を維持)は必要になれば別イシュー。

4g. checkbox_card/radio_card(カード型選択 UI)

chakra-ui の forms/checkbox-card.md/forms/radio-card.md 相当。ark-ui には対応する headless anatomy が存在しない(chakra-ui 独自の slot recipe)ため、fandhe-frontend-headless-ui には手を入れず、pre-styled-ui 層のみで新規 anatomy data-scope="checkbox-card"/"radio-card" を定義する(crate::card が pre-styled 層で独自 anatomy data-scope="card" を持つ先例と同型の構成、詳細は各モジュール冒頭の rustdoc 参照)。

  • 状態機械の再利用(新規状態機械は作らない): checkbox_card は fandhe_frontend_headless_ui::checkbox::{Checkbox, CheckboxProps, CheckedState} を、radio_card は fandhe_frontend_headless_ui::radio_group::RadioGroup をそのまま利用する。Checkbox/RadioGroup 自体は再エクスポートしない(checkbox/radio_group モジュールと同じ「未スタイル root の静かな適用漏れ防止」判断。呼び出し側は headless モジュールを直接 import する)。
  • anatomy パーツ構成: checkbox_card は root(<label>)/control/ content/label/description/addon/indicator(チェックボックス外枠)/ indicator-check(チェックマーク本体、checkbox::indicator 相当)/ hidden-input の 9 パーツ。radio_card は root(role="radiogroup")/ label/item(<label>)/item-control/item-content/item-text/ item-description/item-addon/item-indicator(ラジオ円、radio_group::item_control 相当)/item-hidden-input の 10 パーツ。chakra-ui の単一 Indicator を「外枠 + マーク」の 2 要素に分けるのは、SlotRecipe が疑似要素を持たず既存 checkbox/radio-group の実証済み border/transform/box-shadow 描画をそのまま再利用するため。
  • hidden-input/item-hidden-input の属性契約: 対応する headless モジュール(crates/headless-ui/src/checkbox.rs/radio_group.rs)の hidden_input/item_hidden_input と同一ロジックで出力する(両ファイルを合わせて確認する契約)。
  • size/color-palette 軸: §4d の統一方針に従い root へのみクラスを付与し、--fandhe-checkbox-card-*/--fandhe-radio-card-* の root スコープ custom property 経由で子孫パーツへ伝搬する。
  • フォーカスリング: 実フォーカスは hidden-input が受けるため、radio_group の item と同型の StateCondition::FocusWithin(no-JS フォールバック)のみを root(checkbox_card)/item(radio_card)へ登録する。data-focus-visible(wasm 配線によるキーボード操作専用リング)は crates/wasm-full/src/focus_visible.rs の (scope, part) マッピングに "checkbox-card"/"radio-card" が未登録のため現状スコープ外。

4h. 静的部品 status/empty_state

chakra-ui の feedback/status.md/feedback/empty-state.md 相当。状態機械を要しない静的マークアップ部品であり、fandhe-frontend-headless-ui には手を入れない(headless anatomy 自体が存在しないため checkbox_card/ radio_card と同型に pre-styled 層のみで新規 anatomy を定義する)。

  • status(scope "status"、root/indicator の 2 パーツ): size(ドット径・フォントサイズ)/color-palette(Alert/Badge/Spinner と同じ --fandhe-palette-* セマンティック色)の 2 軸 variant を root へ付与する。indicator の直径は root の variant が設定する --fandhe-status-dot-size custom property を継承経由で参照する(§4d の「root variant が子孫スコープの custom property を設定する」統一方針と同型)。role/aria-live は付与しない: ラベルテキスト自体が状態を伝える静的表示であり、[spinner::spinner] のような非同期読み込み中の live region 告知とは用途が異なる(呼び出し側が動的な状態変化を告知したい場合は attrs へ明示的に role/aria-live を足す設計)。
  • empty_state(scope "empty-state"、root/content/indicator/ title/description/actions の 6 パーツ): crate::card と同型の中立レイアウトコンテナであり color-palette 軸は提供しない。size は root の --fandhe-empty-state-* custom property 経由で padding・content の gap・indicator/title/description の文字サイズを連動させる(イシュー #1560、chakra-ui EmptyState の size スケールに整合)。title/description は <div>(見出し要素 <h1>〜<h6> にしない)とし、埋め込み位置に応じて見出しレベルが変わり得る呼び出し文脈で固定レベルを強制しない(crate::alert::title と同型の判断)。indicator はアイコン等を children として受け取り、外部リソース・アイコンフォントを本クレートが直接参照することはない。イシュー #2047(shadcn/ui Empty 突合)で root の variant(EmptyStateVariant、既定 Plain。Outline は破線枠、Subtle は淡色単色背景)と indicator の variant(indicator_with 関数、EmptyStateIndicatorVariant、既定 Plain。Boxed は bg-muted の角丸タイル)を追加した。破壊的変更: variant は EmptyStateProps の必須フィールドとして追加したため、フィールド全列挙の構造体リテラル EmptyStateProps { size } は本変更以降コンパイルできない。既存の呼び出しは EmptyStateProps { size, ..Default::default() }(または EmptyStateProps { size, variant: EmptyStateVariant::Plain })へ移行する必要がある。バイト不変なのは「既定 EmptyStateProps::default()(variant: EmptyStateVariant::Plain)を渡したときの CSS 出力・HTML 出力」および既存の CSS ブロック自体であり、Outline/Subtle/Boxed の各 variant 用に CSS ブロックが新規追加されている点は不変ではない。shadcn の EmptyHeader(indicator/title/description のグルーピング用 slot)は非追随(content の gap + section-gap で同等の視覚リズムを実現済み)。

4i. タイポグラフィ静的部品(Heading / Text / Em / Mark / Blockquote / List)

chakra-ui typography/heading.md / text.md / em.md / mark.md / blockquote.md / list.md 相当の 6 静的部品。headless 状態機械を要しない「単一 recipe / slot recipe 静的部品」(badge/skeleton と同型)で、h1-h6/p/em/mark/blockquote/ul・ol・li の素の HTML 意味論をそのまま styled 化する。

variant 表

モジュールパーツタグ選択variant 軸colorPalette備考
headingroot(単一)HeadingLevel(h1〜h6、意味論レベル)HeadingSize(xs/sm/md/lg/xl(既定)/xl2/xl3/xl4/xl5/xl6(イシュー #2440)、font-size/line-height、視覚サイズ)・HeadingWeight(normal/medium/semibold(既定)/bold、イシュー #2056)なしタグ選択(意味論)とサイズ/ウェイト variant(視覚)は独立。chakra の 7xl のみテーマトークン範囲外のため非採用。data-bordered(値なし・呼び出し側 attrs 経由の opt-in 状態、shadcn h2 の下罫線相当)も持つ
textroot(単一、<p> 固定)—TextSize(xs/sm/md(既定)/lg/xl/xl2/xl3/xl4/xl5/xl6(イシュー #2440)、イシュー #1442)・TextWeight(normal(既定)/medium/semibold/bold、イシュー #1442)・TextVariant(plain(既定)/muted、イシュー #2055。muted は fg-muted トークンを使う前景色軸)なし—
emroot(単一、<em> 固定)—なしなしvariant 軸を持たない最小部品(link_overlay と同型)
markroot(単一、<mark> 固定)—MarkVariant(subtle(既定)/solid/text/plain)あり(5 値)badge と同型の単一 recipe パターン
blockquoteroot(<figure>)/content(<blockquote>)/caption(<figcaption>)—BlockquoteVariant(subtle(既定)/solid/plain)あり(5 値、root のみ)content が素の <blockquote> のため引用の HTML 意味論を保つ
listroot(<ul>/<ol>)/item(<li>)/indicator(<span aria-hidden="true">)ListType(Unordered(既定)/Ordered)ListVariant(marker(既定)/plain)なしindicator は常時 aria-hidden="true"(呼び出し側が外せない fail-closed、skeleton と同型)

heading/list の「タグ選択」は variant クラスではなく、レンダリングする HTML タグそのものを選ぶ引数である点に注意(recipe::VariantValue を実装しない)。

4j. charts 軸・グリッド・凡例・ツールチップ

chakra-ui charts/axes.md / cartesian-grid.md / legend.md / tooltip.md 相当。charts 基盤(crates/pre-styled-ui/src/charts/{data,scale,svg}.rs)の最初の消費者であり、pre_styled_ui::charts::{axis, grid, legend, tooltip} の 4 サブモジュールとして実装する(新規トップレベルモジュールは追加しない。詳細な設計判断は docs/design/charts-foundation-design.md 参照)。

API 一覧

モジュール主な公開関数戻り値
charts::axisy_axis(scale, ticks, x, props) / x_axis_linear(scale, ticks, y, props) / x_axis_categories(range, categories, y, props)Result<Node, ChartError>
charts::gridcartesian_grid(x_range, y_range, x_positions, y_positions, props)Result<Node, ChartError>
charts::legendlegend(data: &ChartData, props: &LegendProps) / category_legend(data, props)(LegendProps は title/hide_marker/align/marker〔イシュー #2086〕/hidden_series/hidden_categories/controls〔イシュー #2133〕。各 item は <button aria-pressed> を持つ trigger slot)Node(infallible)
charts::tooltipdatum_label(category, series, value) / datum_label_lines(heading, entries, footer)(複数行、イシュー #2086) / datum(cx, cy, r, label, attrs)String / String / Node(いずれも infallible)
charts::tooltip(indicator/hover 強調、イシュー #2131)layer_with(data, active, &TooltipLayerProps) / layer_from_entries_with(entries, active, &TooltipLayerProps)(TooltipLayerProps { indicator: TooltipIndicator }。既定 Dot は既存 layer/layer_from_entries とバイト一致) / frame_with(&FrameProps, children)(FrameProps { has_active: bool })Node(いずれも infallible)
charts::data(系列設定、イシュー #2077)Series::with_label(label) / with_color(SeriesColor) / with_icon(Node) / display_label()Series / &str
charts::data(系列色、イシュー #2077)SeriesColor::token(name) / chart_slot(1..=6) / palette(ColorPalette) / ChartData::series_color_var(index)Result<SeriesColor, ThemeError>(palette のみ infallible) / String

各モジュールは css() を公開し、stylesheet.rs の一元化リスト(all_styled_component_css)へ "charts/axis" 等のキーで登録済み。

anatomy / recipe

  • axis/grid/tooltip は scope "chart" を共有する(slot 名が互いに素なため CSS セレクタは衝突しない、SlotRecipe は scope の一意性を要求しない)。slot: x-axis/y-axis/axis-line/tick-line/ tick-label(axis)・grid/grid-line(grid)・datum(tooltip)。
  • legend は独立 scope "chart-legend" を持つ(SVG 外の通常 HTML <ul>/<li>/<span> のため)。slot: root/title/item/marker/ label/icon(イシュー #2077、Series::icon 指定時に marker の代わりに描画される代替スロット。shadcn/ui ChartConfig.icon 相当)。align(root slot、Start(既定)/Center/End)・marker(marker slot、Circle(既定)/Square)の 2 variant 軸をイシュー #2086(shadcn/ui Charts 突合)で純追加した。
  • grid の線種は GridLines(Solid(既定)/Dashed)の 1 軸 variant。
  • tooltip の hover 強調は crate::recipe::StateCondition::Hover(:hover 擬似クラス)を使う唯一の消費者。base で背景色ハロー(--fandhe-color-bg)を敷き、hover で前景色(--fandhe-color-fg)へ切り替える方式(イシュー #1593)。

SSR ツールチップ方式(JS 不使用)

マウス追従型のリッチツールチップ(recharts <Tooltip> の cursor 追従)は JS ランタイムが必須のためスコープ外(#2128 系〔#2129 が SSR ツールチップ DOM・hit-area、#2130 が JS 配線〕が実装を担う)。代わりに tooltip::datum がデータ点(<circle>)へ子 <title> 要素(ブラウザネイティブな hover 表示)と aria-label 属性(同一文字列)を埋め込み、StateCondition::Hover による CSS のみの視覚強調と組み合わせて「ホバーで詳細が分かる」体験を実現する。静的テキストは 1 行の datum_label と複数行の datum_label_lines(見出し省略可・整形済み値・footer、イシュー #2086、shadcn/ui chart-tooltip-default/-advanced 相当)の 2 択。icon 合成は #2129 のスコープ外であり本モジュールには含まれない。

indicator バリアント・hover 強調(イシュー #2131)

tooltip-indicator(ツールチップの色見本)の見た目は TooltipIndicator(Dot〔既定〕/Line/Dashed/None)から選べる。layer_with/ layer_from_entries_with(layer/layer_from_entries の indicator 選択版)が TooltipLayerProps { indicator } を受け取る。Dot(既定)/None は CSS class を持たず(golden 純追加原則)、None は tooltip-indicator の <span> 自体を省略する(shadcn/ui hideIndicator 相当)。

hover 強調(active な点の拡張・非 active 点の減光)は「祖先の状態 → 継承される custom property → 各要素が消費」方式(table::root の --fandhe-table-stripe-bg と同型、SlotRecipe が子孫・兄弟セレクタを持たないため):

要素属性CSS
tooltip::frame / 8 個の chart 部品それぞれの rootdata-has-active(tooltip::frame_with の FrameProps { has_active: true } で明示できる)--fandhe-chart-inactive-opacity: 0.4(pie/donut/radial は追加で --fandhe-chart-active-scale: 1.05)を宣言
視覚要素(datum/point/bar/segment)data-index(既存語彙)opacity: var(--fandhe-chart-inactive-opacity, 1) + transition。祖先が data-has-active を持たなければ 1 へフォールバックし常時フル不透明
同要素data-active(既存語彙)opacity: 1 で上記を上書き。donut の外径拡張済み active_index との二重拡大を避けるため、pie/donut/radial は transform: scale(var(--fandhe-chart-active-scale, 1))(root[data-has-active] が無ければ 1 で拡大なし)、他は固定 scale(1.5)

実際のマウス操作で data-has-active を付け外しする wasm-full 側の配線は本イシュー(#2131)の時点では未実装(#2130 は data-active/hidden の付け外しのみ実装し、祖先属性の付け外しはスコープに含まれなかった)。このため上記の減光 CSS は、SSR が [FrameProps { has_active: true }] を明示する静的 Demo(/themes/charts/)以外の経路では発火しない。prefers-reduced-motion: reduce は Theme::to_css の --fandhe-motion-duration-*: 0ms 一括上書きで自動対応するため、本モジュールには @media を追加していない。

系列設定(label/color/icon、イシュー #2077)

shadcn/ui の ChartConfig(系列キー → label/color/icon を 1 箇所で定義する設定オブジェクト)相当の機能を、charts::data::Series の任意フィールド(Series::with_label/with_color/with_icon)として提供する。ChartData の消費者(legend・line_chart/area_chart/bar_chart/ radar_chart)はいずれも ChartData::series_color_var(index) を経由して色を解決するため、系列 1 箇所への設定が全消費者へ一元的に反映される(chakra-ui useChart の series: [{ name, color, label }] と同型の設計。shadcn のような並列の ChartConfig マップは設けない)。色は charts::data::SeriesColor(theme.rs の TokenName allowlist を通過した色トークン名のみを保持する newtype)経由でのみ指定でき、style/fill/ stroke へ渡る値は var(--fandhe-color-<name>) の固定形以外を生成しない(REQ-1 相当)。--fandhe-color-chart-1〜-6(6 段階)は shadcn/ui の --chart-1〜-5(5 段階)のスーパーセットのため変更していない(Phase 0 イシュー #2005、docs/design/color-token-system.md §9.2 で決定済み)。

4k. LineChart / AreaChart / Sparkline(charts 基盤の消費者)

docs/design/charts-foundation-design.md が提供する charts::data::ChartData(カテゴリ + 系列の値モデル)・charts::scale::LinearScale(線形座標写像)・charts::svg(SVG ノード木ヘルパー、fmt_coord/ViewBox/svg_root/ PathBuilder)を消費し、「プロット領域(折れ線・面・スパーク)のみを描く自己完結 SVG」として実装する。軸・グリッド・凡例・ツールチップ(chakra の CartesianGrid/XAxis/YAxis/ChartLegend/ChartTooltip 相当)は §4j のスコープであり本 3 部品には含まれない。

API 概要

モジュール入力 (*Props)出力関数既定 viewBox
line_chartdata: &ChartData / aria_label / width / height / sizeline_chart(&props, attrs) -> Result<Node, ChartError>300 × 150
area_chart同上area_chart(&props, attrs) -> Result<Node, ChartError>300 × 150
sparklinevalues: &[f64] / aria_label / width / height / sizesparkline(&props, attrs) -> Result<Node, ChartError>112 × 48(chakra w={28} h={12} トークン相当)

いずれも *Props::new(data_or_values, aria_label) が既定寸法・Size::Md で組み立てる便利コンストラクタを提供する。

variant 表

軸値適用パーツ効果
size(Size)Sm/Md(既定)/Lgroot--fandhe-<scope>-height custom property 経由で plot(svg)の CSS 表示高さを切替(viewBox の描画座標系とは独立、qr_code と同型)

color-palette 軸は非提供。系列色は charts::series_color_var(index)(var(--fandhe-color-chart-1..6) を系列数に応じて循環)を stroke/fill 属性へ直接付与する(CSS variant ではなく描画時の固定色指定、複数系列を同時に描く都合上 SlotRecipe の軸機構に載せない判断)。

座標写像・エッジケース

  • x 軸: カテゴリ index を等間隔配置(単一カテゴリは中央 1 点)。
  • y 軸: ChartData::domain()(フラットデータの非退化パディング込み)を LinearScale::new(domain, (height, 0.0)) で写像(nice() は適用しない)。
  • 単一カテゴリ(n == 1): 折れ線・面のいずれも生成せず、中央に半径固定の circle マーカーのみを描く。
  • 負値・フラットデータ: charts 基盤の domain()/fmt_coord の契約に従い決定的に描画する(golden テスト crates/pre-styled-ui/tests/charts_line_area_sparkline.rs 参照)。

AreaChart の shadcn/ui 突合バリアント(イシュー #2081)

AreaChartProps は shadcn/ui Charts(area、10 バリアント)と突合した以下の追加フィールドを持つ(すべて既定値で #2081 以前の出力と完全に同一の HTML を生成する、golden 純追加原則)。

フィールド型既定効果
curveAreaCurve(Linear/Natural/Step)Linear曲線種。Natural は d3-shape curveNatural 相当の自然三次スプライン(charts::curve::natural_control_points)、Step は区間中点で段差になる補間(charts::curve::step_points)。n == 2 の Natural は直線へ退化する
stackAreaStack(None/Normal/Expand)None積み上げ。Normal/Expand は全系列の負値を ChartError::NegativeValue として拒否する。Expand は domain (0.0, 1.0) 固定でカテゴリ合計比率を描く
fillAreaFill(Solid/Gradient)Solid塗り。Gradient は系列ごとの <linearGradient>(縦方向、stop-opacity 0.8→0.1)を <defs> に生成し series-area へ url(#...) 参照を張る
gradient_id&str"fandhe-area"fill: Gradient 時の <linearGradient id> 接頭辞。css::is_valid_identifier を満たさなければ ChartError::InvalidGradientId。同一ページに複数チャートを置く場合は呼び出し側が一意化する
show_x_axis / show_y_axis / show_gridboolfalseX 軸(カテゴリラベル)・Y 軸(nice() 適用済み数値目盛。stack: AreaStack::Expand は domain 固定のため nice() 対象外)・水平グリッド線の描画有無。プロット領域の余白は軸ラベル単位で個別に確保する(show_y_axis のみ Y 側 40px、show_x_axis のみ X 側 24px。show_grid 単独は余白を確保しない)。いずれか 1 つでも true なら余白差し引き後の領域が 0 以下かを検査し、0 以下なら ChartError::PlotAreaTooSmall。stack: AreaStack::Normal/Expand にも同じ規則で適用される(積み上げ時に無視される欠陥の是正)。Expand の Y 軸目盛ラベルはカテゴリ合計比率を % 表示する

意図的に対応しない点(crate::area_chart モジュール doc「意図的に合わせなかった点」参照): series-area の既定 fill-opacity: 0.2(chakra-ui 値、shadcn の 0.4 は不採用)・dots/label/横向きレイアウト・tickFormatter(アプリ側整形の責務)・マウス追従ツールチップ/期間切替/凡例トグル等の実行時インタラクション(#2128/#2132)。

LineChart の shadcn/ui 突合バリアント(イシュー #2083)

LineChartProps は shadcn/ui Charts(line、10 バリアント)と突合した以下の追加フィールドを持つ(すべて既定値で #2083 以前の出力と完全に同一の HTML を生成する、golden 純追加原則)。curve は area_chart と同じ charts::Curve(charts::curve モジュールへ統合済み)を共有する。

フィールド型既定効果
curvecharts::Curve(Linear/Natural/Step)Linear曲線種。area_chart::AreaCurve と同じジオメトリ(charts::curve::line_path_d)を共有する
dotsLineDots(None/Filled/Hollow)Noneデータ点マーカー。Filled は系列色(または color_by_category 有効時はカテゴリ色)で塗りつぶし、Hollow は背景色塗り + 系列色輪郭の静的近似(shadcn chart-line-dots-custom の任意アイコン形状は非対応)
labelLineLabel(None/Value/Category)Noneデータ点上のラベル。Value は fmt_coord 済みの値、Category はカテゴリ名。有効時はプロット領域上側へ LABEL_TOP_MARGIN(20px)を確保する
color_by_categoryboolfalsedots 有効時、点の色をカテゴリ index で charts::series_color_var の 6 色循環へ切り替える(shadcn chart-line-dots-colors 相当)。false は系列色のまま
show_x_axis / show_y_axis / show_gridboolfalseX 軸・Y 軸・水平グリッド線の描画有無。area_chart と同じ余白規則(show_y_axis のみ左 40px、show_x_axis のみ下 24px)を共有する。label の上余白とは独立に確保する。いずれかの余白差し引き後の領域が 0 以下なら ChartError::PlotAreaTooSmall

意図的に対応しない点(crate::line_chart モジュール doc「意図的に合わせなかった点」参照): tickFormatter(アプリ側整形の責務)・任意アイコンによるカスタム点形状・activeDot(hover 時の点拡大)・マウス追従ツールチップ・hit-area data-*(#2128)・期間切替・凡例トグル(#2132)・点ごとの任意色(color_by_category のトークン循環で代替)・積み上げ・横向き(shadcn line registry に存在しない)。

RadarChart の shadcn/ui 突合バリアント(イシュー #2085)

RadarChartProps は shadcn/ui Charts(radar、14 バリアント)と突合した以下の追加フィールドを持つ(すべて既定値で #2085 以前の出力と完全に同一の HTML を生成する、golden 純追加原則)。

フィールド型既定効果
gridRadarGrid(Polygon/Circle/None)Polygon同心グリッドの形状。None は一切描画しない(chart-radar-grid-none 相当。スポークは既定どおり残る)
grid_ringsRadarGridRings(Ticks/Outer)Ticksグリッドの同心リング本数。Outer は外周(plot_radius)の 1 本のみ(chart-radar-grid-custom の静的近似、shadcn polarRadius の px 直接指定は非対応)
grid_fillRadarGridFill(None/Series)Noneグリッドの塗り。Series は先頭系列色(fill-opacity: 0.2)で塗る(chart-radar-grid-fill/-grid-circle-fill 相当)
spokesbooltrueスポーク(中心 → 各軸頂点の線)の描画有無(shadcn radialLines 相当)
fillRadarFill(Solid/None)Solid系列ポリゴンの塗り。None は輪郭のみ(chart-radar-lines-only 相当)
dotsboolfalseデータ点マーカー(半径 4.0、shadcn dot.r 相当)の描画有無(chart-radar-dots 相当)
axis_labelRadarAxisLabel(Category/ValueAndCategory)Category軸ラベルの内容。ValueAndCategory は各系列の値を / 区切りで併記した 2 行(chart-radar-label-custom の静的近似)
radius_axisboolfalse半径軸(値目盛ラベル)の描画有無。角度は軸 0/1 中間角に固定(shadcn angle prop の任意指定は非対応、chart-radar-radius の静的近似)

意図的に対応しない点(crate::radar_chart モジュール doc「意図的に合わせなかった点」参照): fill-opacity は shadcn の 0.6 ではなく既存の 0.2 を維持(既存 golden の色味変更禁止・area_chart との統一)・tickFormatter(アプリ側整形の責務)・グリッド外周の px 直接指定・半径軸の角度指定・マウス追従ツールチップ/hover 強調(#2086/#2128)。凡例の系列トグルの SSR 構造(range/hidden_series)は #2133 で追加済み。ChartLegend/icon は radar 部品へ内包せず charts::legend との合成で表現する(Series::with_icon、イシュー #2077)。

PieChart / DonutChart の shadcn/ui 突合バリアント(イシュー #2084)

PieChartProps/DonutChartProps は shadcn/ui Charts(pie、11 バリアント)と突合した以下の追加フィールドを持つ(すべて既定値で #2084 以前の出力と完全に同一の HTML/CSS を生成する、golden 純追加原則)。PieSeparator/ PieLabelContent/PieLabelPosition は pie_chart モジュールで定義し、donut_chart は pub use で共有する。

フィールド対象型既定効果
separator両方PieSeparator(Line/None)Lineセグメント間セパレータ。None は segment の stroke を none にする(shadcn chart-pie-separator-none)
label_content両方PieLabelContent(Category/Value)Categoryshow_labels 有効時のラベル内容。Value は fmt_coord 済みの値
label_position両方PieLabelPosition(Inside/Outside)Insideshow_labels 有効時のラベル配置。Outside は外径を OUTSIDE_LABEL_OUTER_RADIUS(34、viewBox 単位)へ縮小し、引き出し線(label-line slot)+ 外側ラベル(outside-label slot、data-align="start"/"end")を描画する(shadcn chart-pie-label)
stackedpie_chart のみboolfalsetrue なら複数系列をリング(0 が最内周)として描画する(shadcn chart-pie-stacked)。各セグメントへ系列名を data-series 属性として付与する。false は従来どおり単一系列専用
active_indexdonut_chart のみOption<usize>None強調表示するセグメントのカテゴリ index(shadcn chart-pie-donut-active)。当該セグメントへ data-active 存在属性を付与し外径を維持、非活性セグメントの外径を ACTIVE_INSET(4)だけ縮める(viewBox の外径 45 が上限のため、活性側を拡張せず非活性側を縮める逆転で表現する)。範囲外は PieChartError::InvalidActiveIndex
center_textdonut_chart のみOption<PieCenterText>None中央テキスト(value・任意の label)。radial_chart::RadialCenterText と同型で、合計値の算出・数値整形は呼び出し側の責務(shadcn chart-pie-donut-text)

新設 pub 関数 charts::legend::category_legend(data, props) -> Node(shadcn chart-pie-legend)は ChartData のカテゴリ 1 件を凡例 item 1 件として描画し、マーカー色を charts::series_color_var(カテゴリ index)で扇形と一致させる(既存 legend() は系列単位のため円グラフのカテゴリ単位凡例には使えない)。

意図的に対応しない点(crate::pie_chart/crate::donut_chart モジュール doc「意図的に合わせなかった点」参照): pie 側の active_index(shadcn に pie-active は存在しない)・カテゴリごとの任意色(ChartData の色モデルは系列単位)・chart-pie-label-list の fill-background(ダーク時可読性の既存判断 #1594/#1596 を維持し fg + 背景色ハローのまま)・弧に沿った回転(引き出しラベルは水平テキストのまま)。マウス追従ツールチップ・hover 強調・hit-area data-*(#2128)・chart-pie-interactive(期間切替)・凡例の系列トグル(#2132)は対象外。

4l. theme モジュール: Theme トークン API と upsert_*(イシュー #547/#606/#1138/#1423/#1678/#2197)

API 一覧

Rust
impl Theme {
    pub fn empty() -> Self;
    pub fn default() -> Self; // 標準トレイト実装

    // 追加専用(既存名は ThemeError::DuplicateTokenName で拒否)
    pub fn push_color(&mut self, name: &str, light: &str, dark: &str) -> Result<(), ThemeError>;
    pub fn push_space(&mut self, name: &str, value: &str) -> Result<(), ThemeError>;
    pub fn push_typography(&mut self, name: &str, value: &str) -> Result<(), ThemeError>;
    pub fn push_radius(&mut self, name: &str, value: &str) -> Result<(), ThemeError>;
    pub fn push_shadow(&mut self, name: &str, light: &str, dark: &str) -> Result<(), ThemeError>;
    pub fn push_z_index(&mut self, name: &str, value: &str) -> Result<(), ThemeError>; // イシュー #1423
    pub fn push_size(&mut self, name: &str, value: &str) -> Result<(), ThemeError>; // イシュー #1678
    pub fn push_breakpoint(&mut self, name: &str, value: &str) -> Result<(), ThemeError>; // イシュー #2197

    // 追加または上書き(イシュー #1138。DuplicateTokenName を返さない)
    pub fn upsert_color(&mut self, name: &str, light: &str, dark: &str) -> Result<(), ThemeError>;
    pub fn upsert_space(&mut self, name: &str, value: &str) -> Result<(), ThemeError>;
    pub fn upsert_typography(&mut self, name: &str, value: &str) -> Result<(), ThemeError>;
    pub fn upsert_radius(&mut self, name: &str, value: &str) -> Result<(), ThemeError>;
    pub fn upsert_shadow(&mut self, name: &str, light: &str, dark: &str) -> Result<(), ThemeError>;
    pub fn upsert_z_index(&mut self, name: &str, value: &str) -> Result<(), ThemeError>; // イシュー #1423
    pub fn upsert_size(&mut self, name: &str, value: &str) -> Result<(), ThemeError>; // イシュー #1678
    pub fn upsert_breakpoint(&mut self, name: &str, value: &str) -> Result<(), ThemeError>; // イシュー #2197

    pub fn to_css(&self) -> String;
}

// var(--fandhe-...) 参照ヘルパ(自由関数、`Theme` の inherent メソッドではない)
pub fn z_index_var(name: &str) -> Result<String, ThemeError>; // イシュー #1423
pub fn size_var(name: &str) -> Result<String, ThemeError>; // イシュー #1678
pub fn breakpoint_var(name: &str) -> Result<String, ThemeError>; // イシュー #2197。参照専用(下記注記参照)

色(colors)・影(shadows)はライト/ダーク 2 値、余白(spaces)・タイポグラフィ(typography)・角丸(radii)・重なり順(z-indices、イシュー #1423 で新設)・size(イシュー #1678 で新設)・breakpoint(イシュー #2197 で新設)はモード非依存の 1 値を取る。

breakpoint トークンは参照専用(イシュー #2197): CSS custom property は @media プレリュードで使えないため、push_breakpoint/ upsert_breakpoint で登録した値は crate::recipe::SlotRecipe::breakpoint が生成する @media (min-width: ...) の閾値には一切影響しない。閾値の唯一の定義元は crate::recipe::Breakpoint::min_width()(const fn)であり、テーマトークンはこの値と Theme::default() 構築時に自動的に同期する(2 箇所の手打ちドリフトを構造的に防ぐ)。JS の matchMedia・利用者の独自スタイルシートから読む用途を想定する。

Theme::default() の既定色トークン一覧(イシュー #1422)

DEFAULT_COLORS は 55 件: bg 系 5 件(bg/bg-subtle/bg-muted/ bg-emphasized/bg-overlay)、fg 系 3 件(fg/fg-muted/fg-subtle)、border 系 4 件(border/border-muted/border-subtle/ border-emphasized)、ステータス系統(accent/info/success/warning/ danger)各 6 件(<p>/-emphasized/-fg/-subtle/-muted/ -fg-subtle)、neutral 系統 6 件(同型)、focus-ring 1 件、chart-1〜chart-6 の系列配色 6 件。chakra-ui semantic token / Radix Themes の色スケールとの対応表・「どの部品がどの semantic 名を使うべきか」の方針・WCAG コントラスト検証手段は docs/design/color-token-system.md を正とする(本 API リファレンスでは値を複製しない)。

push_* と upsert_* の意味論対比

API同名トークンが既存の場合用途
push_color / push_space / push_typography / push_radius / push_shadow / push_z_index / push_size / push_breakpointThemeError::DuplicateTokenName を返して拒否(fail-closed)新規トークンの追加。意図しない上書きを防ぐ既定挙動
upsert_color / upsert_space / upsert_typography / upsert_radius / upsert_shadow / upsert_z_index / upsert_size / upsert_breakpoint挿入順(= Theme::to_css の出力順)を保ったまま値を in-place 置換。存在しなければ末尾追加既存トークン(既定パレット含む)の明示的な上書き。DuplicateTokenName を返すことはない

Theme::default() の既定値を差し替える正規経路

push_* は同名トークンを常に DuplicateTokenName で拒否するため、Theme::default() が持つ既定パレット(bg/fg/accent/font-body 等)を差し替える正規経路が存在しなかった(イシュー #1118 で判明した欠落)。upsert_*(イシュー #1138)がこの欠落を解消する正規経路であり、push_* では代替できない。

Theme::to_css 出力への反映

upsert_* で置換・追加した値は、Theme::to_css の既存の出力構造(:root の light 値ブロック・@media (prefers-color-scheme: dark) の dark 値ブロック・:root[data-theme="..."] による明示上書きブロック)へそのまま反映される。内部表現は Vec による挿入順保持のため、出力は呼び出し順序にのみ依存する決定的な文字列になる。

使用例(イシュー #1118 の実シナリオ)

Rust
use fandhe_frontend_pre_styled_ui::theme::Theme;

let mut theme = Theme::default();
// 既定の font-body を日本語フォントスタックへ差し替える(正規経路)
theme.upsert_typography("font-body", "Noto Sans JP, system-ui, sans-serif")?;
// アクセント色の上書き(light / dark 2 値)
theme.upsert_color("accent", "#0f766e", "#2dd4bf")?;
let css = theme.to_css();

値はいずれも [CssValue::new] の allowlist(ASCII 英数字・空白・# % . , ( ) - _ のみ許可)を通過する文字列に限る。引用符(" ')は allowlist で拒否されるため、フォント名は引用符なしで書く。

セキュリティ上の不変条件(REQ-1 相当、CSS 文脈)

upsert_* も push_* と同一の allowlist 検証(TokenName::new / CssValue::new)を唯一の入口とし、迂回経路を持たない。: ; { } を拒否するため宣言追加・セレクタ脱出・url(javascript:...) は構成不可能であり、< > / " ' を拒否するため </style> 脱出も構成不可能である。検証は全引数に対して完了してから書き込むため、検証失敗時は Theme を一切変更しない(部分書き込みなし)。固定テストは crates/pre-styled-ui/src/theme.rs 内ユニットテスト・crates/pre-styled-ui/tests/theme_css.rs・crates/pre-styled-ui/tests/theme_injection.rs。

upsert_* は「重複拒否の無効化」ではなく、既存トークンを明示的に上書きするための独立したオプトイン API である(push_* の fail-closed 挙動自体は変更しない)。

upsert API は fandhe-frontend-pre-styled-ui v0.38.0 以降に収録される(§2a の crates.io 公開状況・バージョン確認方針を参照)。

push_z_index / upsert_z_index / z_index_var(重なり順トークン、イシュー #1423)は v0.41.0 以降に収録される。既定スケール(hide/base/docked/ dropdown/sticky/popover/overlay/modal/skip-nav/toast/tooltip/ max の 12 件)・部品ごとの割り当て方針・見送り項目の詳細は docs/design/pre-styled-ui-scale-tokens.md を参照。radii(8 段)・shadows(6 段)・spaces(15 段)の拡充も同バージョンから収録される。

push_size / upsert_size / size_var(size variant 軸トークン、イシュー #1678)は v0.44.0 以降に収録される。既定スケール(control-height/ control-padding-x/control-font-size の 3 系統 × xs/sm/md/lg/xl の 5 段、計 15 件)・判断記録の詳細は docs/design/pre-styled-ui-size-and-color-palette-axes.md を参照。

recipe::Size / recipe::ColorPalette の共通軸拡張(イシュー #1678)

recipe::Size(5 段: Xs/Sm/Md/Lg/Xl)・recipe::ColorPalette(6 値: Accent/Info/Success/Warning/Danger/Neutral)は styled 部品が共用する 2 つの標準 variant 軸である。イシュー #1678 で既存段・既存値へ純追加した拡張であり、各部品の実際の登録段は部品ごとに異なる(本書 §4d の variant 表・各部品モジュールの rustdoc が実際の登録内容の正)。recipe::palette_scale_declarations(6 役割版、palette_declarations の 3 役割はそのまま維持)も同イシューで新設した。判断根拠・非採用事項・再評価トリガーの詳細は docs/design/pre-styled-ui-size-and-color-palette-axes.md を参照。

recipe::Breakpoint / SlotRecipe::breakpoint(イシュー #2197)

Rust
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Breakpoint { Sm, Md, Lg, Xl }

impl Breakpoint {
    pub const ALL: [Breakpoint; 4];
    pub const fn value(self) -> &'static str;      // "sm" / "md" / "lg" / "xl"
    pub const fn min_width(self) -> &'static str;   // "640px" / "768px" / "1024px" / "1280px"
}

impl SlotRecipe {
    pub fn breakpoint(
        self,
        slot: &'static str,
        bp: Breakpoint,
        declarations: Vec<Declaration>,
    ) -> Self; // builder、自己消費
}

StateCondition と並ぶ条件だが、variant 軸でも状態条件でもない別カテゴリのため独立した enum とした。SlotRecipe::breakpoint が登録した規則は SlotRecipe::css() の出力で「states の後・hover ブロック(@media (hover: hover))の前」に、Breakpoint::ALL の昇順(sm → xl、mobile-first)で @media (min-width: <bp.min_width()>) ブロックとして出力される(1 breakpoint = 1 ブロック、同一 breakpoint 内は登録順)。セレクタは base と同じ詳細度 [data-scope][data-part](0,2,0)のため、同一 slot・同一プロパティを variant(0,3,0)が宣言していると variant が常に勝つ。breakpoint × variant / breakpoint × state の複合条件(@media 内の .fd-* クラス・:hover 規則)は未実装(スコープ外、docs/design/pre-styled-ui-scale-tokens.md §3.6/§7 参照)。段の値(chakra-ui v3・shadcn/ui〔Tailwind v4〕と sm 以外で完全一致、2xl は見送り)の採用根拠は同文書 §3.6 を参照。最初の消費者は breadcrumb の list(gap を Breakpoint::Sm で拡張、イシュー #2198)。

recipe::ContainerBreakpoint / SlotRecipe::container_slot / container / container_variant(イシュー #2199)

Rust
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContainerBreakpoint { Sm, Md, Lg, Xl }

impl ContainerBreakpoint {
    pub const ALL: [ContainerBreakpoint; 4];
    pub const fn value(self) -> &'static str;      // "sm" / "md" / "lg" / "xl"
    pub const fn min_width(self) -> &'static str;   // "384px" / "448px" / "512px" / "576px"
}

impl SlotRecipe {
    pub fn container_slot(self, slot: &'static str) -> Self; // builder、自己消費

    pub fn container(
        self,
        slot: &'static str,
        cb: ContainerBreakpoint,
        declarations: Vec<Declaration>,
    ) -> Self; // builder、自己消費

    pub fn container_variant<V: VariantValue>(
        self,
        v: V,
        slot: &'static str,
        cb: ContainerBreakpoint,
        declarations: Vec<Declaration>,
    ) -> Self; // builder、自己消費
}

Breakpoint(viewport 幅の @media)と並列・独立の enum で、ContainerBreakpoint は要素自身の inline サイズを基準にする @container クエリを表現する。container_slot(slot) は container-type: inline-size; container-name: fd-<scope>-<slot>; を当該 slot の base ブロック群の直後へ出力し、container()/ container_variant() が登録した規則は SlotRecipe::css() の出力で「breakpoints の後・hover ブロックの前」に、ContainerBreakpoint::ALL の昇順(sm → xl、mobile-first)で @container fd-<scope>-<container_slot> (min-width: <cb.min_width()>) ブロックとして出力される(1 段 = 1 ブロック、同一段内は登録順)。container() は base と同じ詳細度 [data-scope][data-part](0,2,0)のセレクタ、container_variant() は variant と同じ [data-scope][data-part].fd-<scope>--<axis>-<value>(0,3,0)のセレクタを使う。container_slot() を呼んでいない、または宣言した slot が未宣言・不正識別子の場合は container()/container_variant() に登録された規則を一切出力しない(fail-closed、孤児 @container を出さない)。段の値(shadcn/ui〔Tailwind v4〕既定のコンテナクエリスケールと一致、3xs〜xs・2xl 以上は見送り)・container slot の決め方(無名 @container を不採用とする理由を含む)の採用根拠は docs/design/pre-styled-ui-scale-tokens.md §3.7 を参照。最初の消費者は field の orientation="responsive"(group を container slot・root を container_variant の対象、イシュー #2199)。

4m. sidebar(イシュー #2073、親 #2071。headless anatomy は #2072)

fandhe_frontend_headless_ui::sidebar(22 パーツ: provider/root/ header/content/footer/separator/input/group/group-label/ group-content/group-action/menu/menu-item/menu-button/ menu-action/menu-badge/menu-sub/menu-sub-item/menu-sub-button/ rail/trigger/inset)へ、variant(sidebar/floating/inset)3 種・collapsible(offcanvas/icon/none)3 種・side(left/right)2 種・モバイル drawer 表示という shadcn/ui Sidebar の意匠を重ねる薄い委譲層。command/item と同型の位置付けで、size/variant/color-palette いずれの軸クラスも持たない(見た目は headless の data-* を属性セレクタで参照するのみ)。

  • 公開 API: 22 パーツの同名ラッパー関数(headless-ui と同一シグネチャ、呼び出し側 class は drop_class_attr で除去して委譲)+ stylesheet() + menu_skeleton(show_icon, attrs)(下記参照)。状態機械 [fandhe_frontend_headless_ui::sidebar::Sidebar]/SidebarAction/ SidebarState と静的 props 型(SidebarProps/SidebarCollapsible/ SidebarVariant/SidebarSide/SidebarMenuButtonProps/ SidebarMenuButtonSize/SidebarMenuButtonVariant/ SidebarMenuSubButtonProps/SidebarMenuSubButtonSize)を選択的に再エクスポートする(provider/root/rail/trigger が &Sidebar を引数に取るため、command とは異なり drawer と同型で状態機械を再エクスポートする)。
  • 軸を持たない理由: command/input_group と同型(子の寸法に従属するレイアウト部品。headless にも shadcn/ui にも軸が無い)。
  • --fandhe-sidebar-*(scope 接頭辞)と --fandhe-color-sidebar-*(色トークン)の使い分け: 幅トークン(--fandhe-sidebar-width/ --fandhe-sidebar-width-icon/--fandhe-sidebar-width-mobile、--fandhe-drawer-size と同型のフォールバック付き)は recipe scope 接頭辞 custom property。色は docs/design/color-token-system.md §9.3 の決定に従い Theme::default() が追加する --fandhe-color-sidebar-* 7 ロール(sidebar-bg/sidebar-fg/sidebar-accent/sidebar-accent-fg/ sidebar-muted/sidebar-border/sidebar-focus-ring)を参照する。
  • menu_skeleton(ローディング装飾): headless モジュール doc の申し送りどおり、ランダム幅を持つ shadcn/ui 実装は SSR 決定性を壊すため採用せず、[crate::skeleton::skeleton](Circle〔show_icon が true のときのみ〕+ Text)を固定幅で合成する決定的な fandhe_frontend_headless_ui::sidebar::menu_item を返す。自前の data-* は一切出力しない。
  • icon 折りたたみ時のテキスト非表示: headless menu_button はテキストを span で包まないため、data-collapsible="icon" かつ折りたたみ時は menu-button を overflow: hidden + 固定幅で視覚的に切り落とす(アイコンを children の先頭に置く呼び出し規約が前提。改善はスコープ外、後述)。
  • モバイル + collapsed の詳細度調整: root の折りたたみ幅規則(属性 4 個)が data-mobile 単独(属性 3 個)より詳細度で勝つため、recipe().css() に加え raw CSS(属性 4 個 + ソース順で後)で [data-mobile][data-state="collapsed"] の幅を固定し、開閉は transform のみで表現する。
  • backdrop を追加しない理由: headless anatomy に backdrop パーツが無いため(command の dialog と同型の判断)。
  • 責務境界(docs/policy/intentional-non-adoption.md §3.25 規則 1): Cmd/Ctrl+B のグローバルショートカット・モバイル判定(メディアクエリ)・menu-button の tooltip hover 配線は fandhe-frontend-wasm-full(後続イシュー #2074)の責務として実装しない。
  • スコープ外: #2074(wasm-full 配線、Cmd/Ctrl+B・モバイル判定・tooltip hover)・backdrop パーツの新設・menu-button テキストの headless 側 span 化による改善・@media (min-width) 対応(breakpoint 機構 #2196/#2197 が未実装のため)。
  • docs サイト: sidebar(イシュー #2075 でページ登録・showcase Demo・SPEC_TABLES 原稿を追加)。

5. 関連ドキュメント

  • docs/api/headless-ui-api.md: 本クレートの下層。Format 系 / Locale(§4e)は本クレートに対応モジュールを持たないため、掲載は同ドキュメントを正とする(本クレートからの到達経路は §2 表「headless 由来ユーティリティ」行・§3a 参照)
  • docs/api/component-api.md: Node/el/text/ raw_html/render の凍結 API 表面
  • docs/api/pre-styled-recipe-api.md: variant API・静的 CSS 生成の詳細
  • examples/headless-pre-styled-ui/README.md: 本クレート v0.4.0 へ統合済みのショーケースサンプル(§2 参照)
  • .claude/skills/chakra-ui/: 設計時の参考にした chakra-ui リファレンススキル
  • docs/internal/pre-styled-ui-implementation-notes.md: 実装経緯・ロードマップ・トレーサビリティの記録(docs サイト非掲載のためリンク化しない)