---
title: "コンポーネント — zenit Zig UI ドキュメント"
description: "zenit のコンポーネントが何を約束するのか、いつ下位レイヤーに降りるべきか、そしてマウント・オーバーレイ・テストという 3 つの契約を説明します。"
url: https://zenit.z.express/ja/docs/guide/components
language: ja
alternate_en: https://zenit.z.express/docs/guide/components.md
alternate_zh: https://zenit.z.express/zh/docs/guide/components.md
alternate_es: https://zenit.z.express/es/docs/guide/components.md
alternate_ko: https://zenit.z.express/ko/docs/guide/components.md
alternate_fr: https://zenit.z.express/fr/docs/guide/components.md
alternate_de: https://zenit.z.express/de/docs/guide/components.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# 組み込みコンポーネント

zenit のコンポーネントが何を約束するのか、いつ下位レイヤーに降りるべきか、そしてマウント・オーバーレイ・テストという 3 つの契約を説明します。コンポーネントごとの録画、props、結果型は[コンポーネントサイト](https://zenit.z.express/ja/components)にあります。

## ここでいう「コンポーネント」とは

zenit のコンポーネントは、色と角丸を組み合わせるだけの helper ではありません。公開コンポーネントは通常、ノード構造、永続状態、イベントルーティング、フォーカスとキーボードのセマンティクス、アクセシビリティ属性、テーマ token、オーバーレイのライフタイム、そして harness が特定・読み戻しできるテスト境界をまとめて提供します。すべて `ui.widgets` からエクスポートされます。

**51**コンポーネントサイトの Story

**6**ユースケース分類（別途 Labs あり）

**1:1**Story と E2E test\_id

ANATOMY

mount はまず子 Scope を作成します。子 Scope が所有するものはすべて親 Scope と一緒に解放されます。

## まず適切なレイヤーを選ぶ

| 要件 | レイヤー | 引き続き自分で担うもの |
| --- | --- | --- |
| 標準的なプロダクト用コントロール | `ui.widgets.*` | ビジネス状態、文言、コールバック、配置 |
| 独自の見た目＋実績あるインタラクション | `ui.hooks` · `ui.interaction` · `ui.select_headless` | 描画、token、a11y ラベル、合成の境界 |
| まったく新しいインタラクション | `ui.Node` + `ui.events` | ヒットテスト、キーボード、フォーカス、IME、a11y、テスト契約 |

> TIP
> 
> **まずは widgets から。** 既存コンポーネントの振る舞いモデルが合わないときだけ下位レイヤーに降りてください。見た目を変えるためだけに、フォーカス、IME、オーバーレイの dismiss、アクセシビリティを再実装する価値はほとんどありません。

## コンポーネントカタログ

各コンポーネントには[コンポーネントサイト](https://zenit.z.express/ja/components)に専用ページがあります。実際の `zenit Storybook.app` の録画、ソースから生成された props と結果型、そして録画が証明している振る舞いです。harness は `test_id` で各 story を特定し、仮想カーソルや入力を操作して、Metal drawable を直接録画します。

| 分類 | 数 | 代表例 |
| --- | --- | --- |
| 操作と選択 | 7 | [Button](https://zenit.z.express/ja/components/button) · [Checkbox](https://zenit.z.express/ja/components/checkbox) · [Switch](https://zenit.z.express/ja/components/switch) · [RadioGroup](https://zenit.z.express/ja/components/radio) · [Slider](https://zenit.z.express/ja/components/slider) … |
| 入力とフォーム | 10 | [Input](https://zenit.z.express/ja/components/input) · [Textarea](https://zenit.z.express/ja/components/textarea) · [Select](https://zenit.z.express/ja/components/select) · [Input · Select · Button](https://zenit.z.express/ja/components/formcompose) · [ComboBox](https://zenit.z.express/ja/components/combobox) … |
| データ表示 | 17 | [Badge](https://zenit.z.express/ja/components/badge) · [Tag](https://zenit.z.express/ja/components/tag) · [Chip](https://zenit.z.express/ja/components/chip) · [Card](https://zenit.z.express/ja/components/card) · [Alert](https://zenit.z.express/ja/components/alert) … |
| ナビゲーション | 4 | [Tabs](https://zenit.z.express/ja/components/tabs) · [Accordion](https://zenit.z.express/ja/components/accordion) · [Menu](https://zenit.z.express/ja/components/menu) · [DropdownMenu](https://zenit.z.express/ja/components/dropdown) |
| オーバーレイとフィードバック | 5 | [Notifier](https://zenit.z.express/ja/components/notification) · [Tooltip](https://zenit.z.express/ja/components/tooltip) · [Popover](https://zenit.z.express/ja/components/popover) · [Modal](https://zenit.z.express/ja/components/modal) · [Sheet](https://zenit.z.express/ja/components/sheet) |
| レイアウトと大規模データ | 8 | [GlassBox](https://zenit.z.express/ja/components/glassbox) · [Divider](https://zenit.z.express/ja/components/divider) · [HStack / VStack](https://zenit.z.express/ja/components/stack) · [ui.box](https://zenit.z.express/ja/components/layoutbox) · [VirtualList](https://zenit.z.express/ja/components/virtuallist) … |
| 実験と機能検証 | 13 | [glasslab](https://zenit.z.express/ja/components/glasslab) · [glassislands](https://zenit.z.express/ja/components/glassislands) · [glassmotion](https://zenit.z.express/ja/components/glassmotion) · [glasschrome](https://zenit.z.express/ja/components/glasschrome) · [canvasevents](https://zenit.z.express/ja/components/canvasevents) … |

[Video](https://zenit.z.express/media/stories/button.mp4?v=6203a01415)

Button の Story：variant × size のマトリクスを一巡し、Primary で hover と press を見せます。詳細は [/components/button](https://zenit.z.express/ja/components/button)。

## Mount・状態・クリーンアップ

多くの視覚コンポーネントは builder 形式 `Config.mount(scope, cx)` を使います。`ui.widgets.Button(props)` は builder を返し、実際にツリーを構築するのは `mount` です。戻り値はルートの `*Node` か、wrapper、trigger、body、panel、state などのハンドルを持つ結果構造体です。

`mount_form.zig`

```zig
const EditorActions = struct {
    document: *Document,

    fn save(self: *EditorActions) void {
        self.document.save();
    }
};

const bindings = try cx.bindState(EditorActions, .{ .document = document });

const save = try ui.widgets.Button(.{
    .label = "Save",
    .variant = .primary,
    .on_click = cx.on(EditorActions, bindings, EditorActions.save),
}).mount(scope, cx);

const name = try ui.widgets.Input(.{
    .label_text = "Project name",
    .placeholder = "Untitled",
    .required = true,
    .width = 320,
}).mount(scope, cx);

try form.appendChild(cx.allocator, name);
try form.appendChild(cx.allocator, save);
```

一部のコンポーネントは関数形式で、props、scope、cx を一度に渡します。`mountScrollArea`、`mountGrid`、そして `Select`、`ComboBox`、`TagsInput`、`NumberStepper`、`FileUpload`、`DataTable` です（これらは `ui.widgets` 内の `mountX` 関数のエイリアスで、`ui.widgets.Select(props, scope, cx)` のように呼び出します）。

`scroll_area.zig`

```zig
// Function form: props, scope and cx in one call; returns a handle struct.
const area = try ui.widgets.mountScrollArea(.{ .height = 320 }, scope, cx);
try area.content.appendChild(cx.allocator, list);
try root.appendChild(cx.allocator, area.container);
```

| コンポーネント | 戻り値 |
| --- | --- |
| `Button` · `Input` | `*Node` |
| `Modal` | `ModalResult{ overlay, dialog, body, portaled }` |
| `Tooltip` | `TooltipResult{ wrapper, trigger, content }` |
| `Select` | `SelectMount{ wrapper, trigger, panel, state, is_open }` |
| `mountScrollArea` | `ScrollAreaResult{ container, content, state }` |
| `mountGrid` | `GridResult{ root, state }` |

config を後から書き込める live props として扱わないでください。コンポーネントは mount 時に config を読んでノードと状態を構築します。config 内の `signal.get()` はスナップショットとして一度だけ読まれます。変化し続けるデータは、返された state、Signal（Modal の `.visible(sig)` など）、またはコンポーネントの公開メソッドを通じて渡してください。一時的な config へのポインタをフレームをまたいで保持してもいけません。コンポーネントの内部状態は mount が作る子 Scope 上にあり、親 Scope が dispose されると一緒に解放されます。

## オーバーレイをコンポーネントで作るべき理由

Tooltip、Popover、Menu、Modal、Sheet、そしてアプリ内通知の [Notifier](https://zenit.z.express/ja/components/notification) は、いずれも `OverlayStack` にレイヤーを登録します。barrier を持つレイヤー（Modal / Sheet）はウィンドウレベルの portal（`cx.root` 配下の `WindowOverlayPortal`）に付け替えられ、祖先の overflow クリップから抜け出します。z 値はセマンティックな tier から決まります：overlay 100、dialog 1000、toast 2000（Notifier の層）、tooltip 3000、devtools overlay は 32000 固定。オーバーレイ内から開いたオーバーレイは常にホストより上に重なります。box に absolute と大きな z-index を設定しても、これらの振る舞いは得られません。

`modal.zig`

```zig
const visible = try scope.createSignal(bool, false);

const modal = try ui.widgets.Modal(.{
    .title = "Delete document?",
    .width = 420,
}).visible(visible).mount(scope, cx);

try modal.body.appendChild(cx.allocator, confirm_content);
// modal.portaled == true: the barrier already lives under the window-level
// portal. Do not append modal.overlay to the current tree.

// Open it from any handler:
visible.set(true);
```

OVERLAY PORTAL

mount を呼ぶ位置は、オーバーレイの描画位置を決めません。barrier は portal に入り、z は tier が決め、Escape はスタックの先頭から下へたどって最初に閉じられるレイヤーを探します。

-   **内部のクリック**は dialog / panel 内にとどまり、outside-dismiss を発火しません。barrier のクリックは `close_on_overlay` に従って閉じます。
    
-   **Escape** は最上位のオーバーレイだけを閉じ、入れ子のレイヤーは 1 段ずつ戻ります。
    
-   **フォーカス**：開いた Modal はフォーカスを trap して自動フォーカスし、閉じると元の位置に戻します。
    
-   **Scope の dispose** は barrier のサブツリーを portal から能動的に外すため、ページを切り替えても透明なヒットレイヤーが残りません。
    
-   **退場トランジション**の間はオーバーレイを表示したままにし、アニメーションが確定してから停止します。終了状態へ飛ぶことはありません。
    

ブロックしないアプリ全体の通知には `ui.widgets.Notifier`（旧 ToastManager の後継）を使います。`Notifier.init(scope, cx, .{})` は toast tier にレイヤーを登録し、ウィンドウ portal があればそこにウィンドウ全体のコンテナをマウントします（`portaled` が false の場合は自分で `container` をルートに追加します）。その後 `show(.{ .tone = .success, .title = "Saved" })` が id を返し、`update` でカードをその場で書き換え、`dismiss` で片付けます。閉じる操作、期限切れ、ボタン操作は `Listener` イベントとしてアプリに戻ります。履歴やおやすみモードなどの通知センターのロジックはアプリ側で実装します。

[Video](https://zenit.z.express/media/stories/modal.mp4?v=f1b080996c)

harness が中央の dialog を開き、内部クリックで誤って閉じないことを検証します。[Modal](https://zenit.z.express/ja/components/modal)

[Video](https://zenit.z.express/media/stories/tooltip.mp4?v=64c28daff2)

仮想カーソルが trigger にホバーし、吹き出しはレイアウトに触れずに tooltip tier に入ります。[Tooltip](https://zenit.z.express/ja/components/tooltip)

## Showcase はそのまま回帰テストでもある

`e2e/storybook.test.ts` は `nav.<key>` で各 story に切り替え、サブツリーのテキストを収集して期待するデータ値がすべて現れることをアサートしてからスクリーンショットを撮ります。インタラクティブなコンポーネントでは、操作後の状態、ジオメトリ、ピクセルもアサートします。コンポーネントサイトの録画は同じアプリと同じセマンティック id を再利用するので、「デモ」と「テスト」が 2 つの実装に分岐することはありません。

| ゲート | 検出できる問題 |
| --- | --- |
| テキスト / 状態の読み戻し | 空のパネル、書き戻さないコールバック、誤ったフィルター / ページ値 |
| ジオメトリのアサーション | padding box、portal の中央配置、Sheet の端揃え、ドラッグ移動量 |
| ピクセルのアサーション | 空白の GPU レイヤー、blend の劣化、グレースケール化した絵文字、誤った z 順序 |
| 録画 | hover、press、カーソル、IME、スクロール、入場アニメーションのタイミング |

> WARNING
> 
> **Storybook はビジネスコンポーネントではありません。** エディタ、ファイルブラウザ、コマンドパレット、ドメインモデルはアプリ層に属します。公開ライブラリが約束するのは、再利用可能な振る舞いと視覚プリミティブだけです。
