Aller au contenu
Hanami UI

Hébergement

Pterodactyl

Hanami UI

Installer Héberger
Design system · v1

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

Tokens partagés

Une seule source (format W3C) génère les variables CSS, le SCSS, le TypeScript et le thème Tailwind.

Tous les frameworks

Les composants interactifs sont des Web Components ; les autres sont de simples classes CSS.

Hiru et yozakura

Un thème clair et un thème sombre « cerisiers la nuit », applicables à toute la page ou à un seul bloc.

Accessible

Contrastes AA vérifiés, navigation clavier, rôles ARIA et respect du mouvement réduit.

Démarrer

Installation

Trois façons d'ajouter Hanami UI à un site. Toutes les adresses ci-dessous pointent vers ce serveur.

CDN npm Sources

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.

index.html
<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é.

terminal
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.tgz
main.ts
import '@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).

terminal
git clone https://github.com/KawaiiTenshi555/DesignSystem.git
cd DesignSystem
npm ci
npm run build && npm run build:site
npm start   # http://localhost:8080

Ce que contient le paquet

ImportContenu
@kawaiitenshi/hanami-ui/cssTokens, composants CSS, effets de survol, utilitaires.
@kawaiitenshi/hanami-ui/css/fontsPolices auto-hébergées (aucune requête vers Google).
@kawaiitenshi/hanami-ui/css/baseRemise à zéro et typographie de page (facultatif).
@kawaiitenshi/hanami-uiWeb Components <hn-*>, toast(), thèmes, icônes.
@kawaiitenshi/hanami-ui/vuePlugin et composants Vue 3 (v-model compris).
@kawaiitenshi/hanami-ui/reactComposants React 17 à 19, compatibles Next.js.
@kawaiitenshi/hanami-ui/tokensTokens en TypeScript (valeurs et var(--hn-…)).
@kawaiitenshi/hanami-ui/scssVariables et mixins SCSS.
@kawaiitenshi/hanami-ui/tailwindThème Tailwind v4 (preset v3 : /tailwind-preset).

Sur un site existant

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.

Fondations

Couleurs

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.

桜SakuraCouleur principale : boutons, liens, sélection.
墨SumiEncre : textes, bordures et fonds du thème clair.
夜YoruNuit : neutres du thème sombre yozakura.
抹茶MatchaSuccès et validation.
金KinAvertissements, l’or des lanternes.
紅BeniErreurs et actions destructrices.
藍AiInformations.
藤FujiAccent secondaire, glycine.

Couleurs sémantiques

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.

mon-composant.css
.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);
}

Fonds et surfaces

VariableHiru (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

Textes

VariableHiru (clair)Yozakura (sombre)
--hn-color-text
--hn-color-text-muted
--hn-color-text-subtle
--hn-color-text-inverse
--hn-color-text-disabled

Bordures et états

VariableHiru (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

Principale

VariableHiru (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

Neutre et accent

VariableHiru (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

États

VariableHiru (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

Décor

VariableHiru (clair)Yozakura (sombre)
--hn-color-petal
--hn-color-petal-alt
--hn-color-petal-deep

Contrastes

  • Texte courant et texte atténué : au moins 4,5:1 sur les fonds, dans les deux thèmes (WCAG AA).
  • Bouton principal : texte blanc sur sakura-600 (5,3:1) le jour, texte nuit sur sakura-400 (8,2:1) la nuit.
  • Bordures de champs : au moins 3:1 pour rester repérables.
  • Le script npm test vérifie ces ratios à chaque modification des tokens.
Fondations

Thèmes hiru et yozakura

昼 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.

Hiru

Pique-nique à Ueno

Samedi 4 avril, sous le grand cerisier.

Mankai
Yozakura

Promenade nocturne

Lanternes allumées de 18 h à 22 h.

Illuminations
Code
html
<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>
ValeurEffet
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).

Changer de thème

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.

Code
html
<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 :

ts
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) :

html
<script>
  (function(){try{var t=localStorage.getItem('hn-theme');if(t)document.documentElement.setAttribute('data-hn-theme',t)}catch(e){}})();
</script>
Fondations

Typographie

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ôlePoliceVariable
TitresShippori Mincho B1--hn-font-display
Texte et interfaceZen Maru Gothic--hn-font-body
Code et donnéesM PLUS 1 Code--hn-font-mono

Titres

Hanami

Les cerisiers de Kyoto

Prévisions de floraison

Parc Maruyama

Accès et horaires

Gare la plus proche
Gion-Shijō, ligne Keihan
Code
html
<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>

Texte

Saison 2026

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.
Kobayashi Issa
Code
html
<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>

Échelle

--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.5rem

Contenu riche

La classe hn-prose met en forme du HTML brut (articles, Markdown converti) sans ajouter de classe à chaque balise.

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
Code
html
<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>
Fondations

Espacements, arrondis et ombres

Une grille de 4 px, des arrondis généreux comme des pétales et des ombres teintées de prune.

Espacements

--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

Arrondis

--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

Ombres

--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-glow
Fondations

Mouvement

Les 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.
Code
html
<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éeValeurUsage
--hn-duration-instant80 msAppui sur un bouton.
--hn-duration-fast150 msCouleurs au survol.
--hn-duration-normal240 msMenus, infobulles, interrupteurs.
--hn-duration-slow400 msModales, tiroirs, onglets.
--hn-duration-slower700 msGrandes transitions, zoom d'image.

Animations prêtes à l'emploi

hn-animate-bloom
hn-animate-fade-up
hn-animate-sway
Code
html
<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>
Fondations

Icônes

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.

Code
html
<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>
  • Sans attribut label, l'icône est décorative et ignorée par les lecteurs d'écran.
  • Avec label="…", elle devient une image avec ce texte alternatif.
  • Ajoutez vos propres icônes avec registerIcon('mon-icone', '<svg …>').
API de <hn-icon>
AttributValeursRôle
nametexteNom de l’icône.
labeltexteTexte alternatif (sinon décorative).
sizetexteTaille (px ou unité CSS).
stroke-widthtexteÉpaisseur du trait.
Fondations

Motifs traditionnels

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.

Code
html
<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.

Composant

Boutons

Une classe hn-btn sur un <button> ou un lien, puis une variante, une taille et une forme.

Variantes

Code
html
<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).

Tailles et formes

Code
html
<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>

Avec icône, chargement et désactivé

Lien désactivé
Code
html
<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.

Groupe

Code
html
<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>

Personnaliser

Chaque bouton lit ses couleurs dans des variables locales : surchargez-les pour créer une variante sans écrire de nouveau sélecteur d'état.

css
.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);
}
Composant

Formulaires

Des éléments natifs stylés : ils fonctionnent tels quels avec v-model, ngModel, les formulaires React et l'envoi HTML classique.

Champs

Tel qu'il apparaîtra sur la réservation.

Il manque le domaine, par exemple sakura@exemple.jp.

Code
html
<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>

Icône, préfixe et taille

https://
Code
html
<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">

Cases, boutons radio et interrupteurs

Options
Créneau
Notifications
Code
html
<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é.

Curseur et fichier

Code
html
<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.

Composant

Badges, tags et avatars

Des étiquettes pour un statut, des tags pour filtrer, des avatars pour les personnes.

Badges

Nouveau 12 Brouillon Fuji Ouvert Pluie Complet Info Archivé Mankai 満開
Code
html
<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>

Tags

Nara
Code
html
<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">

Avatars

ST YK HM AI 花
ST YK RN +4
Code
html
<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>
Composant

Cartes

Un conteneur pour un sujet : un parc, une réservation, un réglage. Les parties sont toutes facultatives.

丸山公園

Parc Maruyama

Kyoto · 680 cerisiers

Mankai
Le grand cerisier pleureur est illuminé chaque soir jusqu'à minuit.
ST

Réservation n° 0404

Samedi 4 avril, 10 h

Emplacement C-12 près de l'étang, pour 6 personnes.

Carte sakura

Une fleur en filigrane qui tourne au survol, pour mettre une offre en avant.
Code
html
<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>

Variantes

Par défaut
Bordure fine et ombre légère.
--elevated
Sans bordure, ombre plus marquée.
--flat
Fond teinté, sans ombre.
--outline
Transparente, bordure seule.
Code
html
<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.

État vide

Aucun pique-nique prévu

Choisissez un parc et une date pour réserver votre emplacement sous les cerisiers.

Code
html
<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>
Composant

Alertes et toasts

Les alertes restent dans la page ; les toasts apparaissent un moment dans un coin puis s'effacent.

Alertes

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.

Mankai à Kyoto

Les cerisiers du parc Maruyama sont en pleine floraison.

Code
html
<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>

Toasts

Code
html
<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>
ts
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() },
});
  • Le minuteur se met en pause au survol et au focus clavier.
  • Les toasts sont annoncés aux lecteurs d'écran (région aria-live) et passent au-dessus d'une modale ouverte.
  • Le contenu est inséré comme texte : pas de risque d'injection HTML.
Web Component

Modales et tiroirs

<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.

Modale

Choisissez votre coin sous les cerisiers du parc Ueno.

Code
html
<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.

Tailles et fermeture contrôlée

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.

Code
html
<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>
js
dialog.addEventListener('hn-request-close', (event) => {
  // event.detail.reason : 'escape' | 'backdrop' | 'close-button' | 'api'
  if (formulaireModifie) event.preventDefault();
});
API de <hn-dialog>
AttributValeursRôle
openbooléenAffiche la fenêtre.
labeltexteTitre affiché et nom accessible.
sizesm md lg xl fullLargeur.
persistentbooléenIgnore Échap et le clic sur le fond.
no-close-buttonbooléenMasque la croix de fermeture.

Événements : hn-open hn-close hn-request-close

Méthodes : show() hide() toggle() requestClose(reason)

Emplacements (slots) : header (défaut) footer

Tiroir

Code
html
<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>
API de <hn-drawer>
AttributValeursRôle
openbooléenAffiche le tiroir.
labeltexteTitre affiché et nom accessible.
placementend start top bottomBord d’apparition.
persistentbooléenIgnore Échap et le clic sur le fond.
no-close-buttonbooléenMasque la croix de fermeture.

Événements : hn-open hn-close hn-request-close

Méthodes : show() hide() toggle()

Emplacements (slots) : header (défaut) footer

Web Component

Onglets et accordéons

Des onglets accessibles au clavier (flèches, Début, Fin) avec un indicateur qui glisse, et des accordéons natifs.

Onglets

Printemps Été Automne Hiver Les cerisiers fleurissent de fin mars à début avril. Festivals de lanternes et feux d'artifice. Les érables rougissent : c'est le momijigari. Fermé pour l'hiver.
Code
html
<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>
Jour Semaine Mois 12 réservations aujourd'hui. 84 réservations cette semaine. 310 réservations ce mois-ci.
Code
html
<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.

API de <hn-tabs>
AttributValeursRôle
valuetexteNom du panneau actif.
variantline pillsStyle.
labeltexteNom accessible de la liste d’onglets.
activationauto manualActivation au clavier.

Événements : hn-change

Méthodes : select(value)

Emplacements (slots) : nav (hn-tab, automatique) (défaut) hn-tab-panel

Accordéon

De simples <details> : aucun JavaScript, et l'attribut name n'en laisse qu'un ouvert à la fois.

Quand fleurissent les cerisiers ?
À Tokyo, généralement entre le 20 mars et le 5 avril. La pleine floraison dure environ une semaine.
Peut-on pique-niquer partout ?
Non : certains jardins comme Shinjuku Gyoen interdisent l'alcool et limitent les bâches.
Que faire en cas de pluie ?
Votre réservation peut être déplacée gratuitement jusqu'à la veille 18 h.
Code
html
<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>
Composant

Données et chargement

Tableaux, barres de progression, spinner en forme de fleur, squelettes et séparateurs.

Tableau

Prévisions 2026 (dates indicatives)
VilleParcKaikaMankaiÉtat
TokyoUeno24 mars1 avr.Ouvert
KyotoMaruyama26 mars4 avr.Affluence
OsakaChâteau27 mars5 avr.Ouvert
SapporoMaruyama28 avr.3 maiÀ venir
Code
html
<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).

Progression

45 % 80 %
Code
html
<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>

Spinner et squelette

Code
html
<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>

Séparateurs

ou
Ueno Maruyama Château d'Osaka
Code
html
<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>
Web Component

Carrousel

<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.

Code
html
<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>

Plusieurs diapositives et lecture automatique

Code
html
<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.

API de <hn-carousel>
AttributValeursRôle
loopbooléenRevient au début après la dernière diapositive.
autoplaytexteLecture automatique (intervalle en ms, défaut 5000).
labeltexteNom accessible.
no-arrowsbooléenMasque les flèches.
no-dotsbooléenMasque les pastilles.

Événements : hn-slide-change

Méthodes : next() prev() goTo(index)

Emplacements (slots) : (défaut) diapositives

Web Component

Pétales

<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.

Code
html
<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.

API de <hn-petals>
AttributValeursRôle
counttexteNombre de pétales (défaut 24).
speedtexteVitesse (défaut 1).
windtexteForce du vent (défaut 1).
sizetexteTaille relative (défaut 1).
fixedbooléenCouvre toute la fenêtre.
pausedbooléenMet l’animation en pause.
Effets

Effets de survol

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ève
hn-hover-growgrandit
hn-hover-bloomhalo rose
hn-hover-haloonde autour
hn-hover-tintse teinte
hn-hover-shinereflet
hn-hover-petalun pétale s'envole
hn-hover-floatse balance
hn-hover-tiltperspective
hn-hover-zoomzoom sur l'image
Code
html
<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>

Sur du texte et des listes

Soulignement qui se dessine · surlignage au pinceau

UenoMaruyamaYoshinoHirosaki
Survolez la carte
Code
html
<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.

Combiner

Carte qui s'élève et laisse s'envoler un pétale.
Code
html
<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à.

Effets

Utilitaires de mise en page

Quelques classes pour assembler un écran sans écrire de CSS ; pour le reste, utilisez vos propres styles ou Tailwind.

ClasseEffetRéglage
hn-containerLargeur maximale centrée avec marges.--narrow, --wide
hn-stackEmpile verticalement avec un écart.--hn-stack-gap
hn-clusterAligne en ligne et passe à la ligne.--hn-cluster-gap
hn-gridGrille fluide (autant de colonnes que possible).--hn-grid-min, --hn-grid-gap
hn-grid--2/3/4Colonnes fixes, une seule sur mobile.
hn-surfaceBloc avec fond, bordure et arrondi.--sunken, --raised
hn-hide-mobile / hn-hide-desktopMasque sous ou au-dessus de 768 px.
hn-sr-onlyVisible seulement des lecteurs d'écran.
hn-truncateCoupe le texte avec des points de suspension.
hn-stackÉlément 1Élément 2
hn-clusterAB
hn-surface--raised
Code
html
<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>
Intégration

HTML, PHP, WordPress…

Aucun outil de build : trois lignes dans le <head>, puis des classes et des balises <hn-*>.

index.html
<!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>

Script classique

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.

html
<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>

Déclencheurs sans JavaScript

AttributEffet 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.

Intégration

Vue 3 et Nuxt

Un plugin enregistre les composants Hn*, qui gèrent v-model et les slots nommés.

main.ts
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');
App.vue
<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>
  • Liaisons disponibles : 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.
  • Un champ placé dans <HnField> reçoit automatiquement son id, aria-describedby et aria-invalid.
  • Les balises <hn-*> restent utilisables directement ; déclarez-les alors comme éléments personnalisés :
vite.config.ts
import vue from '@vitejs/plugin-vue';

export default {
  plugins: [
    vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('hn-') } } }),
  ],
};

Nuxt 3

nuxt.config.ts
export default defineNuxtConfig({
  css: ['@kawaiitenshi/hanami-ui/css/fonts', '@kawaiitenshi/hanami-ui/css'],
  vue: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('hn-') } },
});
plugins/hanami.ts
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.

Intégration

React et Next.js

Des composants typés pour React 17, 18 et 19. Les événements des Web Components deviennent des props onClose, onChange…

Reservation.tsx
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>
    </>
  );
}
  • Composants CSS : Button, ButtonLink, Badge, Tag, Alert, Card (et ses parties), Avatar, Spinner, Skeleton, Progress, Field, Input, Textarea, Select, Checkbox, Radio, Switch, Slider.
  • Web Components : Sidebar, Dialog, Drawer, Tabs, Tab, TabPanel, Dropdown, Tooltip, Carousel, Petals, ThemeToggle, CopyButton, Icon.
  • Hooks : useDisclosure() pour l'état ouvert/fermé, useTheme() pour lire et changer le thème.
  • Une modale contrôlée doit remettre son état à false dans onClose (Échap, clic sur le fond, croix).

Next.js (App Router)

Le module React est marqué 'use client'. Importez le CSS une fois dans le layout racine :

app/layout.tsx
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.

Intégration

Angular

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.

angular.json (extrait)
{
  "styles": [
    "node_modules/@kawaiitenshi/hanami-ui/dist/hanami-fonts.css",
    "node_modules/@kawaiitenshi/hanami-ui/dist/hanami.css",
    "src/styles.css"
  ]
}
main.ts
import '@kawaiitenshi/hanami-ui'; // enregistre les <hn-*>
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent);
reservation.component.ts
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.
  • Les générateurs de classes sont utilisables dans les composants : [class]="buttonClass({ variant: 'outline' })".
  • Avec le rendu serveur (Angular SSR), l'import ne plante pas : les composants s'activent côté navigateur.
Intégration

AngularJS (1.x)

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.

index.html
<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().

Intégration

Svelte, Astro, Laravel, Symfony…

Tout ce qui produit du HTML peut utiliser Hanami UI : il suffit du CSS et du script.

Svelte 5

Reservation.svelte
<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.

Astro

src/layouts/Base.astro
---
import '@kawaiitenshi/hanami-ui/css';
---
<html lang="fr" data-hn-theme="auto">
  <body>
    <slot />
    <script>import '@kawaiitenshi/hanami-ui';</script>
  </body>
</html>

Laravel (Blade), Symfony (Twig), WordPress

Ajoutez les balises du CDN au gabarit principal, puis utilisez les classes et balises dans vos vues :

resources/views/reservation.blade.php
<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>
Intégration

TypeScript

Le paquet est écrit en TypeScript et livre ses déclarations : éléments, événements, tokens et adaptateurs sont typés.

ts
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 actif

Générateurs de classes

Pratiques avec n'importe quel moteur de templates ou de CSS-in-JS :

ts
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 clsx

Autocomplétion dans l'éditeur

Pour que VS Code propose les balises <hn-*> et leurs attributs dans le HTML :

.vscode/settings.json
{
  "html.customData": ["./node_modules/@kawaiitenshi/hanami-ui/dist/vscode.html-data.json"]
}
Intégration

Tailwind CSS et SCSS

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.

Tailwind CSS v4

app.css
@import "tailwindcss";
@import "@kawaiitenshi/hanami-ui/css";
@import "@kawaiitenshi/hanami-ui/tailwind";
html
<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.

Tailwind CSS v3

tailwind.config.js
module.exports = {
  presets: [require('@kawaiitenshi/hanami-ui/tailwind-preset')],
  content: ['./src/**/*.{html,js,ts,vue,jsx,tsx}'],
};

SCSS

styles.scss
// 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).

Hébergement

Héberger sur Pterodactyl

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.

  1. Importer l'eggPanel admin → Nests → Import Egg → pterodactyl/egg-hanami-ui.json
  2. Créer le serveurChoisir l'egg « Hanami UI », 256 Mo de RAM suffisent, une allocation de port.
  3. Renseigner le dépôtAdresse Git, branche et, pour un dépôt privé, un token GitHub en lecture seule.
  4. DémarrerL'installation lance le build ; la console affiche « Hanami UI en ligne ».

Variables de l'egg

VariableRôle
GIT_ADDRESSDépôt à cloner (par défaut ce dépôt GitHub).
BRANCHBranche ou tag à déployer.
USERNAME / ACCESS_TOKENIdentifiants pour un dépôt privé (token « fine-grained », lecture du contenu).
AUTO_UPDATE1 : récupère et reconstruit à chaque redémarrage.
CORS_ORIGINSites autorisés à charger le CDN (* par défaut).

Sans egg personnalisé

Avec l'egg Node.js générique, publiez une archive prête à l'emploi : elle ne demande aucun npm install sur le serveur.

sur votre poste
npm run release   # crée release/hanami-ui-site-1.0.0.zip
  1. Dans le panel : Fichiers → Envoyer l'archive, puis « Décompresser ».
  2. Réglez le fichier de démarrage sur server/server.mjs (variable du fichier principal de l'egg Node.js).
  3. Démarrez le serveur.

Déploiement automatique

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.

bash
PTERODACTYL_URL=https://panel.mon-domaine.fr \
PTERODACTYL_API_KEY=ptlc_xxxxxxxxxxxxxxxx \
PTERODACTYL_SERVER_ID=1a2b3c4d \
npm run deploy

La 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.

Adresses servies

CheminContenuCache
/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.