Définition
L’attribut alt (texte alternatif) est un attribut HTML obligatoire pour les balises <img> qui fournit une description textuelle du contenu d’une image. Essentiel pour l’accessibilité web, il permet aux lecteurs d’écran de décrire les images aux utilisateurs malvoyants. En SEO, l’alt text aide les moteurs de recherche à comprendre le contenu des images et améliore le référencement dans Google Images.
Syntaxe et bonnes pratiques
Implementation HTML
<!-- Exemples d'alt text optimisés -->
<!-- Produit e-commerce -->
<img src="nike-air-max-90-blanc-rouge.jpg"
alt="Chaussures Nike Air Max 90 blanches avec détails rouges, vue de profil"
width="800"
height="600">
<!-- Photo éditoriale -->
<img src="equipe-marketing.jpg"
alt="Équipe marketing de 5 personnes en réunion autour d'une table avec ordinateurs portables"
loading="lazy">
<!-- Infographie -->
<img src="statistiques-seo-2024.png"
alt="Infographie : 73% des entreprises investissent plus en SEO en 2024 vs 2023"
width="1200"
height="800">
<!-- Logo -->
<img src="logo-entreprise.svg"
alt="Logo MonEntreprise"
width="200"
height="50">
<!-- Image décorative (alt vide) -->
<img src="separateur-decoratif.png"
alt=""
role="presentation">
<!-- Image complexe avec description longue -->
<img src="schema-architecture-site.png"
alt="Schéma de l'architecture du site"
aria-describedby="schema-description">
<div id="schema-description" class="sr-only">
Schéma montrant la structure hiérarchique du site avec page d'accueil en haut,
5 catégories principales au second niveau, et pages produits au troisième niveau.
</div>
Génération dynamique
<?php
// Génération automatique alt text optimisé
function generate_alt_text($image_data) {
$alt_components = [];
// Type d'image
switch($image_data['type']) {
case 'product':
// Format: [Produit] [Marque] [Modèle] [Couleur] [Caractéristique]
$alt_components[] = $image_data['product_type'];
$alt_components[] = $image_data['brand'];
$alt_components[] = $image_data['model'];
if (!empty($image_data['color'])) {
$alt_components[] = $image_data['color'];
}
if (!empty($image_data['key_feature'])) {
$alt_components[] = $image_data['key_feature'];
}
break;
case 'team':
// Format: [Rôle] [Nom] [Action/Contexte]
$alt_components[] = $image_data['role'];
$alt_components[] = $image_data['name'];
if (!empty($image_data['context'])) {
$alt_components[] = $image_data['context'];
}
break;
case 'infographic':
// Format: Infographie : [Statistique principale ou sujet]
$alt_components[] = "Infographie :";
$alt_components[] = $image_data['main_point'];
break;
}
$alt_text = implode(' ', array_filter($alt_components));
// Limiter longueur (125 caractères recommandés)
if (strlen($alt_text) > 125) {
$alt_text = substr($alt_text, 0, 122) . '...';
}
return htmlspecialchars($alt_text, ENT_QUOTES, 'UTF-8');
}
// Usage
$alt = generate_alt_text([
'type' => 'product',
'product_type' => 'Smartphone',
'brand' => 'Samsung',
'model' => 'Galaxy S24 Ultra',
'color' => 'Titanium Gray',
'key_feature' => '256GB'
]);
// Résultat: "Smartphone Samsung Galaxy S24 Ultra Titanium Gray 256GB"
?>
Optimisation SEO
Stratégies par type d’image
// Framework optimisation alt text SEO
const altTextOptimization = {
product_images: {
primary_image: {
format: '[Product Type] [Brand] [Model] [Distinguishing Feature]',
example: 'Running shoes Nike Pegasus 40 black and gold',
seo_tips: [
'Include target keywords naturally',
'Mention color and key features',
'Avoid keyword stuffing',
'Be specific, not generic'
]
},
gallery_images: {
format: '[Product Name] - [View/Detail]',
examples: [
'Nike Pegasus 40 - side view',
'Nike Pegasus 40 - sole detail',
'Nike Pegasus 40 - heel cushioning close-up'
]
},
lifestyle_images: {
format: '[Person/Context] using/wearing [Product]',
example: 'Runner on trail wearing Nike Pegasus 40 shoes'
}
},
blog_images: {
hero_images: {
strategy: 'Describe the scene and relate to article topic',
example: 'SEO specialist analyzing Google Analytics data on laptop'
},
screenshots: {
strategy: 'Describe what the screenshot shows',
example: 'Google Search Console performance report showing traffic increase'
},
charts_graphs: {
strategy: 'Summarize key data point',
example: 'Bar chart: Organic traffic increased 150% from Jan to Dec 2024'
}
},
avoid_these: {
keyword_stuffing: {
bad: 'SEO SEO optimization SEO tools best SEO SEO software',
good: 'SEO analysis dashboard showing keyword rankings'
},
generic_descriptions: {
bad: 'Image 1', 'Photo', 'Picture of product',
good: 'Specific, descriptive alternative text'
},
file_names: {
bad: 'DSC_0001.jpg', 'image.png',
good: 'Descriptive text, not filename'
}
}
};
Impact sur Google Images
# Analyse performance Google Images
def analyze_image_seo_performance(site_url):
"""
Évalue impact alt text sur trafic Google Images
"""
from datetime import datetime, timedelta
analysis = {
'image_search_traffic': {},
'top_performing_images': [],
'optimization_opportunities': [],
'alt_text_quality': {}
}
# Récupérer données Search Console
image_queries = get_search_console_data(
site_url,
search_type='image',
date_range=90
)
# Analyser images performantes
for query_data in image_queries:
image_url = query_data['url']
# Extraire et analyser alt text
alt_text = extract_alt_text(image_url)
quality_score = assess_alt_text_quality(alt_text, query_data['query'])
if query_data['clicks'] > 50: # Images performantes
analysis['top_performing_images'].append({
'url': image_url,
'alt_text': alt_text,
'clicks': query_data['clicks'],
'impressions': query_data['impressions'],
'ctr': query_data['ctr'],
'avg_position': query_data['position']
})
# Identifier opportunités
if quality_score < 70 and query_data['impressions'] > 100:
analysis['optimization_opportunities'].append({
'url': image_url,
'current_alt': alt_text,
'issues': identify_alt_text_issues(alt_text),
'suggested_alt': generate_improved_alt(
image_url,
query_data['query']
),
'potential_traffic_increase': estimate_traffic_increase(
quality_score,
query_data['impressions']
)
})
return analysis
def assess_alt_text_quality(alt_text, target_query):
"""
Évalue qualité alt text pour SEO
"""
score = 100
if not alt_text:
return 0
# Longueur
length = len(alt_text)
if length < 10:
score -= 30
elif length > 125:
score -= 20
# Pertinence avec requête
query_words = target_query.lower().split()
alt_words = alt_text.lower().split()
word_matches = sum(1 for word in query_words if word in alt_words)
if word_matches == 0:
score -= 40
elif word_matches < len(query_words) / 2:
score -= 20
# Keyword stuffing
keyword_density = calculate_keyword_density(alt_text)
if keyword_density > 30:
score -= 30
# Descriptif vs générique
generic_terms = ['image', 'photo', 'picture', 'img', 'pic']
if any(term in alt_text.lower() for term in generic_terms):
score -= 20
return max(0, score)
Accessibilité
Standards WCAG
// Conformité accessibilité WCAG 2.1
const accessibilityStandards = {
wcag_requirements: {
'guideline_1.1.1': {
level: 'A',
requirement: 'All non-text content has text alternative',
implementation: {
informative_images: {
requirement: 'Descriptive alt text',
example: 'alt="Red stop sign at intersection"'
},
decorative_images: {
requirement: 'Empty alt attribute',
example: 'alt="" role="presentation"'
},
functional_images: {
requirement: 'Describe function, not appearance',
example: 'alt="Submit form" not "Green button"'
},
complex_images: {
requirement: 'Brief alt + long description',
example: `alt="Sales chart 2024"
aria-describedby="chart-detailed-desc"`
}
}
}
},
screen_reader_testing: {
test_process: [
'Navigate with screen reader enabled',
'Verify all images announced correctly',
'Check context makes sense without visuals',
'Ensure no redundant information'
],
common_issues: {
'redundant_text': {
bad: 'alt="Image of red car"',
good: 'alt="Red car"',
reason: 'Screen readers already announce "image"'
},
'file_extensions': {
bad: 'alt="banner.jpg"',
good: 'alt="Summer sale banner: 50% off all items"'
},
'empty_for_important': {
bad: '<img src="chart.png" alt="">',
good: '<img src="chart.png" alt="Sales increased 45% in Q4">'
}
}
}
};
// Validation accessibilité
function validateImageAccessibility(images) {
const issues = [];
images.forEach(img => {
// Vérifier présence alt
if (!img.hasAttribute('alt')) {
issues.push({
element: img,
issue: 'Missing alt attribute',
severity: 'error',
fix: 'Add alt="" for decorative or descriptive text for informative'
});
}
const altText = img.getAttribute('alt');
// Alt text trop long
if (altText && altText.length > 125) {
issues.push({
element: img,
issue: 'Alt text too long',
severity: 'warning',
fix: 'Shorten to under 125 characters or use aria-describedby'
});
}
// Vérifier contexte
if (img.parentElement.tagName === 'A' && !altText) {
issues.push({
element: img,
issue: 'Image link without alt text',
severity: 'error',
fix: 'Add alt text describing link destination'
});
}
});
return issues;
}
Cas d’usage spécifiques
E-commerce et produits
# Système alt text e-commerce scalable
class EcommerceAltTextGenerator:
def __init__(self):
self.templates = {
'main_product': '{product_name} - {color} {material}',
'angle_view': '{product_name} - {angle} view',
'detail_shot': '{product_name} - {detail_part} detail',
'lifestyle': '{context} wearing {product_name}',
'size_chart': 'Size chart for {product_name}',
'color_swatch': '{color} color option for {product_name}'
}
def generate_product_alts(self, product, images):
"""
Génère alt texts pour set complet images produit
"""
alt_texts = {}
for idx, image in enumerate(images):
image_type = self.detect_image_type(image)
if image_type == 'main':
alt_texts[image['url']] = self.templates['main_product'].format(
product_name=product['name'],
color=product['color'],
material=product.get('material', '')
).strip()
elif image_type == 'angle':
angle = self.detect_angle(image)
alt_texts[image['url']] = self.templates['angle_view'].format(
product_name=product['name'],
angle=angle
)
elif image_type == 'detail':
detail = self.detect_detail_focus(image)
alt_texts[image['url']] = self.templates['detail_shot'].format(
product_name=product['name'],
detail_part=detail
)
return alt_texts
def bulk_update_alt_texts(self, products):
"""
Mise à jour en masse alt texts
"""
updates = []
for product in products:
current_alts = self.get_current_alts(product['id'])
suggested_alts = self.generate_product_alts(
product,
product['images']
)
for image_url, suggested_alt in suggested_alts.items():
if image_url not in current_alts or \
self.needs_improvement(current_alts[image_url]):
updates.append({
'product_id': product['id'],
'image_url': image_url,
'old_alt': current_alts.get(image_url, ''),
'new_alt': suggested_alt,
'reason': self.get_update_reason(
current_alts.get(image_url, ''),
suggested_alt
)
})
return self.prioritize_updates(updates)
Images techniques et infographies
// Alt text pour contenu complexe
const complexImageAltStrategies = {
charts_graphs: {
approach: 'Summarize key finding or trend',
examples: {
line_chart: 'Line graph showing organic traffic increased from 10,000 to 25,000 visitors between January and December 2024',
pie_chart: 'Pie chart: Mobile devices account for 65% of total traffic, desktop 30%, tablet 5%',
bar_chart: 'Bar chart comparing conversion rates: Email 4.2%, Organic Search 3.1%, Social Media 1.8%'
},
longDescription: function(chartData) {
// Pour descriptions détaillées
return `
<div id="chart-desc-${chartData.id}" class="sr-only">
<h4>Detailed Chart Description</h4>
<p>This chart shows ${chartData.type} data for ${chartData.metric}
over the period ${chartData.dateRange}.</p>
<ul>
${chartData.dataPoints.map(point =>
`<li>${point.label}: ${point.value}</li>`
).join('')}
</ul>
</div>
`;
}
},
screenshots: {
software_ui: {
format: '[Software name] screenshot: [What it shows]',
example: 'Google Analytics screenshot: Real-time visitor report showing 1,247 active users'
},
error_messages: {
format: 'Error message: [Error text]',
example: 'Error message: 404 Page Not Found'
},
code_snippets: {
format: 'Code snippet: [Language] [What it does]',
example: 'Code snippet: JavaScript function to lazy load images'
}
},
diagrams: {
flowchart: {
brief: 'Flowchart of [process name]',
detailed: 'Use aria-describedby for step-by-step description'
},
architecture: {
brief: '[System] architecture diagram',
detailed: 'List components and connections in description'
}
}
};
Audit et monitoring
Outils d’audit
# Audit complet alt texts d'un site
def comprehensive_alt_text_audit(site_url):
"""
Audit exhaustif des alt texts
"""
from bs4 import BeautifulSoup
import requests
audit_results = {
'total_images': 0,
'missing_alt': [],
'empty_alt': [],
'poor_quality_alt': [],
'duplicate_alt': {},
'keyword_stuffed': [],
'too_long': [],
'decorative_with_text': [],
'score': 0
}
# Crawler pages du site
pages = crawl_site_pages(site_url)
all_alts = {}
for page_url in pages:
soup = BeautifulSoup(requests.get(page_url).content, 'html.parser')
images = soup.find_all('img')
for img in images:
audit_results['total_images'] += 1
src = img.get('src', '')
alt = img.get('alt')
# Alt manquant
if alt is None:
audit_results['missing_alt'].append({
'page': page_url,
'image': src,
'context': get_image_context(img)
})
continue
# Alt vide
if alt == '':
# Vérifier si décoratif
if is_likely_decorative(img):
continue # OK pour images décoratives
else:
audit_results['empty_alt'].append({
'page': page_url,
'image': src,
'likely_purpose': detect_image_purpose(img)
})
continue
# Qualité alt text
quality_check = check_alt_quality(alt, src, img)
if quality_check['score'] < 60:
audit_results['poor_quality_alt'].append({
'page': page_url,
'image': src,
'alt': alt,
'issues': quality_check['issues'],
'suggestions': quality_check['suggestions']
})
# Duplicatas
if alt in all_alts:
if alt not in audit_results['duplicate_alt']:
audit_results['duplicate_alt'][alt] = []
audit_results['duplicate_alt'][alt].append({
'page': page_url,
'image': src
})
all_alts[alt] = src
# Keyword stuffing
if detect_keyword_stuffing(alt):
audit_results['keyword_stuffed'].append({
'page': page_url,
'image': src,
'alt': alt,
'keyword_density': calculate_keyword_density(alt)
})
# Longueur excessive
if len(alt) > 125:
audit_results['too_long'].append({
'page': page_url,
'image': src,
'alt': alt,
'length': len(alt),
'truncated_suggestion': alt[:120] + '...'
})
# Calculer score global
audit_results['score'] = calculate_overall_score(audit_results)
# Générer recommandations
audit_results['recommendations'] = generate_recommendations(audit_results)
return audit_results
L’alt text bien optimisé améliore simultanément l’accessibilité, l’expérience utilisateur et le référencement des images, constituant un élément essentiel d’une stratégie SEO complète.