Skip to content

Type de ressource : Composant Astro (UI atomique / Îlot interactif)
Fichier source : src/components/[ui|interactive]/[NomComposant].astro
Statut : [Actif / Déprécié / En développement]

[Description concise du rîle de ce composant dans l’interface utilisateur]

  • [Objectif principal : ex. afficher un bouton d’action rĂ©utilisable]
  • [BĂ©nĂ©fice pour l’UX ou l’architecture]
Propriété Valeur
Type [UI atomique / Îlot interactif]
Hydratation [Aucune / client:load / client:visible / client:idle]
Framework JS [Aucun / React / Vue / Svelte / Web Component]
Accessibilité [WCAG AA conforme / Partiellement conforme / Non testé]
Responsive [Mobile-first / Desktop-first]
src/components/
├── ui/ # Composants atomiques (HTML + CSS purs)
│ ├── Button.astro
│ ├── Badge.astro
│ └── [NomComposant].astro
└── interactive/ # Îlots dynamiques (React/Vue/Svelte)
├── MobileNav.tsx
├── Carousel.tsx
└── [NomComposant].tsx
Composant parent Fichier Relation
[Nom du parent] src/components/[chemin] [Composition / Wrapper]
Composant enfant Fichier RĂŽle
[Nom de l’enfant] src/components/[chemin] [Description]

3.1. Variante : Composant UI atomique (HTML + CSS purs)

Section titled “3.1. Variante : Composant UI atomique (HTML + CSS purs)”
src/components/ui/[NomComposant].astro
---
interface Props {
label: string;
variant?: 'primary' | 'secondary' | 'outline';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
href?: string;
onClick?: () => void;
}
const {
label,
variant = 'primary',
size = 'md',
disabled = false,
href,
onClick,
} = Astro.props;
const baseClasses = 'inline-flex items-center justify-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2';
const variantClasses = {
primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500',
secondary: 'bg-gray-600 text-white hover:bg-gray-700 focus:ring-gray-500',
outline: 'border-2 border-blue-600 text-blue-600 hover:bg-blue-50 focus:ring-blue-500',
};
const sizeClasses = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
};
const classes = `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`;
---
{href ? (
<a
href={href}
class={classes}
aria-disabled={disabled}
>
{label}
</a>
) : (
<button
class={classes}
disabled={disabled}
onClick={onClick}
>
{label}
</button>
)}
src/components/interactive/[NomComposant].tsx
import { useState } from 'react';
interface Props {
items: Array<{
id: string;
title: string;
content: string;
}>;
autoPlay?: boolean;
interval?: number;
}
export default function Carousel({ items, autoPlay = true, interval = 5000 }: Props) {
const [currentIndex, setCurrentIndex] = useState(0);
const nextSlide = () => {
setCurrentIndex((prev) => (prev + 1) % items.length);
};
const prevSlide = () => {
setCurrentIndex((prev) => (prev - 1 + items.length) % items.length);
};
// Auto-play
useState(() => {
if (autoPlay) {
const timer = setInterval(nextSlide, interval);
return () => clearInterval(timer);
}
}, [autoPlay, interval]);
return (
<div className="carousel" role="region" aria-label="Carrousel" aria-roledescription="carousel">
<div className="carousel-track">
{items.map((item, index) => (
<div
key={item.id}
className={`carousel-slide ${index === currentIndex ? 'active' : ''}`}
role="group"
aria-roledescription="slide"
aria-label={`${index + 1} sur ${items.length}`}
aria-hidden={index !== currentIndex}
>
<h3>{item.title}</h3>
<p>{item.content}</p>
</div>
))}
</div>
<button
onClick={prevSlide}
aria-label="Diapositive précédente"
className="carousel-prev"
>
←
</button>
<button
onClick={nextSlide}
aria-label="Diapositive suivante"
className="carousel-next"
>
→
</button>
<div className="carousel-indicators">
{items.map((_, index) => (
<button
key={index}
onClick={() => setCurrentIndex(index)}
className={`indicator ${index === currentIndex ? 'active' : ''}`}
aria-label={`Aller Ă  la diapositive ${index + 1}`}
aria-current={index === currentIndex}
/>
))}
</div>
</div>
);
}
src/pages/[page].astro
---
import Button from '../components/ui/Button.astro';
import Carousel from '../components/interactive/Carousel';
const items = [
{ id: '1', title: 'Témoignage 1', content: 'Contenu...' },
{ id: '2', title: 'Témoignage 2', content: 'Contenu...' },
];
---
<!-- Composant UI (pas d'hydratation) -->
<Button label="En savoir plus" variant="primary" href="/about" />
<!-- Îlot interactif (hydratation diffĂ©rĂ©e) -->
<Carousel client:visible items={items} autoPlay={true} interval={5000} />
Prop Type Description Exemple
label string Texte affiché dans le bouton "En savoir plus"
Prop Type Description Valeur par défaut
variant 'primary' | 'secondary' | 'outline' Style visuel du bouton 'primary'
size 'sm' | 'md' | 'lg' Taille du bouton 'md'
disabled boolean État dĂ©sactivĂ© false
href string URL de redirection (rend le composant en <a>) undefined
onClick () => void Callback au clic undefined
Slot Description Obligatoire
default Contenu personnalisĂ© (remplace label) ❌
icon Icîne à afficher avant le label ❌
Type de composant Stratégie Rationale
Composants UI atomiques Aucune HTML + CSS purs, pas d’interaction
Navigation mobile client:load Critique pour l’UX immĂ©diate
Carrousel témoignages client:visible Chargement différé (plus bas dans la page)
Formulaire de contact client:load Interaction immédiate requise
Graphiques / Charts client:visible Chargement différé
<!-- Aucune hydratation (HTML pur) -->
<Button label="Cliquez ici" />
<!-- Hydratation immédiate -->
<MobileNav client:load items={navItems} />
<!-- Hydratation au scroll (viewport) -->
<Carousel client:visible items={testimonials} />
<!-- Hydratation au repos (idle) -->
<Analytics client:idle />
Métrique Sans hydratation Avec client:load Avec client:visible
HTML size +2 KB +2 KB +2 KB
JS size 0 KB +15 KB +15 KB (chargé plus tard)
TTI (Time to Interactive) Immédiat +50ms +200ms (aprÚs scroll)
INP N/A Impacté Impacté
/* Le composant utilise les design tokens définis dans tokens.css */
.button {
background-color: var(--color-primary, #3b82f6);
color: var(--color-text-inverse, #ffffff);
padding: var(--spacing-md, 1rem) var(--spacing-lg, 1.5rem);
border-radius: var(--radius-md, 0.5rem);
font-family: var(--font-family-base, system-ui);
font-size: var(--font-size-base, 1rem);
font-weight: var(--font-weight-medium, 500);
transition: background-color var(--transition-fast, 150ms) ease;
}
.button:hover {
background-color: var(--color-primary-dark, #2563eb);
}
.button:focus-visible {
outline: 2px solid var(--color-focus, #3b82f6);
outline-offset: 2px;
}
/* Mobile-first approach */
.button {
width: 100%;
padding: var(--spacing-sm, 0.75rem) var(--spacing-md, 1rem);
font-size: var(--font-size-sm, 0.875rem);
}
/* Tablet et desktop */
@media (min-width: 768px) {
.button {
width: auto;
padding: var(--spacing-md, 1rem) var(--spacing-lg, 1.5rem);
font-size: var(--font-size-base, 1rem);
}
}
/* Support automatique via prefers-color-scheme */
@media (prefers-color-scheme: dark) {
.button {
background-color: var(--color-primary-dark, #2563eb);
}
}
/* Ou via classe manuelle */
[data-theme="dark"] .button {
background-color: var(--color-primary-dark, #2563eb);
}
CritÚre Implémentation Statut
1.1.1 Non-text Content Attribut alt obligatoire pour les images ✅
1.3.1 Info and Relationships Structure HTML sĂ©mantique (<button>, <a>) ✅
1.4.3 Contrast (Minimum) Contraste texte/fond ≄ 4.5:1 ✅
1.4.11 Non-text Contrast Contraste Ă©lĂ©ments interactifs ≄ 3:1 ✅
2.1.1 Keyboard Navigation clavier complùte (Tab, Enter, Space) ✅
2.1.2 No Keyboard Trap Pas de piùge au clavier ✅
2.4.7 Focus Visible Focus visible sur tous les Ă©lĂ©ments interactifs ✅
4.1.2 Name, Role, Value Attributs ARIA appropriĂ©s (aria-label, aria-expanded) ✅
<!-- Bouton -->
<button
aria-label="Fermer la modale"
aria-expanded="false"
aria-controls="modal-content"
>
×
</button>
<!-- Carrousel -->
<div
role="region"
aria-label="Carrousel de témoignages"
aria-roledescription="carousel"
>
<div
role="group"
aria-roledescription="slide"
aria-label="1 sur 5"
aria-hidden="false"
>
<!-- Contenu -->
</div>
</div>
<!-- Navigation -->
<nav aria-label="Navigation principale">
<ul role="menubar">
<li role="none">
<a role="menuitem" href="/">Accueil</a>
</li>
</ul>
</nav>
Touche Action
Tab Navigation vers l’élĂ©ment suivant
Shift + Tab Navigation vers l’élĂ©ment prĂ©cĂ©dent
Enter / Space Activation du bouton ou du lien
Escape Fermeture de la modale ou du menu
FlĂšches Navigation dans les menus / carrousels
Terminal window
# Test automatique avec axe-core
npx axe http://localhost:4321/
# Test avec Lighthouse
npx lighthouse http://localhost:4321/ --view
# Test manuel
# 1. Navigation complĂšte au clavier (Tab, Shift+Tab)
# 2. Vérification du focus visible
# 3. Test avec un lecteur d'écran (VoiceOver, NVDA)
# 4. Vérification du contraste avec WebAIM Contrast Checker
  • ✅ HTML statique par dĂ©faut (pas de JavaScript sauf si nĂ©cessaire)
  • ✅ CSS scoped (pas de fuite de styles)
  • ✅ Images optimisĂ©es (WebP/AVIF via Cloudflare)
  • ✅ Pas de JavaScript bloquant sur le chemin critique
  • ✅ Lazy loading des composants interactifs (client:visible)
Métrique Objectif Stratégie
LCP < 2.5s Images optimisées, pas de JS bloquant
INP < 200ms JavaScript minimal, ßlots ciblés
CLS < 0.1 Dimensions explicites, pas de layout shift
Type HTML CSS JS (hydraté) Total
UI atomique 2 KB 1 KB 0 KB 3 KB
Îlot interactif 2 KB 1 KB 15 KB 18 KB
SymptĂŽme Cause probable Solution
Composant non visible Import incorrect VĂ©rifier le chemin d’import
Styles cassés Conflit CSS Vérifier les spécificités et le scope
Hydratation ne fonctionne pas Directive manquante Ajouter client:load ou client:visible
Accessibilité dégradée Structure HTML incorrecte Valider avec axe-core
Performance dégradée JavaScript inutile Vérifier les hydratations
Focus invisible CSS outline: none Retirer ou remplacer par focus-visible
Terminal window
# Test local
npm run dev
# Vérification de l'accessibilité
npx axe http://localhost:4321/
# Vérification des performances
npx lighthouse http://localhost:4321/
# Test responsive
# Utiliser les DevTools du navigateur (mode responsive)
# Test des interactions
# Vérifier tous les états (hover, focus, active, disabled)
Terminal window
# Logs de développement
npm run dev
# Vérification des types
npx astro check
# Analyse du bundle
npm run build -- --analyze
  • ✅ SĂ©parer les composants UI (atomiques) des composants interactifs (Ăźlots)
  • ✅ Utiliser les design tokens pour tous les styles (couleurs, spacing, typographie)
  • ✅ Typer les props avec TypeScript
  • ✅ Valider les props obligatoires
  • ✅ Utiliser des valeurs par dĂ©faut pour les props optionnelles
  • ✅ Toujours utiliser des Ă©lĂ©ments HTML sĂ©mantiques (<button>, <a>, <nav>)
  • ✅ Fournir des attributs ARIA appropriĂ©s
  • ✅ Assurer une navigation clavier complĂšte
  • ✅ VĂ©rifier le contraste (WCAG AA minimum)
  • ✅ Tester avec un lecteur d’écran
  • ✅ Éviter l’hydratation sauf si interaction requise
  • ✅ Utiliser client:visible pour les composants bas de page
  • ✅ Optimiser les images (WebP/AVIF, dimensions explicites)
  • ✅ Éviter le JavaScript bloquant sur le chemin critique
  • ✅ Mesurer les Core Web Vitals rĂ©guliĂšrement
  • ✅ Documenter les props et slots dans ce fichier
  • ✅ CrĂ©er des exemples d’utilisation
  • ✅ Tester l’accessibilitĂ© rĂ©guliĂšrement
  • ✅ VĂ©rifier les performances avec Lighthouse
  • ✅ Mettre Ă  jour les composants lors des changements de design
  • [Évolution 1 : ex. Support du mode sombre]
  • [Évolution 2 : ex. Ajout de variantes de taille]
  • [Évolution 3 : ex. IntĂ©gration d’animations]
  • ❌ Les composants UI ne peuvent pas avoir d’état (pas de JavaScript)
  • ❌ Les Ăźlots interactifs augmentent la taille du bundle JS
  • ❌ L’hydratation client:visible nĂ©cessite l’Intersection Observer API
  • Documentation Astro : Components
  • Documentation Astro : Islands Architecture
  • Documentation Astro : Client Directives
  • DAT L2 : §4 - StratĂ©gie d’Hydratation & Gestion du JavaScript
  • DAT L2 : §11 - AccessibilitĂ©
  • DAT L2 : §12 - Design System
  • Composants associĂ©s :
    • src/components/ui/[ComposantParent].astro
    • src/components/interactive/[ComposantEnfant].tsx
  • Design tokens : src/styles/tokens.css
Date Version Description Auteur
YYYY-MM-DD 1.0 Création initiale [Nom]
YYYY-MM-DD 1.1 [Modification] [Nom]
---
**Points clés de ce template :**
- Couvre les deux types de composants (UI atomique et Ăźlot interactif)
- Détaille la stratégie d'hydratation (critique pour les performances)
- Inclut la conformité WCAG 2.1 AA (accessibilité)
- Fournit l'intégration avec le design system (tokens CSS)
- Sépare clairement les props, les slots, et les exemples d'utilisation
- Inclut les bonnes pratiques de performance et de maintenance
- Documente l'impact sur les Core Web Vitals
Edit page

Last updated: