Préparer son hanami
Arrivez tôt : les meilleurs emplacements partent dès l'aube.
- Une bâche bleue (rejaa shiito)
- Des bentos et du thé chaud
- Un sac pour repartir avec vos déchets
Hanami UI
Des composants doux comme une après-midi sous les cerisiers. Tokens, CSS et Web Components à brancher dans Vue, React, Angular, AngularJS, Svelte ou une simple page HTML.
npm i https://hanami.example.com/npm/kawaiitenshi-hanami-ui-latest.tgz
Une seule source (format W3C) génère les variables CSS, le SCSS, le TypeScript et le thème Tailwind.
Les composants interactifs sont des Web Components ; les autres sont de simples classes CSS.
Un thème clair et un thème sombre « cerisiers la nuit », applicables à toute la page ou à un seul bloc.
Contrastes AA vérifiés, navigation clavier, rôles ARIA et respect du mouvement réduit.
Trois façons d'ajouter Hanami UI à un site. Toutes les adresses ci-dessous pointent vers ce serveur.
Sans outil de build : collez ces lignes dans le <head>. Le chemin /cdn/v1/ suit automatiquement la dernière version 1.x ; utilisez /cdn/1.0.0/ pour figer une version.
<link rel="stylesheet" href="https://hanami.example.com/cdn/v1/hanami-fonts.css">
<link rel="stylesheet" href="https://hanami.example.com/cdn/v1/hanami.min.css">
<script type="module" src="https://hanami.example.com/cdn/v1/hanami.min.js"></script>Pour un script classique (sites anciens, AngularJS, jQuery), utilisez hanami.iife.min.js : il expose l'objet global Hanami.
Le serveur publie une archive npm à chaque version. Elle s'installe comme n'importe quel paquet, sans registre privé.
npm install https://hanami.example.com/npm/kawaiitenshi-hanami-ui-1.0.0.tgz
# ou toujours la dernière version :
npm install https://hanami.example.com/npm/kawaiitenshi-hanami-ui-latest.tgzimport '@kawaiitenshi/hanami-ui/css/fonts'; // polices auto-hébergées
import '@kawaiitenshi/hanami-ui/css'; // tokens + composants
import { toast } from '@kawaiitenshi/hanami-ui'; // enregistre les <hn-*>Depuis le dépôt : le build produit dist/ (le paquet) et public/ (ce site).
git clone https://github.com/KawaiiTenshi555/DesignSystem.git
cd DesignSystem
npm ci
npm run build && npm run build:site
npm start # http://localhost:8080| Import | Contenu |
|---|---|
@kawaiitenshi/hanami-ui/css | Tokens, composants CSS, effets de survol, utilitaires. |
@kawaiitenshi/hanami-ui/css/fonts | Polices auto-hébergées (aucune requête vers Google). |
@kawaiitenshi/hanami-ui/css/base | Remise à zéro et typographie de page (facultatif). |
@kawaiitenshi/hanami-ui | Web Components <hn-*>, toast(), thèmes, icônes. |
@kawaiitenshi/hanami-ui/vue | Plugin et composants Vue 3 (v-model compris). |
@kawaiitenshi/hanami-ui/react | Composants React 17 à 19, compatibles Next.js. |
@kawaiitenshi/hanami-ui/tokens | Tokens en TypeScript (valeurs et var(--hn-…)). |
@kawaiitenshi/hanami-ui/scss | Variables et mixins SCSS. |
@kawaiitenshi/hanami-ui/tailwind | Thème Tailwind v4 (preset v3 : /tailwind-preset). |
hanami.css ne modifie aucune balise : tout passe par des classes hn-* et des variables --hn-*,
vous pouvez donc l'ajouter à un site en place sans rien casser. Pour appliquer la typographie Hanami à une zone,
ajoutez class="hn-root" sur son conteneur. La feuille hanami-base.css, elle, stylise
body, les titres et les liens : réservez-la aux sites construits avec Hanami UI.
Huit gammes nommées d'après les couleurs traditionnelles japonaises (日本の伝統色), calculées en OKLCH pour une progression régulière. Cliquez sur une teinte pour copier sa variable.
Les composants n'utilisent jamais les gammes directement : ils passent par des rôles (--hn-color-primary,
--hn-color-text-muted…) qui changent de valeur selon le thème. Utilisez ces rôles dans votre propre CSS
pour que vos écrans suivent le thème sans effort.
.reservation {
background: var(--hn-color-surface);
border: 1px solid var(--hn-color-border);
color: var(--hn-color-text);
}
.reservation__prix {
color: var(--hn-color-primary-text);
}| Variable | Hiru (clair) | Yozakura (sombre) |
|---|---|---|
--hn-color-bg | ||
--hn-color-bg-subtle | ||
--hn-color-bg-muted | ||
--hn-color-surface | ||
--hn-color-surface-raised | ||
--hn-color-surface-overlay | ||
--hn-color-surface-sunken |
| Variable | Hiru (clair) | Yozakura (sombre) |
|---|---|---|
--hn-color-text | ||
--hn-color-text-muted | ||
--hn-color-text-subtle | ||
--hn-color-text-inverse | ||
--hn-color-text-disabled |
| Variable | Hiru (clair) | Yozakura (sombre) |
|---|---|---|
--hn-color-border | ||
--hn-color-border-strong | ||
--hn-color-border-input | ||
--hn-color-hover | ||
--hn-color-active | ||
--hn-color-focus-ring | ||
--hn-color-overlay | ||
--hn-color-highlight |
| Variable | Hiru (clair) | Yozakura (sombre) |
|---|---|---|
--hn-color-primary | ||
--hn-color-primary-hover | ||
--hn-color-primary-active | ||
--hn-color-primary-soft | ||
--hn-color-primary-soft-hover | ||
--hn-color-primary-text | ||
--hn-color-primary-border | ||
--hn-color-on-primary | ||
--hn-color-link | ||
--hn-color-link-hover |
| Variable | Hiru (clair) | Yozakura (sombre) |
|---|---|---|
--hn-color-neutral-soft | ||
--hn-color-neutral-soft-hover | ||
--hn-color-neutral-text | ||
--hn-color-accent | ||
--hn-color-accent-soft | ||
--hn-color-accent-text |
| Variable | Hiru (clair) | Yozakura (sombre) |
|---|---|---|
--hn-color-success | ||
--hn-color-success-hover | ||
--hn-color-success-soft | ||
--hn-color-success-text | ||
--hn-color-success-border | ||
--hn-color-on-success | ||
--hn-color-warning | ||
--hn-color-warning-hover | ||
--hn-color-warning-soft | ||
--hn-color-warning-text | ||
--hn-color-warning-border | ||
--hn-color-on-warning | ||
--hn-color-danger | ||
--hn-color-danger-hover | ||
--hn-color-danger-soft | ||
--hn-color-danger-text | ||
--hn-color-danger-border | ||
--hn-color-on-danger | ||
--hn-color-info | ||
--hn-color-info-hover | ||
--hn-color-info-soft | ||
--hn-color-info-text | ||
--hn-color-info-border | ||
--hn-color-on-info |
| Variable | Hiru (clair) | Yozakura (sombre) |
|---|---|---|
--hn-color-petal | ||
--hn-color-petal-alt | ||
--hn-color-petal-deep |
sakura-600 (5,3:1) le jour, texte nuit sur sakura-400 (8,2:1) la nuit.npm test vérifie ces ratios à chaque modification des tokens.
昼 Hiru, le jour, sur papier washi. 夜桜 Yozakura, les cerisiers illuminés la nuit.
Le thème se choisit avec l'attribut data-hn-theme, sur la page entière ou sur n'importe quel bloc.
Pique-nique à Ueno
Samedi 4 avril, sous le grand cerisier.
Promenade nocturne
Lanternes allumées de 18 h à 22 h.
<div class="doc-theme-pair">
<div data-hn-theme="light">
<span class="hn-eyebrow">Hiru</span>
<p class="hn-h4">Pique-nique à Ueno</p>
<p class="hn-muted">Samedi 4 avril, sous le grand cerisier.</p>
<div class="hn-cluster"><button class="hn-btn hn-btn--sm">Réserver</button><span class="hn-badge">Mankai</span></div>
</div>
<div data-hn-theme="dark">
<span class="hn-eyebrow">Yozakura</span>
<p class="hn-h4">Promenade nocturne</p>
<p class="hn-muted">Lanternes allumées de 18 h à 22 h.</p>
<div class="hn-cluster"><button class="hn-btn hn-btn--sm">Réserver</button><span class="hn-badge">Illuminations</span></div>
</div>
</div>| Valeur | Effet |
|---|---|
data-hn-theme="light" | Thème clair (valeur par défaut, même sans attribut). |
data-hn-theme="dark" | Thème sombre yozakura. |
data-hn-theme="auto" | Suit le réglage du système (prefers-color-scheme). |
Le plus simple : le composant <hn-theme-toggle> (en haut à droite de cette page). Le choix est mémorisé
et la transition se fait en cercle depuis le bouton quand le navigateur le permet.
<hn-theme-toggle></hn-theme-toggle>
<button class="hn-btn hn-btn--outline hn-btn--sm" data-hn-set-theme="light">Clair</button>
<button class="hn-btn hn-btn--outline hn-btn--sm" data-hn-set-theme="dark">Sombre</button>
<button class="hn-btn hn-btn--outline hn-btn--sm" data-hn-set-theme="auto">Système</button>Les boutons ci-dessus n'ont aucun JavaScript : l'attribut data-hn-set-theme placé sur un bouton change le thème au clic. Côté code :
import { setTheme, toggleTheme, onThemeChange, initTheme } from '@kawaiitenshi/hanami-ui';
initTheme('auto'); // restaure le choix enregistré, sinon suit le système
setTheme('dark'); // 'light' | 'dark' | 'auto'
toggleTheme(); // alterne clair / sombre
onThemeChange(({ theme, resolved }) => console.log(theme, resolved));Pour éviter un flash au chargement, placez ce script en tout début de <head> (il est aussi exporté sous le nom themeScript) :
<script>
(function(){try{var t=localStorage.getItem('hn-theme');if(t)document.documentElement.setAttribute('data-hn-theme',t)}catch(e){}})();
</script>Un mincho élégant pour les titres, une gothique arrondie pour lire, une police à chasse fixe pour le code. Les trois sont auto-hébergées et retombent sur les polices japonaises du système.
| Rôle | Police | Variable |
|---|---|---|
| Titres | Shippori Mincho B1 | --hn-font-display |
| Texte et interface | Zen Maru Gothic | --hn-font-body |
| Code et données | M PLUS 1 Code | --hn-font-mono |
Hanami
<p class="hn-display">Hanami</p>
<h1 class="hn-h1">Les cerisiers de Kyoto</h1>
<h2 class="hn-h2">Prévisions de floraison</h2>
<h3 class="hn-h3">Parc Maruyama</h3>
<h4 class="hn-h4">Accès et horaires</h4>
<h5 class="hn-h5">Gare la plus proche</h5>
<h6 class="hn-h6">Gion-Shijō, ligne Keihan</h6>Chaque printemps, les Japonais se réunissent sous les cerisiers en fleurs pour le hanami.
Les prévisions annoncent l'éclosion (開花 kaika) puis la pleine floraison
(満開 mankai) environ une semaine plus tard. Consultez la palette,
copiez la classe hn-btn ou appuyez sur / pour chercher.
Pensez à réserver avant le 20 mars.
Sous les fleurs de cerisier, il n'y a pas d'étrangers.
<span class="hn-eyebrow">Saison 2026</span>
<p class="hn-lead">Chaque printemps, les Japonais se réunissent sous les cerisiers en fleurs pour le hanami.</p>
<p>
Les prévisions annoncent l'éclosion (<span lang="ja">開花</span> kaika) puis la pleine floraison
(<span lang="ja">満開</span> mankai) environ une semaine plus tard. Consultez <a class="hn-link" href="#couleurs">la palette</a>,
copiez la classe <code class="hn-code">hn-btn</code> ou appuyez sur <kbd class="hn-kbd">/</kbd> pour chercher.
Pensez à <mark class="hn-mark">réserver avant le 20 mars</mark>.
</p>
<blockquote class="hn-blockquote">
Sous les fleurs de cerisier, il n'y a pas d'étrangers.
<footer>Kobayashi Issa</footer>
</blockquote>--hn-text-xsSous les cerisiers0.75rem--hn-text-smSous les cerisiers0.875rem--hn-text-mdSous les cerisiers1rem--hn-text-lgSous les cerisiers1.125rem--hn-text-xlSous les cerisiers1.25rem--hn-text-2xlSous les cerisiers1.5rem--hn-text-3xlSous les cerisiers1.875rem--hn-text-4xlSous les cerisiers2.25rem--hn-text-5xlSous les cerisiers3rem--hn-text-6xlSous les cerisiers3.75rem--hn-text-7xlSous les cerisiers4.5remLa classe hn-prose met en forme du HTML brut (articles, Markdown converti) sans ajouter de classe à chaque balise.
Arrivez tôt : les meilleurs emplacements partent dès l'aube.
<article class="hn-prose">
<h2>Préparer son hanami</h2>
<p>Arrivez tôt : les meilleurs emplacements partent dès l'aube.</p>
<ul>
<li>Une bâche bleue (<em>rejaa shiito</em>)</li>
<li>Des bentos et du thé chaud</li>
<li>Un sac pour repartir avec vos déchets</li>
</ul>
</article>Une grille de 4 px, des arrondis généreux comme des pétales et des ombres teintées de prune.
--hn-space-10.25rem--hn-space-20.5rem--hn-space-30.75rem--hn-space-41rem--hn-space-51.25rem--hn-space-61.5rem--hn-space-82rem--hn-space-102.5rem--hn-space-123rem--hn-space-164rem--hn-space-205rem--hn-space-246rem--hn-space-0-50.125rem--hn-space-1-50.375rem--hn-radius-petal arrondit trois coins et en laisse un pointu, comme un pétale ; utilisez-le avec parcimonie pour un bouton ou une étiquette mis en avant.
--hn-radius-xs--hn-radius-sm--hn-radius-md--hn-radius-lg--hn-radius-xl--hn-radius-2xl--hn-radius-full--hn-radius-petal--hn-shadow-glow est le halo rose du survol des boutons principaux ; les autres ombres s'adaptent au thème.
--hn-shadow-xs--hn-shadow-sm--hn-shadow-md--hn-shadow-lg--hn-shadow-xl--hn-shadow-glowLes animations s'ouvrent comme une fleur : un départ vif et une arrivée très douce. Elles sont toutes coupées si l'utilisateur a activé la réduction des animations.
--hn-ease-standardTransitions d’interface.--hn-ease-bloomÉclosion : départ vif, arrivée très douce.--hn-ease-petalChute de pétale, balancée.--hn-ease-popLéger rebond.--hn-ease-exitSorties.<div class="doc-easings"><div class="doc-easing"><div class="doc-easing__track"><span class="doc-easing__dot" style="transition-timing-function:var(--hn-ease-standard)"></span></div><code>--hn-ease-standard</code><small>Transitions d’interface.</small></div><div class="doc-easing"><div class="doc-easing__track"><span class="doc-easing__dot" style="transition-timing-function:var(--hn-ease-bloom)"></span></div><code>--hn-ease-bloom</code><small>Éclosion : départ vif, arrivée très douce.</small></div><div class="doc-easing"><div class="doc-easing__track"><span class="doc-easing__dot" style="transition-timing-function:var(--hn-ease-petal)"></span></div><code>--hn-ease-petal</code><small>Chute de pétale, balancée.</small></div><div class="doc-easing"><div class="doc-easing__track"><span class="doc-easing__dot" style="transition-timing-function:var(--hn-ease-pop)"></span></div><code>--hn-ease-pop</code><small>Léger rebond.</small></div><div class="doc-easing"><div class="doc-easing__track"><span class="doc-easing__dot" style="transition-timing-function:var(--hn-ease-exit)"></span></div><code>--hn-ease-exit</code><small>Sorties.</small></div></div>
<button id="demo-easings-play" class="hn-btn hn-btn--soft hn-btn--sm" type="button" style="align-self:flex-start">Lancer les courbes</button>| Durée | Valeur | Usage |
|---|---|---|
--hn-duration-instant | 80 ms | Appui sur un bouton. |
--hn-duration-fast | 150 ms | Couleurs au survol. |
--hn-duration-normal | 240 ms | Menus, infobulles, interrupteurs. |
--hn-duration-slow | 400 ms | Modales, tiroirs, onglets. |
--hn-duration-slower | 700 ms | Grandes transitions, zoom d'image. |
<div class="hn-card hn-animate-bloom" style="padding:1rem 1.25rem">hn-animate-bloom</div>
<div class="hn-card hn-animate-fade-up" style="padding:1rem 1.25rem;--hn-delay:120ms">hn-animate-fade-up</div>
<div class="hn-card hn-animate-sway" style="padding:1rem 1.25rem">hn-animate-sway</div>
<span class="hn-logo hn-animate-spin" style="width:2.5rem;height:2.5rem"></span>
Une sélection d'icônes Lucide plus la fleur et le pétale Hanami,
via <hn-icon>. Elles prennent la couleur et la taille du texte. Cliquez pour copier.
<hn-icon name="sakura" style="color:var(--hn-color-primary);font-size:2rem"></hn-icon>
<hn-icon name="petal" style="color:var(--hn-color-petal-deep);font-size:2rem"></hn-icon>
<hn-icon name="heart" size="28"></hn-icon>
<hn-icon name="bell" label="Notifications" size="28"></hn-icon>
<button class="hn-btn"><hn-icon name="calendar"></hn-icon>Planifier</button>label, l'icône est décorative et ignorée par les lecteurs d'écran.label="…", elle devient une image avec ce texte alternatif.registerIcon('mon-icone', '<svg …>').<hn-icon>| Attribut | Valeurs | Rôle |
|---|---|---|
name | texte | Nom de l’icône. |
label | texte | Texte alternatif (sinon décorative). |
size | texte | Taille (px ou unité CSS). |
stroke-width | texte | Épaisseur du trait. |
Trois motifs 和柄 (wagara) en fond, dessinés en SVG et colorés par le thème.
Ajoutez hn-pattern-fade pour les estomper vers le bas.
青海波 Seigaiha
Les vagues de l'océan : calme et chance.
麻の葉 Asanoha
Feuilles de chanvre : croissance et vitalité.
花びら Hanabira
Pétales éparpillés sur le papier.
<div class="hn-surface hn-pattern-seigaiha hn-pattern-fade" style="min-height:11rem">
<p class="hn-h5"><span lang="ja">青海波</span> Seigaiha</p>
<p class="hn-muted hn-text-sm">Les vagues de l'océan : calme et chance.</p>
</div>
<div class="hn-surface hn-pattern-asanoha" style="min-height:11rem">
<p class="hn-h5"><span lang="ja">麻の葉</span> Asanoha</p>
<p class="hn-muted hn-text-sm">Feuilles de chanvre : croissance et vitalité.</p>
</div>
<div class="hn-surface hn-pattern-petals" style="min-height:11rem">
<p class="hn-h5"><span lang="ja">花びら</span> Hanabira</p>
<p class="hn-muted hn-text-sm">Pétales éparpillés sur le papier.</p>
</div>Variables de réglage : --hn-pattern-color, --hn-pattern-opacity et --hn-pattern-scale. La classe hn-bg-haru ajoute le ciel de printemps utilisé en haut de cette page.
Une classe hn-btn sur un <button> ou un lien, puis une variante, une taille et une forme.
<button class="hn-btn" type="button">Réserver</button>
<button class="hn-btn hn-btn--secondary" type="button">Plus tard</button>
<button class="hn-btn hn-btn--soft" type="button">Ajouter</button>
<button class="hn-btn hn-btn--outline" type="button">Partager</button>
<button class="hn-btn hn-btn--ghost" type="button">Annuler</button>
<button class="hn-btn hn-btn--link" type="button">En savoir plus</button>
<button class="hn-btn hn-btn--danger" type="button">Supprimer</button>
<button class="hn-btn hn-btn--success" type="button">Valider</button>Une seule action principale par écran : les autres prennent --secondary, --outline ou --ghost. Survolez-les : chaque variante a son propre retour visuel (halo rose, élévation, teinte).
<button class="hn-btn hn-btn--xs" type="button">Très petit</button>
<button class="hn-btn hn-btn--sm" type="button">Petit</button>
<button class="hn-btn" type="button">Normal</button>
<button class="hn-btn hn-btn--lg" type="button">Grand</button>
<button class="hn-btn hn-btn--pill" type="button">Pilule</button>
<button class="hn-btn hn-btn--petal hn-btn--lg" type="button">Pétale</button><button class="hn-btn" type="button"><hn-icon name="calendar"></hn-icon>Planifier</button>
<button class="hn-btn hn-btn--outline" type="button">Suivant<hn-icon name="arrow-right"></hn-icon></button>
<button class="hn-btn hn-btn--soft hn-btn--icon" type="button" aria-label="Ajouter aux favoris"><hn-icon name="heart"></hn-icon></button>
<button class="hn-btn hn-btn--ghost hn-btn--icon hn-btn--sm" type="button" aria-label="Plus d'options"><hn-icon name="ellipsis"></hn-icon></button>
<button class="hn-btn" type="button" aria-busy="true">Enregistrement</button>
<button class="hn-btn" type="button" disabled>Complet</button>
<a class="hn-btn hn-btn--outline" href="#boutons" aria-disabled="true">Lien désactivé</a>aria-busy="true" remplace le libellé par une fleur qui tourne et bloque les clics ; le bouton garde sa largeur.
<div class="hn-btn-group" role="group" aria-label="Période">
<button class="hn-btn hn-btn--outline" type="button" aria-pressed="true">Jour</button>
<button class="hn-btn hn-btn--outline" type="button" aria-pressed="false">Semaine</button>
<button class="hn-btn hn-btn--outline" type="button" aria-pressed="false">Mois</button>
</div>
<div class="hn-btn-group">
<button class="hn-btn" type="button">Réserver</button>
<button class="hn-btn hn-btn--icon" type="button" aria-label="Autres options"><hn-icon name="chevron-down"></hn-icon></button>
</div>Chaque bouton lit ses couleurs dans des variables locales : surchargez-les pour créer une variante sans écrire de nouveau sélecteur d'état.
.hn-btn--matcha {
--hn-btn-bg: var(--hn-matcha-600);
--hn-btn-bg-hover: var(--hn-matcha-700);
--hn-btn-bg-active: var(--hn-matcha-800);
--hn-btn-color: #fff;
--hn-btn-radius: var(--hn-radius-full);
}
Des éléments natifs stylés : ils fonctionnent tels quels avec v-model, ngModel,
les formulaires React et l'envoi HTML classique.
Tel qu'il apparaîtra sur la réservation.
Il manque le domaine, par exemple sakura@exemple.jp.
<div class="hn-field">
<label class="hn-label" for="f-nom">Nom <span class="hn-label__required" aria-hidden="true">*</span></label>
<input class="hn-input" id="f-nom" placeholder="Sakura Tanaka" required aria-describedby="f-nom-hint">
<p class="hn-hint" id="f-nom-hint">Tel qu'il apparaîtra sur la réservation.</p>
</div>
<div class="hn-field">
<label class="hn-label" for="f-mail">Email</label>
<input class="hn-input" id="f-mail" type="email" value="sakura@" aria-invalid="true" aria-describedby="f-mail-err">
<p class="hn-field__error" id="f-mail-err">Il manque le domaine, par exemple sakura@exemple.jp.</p>
</div>
<div class="hn-field">
<label class="hn-label" for="f-parc">Parc</label>
<select class="hn-select" id="f-parc">
<option>Ueno, Tokyo</option>
<option>Maruyama, Kyoto</option>
<option>Château d'Osaka</option>
</select>
</div>
<div class="hn-field">
<label class="hn-label" for="f-date">Date</label>
<input class="hn-input" id="f-date" type="date" value="2026-04-04">
</div>
<div class="hn-field">
<label class="hn-label" for="f-msg">Message</label>
<textarea class="hn-textarea" id="f-msg" rows="3" placeholder="Allergies, accessibilité, nombre d'enfants…"></textarea>
</div>
<div class="hn-field">
<label class="hn-label" for="f-off">Code de réservation</label>
<input class="hn-input" id="f-off" value="HNM-2026-0404" readonly>
<input class="hn-input hn-input--sm" value="Champ désactivé" disabled aria-label="Champ désactivé">
</div><div class="hn-input-icon">
<hn-icon name="search"></hn-icon>
<input class="hn-input" type="search" placeholder="Rechercher un parc" aria-label="Rechercher un parc">
</div>
<div class="hn-input-group">
<span class="hn-input-group__addon">https://</span>
<input class="hn-input" placeholder="mon-hanami.jp" aria-label="Adresse du site">
</div>
<div class="hn-input-group">
<input class="hn-input" placeholder="Votre email" aria-label="Votre email">
<button class="hn-btn" type="button">S'inscrire</button>
</div>
<input class="hn-input hn-input--lg hn-input--pill" placeholder="Grand et arrondi" aria-label="Exemple de grand champ"><fieldset class="hn-fieldset">
<legend>Options</legend>
<label class="hn-check"><input type="checkbox" class="hn-checkbox" checked> Bâche fournie</label>
<label class="hn-check"><input type="checkbox" class="hn-checkbox"> Panier bento</label>
<label class="hn-check"><input type="checkbox" class="hn-checkbox" data-demo-indeterminate> Toutes les boissons</label>
<label class="hn-check"><input type="checkbox" class="hn-checkbox" disabled> Lanterne (épuisé)</label>
</fieldset>
<fieldset class="hn-fieldset">
<legend>Créneau</legend>
<label class="hn-check"><input type="radio" name="creneau" class="hn-radio" checked> Matin</label>
<label class="hn-check"><input type="radio" name="creneau" class="hn-radio"> Après-midi</label>
<label class="hn-check">
<input type="radio" name="creneau" class="hn-radio">
<span class="hn-check__text">Soirée yozakura<span class="hn-check__hint">Cerisiers illuminés jusqu'à 22 h</span></span>
</label>
</fieldset>
<fieldset class="hn-fieldset">
<legend>Notifications</legend>
<label class="hn-check"><input type="checkbox" role="switch" class="hn-switch" checked> Prévisions de floraison</label>
<label class="hn-check"><input type="checkbox" role="switch" class="hn-switch"> Alertes météo</label>
<label class="hn-check"><input type="checkbox" role="switch" class="hn-switch hn-switch--sm"> Version compacte</label>
</fieldset>Un interrupteur est une case à cocher avec role="switch" : une petite fleur apparaît dans le curseur quand il est activé.
<div class="hn-field">
<label class="hn-label" for="f-range">Floraison : <output id="f-range-out" for="f-range"></output></label>
<input class="hn-range" id="f-range" type="range" min="0" max="100" value="62" data-demo-range="f-range-out">
</div>
<div class="hn-field">
<label class="hn-label" for="f-file">Photo de l'emplacement</label>
<input class="hn-file" id="f-file" type="file" accept="image/*">
</div>La partie remplie du curseur est dessinée en CSS pur : elle suit la valeur même quand un framework la modifie, sans script.
Des étiquettes pour un statut, des tags pour filtrer, des avatars pour les personnes.
<span class="hn-badge">Nouveau</span>
<span class="hn-badge hn-badge--solid">12</span>
<span class="hn-badge hn-badge--neutral">Brouillon</span>
<span class="hn-badge hn-badge--accent">Fuji</span>
<span class="hn-badge hn-badge--success hn-badge--dot">Ouvert</span>
<span class="hn-badge hn-badge--warning">Pluie</span>
<span class="hn-badge hn-badge--danger">Complet</span>
<span class="hn-badge hn-badge--info">Info</span>
<span class="hn-badge hn-badge--outline">Archivé</span>
<span class="hn-badge hn-badge--lg">Mankai 満開</span>
<span class="hn-badge-anchor">
<button class="hn-btn hn-btn--secondary hn-btn--icon" type="button" aria-label="Notifications, 3 non lues"><hn-icon name="bell"></hn-icon></button>
<span class="hn-badge hn-badge--solid" aria-hidden="true">3</span>
</span><div class="hn-cluster">
<button class="hn-tag" type="button" aria-pressed="true"><hn-icon name="flower"></hn-icon>Kyoto</button>
<button class="hn-tag" type="button" aria-pressed="false">Tokyo</button>
<button class="hn-tag" type="button" aria-pressed="false">Osaka</button>
<span class="hn-tag">Nara <button class="hn-tag__remove" type="button" aria-label="Retirer Nara"></button></span>
<label class="hn-tag"><input type="checkbox" checked> Pique-nique</label>
</div>
<input id="demo-tag-input" class="hn-input hn-input--sm" style="max-width:18rem" placeholder="Ajouter un tag puis Entrée" aria-label="Ajouter un tag"><span class="hn-avatar hn-avatar--xs">ST</span>
<span class="hn-avatar hn-avatar--sm">YK</span>
<span class="hn-avatar">HM<span class="hn-avatar__status"></span></span>
<span class="hn-avatar hn-avatar--lg">AI<span class="hn-avatar__status hn-avatar__status--away"></span></span>
<span class="hn-avatar hn-avatar--xl hn-avatar--square">花</span>
<div class="hn-avatar-group">
<span class="hn-avatar">ST</span>
<span class="hn-avatar">YK</span>
<span class="hn-avatar">RN</span>
<span class="hn-avatar">+4</span>
</div>
<span class="hn-status-dot hn-status-dot--live" role="img" aria-label="En direct"></span>Un conteneur pour un sujet : un parc, une réservation, un réglage. Les parties sont toutes facultatives.
Kyoto · 680 cerisiers
Samedi 4 avril, 10 h
<article class="hn-card hn-card--interactive">
<figure class="hn-card__media"><div class="doc-photo doc-photo--1">丸山公園</div></figure>
<div class="hn-card__header">
<div>
<h3 class="hn-card__title"><a class="hn-card__link" href="#cartes">Parc Maruyama</a></h3>
<p class="hn-card__subtitle">Kyoto · 680 cerisiers</p>
</div>
<span class="hn-badge hn-badge--success">Mankai</span>
</div>
<div class="hn-card__body">Le grand cerisier pleureur est illuminé chaque soir jusqu'à minuit.</div>
</article>
<article class="hn-card">
<div class="hn-card__header">
<span class="hn-avatar">ST</span>
<div>
<h3 class="hn-card__title">Réservation n° 0404</h3>
<p class="hn-card__subtitle">Samedi 4 avril, 10 h</p>
</div>
</div>
<div class="hn-card__body">Emplacement C-12 près de l'étang, pour 6 personnes.</div>
<div class="hn-card__footer hn-card__footer--divided">
<button class="hn-btn hn-btn--sm" type="button">Modifier</button>
<button class="hn-btn hn-btn--sm hn-btn--ghost" type="button">Annuler</button>
</div>
</article>
<article class="hn-card hn-card--sakura">
<div class="hn-card__header"><h3 class="hn-card__title">Carte sakura</h3></div>
<div class="hn-card__body">Une fleur en filigrane qui tourne au survol, pour mettre une offre en avant.</div>
<div class="hn-card__footer"><a class="hn-btn hn-btn--soft hn-btn--sm" href="#cartes">Découvrir</a></div>
</article><div class="hn-card"><div class="hn-card__body"><strong>Par défaut</strong><br>Bordure fine et ombre légère.</div></div>
<div class="hn-card hn-card--elevated"><div class="hn-card__body"><strong>--elevated</strong><br>Sans bordure, ombre plus marquée.</div></div>
<div class="hn-card hn-card--flat"><div class="hn-card__body"><strong>--flat</strong><br>Fond teinté, sans ombre.</div></div>
<div class="hn-card hn-card--outline"><div class="hn-card__body"><strong>--outline</strong><br>Transparente, bordure seule.</div></div>hn-card--interactive soulève la carte au survol ; avec hn-card__link sur le lien du titre, toute la carte devient cliquable sans imbriquer de liens.
Aucun pique-nique prévu
Choisissez un parc et une date pour réserver votre emplacement sous les cerisiers.
<div class="hn-empty">
<span class="hn-empty__icon" aria-hidden="true"></span>
<p class="hn-empty__title">Aucun pique-nique prévu</p>
<p class="hn-empty__text">Choisissez un parc et une date pour réserver votre emplacement sous les cerisiers.</p>
<button class="hn-btn hn-btn--soft" type="button"><hn-icon name="plus"></hn-icon>Nouvelle réservation</button>
</div>Les alertes restent dans la page ; les toasts apparaissent un moment dans un coin puis s'effacent.
Prévisions mises à jour
L'éclosion est attendue le 24 mars à Tokyo, deux jours plus tôt que l'an dernier.
Réservation confirmée
Un email récapitulatif vous a été envoyé.
Pluie prévue samedi
Les pétales tombent vite sous la pluie : pensez à avancer votre sortie.
Paiement refusé
Votre banque a refusé la transaction. Vérifiez la date d'expiration de la carte.
Mankai à Kyoto
Les cerisiers du parc Maruyama sont en pleine floraison.
<div class="hn-alert" role="status">
<p class="hn-alert__title">Prévisions mises à jour</p>
<p class="hn-alert__body">L'éclosion est attendue le 24 mars à Tokyo, deux jours plus tôt que l'an dernier.</p>
</div>
<div class="hn-alert hn-alert--success" role="status">
<p class="hn-alert__title">Réservation confirmée</p>
<p class="hn-alert__body">Un email récapitulatif vous a été envoyé.</p>
<button class="hn-close" type="button" aria-label="Fermer" onclick="this.closest('.hn-alert').remove()"></button>
</div>
<div class="hn-alert hn-alert--warning" role="status">
<p class="hn-alert__title">Pluie prévue samedi</p>
<p class="hn-alert__body">Les pétales tombent vite sous la pluie : pensez à avancer votre sortie.</p>
<div class="hn-alert__actions">
<button class="hn-btn hn-btn--sm" type="button">Changer de date</button>
<button class="hn-btn hn-btn--sm hn-btn--ghost" type="button">Garder samedi</button>
</div>
</div>
<div class="hn-alert hn-alert--danger" role="alert">
<p class="hn-alert__title">Paiement refusé</p>
<p class="hn-alert__body">Votre banque a refusé la transaction. Vérifiez la date d'expiration de la carte.</p>
</div>
<div class="hn-alert hn-alert--sakura" role="status">
<p class="hn-alert__title">Mankai à Kyoto</p>
<p class="hn-alert__body">Les cerisiers du parc Maruyama sont en pleine floraison.</p>
</div><button class="hn-btn" type="button" onclick="Hanami.toast({ title: 'Mankai !', message: 'Pleine floraison à Kyoto.' })">Sakura</button>
<button class="hn-btn hn-btn--outline" type="button" onclick="Hanami.toast.success('Réservation enregistrée.')">Succès</button>
<button class="hn-btn hn-btn--outline" type="button" onclick="Hanami.toast.warning('Pluie annoncée samedi.')">Avertissement</button>
<button class="hn-btn hn-btn--outline" type="button" onclick="Hanami.toast.error('Le parc est fermé ce soir.')">Erreur</button>
<button class="hn-btn hn-btn--ghost" type="button" onclick="Hanami.toast({ title: 'Emplacement supprimé', message: 'C-12, samedi 4 avril.', variant: 'info', action: { label: 'Annuler', onClick: () => Hanami.toast.success('Emplacement restauré.') } })">Avec action</button>import { toast } from '@kawaiitenshi/hanami-ui';
toast('Enregistré');
toast.success('Réservation enregistrée.');
toast({
title: 'Emplacement supprimé',
message: 'C-12, samedi 4 avril.',
variant: 'info', // 'sakura' | 'info' | 'success' | 'warning' | 'danger'
duration: 8000, // 0 = reste affiché
position: 'top-center', // top-start, top-end, bottom-start, bottom-center, bottom-end…
action: { label: 'Annuler', onClick: () => restaurer() },
});aria-live) et passent au-dessus d'une modale ouverte.
<hn-dialog> et <hn-drawer> reposent sur l'élément natif
<dialog> : focus piégé, touche Échap, retour du focus à la fermeture. Sur mobile, la modale monte du bas de l'écran.
<button class="hn-btn" type="button" data-hn-open="demo-dialog">Réserver un emplacement</button>
<hn-dialog id="demo-dialog" label="Réserver un emplacement">
<form class="hn-stack" id="demo-dialog-form" data-demo-form>
<p>Choisissez votre coin sous les cerisiers du parc Ueno.</p>
<div class="hn-field">
<label class="hn-label" for="dlg-nom">Nom</label>
<input class="hn-input" id="dlg-nom" autofocus required>
</div>
<div class="hn-field">
<label class="hn-label" for="dlg-pers">Personnes</label>
<input class="hn-input" id="dlg-pers" type="number" min="1" max="12" value="4">
</div>
</form>
<div slot="footer" class="hn-dialog-actions">
<button class="hn-btn hn-btn--ghost" type="button" data-hn-close>Annuler</button>
<button class="hn-btn" type="submit" form="demo-dialog-form">Réserver</button>
</div>
</hn-dialog>
data-hn-open="id" et data-hn-close suffisent dans du HTML statique. Dans un framework, liez l'attribut
open à votre état et écoutez hn-close pour le remettre à false.
Cette action est définitive.
Les emplacements sont attribués dans l'ordre des réservations. Chaque groupe dispose d'un espace de 3 × 3 mètres.
Les feux, barbecues et amplificateurs sont interdits. Les déchets doivent être remportés.
En cas de pluie, la réservation peut être déplacée gratuitement jusqu'à la veille 18 h.
Échap et le clic sur le fond sont ignorés : seul ce bouton ferme la fenêtre.
<button class="hn-btn hn-btn--outline" type="button" data-hn-open="demo-dialog-sm">Petite</button>
<button class="hn-btn hn-btn--outline" type="button" data-hn-open="demo-dialog-lg">Grande</button>
<button class="hn-btn hn-btn--outline" type="button" data-hn-open="demo-request-close">Empêcher la fermeture</button>
<button class="hn-btn hn-btn--outline" type="button" data-hn-open="demo-persistent">Persistante</button>
<hn-dialog id="demo-dialog-sm" size="sm" label="Supprimer la réservation ?">
<p>Cette action est définitive.</p>
<div slot="footer" class="hn-dialog-actions">
<button class="hn-btn hn-btn--ghost" type="button" data-hn-close>Garder</button>
<button class="hn-btn hn-btn--danger" type="button" data-hn-close>Supprimer</button>
</div>
</hn-dialog>
<hn-dialog id="demo-dialog-lg" size="lg" label="Conditions de réservation">
<div class="hn-prose">
<p>Les emplacements sont attribués dans l'ordre des réservations. Chaque groupe dispose d'un espace de 3 × 3 mètres.</p>
<p>Les feux, barbecues et amplificateurs sont interdits. Les déchets doivent être remportés.</p>
<p>En cas de pluie, la réservation peut être déplacée gratuitement jusqu'à la veille 18 h.</p>
</div>
</hn-dialog>
<hn-dialog id="demo-request-close" label="Note pour l'organisateur">
<form class="hn-field"><label class="hn-label" for="rc-note">Votre note</label><input class="hn-input" id="rc-note" placeholder="Tapez quelque chose, puis Échap"></form>
</hn-dialog>
<hn-dialog id="demo-persistent" label="Conditions à accepter" persistent no-close-button>
<p>Échap et le clic sur le fond sont ignorés : seul ce bouton ferme la fenêtre.</p>
<div slot="footer" class="hn-dialog-actions"><button class="hn-btn" type="button" data-hn-close>J'accepte</button></div>
</hn-dialog>dialog.addEventListener('hn-request-close', (event) => {
// event.detail.reason : 'escape' | 'backdrop' | 'close-button' | 'api'
if (formulaireModifie) event.preventDefault();
});<hn-dialog>| Attribut | Valeurs | Rôle |
|---|---|---|
open | booléen | Affiche la fenêtre. |
label | texte | Titre affiché et nom accessible. |
size | sm md lg xl full | Largeur. |
persistent | booléen | Ignore Échap et le clic sur le fond. |
no-close-button | booléen | Masque la croix de fermeture. |
<button class="hn-btn hn-btn--outline" type="button" data-hn-open="demo-drawer"><hn-icon name="filter"></hn-icon>Filtres</button>
<button class="hn-btn hn-btn--outline" type="button" data-hn-open="demo-drawer-bottom">Depuis le bas</button>
<hn-drawer id="demo-drawer" label="Filtrer les parcs">
<div class="hn-stack">
<label class="hn-check"><input type="checkbox" class="hn-checkbox" checked> Pleine floraison</label>
<label class="hn-check"><input type="checkbox" class="hn-checkbox"> Illuminations le soir</label>
<label class="hn-check"><input type="checkbox" class="hn-checkbox"> Accessible en fauteuil</label>
</div>
<div slot="footer" class="hn-dialog-actions"><button class="hn-btn" type="button" data-hn-close>Afficher 12 parcs</button></div>
</hn-drawer>
<hn-drawer id="demo-drawer-bottom" placement="bottom" label="Partager">
<div class="hn-cluster">
<button class="hn-btn hn-btn--soft" type="button"><hn-icon name="link"></hn-icon>Copier le lien</button>
<button class="hn-btn hn-btn--soft" type="button"><hn-icon name="mail"></hn-icon>Email</button>
</div>
</hn-drawer><hn-drawer>| Attribut | Valeurs | Rôle |
|---|---|---|
open | booléen | Affiche le tiroir. |
label | texte | Titre affiché et nom accessible. |
placement | end start top bottom | Bord d’apparition. |
persistent | booléen | Ignore Échap et le clic sur le fond. |
no-close-button | booléen | Masque la croix de fermeture. |
<hn-sidebar> se replie en colonne d'icônes (avec libellé au survol), mémorise son état
et devient un tiroir sous le point de rupture. Le sommaire de cette page en est une.
<div class="doc-frame">
<div class="hn-app">
<hn-sidebar id="demo-sidebar" breakpoint="0" label="Navigation de démonstration">
<a slot="header" class="hn-sidebar-brand" href="#sidebar"><span class="hn-logo"></span><span class="hn-sidebar-brand__text">Hanami</span></a>
<div class="hn-sidebar-group">
<p class="hn-sidebar-group__title">Principal</p>
<a class="hn-sidebar-item" href="#sidebar" aria-current="page"><hn-icon name="house"></hn-icon><span class="hn-sidebar-item__label">Accueil</span></a>
<a class="hn-sidebar-item" href="#sidebar"><hn-icon name="calendar-days"></hn-icon><span class="hn-sidebar-item__label">Floraisons</span><span class="hn-badge">3</span></a>
<details class="hn-sidebar-sub">
<summary class="hn-sidebar-item"><hn-icon name="map-pin"></hn-icon><span class="hn-sidebar-item__label">Parcs</span></summary>
<div class="hn-sidebar-sub__items">
<a class="hn-sidebar-item" href="#sidebar"><span class="hn-sidebar-item__label">Ueno</span></a>
<a class="hn-sidebar-item" href="#sidebar"><span class="hn-sidebar-item__label">Maruyama</span></a>
</div>
</details>
</div>
<div class="hn-sidebar-group">
<p class="hn-sidebar-group__title">Compte</p>
<a class="hn-sidebar-item" href="#sidebar"><hn-icon name="settings"></hn-icon><span class="hn-sidebar-item__label">Réglages</span></a>
</div>
<div slot="footer" class="hn-cluster" style="--hn-cluster-gap:.625rem;flex-wrap:nowrap">
<span class="hn-avatar hn-avatar--sm">ST</span>
<span class="hn-sidebar-item__label">Sakura Tanaka</span>
</div>
</hn-sidebar>
<div class="hn-app__main">
<div class="hn-app__header" style="position:relative">
<button class="hn-btn hn-btn--ghost hn-btn--sm" type="button" data-hn-toggle="demo-sidebar"><hn-icon name="panel-left"></hn-icon>Replier</button>
</div>
<div class="hn-app__content"><p class="hn-muted">Le bouton en bas de la barre la replie aussi. Survolez les icônes une fois repliée.</p></div>
</div>
</div>
</div><div class="hn-app">
<hn-sidebar persist="app" breakpoint="1024">
<a slot="header" class="hn-sidebar-brand" href="/"><span class="hn-logo"></span><span class="hn-sidebar-brand__text">Mon site</span></a>
<div class="hn-sidebar-group">
<a class="hn-sidebar-item" href="/" aria-current="page">
<hn-icon name="house"></hn-icon><span class="hn-sidebar-item__label">Accueil</span>
</a>
</div>
</hn-sidebar>
<div class="hn-app__main">
<header class="hn-app__header">
<button class="hn-btn hn-btn--ghost hn-btn--icon" data-hn-toggle="…" aria-label="Menu"><hn-icon name="menu"></hn-icon></button>
</header>
<main class="hn-app__content">…</main>
</div>
</div>breakpoint (1024 px par défaut), la barre sort du flux : toggle() ou data-hn-toggle l'ouvrent en tiroir, avec fond assombri et focus piégé.aria-current="page" sur le lien actif : un petit trait rose le signale.placement="end" place la barre à droite.<hn-sidebar>| Attribut | Valeurs | Rôle |
|---|---|---|
collapsed | booléen | Mode replié (icônes seules). |
open | booléen | Tiroir ouvert (mode mobile). |
breakpoint | texte | Largeur (px) sous laquelle la barre devient un tiroir. Défaut : 1024. |
persist | texte | Clé localStorage pour mémoriser l’état replié. |
placement | start end | Côté de l’écran. |
label | texte | Nom accessible de la navigation. |
no-toggle | booléen | Masque le bouton de repli intégré. |
Des onglets accessibles au clavier (flèches, Début, Fin) avec un indicateur qui glisse, et des accordéons natifs.
<hn-tabs label="Saisons">
<hn-tab panel="haru">Printemps</hn-tab>
<hn-tab panel="natsu">Été</hn-tab>
<hn-tab panel="aki">Automne</hn-tab>
<hn-tab panel="fuyu" disabled>Hiver</hn-tab>
<hn-tab-panel name="haru">Les cerisiers fleurissent de fin mars à début avril.</hn-tab-panel>
<hn-tab-panel name="natsu">Festivals de lanternes et feux d'artifice.</hn-tab-panel>
<hn-tab-panel name="aki">Les érables rougissent : c'est le momijigari.</hn-tab-panel>
<hn-tab-panel name="fuyu">Fermé pour l'hiver.</hn-tab-panel>
</hn-tabs><hn-tabs variant="pills" value="semaine" label="Période">
<hn-tab panel="jour">Jour</hn-tab>
<hn-tab panel="semaine">Semaine</hn-tab>
<hn-tab panel="mois">Mois</hn-tab>
<hn-tab-panel name="jour">12 réservations aujourd'hui.</hn-tab-panel>
<hn-tab-panel name="semaine">84 réservations cette semaine.</hn-tab-panel>
<hn-tab-panel name="mois">310 réservations ce mois-ci.</hn-tab-panel>
</hn-tabs>L'événement hn-change donne le nom du panneau choisi (event.detail.value) ; l'attribut value sélectionne un onglet depuis votre code.
<hn-tabs>| Attribut | Valeurs | Rôle |
|---|---|---|
value | texte | Nom du panneau actif. |
variant | line pills | Style. |
label | texte | Nom accessible de la liste d’onglets. |
activation | auto manual | Activation au clavier. |
De simples <details> : aucun JavaScript, et l'attribut name n'en laisse qu'un ouvert à la fois.
<div class="hn-accordion-group">
<details class="hn-accordion" name="faq" open>
<summary>Quand fleurissent les cerisiers ?</summary>
<div class="hn-accordion__content">À Tokyo, généralement entre le 20 mars et le 5 avril. La pleine floraison dure environ une semaine.</div>
</details>
<details class="hn-accordion" name="faq">
<summary>Peut-on pique-niquer partout ?</summary>
<div class="hn-accordion__content">Non : certains jardins comme Shinjuku Gyoen interdisent l'alcool et limitent les bâches.</div>
</details>
<details class="hn-accordion" name="faq">
<summary>Que faire en cas de pluie ?</summary>
<div class="hn-accordion__content">Votre réservation peut être déplacée gratuitement jusqu'à la veille 18 h.</div>
</details>
</div>Tableaux, barres de progression, spinner en forme de fleur, squelettes et séparateurs.
| Ville | Parc | Kaika | Mankai | État |
|---|---|---|---|---|
| Tokyo | Ueno | 24 mars | 1 avr. | Ouvert |
| Kyoto | Maruyama | 26 mars | 4 avr. | Affluence |
| Osaka | Château | 27 mars | 5 avr. | Ouvert |
| Sapporo | Maruyama | 28 avr. | 3 mai | À venir |
<div class="hn-table-wrap">
<table class="hn-table hn-table--hover">
<caption>Prévisions 2026 (dates indicatives)</caption>
<thead>
<tr><th scope="col">Ville</th><th scope="col">Parc</th><th scope="col" class="hn-table__num">Kaika</th><th scope="col" class="hn-table__num">Mankai</th><th scope="col">État</th></tr>
</thead>
<tbody>
<tr><td>Tokyo</td><td>Ueno</td><td class="hn-table__num">24 mars</td><td class="hn-table__num">1 avr.</td><td><span class="hn-badge hn-badge--success hn-badge--dot">Ouvert</span></td></tr>
<tr><td>Kyoto</td><td>Maruyama</td><td class="hn-table__num">26 mars</td><td class="hn-table__num">4 avr.</td><td><span class="hn-badge hn-badge--warning hn-badge--dot">Affluence</span></td></tr>
<tr aria-selected="true"><td>Osaka</td><td>Château</td><td class="hn-table__num">27 mars</td><td class="hn-table__num">5 avr.</td><td><span class="hn-badge hn-badge--success hn-badge--dot">Ouvert</span></td></tr>
<tr><td>Sapporo</td><td>Maruyama</td><td class="hn-table__num">28 avr.</td><td class="hn-table__num">3 mai</td><td><span class="hn-badge hn-badge--neutral">À venir</span></td></tr>
</tbody>
</table>
</div>Variantes : hn-table--striped, hn-table--hover, hn-table--compact. Les chiffres sont alignés en colonnes (tabular-nums).
<progress class="hn-progress" id="demo-progress" value="45" max="100" aria-label="Floraison">45 %</progress>
<progress class="hn-progress hn-progress--sm hn-progress--success" value="80" max="100" aria-label="Places réservées">80 %</progress>
<progress class="hn-progress hn-progress--lg" aria-label="Chargement"></progress>
<button class="hn-btn hn-btn--soft hn-btn--sm" id="demo-progress-run" type="button" style="align-self:flex-start">Faire fleurir</button><span class="hn-spinner hn-spinner--sm" role="status" aria-label="Chargement"></span>
<span class="hn-spinner" role="status" aria-label="Chargement"></span>
<span class="hn-spinner hn-spinner--lg" role="status" aria-label="Chargement"></span>
<span class="hn-spinner hn-spinner--xl" role="status" aria-label="Chargement" style="color:var(--hn-color-accent)"></span>
<div class="hn-card" style="width:18rem">
<div class="hn-card__header">
<span class="hn-skeleton hn-skeleton--circle"></span>
<div style="flex:1"><span class="hn-skeleton hn-skeleton--text"></span><span class="hn-skeleton hn-skeleton--text"></span></div>
</div>
<div class="hn-card__body"><span class="hn-skeleton hn-skeleton--block" style="--hn-skeleton-height:5rem"></span></div>
</div><div class="hn-divider"></div>
<div class="hn-divider">ou</div>
<div class="hn-divider hn-divider--sakura"></div>
<div class="hn-cluster">Ueno <span class="hn-divider hn-divider--vertical"></span> Maruyama <span class="hn-divider hn-divider--vertical"></span> Château d'Osaka</div>
<hn-carousel> s'appuie sur le défilement natif : il glisse au doigt, à la molette et au clavier,
et ajoute flèches, pastilles et lecture automatique.
<hn-carousel loop label="Parcs à découvrir">
<div class="hn-carousel__slide"><div class="doc-photo doc-photo--1" style="min-height:13rem">上野 Ueno</div></div>
<div class="hn-carousel__slide"><div class="doc-photo doc-photo--2" style="min-height:13rem">丸山 Maruyama</div></div>
<div class="hn-carousel__slide"><div class="doc-photo doc-photo--3" style="min-height:13rem">吉野 Yoshino</div></div>
<div class="hn-carousel__slide"><div class="doc-photo doc-photo--4" style="min-height:13rem">弘前 Hirosaki</div></div>
</hn-carousel><hn-carousel autoplay="4000" style="--hn-carousel-per-view:3;--hn-carousel-gap:1rem" label="Galerie">
<div class="hn-carousel__slide"><div class="doc-photo doc-photo--5">桜</div></div>
<div class="hn-carousel__slide"><div class="doc-photo doc-photo--1">梅</div></div>
<div class="hn-carousel__slide"><div class="doc-photo doc-photo--2">藤</div></div>
<div class="hn-carousel__slide"><div class="doc-photo doc-photo--3">菊</div></div>
<div class="hn-carousel__slide"><div class="doc-photo doc-photo--4">椿</div></div>
</hn-carousel>La lecture automatique s'interrompt au survol, au focus et quand l'onglet est masqué ; un bouton pause est ajouté et elle est désactivée si l'utilisateur réduit les animations.
<hn-carousel>| Attribut | Valeurs | Rôle |
|---|---|---|
loop | booléen | Revient au début après la dernière diapositive. |
autoplay | texte | Lecture automatique (intervalle en ms, défaut 5000). |
label | texte | Nom accessible. |
no-arrows | booléen | Masque les flèches. |
no-dots | booléen | Masque les pastilles. |
<hn-petals> fait tomber des pétales de cerisier derrière un contenu : le 花吹雪
hanafubuki, la tempête de pétales. Décoratif, il s'arrête hors de l'écran et reste figé si les animations sont réduites.
Hanafubuki
40 pétales, un peu de vent.
<div class="doc-petals-stage hn-bg-haru">
<hn-petals count="40" wind="1.4"></hn-petals>
<div style="position:relative;text-align:center">
<p class="hn-h2">Hanafubuki</p>
<p class="hn-muted">40 pétales, un peu de vent.</p>
</div>
</div>
Le parent doit être positionné (position: relative). Ajoutez fixed pour couvrir toute la fenêtre.
Les couleurs viennent de --hn-color-petal, --hn-color-petal-alt et --hn-color-petal-deep.
<hn-petals>| Attribut | Valeurs | Rôle |
|---|---|---|
count | texte | Nombre de pétales (défaut 24). |
speed | texte | Vitesse (défaut 1). |
wind | texte | Force du vent (défaut 1). |
size | texte | Taille relative (défaut 1). |
fixed | booléen | Couvre toute la fenêtre. |
paused | booléen | Met l’animation en pause. |
Quatorze classes à poser sur n'importe quel élément, en plus des états de survol déjà intégrés à chaque composant. Elles ne s'activent qu'avec une souris (pas de survol collant sur mobile) et la plupart répondent aussi au focus clavier.
hn-hover-lifts'élèvehn-hover-growgrandithn-hover-bloomhalo rosehn-hover-haloonde autourhn-hover-tintse teintehn-hover-shinereflethn-hover-petalun pétale s'envolehn-hover-floatse balancehn-hover-tiltperspectivehn-hover-zoomzoom sur l'image<div class="doc-hover-grid">
<div class="doc-hover-tile hn-hover-lift" tabindex="0"><code>hn-hover-lift</code><small>s'élève</small></div>
<div class="doc-hover-tile hn-hover-grow" tabindex="0"><code>hn-hover-grow</code><small>grandit</small></div>
<div class="doc-hover-tile hn-hover-bloom" tabindex="0"><code>hn-hover-bloom</code><small>halo rose</small></div>
<div class="doc-hover-tile hn-hover-halo" tabindex="0"><code>hn-hover-halo</code><small>onde autour</small></div>
<div class="doc-hover-tile hn-hover-tint" tabindex="0"><code>hn-hover-tint</code><small>se teinte</small></div>
<div class="doc-hover-tile hn-hover-shine" tabindex="0"><code>hn-hover-shine</code><small>reflet</small></div>
<div class="doc-hover-tile hn-hover-petal" tabindex="0"><code>hn-hover-petal</code><small>un pétale s'envole</small></div>
<div class="doc-hover-tile hn-hover-float" tabindex="0"><code>hn-hover-float</code><small>se balance</small></div>
<div class="doc-hover-tile hn-hover-tilt" tabindex="0"><code>hn-hover-tilt</code><small>perspective</small></div>
<div class="doc-hover-tile hn-hover-zoom" tabindex="0"><img src="favicon.svg" alt="" width="56" height="56"><code>hn-hover-zoom</code><small>zoom sur l'image</small></div>
</div><p>
<a href="#survol" class="hn-hover-underline">Soulignement qui se dessine</a> ·
<a href="#survol" class="hn-hover-brush" style="color:inherit">surlignage au pinceau</a>
</p>
<div class="hn-cluster hn-hover-dim">
<span class="hn-tag">Ueno</span><span class="hn-tag">Maruyama</span><span class="hn-tag">Yoshino</span><span class="hn-tag">Hirosaki</span>
</div>
<div class="hn-card hn-hover-reveal" style="max-width:22rem">
<div class="hn-card__body hn-cluster hn-justify-between">
<span>Survolez la carte</span>
<span class="hn-hover-reveal__target hn-cluster" style="--hn-cluster-gap:.25rem">
<button class="hn-btn hn-btn--ghost hn-btn--icon hn-btn--sm" type="button" aria-label="Modifier"><hn-icon name="pencil"></hn-icon></button>
<button class="hn-btn hn-btn--ghost hn-btn--icon hn-btn--sm" type="button" aria-label="Supprimer"><hn-icon name="trash-2"></hn-icon></button>
</span>
</div>
</div>hn-hover-dim atténue les voisins de l'élément survolé ; hn-hover-reveal révèle .hn-hover-reveal__target au survol ou au focus.
<div class="hn-card hn-hover-lift hn-hover-petal" style="padding:1.25rem;width:16rem">Carte qui s'élève et laisse s'envoler un pétale.</div>
<button class="hn-btn hn-btn--outline hn-hover-halo" type="button">Bouton avec onde</button>
<button class="hn-btn hn-btn--lg hn-hover-shine" type="button">Bouton avec reflet</button>hn-hover-shine et hn-hover-petal utilisent les pseudo-éléments ::after et ::before : évitez-les sur un élément qui s'en sert déjà.
Quelques classes pour assembler un écran sans écrire de CSS ; pour le reste, utilisez vos propres styles ou Tailwind.
| Classe | Effet | Réglage |
|---|---|---|
hn-container | Largeur maximale centrée avec marges. | --narrow, --wide |
hn-stack | Empile verticalement avec un écart. | --hn-stack-gap |
hn-cluster | Aligne en ligne et passe à la ligne. | --hn-cluster-gap |
hn-grid | Grille fluide (autant de colonnes que possible). | --hn-grid-min, --hn-grid-gap |
hn-grid--2/3/4 | Colonnes fixes, une seule sur mobile. | |
hn-surface | Bloc avec fond, bordure et arrondi. | --sunken, --raised |
hn-hide-mobile / hn-hide-desktop | Masque sous ou au-dessus de 768 px. | |
hn-sr-only | Visible seulement des lecteurs d'écran. | |
hn-truncate | Coupe le texte avec des points de suspension. |
<div class="hn-grid" style="--hn-grid-min:12rem;--hn-grid-gap:1rem">
<div class="hn-surface hn-stack" style="--hn-stack-gap:.5rem"><strong>hn-stack</strong><span class="hn-muted">Élément 1</span><span class="hn-muted">Élément 2</span></div>
<div class="hn-surface hn-surface--sunken hn-cluster"><span class="hn-badge">hn-cluster</span><span class="hn-badge">A</span><span class="hn-badge">B</span></div>
<div class="hn-surface hn-surface--raised"><strong>hn-surface--raised</strong></div>
</div>Aucun outil de build : trois lignes dans le <head>, puis des classes et des balises <hn-*>.
<!doctype html>
<html lang="fr" data-hn-theme="auto">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://hanami.example.com/cdn/v1/hanami-fonts.css">
<link rel="stylesheet" href="https://hanami.example.com/cdn/v1/hanami.min.css">
<link rel="stylesheet" href="https://hanami.example.com/cdn/v1/hanami-base.min.css">
<script type="module" src="https://hanami.example.com/cdn/v1/hanami.min.js"></script>
</head>
<body>
<button class="hn-btn" data-hn-open="reservation">Réserver</button>
<hn-dialog id="reservation" label="Réserver un emplacement">
<p>Samedi 4 avril, parc Ueno.</p>
<div slot="footer">
<button class="hn-btn hn-btn--ghost" data-hn-close>Annuler</button>
<button class="hn-btn" onclick="confirmer()">Confirmer</button>
</div>
</hn-dialog>
<script type="module">
import { toast } from 'https://hanami.example.com/cdn/v1/hanami.min.js';
window.confirmer = () => {
document.getElementById('reservation').hide();
toast.success('Réservation enregistrée.');
};
</script>
</body>
</html>Pour un site qui n'utilise pas les modules ES (vieux thème WordPress, jQuery), chargez la version « iife » : tout est disponible dans window.Hanami.
<script src="https://hanami.example.com/cdn/v1/hanami.iife.min.js" defer></script>
<button class="hn-btn" onclick="Hanami.toast('Bonjour !')">Dire bonjour</button>| Attribut | Effet au clic |
|---|---|
data-hn-open="id" | Ouvre la modale, le tiroir, la barre latérale ou le menu portant cet id. |
data-hn-close="id" | Le ferme. Sans valeur, ferme le composant parent le plus proche. |
data-hn-toggle="id" | Bascule entre ouvert et fermé. |
data-hn-set-theme="dark" | Change le thème (light, dark, auto ou toggle). |
Un exemple complet est en ligne : exemple HTML.
Un plugin enregistre les composants Hn*, qui gèrent v-model et les slots nommés.
import { createApp } from 'vue';
import HanamiUI from '@kawaiitenshi/hanami-ui/vue';
import '@kawaiitenshi/hanami-ui/css/fonts';
import '@kawaiitenshi/hanami-ui/css';
import App from './App.vue';
createApp(App).use(HanamiUI).mount('#app');<script setup lang="ts">
import { ref } from 'vue';
import { toast } from '@kawaiitenshi/hanami-ui/vue';
const ouvert = ref(false);
const nom = ref('');
const parc = ref('ueno');
const notifications = ref(true);
const onglet = ref('printemps');
function confirmer() {
ouvert.value = false;
toast.success(`Réservation au nom de ${nom.value}`);
}
</script>
<template>
<HnField label="Nom" hint="Tel qu'il apparaîtra sur la réservation" required>
<HnInput v-model="nom" placeholder="Sakura Tanaka" />
</HnField>
<HnSelect v-model="parc" :options="[{ value: 'ueno', label: 'Ueno' }, { value: 'maruyama', label: 'Maruyama' }]" />
<HnSwitch v-model="notifications" label="Prévenir à l'éclosion" />
<HnButton :loading="false" @click="ouvert = true">Réserver</HnButton>
<HnDialog v-model:open="ouvert" label="Confirmer">
<p>Réserver pour {{ nom }} ?</p>
<template #footer>
<HnButton variant="ghost" @click="ouvert = false">Annuler</HnButton>
<HnButton @click="confirmer">Confirmer</HnButton>
</template>
</HnDialog>
<HnTabs v-model="onglet">
<HnTab panel="printemps">Printemps</HnTab>
<HnTab panel="ete">Été</HnTab>
<HnTabPanel name="printemps">Sakura</HnTabPanel>
<HnTabPanel name="ete">Hanabi</HnTabPanel>
</HnTabs>
</template>v-model sur HnInput, HnTextarea, HnSelect, HnCheckbox (booléen ou tableau), HnRadio, HnSwitch, HnSlider et HnTabs ; v-model:open sur HnDialog, HnDrawer, HnDropdown et HnSidebar ; v-model:collapsed sur HnSidebar.<HnField> reçoit automatiquement son id, aria-describedby et aria-invalid.<hn-*> restent utilisables directement ; déclarez-les alors comme éléments personnalisés :import vue from '@vitejs/plugin-vue';
export default {
plugins: [
vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('hn-') } } }),
],
};export default defineNuxtConfig({
css: ['@kawaiitenshi/hanami-ui/css/fonts', '@kawaiitenshi/hanami-ui/css'],
vue: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('hn-') } },
});import HanamiUI from '@kawaiitenshi/hanami-ui/vue';
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(HanamiUI);
});Le code est compatible avec le rendu serveur : les Web Components s'activent à l'hydratation. Exemple en ligne : démo Vue.
Des composants typés pour React 17, 18 et 19. Les événements des Web Components deviennent des props onClose, onChange…
import { useState } from 'react';
import '@kawaiitenshi/hanami-ui/css/fonts';
import '@kawaiitenshi/hanami-ui/css';
import { Button, Dialog, Field, Input, Switch, Tabs, Tab, TabPanel, toast, useDisclosure } from '@kawaiitenshi/hanami-ui/react';
export function Reservation() {
const dialog = useDisclosure();
const [nom, setNom] = useState('');
const [onglet, setOnglet] = useState('printemps');
return (
<>
<Field label="Nom" hint="Tel qu'il apparaîtra sur la réservation" required>
<Input value={nom} onChange={(e) => setNom(e.target.value)} placeholder="Sakura Tanaka" />
</Field>
<Switch label="Prévenir à l'éclosion" defaultChecked />
<Button onClick={dialog.show}>Réserver</Button>
<Dialog {...dialog.props} label="Confirmer">
<p>Réserver pour {nom} ?</p>
<div slot="footer">
<Button variant="ghost" onClick={dialog.hide}>Annuler</Button>
<Button onClick={() => { dialog.hide(); toast.success('Réservation enregistrée.'); }}>Confirmer</Button>
</div>
</Dialog>
<Tabs value={onglet} onChange={(e) => setOnglet(e.detail.value)}>
<Tab panel="printemps">Printemps</Tab>
<Tab panel="ete">Été</Tab>
<TabPanel name="printemps">Sakura</TabPanel>
<TabPanel name="ete">Hanabi</TabPanel>
</Tabs>
</>
);
}Button, ButtonLink, Badge, Tag, Alert, Card (et ses parties), Avatar, Spinner, Skeleton, Progress, Field, Input, Textarea, Select, Checkbox, Radio, Switch, Slider.Sidebar, Dialog, Drawer, Tabs, Tab, TabPanel, Dropdown, Tooltip, Carousel, Petals, ThemeToggle, CopyButton, Icon.useDisclosure() pour l'état ouvert/fermé, useTheme() pour lire et changer le thème.false dans onClose (Échap, clic sur le fond, croix).Le module React est marqué 'use client'. Importez le CSS une fois dans le layout racine :
import '@kawaiitenshi/hanami-ui/css/fonts';
import '@kawaiitenshi/hanami-ui/css';
import { themeScript } from '@kawaiitenshi/hanami-ui';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="fr" data-hn-theme="auto" suppressHydrationWarning>
<head><script dangerouslySetInnerHTML={{ __html: themeScript }} /></head>
<body>{children}</body>
</html>
);
}Exemple en ligne : démo React.
Angular lie nativement propriétés et événements des Web Components. Les champs de formulaire étant des éléments natifs,
ngModel et les formulaires réactifs fonctionnent sans adaptateur.
{
"styles": [
"node_modules/@kawaiitenshi/hanami-ui/dist/hanami-fonts.css",
"node_modules/@kawaiitenshi/hanami-ui/dist/hanami.css",
"src/styles.css"
]
}import '@kawaiitenshi/hanami-ui'; // enregistre les <hn-*>
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent);import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { toast } from '@kawaiitenshi/hanami-ui';
@Component({
selector: 'app-reservation',
standalone: true,
imports: [FormsModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<div class="hn-field">
<label class="hn-label" for="nom">Nom</label>
<input class="hn-input" id="nom" [(ngModel)]="nom">
</div>
<button class="hn-btn" (click)="ouvert = true">Réserver</button>
<hn-dialog [open]="ouvert" label="Confirmer" (hn-close)="ouvert = false">
<p>Réserver pour {{ nom }} ?</p>
<div slot="footer">
<button class="hn-btn hn-btn--ghost" (click)="ouvert = false">Annuler</button>
<button class="hn-btn" (click)="confirmer()">Confirmer</button>
</div>
</hn-dialog>
<hn-tabs [value]="onglet" (hn-change)="onglet = $any($event).detail.value">
<hn-tab panel="printemps">Printemps</hn-tab>
<hn-tab panel="ete">Été</hn-tab>
<hn-tab-panel name="printemps">Sakura</hn-tab-panel>
<hn-tab-panel name="ete">Hanabi</hn-tab-panel>
</hn-tabs>
`,
})
export class ReservationComponent {
nom = '';
ouvert = false;
onglet = 'printemps';
confirmer() {
this.ouvert = false;
toast.success('Réservation enregistrée.');
}
}CUSTOM_ELEMENTS_SCHEMA autorise les balises <hn-*> dans les templates.[class]="buttonClass({ variant: 'outline' })".
Depuis AngularJS 1.7, ng-prop-* lie une propriété et ng-on-* écoute un événement :
c'est tout ce qu'il faut pour piloter les Web Components. Testé avec AngularJS 1.8.3.
<link rel="stylesheet" href="https://hanami.example.com/cdn/v1/hanami-fonts.css">
<link rel="stylesheet" href="https://hanami.example.com/cdn/v1/hanami.min.css">
<script src="https://hanami.example.com/cdn/v1/hanami.iife.min.js"></script>
<script src="angular.min.js"></script>
<div ng-app="hanami" ng-controller="ReservationCtrl as vm">
<input class="hn-input" ng-model="vm.nom" placeholder="Votre nom">
<button class="hn-btn" ng-click="vm.ouvert = true">Réserver</button>
<hn-dialog label="Confirmer"
ng-prop-open="vm.ouvert"
ng-on-hn-close="vm.ouvert = false">
<p>Réserver pour {{ vm.nom }} ?</p>
<div slot="footer">
<button class="hn-btn" ng-click="vm.confirmer()">Confirmer</button>
</div>
</hn-dialog>
<hn-tabs ng-on-hn-change="vm.onglet = $event.detail.value">
<hn-tab panel="a">Printemps</hn-tab>
<hn-tab panel="b">Été</hn-tab>
<hn-tab-panel name="a">Sakura</hn-tab-panel>
<hn-tab-panel name="b">Hanabi</hn-tab-panel>
</hn-tabs>
</div>
<script>
angular.module('hanami', []).controller('ReservationCtrl', function () {
var vm = this;
vm.ouvert = false;
vm.confirmer = function () {
vm.ouvert = false;
Hanami.toast.success('Réservation enregistrée.');
};
});
</script>ng-on-* déclenche lui-même le cycle de digest : inutile d'appeler $scope.$apply().
Tout ce qui produit du HTML peut utiliser Hanami UI : il suffit du CSS et du script.
<script>
import '@kawaiitenshi/hanami-ui/css';
import { toast } from '@kawaiitenshi/hanami-ui';
let ouvert = $state(false);
</script>
<button class="hn-btn" onclick={() => (ouvert = true)}>Réserver</button>
<hn-dialog open={ouvert} label="Confirmer" onhn-close={() => (ouvert = false)}>
<p>Samedi 4 avril, parc Ueno.</p>
<div slot="footer">
<button class="hn-btn" onclick={() => { ouvert = false; toast.success('Réservé !'); }}>Confirmer</button>
</div>
</hn-dialog>Svelte transmet open comme propriété et transforme onhn-close en écouteur de l'événement hn-close.
---
import '@kawaiitenshi/hanami-ui/css';
---
<html lang="fr" data-hn-theme="auto">
<body>
<slot />
<script>import '@kawaiitenshi/hanami-ui';</script>
</body>
</html>Ajoutez les balises du CDN au gabarit principal, puis utilisez les classes et balises dans vos vues :
<form method="POST" action="/reservations" class="hn-stack">
@csrf
<div class="hn-field">
<label class="hn-label" for="nom">Nom</label>
<input class="hn-input" id="nom" name="nom" value="{{ old('nom') }}"
@error('nom') aria-invalid="true" @enderror>
@error('nom') <p class="hn-field__error">{{ $message }}</p> @enderror
</div>
<button class="hn-btn" type="submit">Réserver</button>
</form>Le paquet est écrit en TypeScript et livre ses déclarations : éléments, événements, tokens et adaptateurs sont typés.
import { toast, type DialogRequestCloseDetail, type TabsChangeDetail } from '@kawaiitenshi/hanami-ui';
import { tokens, vars } from '@kawaiitenshi/hanami-ui/tokens';
// document.querySelector connaît les balises <hn-*>
const dialog = document.querySelector('hn-dialog')!; // type HnDialog
dialog.show();
dialog.addEventListener('hn-request-close', (event) => {
const { reason } = (event as CustomEvent<DialogRequestCloseDetail>).detail;
if (reason === 'backdrop') event.preventDefault();
});
document.querySelector('hn-tabs')!.addEventListener('hn-change', (event) => {
const { value } = (event as CustomEvent<TabsChangeDetail>).detail;
console.log(value);
});
tokens.color.sakura[600]; // "#C23271"
tokens.theme.dark.color.bg; // "#110A1B"
vars.color.primary; // "var(--hn-color-primary)" : suit le thème actifPratiques avec n'importe quel moteur de templates ou de CSS-in-JS :
import { buttonClass, badgeClass, cx } from '@kawaiitenshi/hanami-ui';
buttonClass({ variant: 'outline', size: 'sm' }); // "hn-btn hn-btn--outline hn-btn--sm"
badgeClass({ variant: 'success', dot: true }); // "hn-badge hn-badge--success hn-badge--dot"
cx('hn-card', isActive && 'hn-card--elevated'); // comme clsxPour que VS Code propose les balises <hn-*> et leurs attributs dans le HTML :
{
"html.customData": ["./node_modules/@kawaiitenshi/hanami-ui/dist/vscode.html-data.json"]
}Les mêmes tokens, exposés sous forme de thème Tailwind et de variables SCSS. Les couleurs suivent le thème clair ou sombre.
@import "tailwindcss";
@import "@kawaiitenshi/hanami-ui/css";
@import "@kawaiitenshi/hanami-ui/tailwind";<div class="bg-hn-surface text-hn-text rounded-xl shadow-md p-6">
<h2 class="font-display text-sakura-700">Ueno</h2>
<p class="text-hn-text-muted">Pleine floraison le 1er avril.</p>
<button class="hn-btn">Réserver</button>
</div>Les gammes donnent sakura-50 à sakura-950 (et sumi, yoru, matcha…), les rôles sont préfixés hn- (bg-hn-primary, text-hn-text-muted), plus font-display, rounded-petal, shadow-glow et ease-bloom.
module.exports = {
presets: [require('@kawaiitenshi/hanami-ui/tailwind-preset')],
content: ['./src/**/*.{html,js,ts,vue,jsx,tsx}'],
};// Dart Sass ≥ 1.71 avec l'importeur Node (sass --pkg-importer=node, ou NodePackageImporter dans Vite) :
@use 'pkg:@kawaiitenshi/hanami-ui/scss' as hn;
// Sinon, chemin direct : @use '@kawaiitenshi/hanami-ui/dist/tokens/tokens' as hn;
.carte-parc {
padding: hn.$hn-space-6;
border-radius: hn.$hn-radius-lg;
background: hn.$hn-color-surface; // var(--hn-color-surface)
color: hn.$hn-sakura-700; // #9D2358
@include hn.hn-up(md) {
padding: hn.$hn-space-8;
}
}Les gammes ($hn-sakura-600) sont des valeurs fixes ; les rôles ($hn-color-primary) renvoient aux variables CSS pour suivre le thème. La map $hn-palettes permet de générer vos propres classes (@each $nom, $gamme in hn.$hn-palettes).
Ce site, le CDN et les archives npm sont servis par server/server.mjs : un serveur Node sans aucune
dépendance qui écoute sur le port attribué par Pterodactyl.
pterodactyl/egg-hanami-ui.json| Variable | Rôle |
|---|---|
GIT_ADDRESS | Dépôt à cloner (par défaut ce dépôt GitHub). |
BRANCH | Branche ou tag à déployer. |
USERNAME / ACCESS_TOKEN | Identifiants pour un dépôt privé (token « fine-grained », lecture du contenu). |
AUTO_UPDATE | 1 : récupère et reconstruit à chaque redémarrage. |
CORS_ORIGIN | Sites autorisés à charger le CDN (* par défaut). |
Avec l'egg Node.js générique, publiez une archive prête à l'emploi : elle ne demande aucun npm install sur le serveur.
npm run release # crée release/hanami-ui-site-1.0.0.zipserver/server.mjs (variable du fichier principal de l'egg Node.js).Le script npm run deploy utilise l'API client de Pterodactyl : il construit l'archive, l'envoie, la décompresse et redémarre le serveur.
PTERODACTYL_URL=https://panel.mon-domaine.fr \
PTERODACTYL_API_KEY=ptlc_xxxxxxxxxxxxxxxx \
PTERODACTYL_SERVER_ID=1a2b3c4d \
npm run deployLa clé se crée dans le panel : Compte → Identifiants API. Le workflow GitHub Actions fourni peut lancer cette commande à chaque tag v* si vous ajoutez ces trois valeurs dans les secrets du dépôt.
| Chemin | Contenu | Cache |
|---|---|---|
/ | Cette documentation. | revalidé à chaque visite |
/cdn/1.0.0/… | Fichiers d'une version précise. | 1 an, immuable |
/cdn/v1/… | Dernière version 1.x. | 5 minutes |
/cdn/latest/… | Dernière version publiée. | 5 minutes |
/npm/… | Archives pour npm install. | 1 an (versions numérotées) |
/healthz | État du serveur en JSON. | aucun |
Les anciennes versions restent en ligne après une mise à jour : un site qui pointe vers /cdn/1.0.0/ ne casse jamais.
Le serveur compresse en Brotli ou gzip, gère les ETag et envoie les en-têtes CORS nécessaires aux polices.
Dans la console Pterodactyl, tapez status, reload ou stop.