Designer Frontend
Une compétence complète pour les designers et développeurs frontend afin de construire des interfaces utilisateur belles, accessibles et performantes avec les meilleures pratiques modernes.
Ce que fait cette compétence
Aide les designers/développeurs frontend avec :
- Conception et développement de composants - Construire des composants réutilisables et accessibles
- Systèmes de design - Implémenter des tokens, des motifs et de la documentation
- Design responsive - Mise en page mobile-first et fluide
- Accessibilité (WCAG 2.1) - Motifs de conception inclusifs
- CSS moderne - Flexbox, Grid, propriétés personnalisées
- Optimisation des performances - Frontends rapides et efficaces
- Motifs de framework - Bonnes pratiques React, Vue, Svelte
- Du design au code - Workflows Figma vers production
Pourquoi cette compétence est importante
Sans approche systématique :
- Implémentations de composants incohérentes
- Problèmes d'accessibilité
- Mauvais comportement responsive
- Code et styles dupliqués
- Difficile à maintenir
- Problèmes de performance
Avec cette compétence :
- Composants cohérents et réutilisables
- Conforme WCAG AA par défaut
- Design responsive mobile-first
- Aligné sur le système de design
- Code maintenable
- Livraison rapide et optimisée
Principes fondamentaux
1. Accessibilité d'abord
- WCAG 2.1 AA minimum
- HTML sémantique
- Navigation au clavier
- Support des lecteurs d'écran
- Gestion du focus
- Contraste des couleurs
2. Responsive mobile-first
- Commencer par le mobile (320px)
- Amélioration progressive
- Typographie fluide
- Mises en page flexibles
- Cibles tactiles confortables
3. Performance par défaut
- CSS/JS minimal
- Chargement paresseux
- Images optimisées
- CSS critique
- Tree shaking
4. Piloté par les composants
- Méthodologie Atomic Design
- Composants réutilisables
- Personnalisation basée sur les props
- Composition plutôt qu'héritage
5. Aligné sur le système de design
- Tokens de design
- Espacement cohérent
- Échelle typographique
- Palette de couleurs
- Bibliothèque de composants
Motifs de composants
Composant Bouton
Motif de bouton accessible et flexible :
// Exemple React
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'ghost';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
loading?: boolean;
children: React.ReactNode;
onClick?: () => void;
}
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'md',
disabled = false,
loading = false,
children,
onClick,
...props
}) => {
return (
<button
className={`btn btn--${variant} btn--${size}`}
disabled={disabled || loading}
onClick={onClick}
aria-busy={loading}
{...props}
>
{loading ? <Spinner /> : children}
</button>
);
};
CSS (avec tokens de design) :
.btn {
/* Styles de base */
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
font-family: var(--font-sans);
font-weight: 600;
text-decoration: none;
border: none;
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.2s ease;
/* Accessibilité */
min-height: 44px; /* Cible tactile WCAG */
&:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 2px;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
/* Variantes */
.btn--primary {
background: var(--color-primary);
color: var(--color-on-primary);
&:hover:not(:disabled) {
background: var(--color-primary-hover);
}
}
.btn--secondary {
background: var(--color-secondary);
color: var(--color-on-secondary);
}
.btn--ghost {
background: transparent;
color: var(--color-primary);
border: 1px solid currentColor;
}
/* Tailles */
.btn--sm {
padding: var(--space-2) var(--space-3);
font-size: var(--text-sm);
}
.btn--md {
padding: var(--space-3) var(--space-4);
font-size: var(--text-base);
}
.btn--lg {
padding: var(--space-4) var(--space-6);
font-size: var(--text-lg);
}
Composant Carte
Carte flexible et accessible :
interface CardProps {
variant?: 'elevated' | 'outlined' | 'filled';
padding?: 'none' | 'sm' | 'md' | 'lg';
interactive?: boolean;
children: React.ReactNode;
}
export const Card: React.FC<CardProps> = ({
variant = 'elevated',
padding = 'md',
interactive = false,
children,
}) => {
const Component = interactive ? 'button' : 'div';
return (
<Component
className={`
card
card--${variant}
card--padding-${padding}
${interactive ? 'card--interactive' : ''}
`}
{...(interactive && { role: 'button', tabIndex: 0 })}
>
{children}
</Component>
);
};
CSS :
.card {
border-radius: var(--radius-lg);
background: var(--color-surface);
}
.card--elevated {
box-shadow: var(--shadow-md);
}
.card--outlined {
border: 1px solid var(--color-border);
}
.card--filled {
background: var(--color-surface-variant);
}
.card--interactive {
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
&:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
&:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 2px;
}
}
.card--padding-sm { padding: var(--space-3); }
.card--padding-md { padding: var(--space-4); }
.card--padding-lg { padding: var(--space-6); }
Composant Champ de Formulaire
Champ de formulaire accessible :
interface InputProps {
label: string;
error?: string;
hint?: string;
required?: boolean;
type?: 'text' | 'email' | 'password' | 'number';
}
export const Input: React.FC<InputProps> = ({
label,
error,
hint,
required = false,
type = 'text',
...props
}) => {
const id = useId();
const hintId = `${id}-hint`;
const errorId = `${id}-error`;
return (
<div className="input-wrapper">
<label htmlFor={id} className="input-label">
{label}
{required && <span aria-label="required">*</span>}
</label>
{hint && (
<p id={hintId} className="input-hint">
{hint}
</p>
)}
<input
id={id}
type={type}
className={`input ${error ? 'input--error' : ''}`}
aria-required={required}
aria-invalid={!!error}
aria-describedby={error ? errorId : hint ? hintId : undefined}
{...props}
/>
{error && (
<p id={errorId} className="input-error" role="alert">
{error}
</p>
)}
</div>
);
};
Tokens de design
Propriétés personnalisées CSS pour le système de design :
:root {
/* Couleurs - Primaire */
--color-primary: #0066FF;
--color-primary-hover: #0052CC;
--color-on-primary: #FFFFFF;
/* Couleurs - Surface */
--color-surface: #FFFFFF;
--color-surface-variant: #F5F5F5;
--color-on-surface: #1A1A1A;
/* Couleurs - Bordures */
--color-border: #E0E0E0;
--color-border-hover: #BDBDBD;
/* Couleurs - Sémantique */
--color-error: #D32F2F;
--color-success: #388E3C;
--color-warning: #F57C00;
--color-info: #1976D2;
/* Échelle d'espacement (base 8px) */
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-5: 1.5rem; /* 24px */
--space-6: 2rem; /* 32px */
--space-8: 3rem; /* 48px */
--space-10: 4rem; /* 64px */
/* Échelle typographique */
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.5rem; /* 24px */
--text-3xl: 1.875rem; /* 30px */
--text-4xl: 2.25rem; /* 36px */
/* Familles de polices */
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-mono: "SF Mono", Monaco, "Cascadia Code", monospace;
/* Hauteurs de ligne */
--leading-tight: 1.25;
--leading-normal: 1.5;
--leading-relaxed: 1.75;
/* Rayons de bordure */
--radius-sm: 0.25rem; /* 4px */
--radius-md: 0.5rem; /* 8px */
--radius-lg: 1rem; /* 16px */
--radius-full: 9999px;
/* Ombres */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15);
/* Anneau de focus */
--color-focus: #0066FF;
/* Transitions */
--transition-fast: 150ms ease;
--transition-base: 200ms ease;
--transition-slow: 300ms ease;
}
/* Mode sombre */
@media (prefers-color-scheme: dark) {
:root {
--color-surface: #1A1A1A;
--color-surface-variant: #2A2A2A;
--color-on-surface: #FFFFFF;
--color-border: #3A3A3A;
}
}
Motifs de design responsive
Points de rupture mobile-first
/* Approche mobile-first */
.container {
padding: var(--space-4);
/* Tablette : 768px et plus */
@media (min-width: 48rem) {
padding: var(--space-6);
}
/* Desktop : 1024px et plus */
@media (min-width: 64rem) {
padding: var(--space-8);
max-width: 1200px;
margin: 0 auto;
}
}
Typographie fluide
/* Typographie responsive */
h1 {
font-size: clamp(2rem, 5vw, 3.5rem);
line-height: var(--leading-tight);
}
h2 {
font-size: clamp(1.5rem, 4vw, 2.5rem);
}
p {
font-size: clamp(1rem, 2vw, 1.125rem);
line-height: var(--leading-normal);
}
Mises en page en grille
/* Grille responsive */
.grid {
display: grid;
gap: var(--space-4);
/* Colonnes auto-ajustées (min 280px) */
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
/* Système de grille 12 colonnes */
.grid-12 {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: var(--space-4);
}
.col-span-4 {
grid-column: span 4;
}
/* Empiler sur mobile */
@media (max-width: 48rem) {
.col-span-4 {
grid-column: span 12;
}
}
Motifs d'accessibilité
Liens d'évitement
export const SkipLink = () => (
<a href="#main-content" className="skip-link">
Aller au contenu principal
</a>
);
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: var(--color-primary);
color: var(--color-on-primary);
padding: var(--space-2) var(--space-4);
text-decoration: none;
z-index: 100;
&:focus {
top: 0;
}
}
Gestion du focus
// Modale avec piège à focus
export const Modal = ({ isOpen, onClose, children }) => {
const modalRef = useRef(null);
useEffect(() => {
if (isOpen) {
// Sauvegarder l'élément actuellement focus
const previouslyFocused = document.activeElement;
// Focus le premier élément focusable dans la modale
const firstFocusable = modalRef.current?.querySelector(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
firstFocusable?.focus();
// Restaurer le focus à la fermeture
return () => previouslyFocused?.focus();
}
}, [isOpen]);
if (!isOpen) return null;
return (
<div
ref={modalRef}
role="dialog"
aria-modal="true"
className="modal-overlay"
>
<div className="modal">
{children}
</div>
</div>
);
};
Labels ARIA
// Bouton icône avec label accessible
export const IconButton = ({ icon, label, ...props }) => (
<button
aria-label={label}
className="icon-button"
{...props}
>
<span aria-hidden="true">{icon}</span>
</button>
);
// État de chargement
export const LoadingButton = ({ loading, children, ...props }) => (
<button
aria-busy={loading}
aria-live="polite"
disabled={loading}
{...props}
>
{loading ? 'Chargement...' : children}
</button>
);
Optimisation des performances
CSS critique
<!-- CSS critique en ligne -->
<style>
/* Styles au-dessus de la ligne de flottaison */
body { margin: 0; font-family: var(--font-sans); }
.header { /* styles critiques de l'en-tête */ }
.hero { /* styles critiques du héros */ }
</style>
<!-- Charger la feuille de style complète de manière asynchrone -->
<link rel="stylesheet" href="styles.css" media="print" onload="this.media='all'">
Chargement paresseux des images
export const LazyImage = ({ src, alt, ...props }) => (
<img
src={src}
alt={alt}
loading="lazy"
decoding="async"
{...props}
/>
);
Découpage du code
// Chargement paresseux React
const Dashboard = lazy(() => import('./Dashboard'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Dashboard />
</Suspense>
);
}
Techniques CSS modernes
Container Queries
.card {
container-type: inline-size;
}
.card-content {
display: flex;
flex-direction: column;
/* Passer à une disposition en ligne lorsque le conteneur > 400px */
@container (min-width: 400px) {
flex-direction: row;
}
}
Auto-fit de la grille CSS
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: var(--space-4);
}
Propriétés personnalisées pour le thème
/* Thème clair (par défaut) */
:root {
--bg: #ffffff;
--text: #000000;
}
/* Thème sombre */
[data-theme="dark"] {
--bg: #000000;
--text: #ffffff;
}
body {
background: var(--bg);
color: var(--text);
}
Structure de la bibliothèque de composants
src/
├── components/
│ ├── Button/
│ │ ├── Button.tsx
│ │ ├── Button.css
│ │ ├── Button.test.tsx
│ │ └── Button.stories.tsx
│ ├── Card/
│ ├── Input/
│ └── index.ts
├── tokens/
│ ├── colors.css
│ ├── spacing.css
│ ├── typography.css
│ └── index.css
├── utils/
│ ├── a11y.ts
│ └── responsive.ts
└── index.ts
Utilisation de cette compétence
Générer un composant
./scripts/generate_component.sh Button
Crée un composant avec :
- Fichier TypeScript/JSX
- Module CSS
- Fichier de test
- Histoire Storybook
- Vérifications d'accessibilité
Configuration du système de design
./scripts/setup_design_system.sh
Crée :
- Tokens de design (propriétés personnalisées CSS)
- Styles de base
- Modèles de composants
- Structure de documentation
Audit d'accessibilité
./scripts/audit_accessibility.sh
Vérifie :
- Ratios de contraste des couleurs
- Navigation au clavier
- Attributs ARIA
- HTML sémantique
- Gestion du focus
Meilleures pratiques
Conception de composants
✅ À FAIRE :
- Utiliser du HTML sémantique
- Rendre les composants accessibles au clavier
- Fournir des labels ARIA
- Prendre en charge les modes clair et sombre
❌ À ÉVITER :
- Composants sans accessibilité
- Tailles fixes (px) au lieu d'unités relatives
- Balises
<div>au lieu d'éléments sémantiques - Ignorer la gestion du focus
- État de chargement manquant pour les actions asynchrones
Accessibilité
Contraste des couleurs :
- Texte normal : ratio de contraste 4.5:1 minimum
- Grand texte (18px+ ou 14px en gras) : ratio 3:1
- Composants d'interface utilisateur : ratio 3:1
Navigation au clavier :
- Tous les éléments interactifs doivent être accessibles au clavier
- Ordre de tabulation logique
- Indicateurs de focus visibles
- Pas de piège à focus
Lecteurs d'écran :
- Utiliser
aria-labelpour les boutons icône aria-livepour les mises à jour dynamiquesaria-expandedpour les sections dépliablesrole="alert"pour les messages d'erreur
Performance
Optimisations CSS :
- Minimiser la spécificité des sélecteurs
- Utiliser des propriétés personnalisées pour les couleurs et l'espacement
- Éviter
@importpour le chargement des feuilles de style - Utiliser
will-changeavec parcimonie
Optimisations JavaScript :
- Découpage du code avec React.lazy()
- Mémoïsation des composants coûteux
- Chargement paresseux des images et des iframes
- Éviter les re-rendus inutiles
Exemples supplémentaires
Barre de navigation avec navigation clavier
export const NavigationBar: React.FC = () => {
const navRef = useRef<HTMLElement>(null);
return (
<nav ref={navRef} aria-label="Navigation principale">
<ul className="nav-list">
<li><a href="/" className="nav-link">Accueil</a></li>
<li><a href="/about" className="nav-link">À propos</a></li>
<li><a href="/contact" className="nav-link">Contact</a></li>
</ul>
</nav>
);
};
.nav-list {
display: flex;
list-style: none;
gap: var(--space-4);
padding: 0;
margin: 0;
}
.nav-link {
text-decoration: none;
color: var(--color-on-surface);
font-weight: 500;
padding: var(--space-2) var(--space-3);
border-radius: var(--radius-md);
transition: background 0.2s;
&:hover {
background: var(--color-surface-variant);
}
&:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 2px;
}
}
Toast de notification accessible
interface ToastProps {
message: string;
type: 'success' | 'error' | 'info';
onClose: () => void;
}
export const Toast: React.FC<ToastProps> = ({ message, type, onClose }) => {
return (
<div
className={`toast toast--${type}`}
role="alert"
aria-live="assertive"
>
<p className="toast-message">{message}</p>
<button
onClick={onClose}
className="toast-close"
aria-label="Fermer la notification"
>
×
</button>
</div>
);
};
.toast {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-3) var(--space-4);
border-radius: var(--radius-md);
box-shadow: var(--shadow-md);
color: white;
}
.toast--success {
background: var(--color-success);
}
.toast--error {
background: var(--color-error);
}
.toast--info {
background: var(--color-info);
}
.toast-close {
background: none;
border: none;
color: inherit;
cursor: pointer;
font-size: 1.25rem;
line-height: 1;
}
Contribution
Cette compétence est ouverte aux améliorations. Les contributions sont les bienvenues :
- Signaler des bugs d'accessibilité
- Suggérer des motifs de composants
- Ajouter des exemples spécifiques à un framework
- Améliorer la documentation
Licence
MIT
Construit avec des bonnes pratiques d'accessibilité, de design responsive et de performance.