---
title: "Composants — Docs zenit Zig UI"
description: "Ce que promet un composant zenit, quand descendre d’une couche, et les trois contrats — montage, overlays et tests."
url: https://zenit.z.express/fr/docs/guide/components
language: fr
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_ja: https://zenit.z.express/ja/docs/guide/components.md
alternate_ko: https://zenit.z.express/ko/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
---

# Composants

Ce que promet un composant zenit, quand descendre d’une couche, et les trois contrats — montage, overlays et tests. Les enregistrements, props et types de résultat de chaque composant se trouvent sur le [site des composants](https://zenit.z.express/fr/components).

## Ce que « composant » signifie ici

Un composant zenit n’est pas un helper qui assemble des couleurs et des rayons d’angle. Un composant public regroupe généralement la structure de nœuds, l’état persistant, le routage des événements, la sémantique du focus et du clavier, les propriétés d’accessibilité, les tokens de thème, la durée de vie des overlays et une frontière de test que le harness peut localiser et relire. Tous sont exportés depuis `ui.widgets`.

**51**Stories sur le site des composants

**6**Catégories d’usage (plus Labs)

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

ANATOMY

mount crée d’abord un Scope enfant ; tout ce qu’il possède est libéré avec le Scope parent.

## Choisissez d’abord la bonne couche

| Besoin | Couche | Reste à votre charge |
| --- | --- | --- |
| Contrôles produit standard | `ui.widgets.*` | État métier, textes, callbacks et placement |
| Visuel sur mesure, interaction éprouvée | `ui.hooks` · `ui.interaction` · `ui.select_headless` | Dessin, tokens, libellés a11y et frontières de composition |
| Un nouveau modèle d’interaction | `ui.Node` + `ui.events` | Hit-testing, clavier, focus, IME, a11y et contrat de test |

> TIP
> 
> **Commencez par les widgets.** Ne descendez d’une couche que si le modèle de comportement d’un composant existant ne convient pas. Un autre look justifie rarement de réimplémenter le focus, l’IME, la fermeture des overlays et l’accessibilité.

## Catalogue

Chaque composant a sa propre page sur le [site des composants](https://zenit.z.express/fr/components) : un enregistrement du vrai `zenit Storybook.app`, les props et types de résultat générés depuis les sources, et ce que l’enregistrement démontre. Le harness localise chaque story par son `test_id`, pilote un curseur virtuel ou la saisie, et enregistre directement le drawable Metal.

| Catégorie | Nombre | Exemples |
| --- | --- | --- |
| Actions | 7 | [Button](https://zenit.z.express/fr/components/button) · [Checkbox](https://zenit.z.express/fr/components/checkbox) · [Switch](https://zenit.z.express/fr/components/switch) · [RadioGroup](https://zenit.z.express/fr/components/radio) · [Slider](https://zenit.z.express/fr/components/slider) … |
| Saisie | 10 | [Input](https://zenit.z.express/fr/components/input) · [Textarea](https://zenit.z.express/fr/components/textarea) · [Select](https://zenit.z.express/fr/components/select) · [Input · Select · Button](https://zenit.z.express/fr/components/formcompose) · [ComboBox](https://zenit.z.express/fr/components/combobox) … |
| Affichage | 17 | [Badge](https://zenit.z.express/fr/components/badge) · [Tag](https://zenit.z.express/fr/components/tag) · [Chip](https://zenit.z.express/fr/components/chip) · [Card](https://zenit.z.express/fr/components/card) · [Alert](https://zenit.z.express/fr/components/alert) … |
| Navigation | 4 | [Tabs](https://zenit.z.express/fr/components/tabs) · [Accordion](https://zenit.z.express/fr/components/accordion) · [Menu](https://zenit.z.express/fr/components/menu) · [DropdownMenu](https://zenit.z.express/fr/components/dropdown) |
| Surcouches | 5 | [Notifier](https://zenit.z.express/fr/components/notification) · [Tooltip](https://zenit.z.express/fr/components/tooltip) · [Popover](https://zenit.z.express/fr/components/popover) · [Modal](https://zenit.z.express/fr/components/modal) · [Sheet](https://zenit.z.express/fr/components/sheet) |
| Mise en page | 8 | [GlassBox](https://zenit.z.express/fr/components/glassbox) · [Divider](https://zenit.z.express/fr/components/divider) · [HStack / VStack](https://zenit.z.express/fr/components/stack) · [ui.box](https://zenit.z.express/fr/components/layoutbox) · [VirtualList](https://zenit.z.express/fr/components/virtuallist) … |
| Labos | 13 | [glasslab](https://zenit.z.express/fr/components/glasslab) · [glassislands](https://zenit.z.express/fr/components/glassislands) · [glassmotion](https://zenit.z.express/fr/components/glassmotion) · [glasschrome](https://zenit.z.express/fr/components/glasschrome) · [canvasevents](https://zenit.z.express/fr/components/canvasevents) … |

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

La story Button parcourt la matrice variant × size, puis survole et presse Primary. Détails sur [/components/button](https://zenit.z.express/fr/components/button).

## Mount, état et nettoyage

La plupart des composants visuels utilisent la forme builder `Config.mount(scope, cx)` : `ui.widgets.Button(props)` renvoie un builder et c’est `mount` qui construit réellement l’arbre. La valeur de retour est soit le `*Node` racine, soit une struct de résultat avec des handles comme wrapper, trigger, body, panel et 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);
```

Quelques composants utilisent la forme fonction et reçoivent props, scope et cx en un seul appel : `mountScrollArea`, `mountGrid`, ainsi que `Select`, `ComboBox`, `TagsInput`, `NumberStepper`, `FileUpload` et `DataTable` (ce sont des alias de fonctions `mountX` dans `ui.widgets`, appelés sous la forme `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);
```

| Composant | Renvoie |
| --- | --- |
| `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 }` |

Ne traitez pas la config comme des props vivantes que vous pourriez modifier plus tard. Un composant lit sa config au montage pour construire nœuds et état — un `signal.get()` dans la config n’est lu qu’une fois, comme instantané. Les données qui continuent de changer passent par le state renvoyé, un Signal (comme `.visible(sig)` de Modal) ou les méthodes publiques du composant ; ne conservez jamais de pointeur vers une config temporaire d’une frame à l’autre. L’état interne d’un composant vit sur le Scope enfant créé par mount et est libéré lors du dispose du Scope parent.

## Pourquoi les overlays doivent être des composants

Tooltip, Popover, Menu, Modal, Sheet et le [Notifier](https://zenit.z.express/fr/components/notification) intégré à l’app enregistrent tous une couche auprès de l’`OverlayStack`. Les couches avec barrier (Modal / Sheet) sont rattachées au portal de la fenêtre (`WindowOverlayPortal` sous `cx.root`) afin d’échapper au clipping overflow de tout ancêtre. Les valeurs z proviennent de tiers sémantiques — overlay 100, dialog 1000, toast 2000 (là où vit le Notifier), tooltip 3000, l’overlay devtools étant fixé à 32000 — et un overlay ouvert depuis un autre passe toujours au-dessus de son hôte. Un positionnement absolu et un grand z-index sur un box ne vous donnent rien de tout cela.

`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

L’endroit où vous appelez mount ne décide pas de l’endroit où l’overlay est dessiné : la barrier va dans le portal, le tier décide du z, et Escape parcourt la pile depuis le haut jusqu’à la première couche refermable.

-   **Les clics internes** restent dans le dialog / panel et ne déclenchent jamais d’outside-dismiss ; un clic sur la barrier le ferme selon `close_on_overlay`.
    
-   **Escape** ne ferme que l’overlay du dessus ; les couches imbriquées se dépilent une à une.
    
-   **Focus** : un Modal ouvert piège le focus et se focalise automatiquement, puis restaure le focus à sa position d’origine à la fermeture.
    
-   **Le dispose du Scope** détache activement le sous-arbre de la barrier du portal : changer de page ne laisse jamais de couche de hit invisible.
    
-   **Les transitions de sortie** gardent l’overlay visible jusqu’à la fin de l’animation, puis le suspendent — sans sauter directement à l’état final.
    

Pour les notifications globales non bloquantes, utilisez `ui.widgets.Notifier` (qui remplace l’ancien ToastManager) : `Notifier.init(scope, cx, .{})` enregistre une couche sur le tier toast et, si le portal de la fenêtre existe, y monte son conteneur plein écran (si `portaled` vaut false, ajoutez vous-même `container` à votre racine). Ensuite `show(.{ .tone = .success, .title = "Saved" })` renvoie un id, `update` transforme une carte sur place et `dismiss` la retire. Fermetures, expirations et actions de boutons reviennent à l’app sous forme d’événements `Listener` ; l’historique, le mode ne pas déranger et le reste de la logique de centre de notifications relèvent de l’app.

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

Le harness ouvre un dialog centré et vérifie que les clics internes ne le ferment pas. [Modal](https://zenit.z.express/fr/components/modal)

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

Un curseur virtuel survole le trigger ; la bulle entre dans le tier tooltip sans toucher au layout. [Tooltip](https://zenit.z.express/fr/components/tooltip)

## Le showcase est aussi une suite de régression

`e2e/storybook.test.ts` passe à chaque story via `nav.<key>`, collecte le texte du sous-arbre et vérifie que chaque valeur de données attendue est présente, puis prend une capture ; les composants interactifs vérifient aussi l’état, la géométrie ou les pixels après interaction. Les enregistrements du site des composants réutilisent la même app et les mêmes ids sémantiques : la démo et le test ne divergent jamais en deux implémentations.

| Contrôle | Ce qu’il détecte |
| --- | --- |
| Relecture texte / état | Panneaux vides, callbacks qui ne réécrivent jamais, valeurs de filtre / page erronées |
| Assertions de géométrie | Padding boxes, centrage dans le portal, alignement du Sheet au bord, deltas de glisser |
| Assertions de pixels | Couches GPU vides, régressions de blend, emoji en niveaux de gris, mauvais ordre z |
| Enregistrements | Timing du hover, du press, du curseur, de l’IME, du défilement et des animations d’entrée |

> WARNING
> 
> **Storybook n’est pas vos composants métier.** Éditeurs, explorateurs de fichiers, palettes de commandes et modèles de domaine relèvent de l’app. La bibliothèque publique ne promet que des comportements réutilisables et des primitives visuelles.
