Espacement & rythme

Un composant possède son padding, jamais sa marge. L’espace entre deux éléments est une propriété de la relation entre eux : il appartient à ce qui les contient.

Ce n’est pas une recommandation. no-outer-margin.test.ts parse chaque composant de la bibliothèque et casse le build sur un utilitaire de marge posé sur un élément racine — auto et zéro exceptés.

Pourquoi la règle existe

muse a livré exactement un composant qui l’enfreignait. Divider portait my-4. Les vingt-quatre appels de la suite l’annulaient avec class="my-0", muse l’annulait deux fois dans son propre ProfileCard, et la charte enseignait le contournement comme l’idiome.

Sorti de son parent flex, cet exemple soude le trait à ce qui le précède. C’est très exactement le « bouton collé au séparateur » que les pages écrites par des agents produisaient en boucle. Un défaut qu’aucun appel ne veut n’est pas un défaut : c’est un bug.

Le voir plutôt que le lire

Les deux panneaux ci-dessous rendent le même contenu. Celui de gauche est ce que produit un flux de blocs sans conteneur qui possède l’espace.

Mauvais Aucun parent ne possède l'espace

Zone de danger

Supprimer cet espace est définitif.


Bon Stack gap="content"

Zone de danger

Supprimer cet espace est définitif.


Le titre et sa description sont liés par bound, pas par le gap de la section : ce sont deux morceaux d’une même chose, pas deux frères.

La gouttière trop serrée

Mauvais gap-3 autour de cartes p-5
Actives12
En pause3
Archivées27
Bon gap-4 — content
Actives12
En pause3
Archivées27

Les quatre barreaux

Le gap ne prend pas un nombre, il prend un barreau nommé — parce que la question sur une page n’est jamais « combien de pixels » mais « est-ce que ces deux choses sont la même chose ».

BarreauValeurDeux choses qui sont…
bound4pxdeux morceaux d’une seule chose — un titre et sa description
tight8pxutilisées ensemble — les boutons d’une barre d’actions
content16pxdes frères dans un bloc — les éléments d’une carte, les cartes d’une grille
section40pxdes sujets distincts — une section de page et la suivante

Il n’y a délibérément rien entre content et section. « Lié, mais un peu moins » est la distinction que personne n’applique deux fois de la même façon, et c’est d’où venaient les trois gaps de colonne différents de la suite. Une mise en page qui a vraiment besoin de 24px écrit class="gap-6", et ça se lit comme l’exception que c’est.

Les composants qui portent le rythme

<Page width="xl">
  <PageHeader title="Stockage" description="Sur tous vos espaces." />

  <Section title="Usage">
    <div class="grid gap-4 lg:grid-cols-2">…</div>
  </Section>
</Page>
ComposantPossède
Pagele centrage, la largeur maximale, le padding extérieur, le rythme entre sections
PageHeaderle bloc h1, la description, les actions, le lien retour
Sectionun bloc h2 et son corps, avec card optionnel
Stackune colonne à un barreau donné
Inlineune ligne qui passe à la ligne, à un barreau donné

Page se place à l’intérieur du conteneur de défilement de la coquille : la coquille garde le rail, la nav mobile et l’unique scroller ; Page possède tout ce qui est en deçà du bord du contenu. C’est cette séparation qui permet à un tableau de bord de prendre width="xl" pendant qu’une page de lecture garde lg.

La gouttière et le padding

Le padding n’est pas un barreau : ce n’est pas une relation, c’est le retrait interne d’un conteneur. Card est à p-5 (20px), et ça interagit avec les gouttières autour.

Une gouttière ne peut jamais être plus serrée que le padding des cartes qu’elle sépare. Des tableaux de bord tournaient ici avec des gouttières gap-3 autour de cartes p-4 : trois cartes statistiques se lisaient comme un seul panneau à coutures.

Le cas que la charte avait oublié

Input, Select, Textarea et SecretField sont tous en h-11 (44px). Button est en h-9 (36px) par défaut, et c’est volontaire — la densité bureau que la suite assume.

Mais un bouton posé sur la même ligne qu’un de ces contrôles est 8px trop court, et la ligne se lit comme une erreur. C’est de l’alignement, pas de la densité, et la réponse est size="lg" sur le bouton :

<form class="flex flex-col gap-3 sm:flex-row">
  <Input class="flex-1" />
  <Button size="lg">Envoyer l'invitation</Button>
</form>

Le formulaire d’invitation de muse lui-même enfreignait cette règle jusqu’à la v0.6.0, et 36 endroits dans la suite l’enfreignent encore. Un bouton empilé sous un champ garde le défaut : la règle ne concerne que les frères sur une ligne.

L’échelle sous-jacente

Grille de 4pt, fournie par l’échelle d’espacement de Tailwind. muse ne définit aucun token --fc-space-* et n’en a pas besoin : les barreaux sont des noms pour des valeurs qui existent déjà.

Les deux seules valeurs que le thème ajoute sont celles dont dépend la géométrie du rail, parce qu’elles sont verrouillées avec l’animation de largeur du SideBar et ne se choisissent pas librement : --spacing-fc-nav-item (44px) et --spacing-fc-nav-content (196px).