use super::{
dimensions::{Dimension, Subcategory},
issue_class::IssueClass,
score::{Scaling, ScoreImpact},
severity::Severity,
};
pub struct Rule {
pub id: &'static str,
pub dimension: Dimension,
pub subcategory: Subcategory,
pub issue_class: IssueClass,
pub severity: Severity,
pub external_ref: Option<&'static str>,
pub external_level: Option<&'static str>,
pub axe_id: Option<&'static str>,
pub title: &'static str,
pub title_en: &'static str,
pub description: &'static str,
pub user_impact: &'static str,
pub user_impact_en: &'static str,
pub technical_impact: &'static str,
pub technical_impact_en: &'static str,
pub score_impact: ScoreImpact,
pub report_visibility: ReportVisibility,
}
pub struct ReportVisibility {
pub executive: bool,
pub standard: bool,
pub technical: bool,
}
const VIS_ALL: ReportVisibility = ReportVisibility {
executive: true,
standard: true,
technical: true,
};
const VIS_STANDARD: ReportVisibility = ReportVisibility {
executive: false,
standard: true,
technical: true,
};
const VIS_TECHNICAL: ReportVisibility = ReportVisibility {
executive: false,
standard: false,
technical: true,
};
pub struct RuleLookup;
impl RuleLookup {
pub fn by_id(id: &str) -> Option<&'static Rule> {
RULES.iter().find(|r| r.id == id)
}
pub fn by_wcag(criterion: &str) -> Option<&'static Rule> {
let search = format!("WCAG {}", criterion);
RULES
.iter()
.find(|r| r.external_ref == Some(search.as_str()))
}
pub fn by_legacy_wcag_id(wcag_id: &str) -> Option<&'static Rule> {
LEGACY_WCAG_MAP
.iter()
.find(|(wid, _)| *wid == wcag_id)
.and_then(|(_, rule_id)| Self::by_id(rule_id))
}
pub fn by_dimension(dim: Dimension) -> Vec<&'static Rule> {
RULES.iter().filter(|r| r.dimension == dim).collect()
}
}
static LEGACY_WCAG_MAP: &[(&str, &str)] = &[
("1.1.1", "a11y.alt_text.missing"),
("1.2.1", "a11y.media.alternative"),
("1.2.2", "a11y.captions.missing"),
("1.2.8", "a11y.media_alternative.missing"),
("1.3.1", "a11y.structure.missing"),
("1.3.4", "a11y.orientation.restricted"),
("1.3.6", "a11y.identify_purpose.missing"),
("1.3.5", "a11y.input_purpose.missing"),
("1.4.1", "a11y.color.link_indicator"),
("1.4.3", "a11y.contrast.weak"),
("1.4.4", "a11y.resize_text.weak"),
("1.4.7", "a11y.background_audio.uncontrolled"),
("1.4.8", "a11y.visual_presentation.weak"),
("1.4.10", "a11y.reflow.missing"),
("1.4.11", "a11y.non_text_contrast.weak"),
("1.4.13", "a11y.hover.content_visibility"),
("2.1.1", "a11y.keyboard.missing"),
("2.1.2", "a11y.keyboard_trap.risk"),
("2.2.1", "a11y.timing.unadjustable"),
("2.2.3", "a11y.timing.essential"),
("2.2.4", "a11y.interruptions.uncontrollable"),
("2.2.5", "a11y.re_authenticate.data_loss"),
("2.2.6", "a11y.timeouts.unwarned"),
("2.4.1", "a11y.bypass_blocks.missing"),
("2.4.2", "a11y.page_title.missing"),
("2.4.3", "a11y.focus_order.weak"),
("2.4.4", "a11y.link_purpose.weak"),
("2.4.6", "a11y.headings.missing"),
("2.4.7", "a11y.focus_visible.missing"),
("2.4.8", "a11y.location.missing"),
("2.4.9", "a11y.link_purpose_only.weak"),
("2.3.3", "a11y.motion.reduced_motion"),
("2.4.10", "a11y.section_headings.missing"),
("2.5.1", "a11y.pointer_gestures.missing_alternative"),
("2.5.2", "a11y.pointer_cancellation.invalid"),
("2.5.3", "a11y.label_in_name.invalid"),
("2.5.4", "a11y.motion_actuation.missing_alternative"),
("2.5.5", "a11y.target_size_enhanced.small"),
("3.1.1", "a11y.language.missing"),
("3.1.3", "a11y.unusual_words.missing_definition"),
("3.1.4", "a11y.abbreviations.missing"),
("3.2.1", "a11y.on_focus.risk"),
("3.2.2", "a11y.on_input.risk"),
("3.3.1", "a11y.error_id.missing_description"),
("3.3.2", "a11y.form_labels.missing"),
("3.3.5", "a11y.help.missing"),
("4.1.1", "a11y.parsing.invalid"),
("4.1.2", "a11y.name_role.missing"),
("4.1.3", "a11y.status_messages.broken"),
("aria-hidden-focus", "a11y.aria_hidden_focus.invalid"),
("aria-required-attr", "a11y.aria_required_attr.missing"),
("aria-prohibited-attr", "a11y.aria_prohibited_attr.invalid"),
("frame-tested", "a11y.frame_tested.cross_origin"),
("frame-title", "a11y.frame_title.missing"),
("form-no-submit", "a11y.form_no_submit.missing"),
("landmark-main-present", "a11y.landmark_main.missing"),
("landmark-unique", "a11y.landmark_unique.invalid"),
("modern-attribute-misuse", "a11y.modern_attributes.invalid"),
(
"presentation-semantic-children",
"a11y.presentation_semantic_children.invalid",
),
];
pub static RULES: &[Rule] = &[
Rule {
id: "a11y.alt_text.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::ContentAlternatives,
issue_class: IssueClass::Missing,
severity: Severity::High,
external_ref: Some("WCAG 1.1.1"),
external_level: Some("A"),
axe_id: Some("image-alt"),
title: "Fehlende Alternativtexte bei Bildern",
title_en: "Missing alternative text on images",
description: "Bilder haben keinen beschreibenden Alternativtext.",
user_impact: "Screenreader-Nutzer erhalten keine Bildinformation.",
user_impact_en: "Screen reader users receive no image information.",
technical_impact: "Nicht-konforme Bildauszeichnung im Markup.",
technical_impact_en: "Non-conformant image markup.",
score_impact: ScoreImpact {
base_penalty: 3.0,
max_penalty: 10.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.media.alternative",
dimension: Dimension::Accessibility,
subcategory: Subcategory::ContentAlternatives,
issue_class: IssueClass::Missing,
severity: Severity::Medium,
external_ref: Some("WCAG 1.2.1"),
external_level: Some("A"),
axe_id: Some("video-caption"),
title: "Fehlende Alternativen für reine Audio- oder Videoinhalte",
title_en: "Missing alternatives for audio-only or video-only content",
description: "Reine Audio- oder Videoinhalte haben keine alternative Textdarstellung oder Tonspur.",
user_impact: "Menschen mit Seh- oder Hörbehinderung können den Inhalt der Medien nicht erfassen.",
user_impact_en: "People with visual or hearing impairments cannot access the media content.",
technical_impact: "Fehlende alternative Medienbeschreibung.",
technical_impact_en: "Missing alternative media description.",
score_impact: ScoreImpact {
base_penalty: 2.0,
max_penalty: 5.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.captions.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::ContentAlternatives,
issue_class: IssueClass::Missing,
severity: Severity::High,
external_ref: Some("WCAG 1.2.2"),
external_level: Some("A"),
axe_id: Some("video-caption"),
title: "Fehlende Untertitel für Videos",
title_en: "Missing captions for videos",
description: "Für aufgezeichnete Videoinhalte sind keine Untertitel vorhanden.",
user_impact: "Gehörlose oder schwerhörige Nutzer können gesprochene Inhalte nicht wahrnehmen.",
user_impact_en: "Deaf or hard-of-hearing users cannot perceive spoken content.",
technical_impact: "Fehlendes track-Element für Untertitel.",
technical_impact_en: "Missing track element for captions.",
score_impact: ScoreImpact {
base_penalty: 3.0,
max_penalty: 10.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.media_alternative.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::ContentAlternatives,
issue_class: IssueClass::Missing,
severity: Severity::Medium,
external_ref: Some("WCAG 1.2.8"),
external_level: Some("AAA"),
axe_id: Some("media-alt"),
title: "Fehlende Medienalternative (AAA)",
title_en: "Missing media alternative (AAA)",
description: "Für aufgezeichnete Videos oder Audios ist keine vollständige Medienalternative vorhanden.",
user_impact: "Nutzer, die keine Videos oder Audios abspielen können, erhalten keine Textalternative.",
user_impact_en: "Users who cannot play video or audio receive no text alternative.",
technical_impact: "Fehlendes Transkript oder Audiobeschreibung.",
technical_impact_en: "Missing transcript or audio description.",
score_impact: ScoreImpact {
base_penalty: 0.5,
max_penalty: 1.5,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.structure.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::StructureSemantics,
issue_class: IssueClass::Missing,
severity: Severity::High,
external_ref: Some("WCAG 1.3.1"),
external_level: Some("A"),
axe_id: None,
title: "Fehlende semantische Struktur",
title_en: "Missing semantic structure",
description: "Struktur ist visuell, aber nicht im HTML-Code hinterlegt.",
user_impact: "Screenreader können Beziehungen zwischen Inhalten nicht erkennen.",
user_impact_en: "Screen readers cannot detect relationships between content elements.",
technical_impact: "Fehlende HTML5-Semantik und ARIA-Rollen.",
technical_impact_en: "Missing HTML5 semantics and ARIA roles.",
score_impact: ScoreImpact {
base_penalty: 2.5,
max_penalty: 8.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.presentation_semantic_children.invalid",
dimension: Dimension::Accessibility,
subcategory: Subcategory::StructureSemantics,
issue_class: IssueClass::Invalid,
severity: Severity::Medium,
external_ref: Some("WCAG 1.3.1"),
external_level: Some("A"),
axe_id: Some("presentation-semantic-children"),
title: "Semantische Kinder in presentational Container",
title_en: "Semantic children inside presentational container",
description: "Ein Element mit role=\"presentation\" oder role=\"none\" enthält semantische Nachfahren.",
user_impact: "Screenreader können relevante Struktur oder Bedienobjekte verlieren.",
user_impact_en: "Screen readers may lose relevant structure or interactive controls.",
technical_impact: "role=\"presentation\"/\"none\" verdeckt semantische Kindelemente.",
technical_impact_en: "role=\"presentation\"/\"none\" hides semantic child elements.",
score_impact: ScoreImpact {
base_penalty: 1.5,
max_penalty: 4.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.headings.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::StructureSemantics,
issue_class: IssueClass::Missing,
severity: Severity::High,
external_ref: Some("WCAG 2.4.6"),
external_level: Some("AA"),
axe_id: Some("heading-order"),
title: "Fehlende oder unzureichende Überschriftenstruktur",
title_en: "Missing or insufficient heading structure",
description: "Keine oder unlogische Überschriftenhierarchie.",
user_impact: "Screenreader-Navigation per Überschrift nicht möglich.",
user_impact_en: "Heading-based screen reader navigation is not possible.",
technical_impact: "Fehlende Heading-Hierarchie im DOM.",
technical_impact_en: "Missing heading hierarchy in the DOM.",
score_impact: ScoreImpact {
base_penalty: 20.0,
max_penalty: 20.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.section_headings.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::StructureSemantics,
issue_class: IssueClass::Missing,
severity: Severity::Low,
external_ref: Some("WCAG 2.4.10"),
external_level: Some("AAA"),
axe_id: Some("heading-order"),
title: "Fehlende Abschnittsüberschriften",
title_en: "Missing section headings",
description: "Längere Inhalte sind nicht durch Abschnittsüberschriften gegliedert.",
user_impact: "Nutzer können Inhalte nicht gezielt ansteuern.",
user_impact_en: "Users cannot jump directly to specific content.",
technical_impact: "Fehlende Sub-Headings in langen Inhaltsblöcken.",
technical_impact_en: "Missing sub-headings in long content blocks.",
score_impact: ScoreImpact {
base_penalty: 1.0,
max_penalty: 3.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.orientation.restricted",
dimension: Dimension::Accessibility,
subcategory: Subcategory::StructureSemantics,
issue_class: IssueClass::Weak,
severity: Severity::High,
external_ref: Some("WCAG 1.3.4"),
external_level: Some("AA"),
axe_id: Some("css-orientation-lock"),
title: "Eingeschränkte Bildschirmausrichtung",
title_en: "Restricted screen orientation",
description: "Die Seite erzwingt eine bestimmte Bildschirmausrichtung (Hoch- oder Querformat).",
user_impact: "Nutzer mit fest montierten Bildschirmen (z. B. am Rollstuhl) können die Seite nicht lesen.",
user_impact_en: "Users with fixed-mount screens (e.g. wheelchair-mounted) cannot read the page.",
technical_impact: "CSS-Medienabfragen blockieren die Ausrichtung.",
technical_impact_en: "CSS media queries lock the orientation.",
score_impact: ScoreImpact {
base_penalty: 3.0,
max_penalty: 10.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.keyboard.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Missing,
severity: Severity::Critical,
external_ref: Some("WCAG 2.1.1"),
external_level: Some("A"),
axe_id: Some("keyboard"),
title: "Inhalte nicht per Tastatur bedienbar",
title_en: "Content not operable by keyboard",
description: "Funktionen nur mit Maus bedienbar.",
user_impact: "Tastaturnutzer sind von Funktionen ausgeschlossen.",
user_impact_en: "Keyboard users are excluded from functionality.",
technical_impact: "Click-Handler ohne Tastatur-Alternative.",
technical_impact_en: "Click handler without keyboard alternative.",
score_impact: ScoreImpact {
base_penalty: 5.0,
max_penalty: 15.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.keyboard_trap.risk",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Risk,
severity: Severity::Critical,
external_ref: Some("WCAG 2.1.2"),
external_level: Some("A"),
axe_id: Some("keyboard-trap"),
title: "Tastaturfalle — Fokus kann nicht verlassen werden",
title_en: "Keyboard trap — focus cannot leave the element",
description: "Tastaturfokus bleibt an bestimmten Stellen hängen.",
user_impact: "Seite wird faktisch unbenutzbar für Tastaturnutzer.",
user_impact_en: "The page becomes effectively unusable for keyboard users.",
technical_impact: "Fehlende Fokus-Verwaltung in modalen Elementen.",
technical_impact_en: "Missing focus management in modal elements.",
score_impact: ScoreImpact {
base_penalty: 10.0,
max_penalty: 15.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.bypass_blocks.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Missing,
severity: Severity::High,
external_ref: Some("WCAG 2.4.1"),
external_level: Some("A"),
axe_id: Some("bypass"),
title: "Fehlende Sprungnavigation",
title_en: "Missing bypass navigation",
description: "Kein Skip-Link oder Mechanismus zum Überspringen wiederkehrender Blöcke.",
user_impact: "Tastaturnutzer müssen bei jedem Seitenwechsel durch die Navigation tabben.",
user_impact_en: "Keyboard users must tab through the navigation on every page change.",
technical_impact: "Fehlender Skip-Link, fehlende HTML5-Landmarks.",
technical_impact_en: "Missing skip link, missing HTML5 landmarks.",
score_impact: ScoreImpact {
base_penalty: 3.0,
max_penalty: 8.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.page_title.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Missing,
severity: Severity::High,
external_ref: Some("WCAG 2.4.2"),
external_level: Some("A"),
axe_id: Some("document-title"),
title: "Fehlender oder unzureichender Seitentitel",
title_en: "Missing or insufficient page title",
description: "Seite hat keinen aussagekräftigen Title-Tag.",
user_impact: "Screenreader-Nutzer können die Seite nicht identifizieren.",
user_impact_en: "Screen reader users cannot identify the page.",
technical_impact: "Leeres oder generisches <title>-Tag.",
technical_impact_en: "Empty or generic <title> tag.",
score_impact: ScoreImpact {
base_penalty: 5.0,
max_penalty: 5.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.landmark_main.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Missing,
severity: Severity::High,
external_ref: Some("WCAG 1.3.1"),
external_level: Some("A"),
axe_id: Some("landmark-main-present"),
title: "Fehlender Hauptbereich",
title_en: "Missing main landmark",
description: "Die Seite hat kein <main>-Element oder role=\"main\".",
user_impact: "Screenreader- und Tastaturnutzer können nicht direkt zum Hauptinhalt springen.",
user_impact_en: "Screen reader and keyboard users cannot jump directly to the main content.",
technical_impact: "Fehlendes main-Landmark im DOM oder Accessibility Tree.",
technical_impact_en: "Missing main landmark in the DOM or accessibility tree.",
score_impact: ScoreImpact {
base_penalty: 3.0,
max_penalty: 8.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.landmark_unique.invalid",
dimension: Dimension::Accessibility,
subcategory: Subcategory::StructureSemantics,
issue_class: IssueClass::Invalid,
severity: Severity::Medium,
external_ref: Some("WCAG 1.3.1"),
external_level: Some("A"),
axe_id: Some("landmark-unique"),
title: "Landmarks nicht eindeutig benannt",
title_en: "Landmarks are not uniquely named",
description: "Mehrere Landmarks desselben Typs haben denselben zugänglichen Namen.",
user_impact: "Screenreader-Nutzer können gleichartige Bereiche nicht unterscheiden.",
user_impact_en: "Screen reader users cannot distinguish similar regions.",
technical_impact: "Mehrfach verwendete Landmark-Rollen ohne eindeutige aria-label/aria-labelledby-Werte.",
technical_impact_en: "Repeated landmark roles without unique aria-label/aria-labelledby values.",
score_impact: ScoreImpact {
base_penalty: 1.5,
max_penalty: 4.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.focus_order.weak",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Weak,
severity: Severity::High,
external_ref: Some("WCAG 2.4.3"),
external_level: Some("A"),
axe_id: Some("focus-order-semantics"),
title: "Unlogische Fokus-Reihenfolge",
title_en: "Illogical focus order",
description: "Tab-Reihenfolge entspricht nicht der visuellen Reihenfolge.",
user_impact: "Verwirrende Navigation für Tastaturnutzer.",
user_impact_en: "Confusing navigation for keyboard users.",
technical_impact: "DOM-Reihenfolge weicht von visueller Reihenfolge ab.",
technical_impact_en: "DOM order diverges from the visual order.",
score_impact: ScoreImpact {
base_penalty: 2.5,
max_penalty: 8.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.link_purpose.weak",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Weak,
severity: Severity::Medium,
external_ref: Some("WCAG 2.4.4"),
external_level: Some("A"),
axe_id: Some("link-name"),
title: "Unklare oder generische Linktexte",
title_en: "Unclear or generic link text",
description: "Links mit Texten wie 'hier', 'mehr', 'weiterlesen'.",
user_impact: "Screenreader-Linklisten zeigen nichtssagende Texte.",
user_impact_en: "Screen reader link lists show meaningless text.",
technical_impact: "Generische Link-Texte ohne Kontext.",
technical_impact_en: "Generic link text without context.",
score_impact: ScoreImpact {
base_penalty: 1.0,
max_penalty: 5.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.focus_visible.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Missing,
severity: Severity::High,
external_ref: Some("WCAG 2.4.7"),
external_level: Some("AA"),
axe_id: Some("focus-visible"),
title: "Fokus-Indikator nicht sichtbar",
title_en: "Focus indicator not visible",
description: "Kein visueller Fokus-Rahmen bei Tastaturnavigation.",
user_impact: "Tastaturnutzer sehen nicht, welches Element fokussiert ist.",
user_impact_en: "Keyboard users cannot see which element is focused.",
technical_impact: "CSS outline:none ohne Alternative.",
technical_impact_en: "CSS outline:none without an alternative.",
score_impact: ScoreImpact {
base_penalty: 3.0,
max_penalty: 8.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.label_in_name.invalid",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Invalid,
severity: Severity::High,
external_ref: Some("WCAG 2.5.3"),
external_level: Some("A"),
axe_id: Some("label-in-name"),
title: "Sichtbarer Text ≠ zugänglicher Name",
title_en: "Visible text ≠ accessible name",
description: "Sichtbarer Text und aria-label stimmen nicht überein.",
user_impact: "Sprachsteuerungsnutzer können Element nicht ansprechen.",
user_impact_en: "Voice-control users cannot address the element.",
technical_impact: "aria-label weicht vom sichtbaren Text ab.",
technical_impact_en: "aria-label diverges from the visible text.",
score_impact: ScoreImpact {
base_penalty: 2.5,
max_penalty: 8.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.timing.unadjustable",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Weak,
severity: Severity::High,
external_ref: Some("WCAG 2.2.1"),
external_level: Some("A"),
axe_id: Some("meta-refresh"),
title: "Nicht anpassbare Zeitbegrenzung",
title_en: "Non-adjustable time limit",
description: "Zeitbegrenzungen auf der Seite können nicht deaktiviert oder verlängert werden.",
user_impact: "Nutzer benötigen mehr Zeit für die Eingabe und können durch Timeouts Daten verlieren.",
user_impact_en: "Users need more time to enter data and may lose data through timeouts.",
technical_impact: "Fehlendes Refresh- oder Timeout-Handling im Script/Markup.",
technical_impact_en: "Missing refresh or timeout handling in the script/markup.",
score_impact: ScoreImpact {
base_penalty: 3.0,
max_penalty: 10.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.timing.essential",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Risk,
severity: Severity::Medium,
external_ref: Some("WCAG 2.2.3"),
external_level: Some("AAA"),
axe_id: Some("no-timing"),
title: "Zeitbegrenzung vorhanden (AAA)",
title_en: "Timing present (AAA)",
description: "Die Seite verwendet essenzielle Zeitbegrenzungen ohne Möglichkeit der Deaktivierung.",
user_impact: "Nutzer können Aufgaben nicht in ihrem eigenen Tempo abschließen.",
user_impact_en: "Users cannot complete tasks at their own pace.",
technical_impact: "Aktive Zeitbegrenzungen auf der Seite.",
technical_impact_en: "Active time limits on the page.",
score_impact: ScoreImpact {
base_penalty: 0.5,
max_penalty: 1.5,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.interruptions.uncontrollable",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Risk,
severity: Severity::Low,
external_ref: Some("WCAG 2.2.4"),
external_level: Some("AAA"),
axe_id: Some("no-interruptions"),
title: "Nicht unterdrückbare Unterbrechungen (AAA)",
title_en: "Non-suppressible interruptions (AAA)",
description: "Die Seite zeigt Warnungen oder Popups, die der Nutzer nicht verschieben kann.",
user_impact: "Nutzer mit kognitiven Einschränkungen oder Screenreader-Nutzer werden abgelenkt.",
user_impact_en: "Users with cognitive impairments or screen reader users are distracted.",
technical_impact: "Unerwartete Dialoge oder assertive Live-Regionen.",
technical_impact_en: "Unexpected dialogs or assertive live regions.",
score_impact: ScoreImpact {
base_penalty: 0.5,
max_penalty: 1.5,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.re_authenticate.data_loss",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Risk,
severity: Severity::Low,
external_ref: Some("WCAG 2.2.5"),
external_level: Some("AAA"),
axe_id: Some("re-authenticate"),
title: "Keine Datenspeicherung bei Reauthentifizierung (AAA)",
title_en: "No data preservation on re-authentication (AAA)",
description: "Eingegebene Daten gehen verloren, wenn eine Session abläuft und sich der Nutzer neu anmeldet.",
user_impact: "Nutzer müssen alle Formulardaten erneut eingeben.",
user_impact_en: "Users must re-enter all form data.",
technical_impact: "Fehlende Session-Datensicherung.",
technical_impact_en: "Missing session data preservation.",
score_impact: ScoreImpact {
base_penalty: 0.5,
max_penalty: 1.5,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.timeouts.unwarned",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Risk,
severity: Severity::Medium,
external_ref: Some("WCAG 2.2.6"),
external_level: Some("AAA"),
axe_id: Some("timeouts"),
title: "Keine Warnung vor Inaktivitäts-Timeout (AAA)",
title_en: "No warning before inactivity timeout (AAA)",
description: "Der Nutzer wird vor einem Timeout nicht über drohenden Datenverlust gewarnt.",
user_impact: "Nutzer verlieren ungespeicherte Daten ohne Vorwarnung.",
user_impact_en: "Users lose unsaved data without warning.",
technical_impact: "Fehlendes Timeout-Warnsystem.",
technical_impact_en: "Missing timeout warning system.",
score_impact: ScoreImpact {
base_penalty: 0.5,
max_penalty: 1.5,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.location.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Missing,
severity: Severity::Low,
external_ref: Some("WCAG 2.4.8"),
external_level: Some("AAA"),
axe_id: Some("location"),
title: "Fehlende Positionsangabe (AAA)",
title_en: "Missing location indication (AAA)",
description: "Es gibt keine Orientierungshilfe (wie Breadcrumbs), um den aktuellen Ort in einem Webauftritt anzuzeigen.",
user_impact: "Nutzer verlieren die Orientierung auf verschachtelten Webseiten.",
user_impact_en: "Users lose orientation within nested websites.",
technical_impact: "Fehlende Breadcrumb-Navigation oder Sitemap-Verbindung.",
technical_impact_en: "Missing breadcrumb navigation or sitemap linkage.",
score_impact: ScoreImpact {
base_penalty: 0.5,
max_penalty: 1.5,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.link_purpose_only.weak",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Weak,
severity: Severity::Medium,
external_ref: Some("WCAG 2.4.9"),
external_level: Some("AAA"),
axe_id: Some("link-name"),
title: "Unklare Linkbeschriftung ohne Kontext (AAA)",
title_en: "Unclear link label without context (AAA)",
description: "Der Zweck eines Links geht nicht allein aus dem Linktext hervor.",
user_impact: "Nutzer müssen den gesamten Absatz lesen, um das Ziel eines Links zu verstehen.",
user_impact_en: "Users must read the entire paragraph to understand a link's destination.",
technical_impact: "Generischer Linktext wie 'Mehr lesen' oder 'Hier klicken'.",
technical_impact_en: "Generic link text such as 'read more' or 'click here'.",
score_impact: ScoreImpact {
base_penalty: 0.5,
max_penalty: 1.5,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.pointer_gestures.missing_alternative",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Missing,
severity: Severity::Medium,
external_ref: Some("WCAG 2.5.1"),
external_level: Some("AAA"),
axe_id: Some("pointer-gestures"),
title: "Fehlende Alternative für Zeigergesten (AAA)",
title_en: "Missing alternative for pointer gestures (AAA)",
description: "Funktionen erfordern komplexe Mehrfinger- oder Pfadgesten ohne einfache Klick-Alternative.",
user_impact: "Nutzer mit motorischen Einschränkungen können die Funktionen nicht bedienen.",
user_impact_en: "Users with motor impairments cannot operate the functionality.",
technical_impact: "Erforderliche Touch-Gesten ohne Klick-Fallback.",
technical_impact_en: "Required touch gestures without a click fallback.",
score_impact: ScoreImpact {
base_penalty: 0.5,
max_penalty: 1.5,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.pointer_cancellation.invalid",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Invalid,
severity: Severity::Medium,
external_ref: Some("WCAG 2.5.2"),
external_level: Some("AAA"),
axe_id: Some("pointer-cancellation"),
title: "Auslösung von Aktionen bei Zeigerberührung (AAA)",
title_en: "Action triggered on pointer down (AAA)",
description: "Aktionen werden sofort beim Drücken (down-event) statt beim Loslassen (up-event) ausgeführt.",
user_impact: "Nutzer können versehentlich Aktionen auslösen, ohne diese abbrechen zu können.",
user_impact_en: "Users may trigger actions accidentally without being able to cancel them.",
technical_impact: "Event-Handler reagieren auf mousedown oder touchstart statt click oder mouseup.",
technical_impact_en: "Event handlers respond to mousedown or touchstart instead of click or mouseup.",
score_impact: ScoreImpact {
base_penalty: 0.5,
max_penalty: 1.5,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.motion_actuation.missing_alternative",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Missing,
severity: Severity::Medium,
external_ref: Some("WCAG 2.5.4"),
external_level: Some("AAA"),
axe_id: Some("motion-actuation"),
title: "Fehlende Alternative für Bewegungseingabe (AAA)",
title_en: "Missing alternative for motion actuation (AAA)",
description: "Aktionen, die durch Schütteln oder Bewegen des Geräts ausgelöst werden, haben keine klassische Bedienalternative.",
user_impact: "Nutzer, die das Gerät nicht bewegen können, sind von den Funktionen ausgeschlossen.",
user_impact_en: "Users who cannot move the device are excluded from the functionality.",
technical_impact: "Nutzen von Device-Motion-APIs ohne UI-Bedienelemente.",
technical_impact_en: "Use of device-motion APIs without UI controls.",
score_impact: ScoreImpact {
base_penalty: 0.5,
max_penalty: 1.5,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.target_size_enhanced.small",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Weak,
severity: Severity::Medium,
external_ref: Some("WCAG 2.5.5"),
external_level: Some("AAA"),
axe_id: Some("target-size"),
title: "Unzureichende Klickzielgröße (AAA)",
title_en: "Insufficient click target size (AAA)",
description: "Bedienelemente haben eine Klickfläche von weniger als 44x44 CSS-Pixeln.",
user_impact: "Nutzer mit zittrigen Händen oder auf Mobilgeräten klicken leicht auf falsche Elemente.",
user_impact_en: "Users with hand tremors or on mobile devices easily tap the wrong elements.",
technical_impact: "Zu kleine Abmessungen von Links, Buttons oder Formularelementen im CSS.",
technical_impact_en: "Links, buttons or form controls dimensioned too small in CSS.",
score_impact: ScoreImpact {
base_penalty: 0.5,
max_penalty: 1.5,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.input_purpose.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::FormsInteraction,
issue_class: IssueClass::Missing,
severity: Severity::Medium,
external_ref: Some("WCAG 1.3.5"),
external_level: Some("AA"),
axe_id: Some("autocomplete-valid"),
title: "Fehlende Eingabezweck-Kennzeichnung",
title_en: "Missing input purpose identification",
description: "Formularfelder ohne autocomplete-Attribut.",
user_impact: "Kein Autofill möglich, Eingabe fehleranfälliger.",
user_impact_en: "No autofill possible, data entry more error-prone.",
technical_impact: "Fehlende autocomplete-Attribute.",
technical_impact_en: "Missing autocomplete attributes.",
score_impact: ScoreImpact {
base_penalty: 1.0,
max_penalty: 5.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.form_labels.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::FormsInteraction,
issue_class: IssueClass::Missing,
severity: Severity::High,
external_ref: Some("WCAG 3.3.2"),
external_level: Some("A"),
axe_id: Some("label"),
title: "Fehlende Formularbeschriftungen",
title_en: "Missing form labels",
description: "Formularfelder ohne sichtbare Labels.",
user_impact: "Nutzer wissen nicht, welche Eingaben erwartet werden.",
user_impact_en: "Users do not know what input is expected.",
technical_impact: "Fehlende <label>-Elemente.",
technical_impact_en: "Missing <label> elements.",
score_impact: ScoreImpact {
base_penalty: 2.5,
max_penalty: 8.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.form_no_submit.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::FormsInteraction,
issue_class: IssueClass::Missing,
severity: Severity::Medium,
external_ref: Some("WCAG 3.2.2"),
external_level: Some("A"),
axe_id: Some("form-no-submit"),
title: "Formular ohne Absende-Button",
title_en: "Form without submit button",
description: "Formular mit Eingabefeldern hat keinen expliziten Submit-Button.",
user_impact: "Tastatur- und Screenreader-Nutzer erkennen den Abschluss der Eingabe schwerer.",
user_impact_en: "Keyboard and screen reader users have a harder time completing the form.",
technical_impact: "Fehlendes Submit-Control im Formular.",
technical_impact_en: "Missing submit control in the form.",
score_impact: ScoreImpact {
base_penalty: 1.5,
max_penalty: 4.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.error_id.missing_description",
dimension: Dimension::Accessibility,
subcategory: Subcategory::FormsInteraction,
issue_class: IssueClass::Missing,
severity: Severity::Medium,
external_ref: Some("WCAG 3.3.1"),
external_level: Some("A"),
axe_id: Some("aria-invalid-without-describedby"),
title: "Unzureichende Fehlermeldung bei Eingabefehlern",
title_en: "Insufficient error message on input errors",
description: "Fehlerhafte Formulareingaben werden nicht präzise identifiziert oder beschrieben.",
user_impact: "Nutzer können Fehler im Formular nicht korrigieren, weil sie nicht wissen, was falsch ist.",
user_impact_en: "Users cannot correct form errors because they do not know what is wrong.",
technical_impact: "Fehlendes aria-invalid-Attribut oder fehlerhaft verknüpfte aria-describedby-IDs.",
technical_impact_en: "Missing aria-invalid attribute or incorrectly linked aria-describedby IDs.",
score_impact: ScoreImpact {
base_penalty: 2.0,
max_penalty: 5.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.help.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::FormsInteraction,
issue_class: IssueClass::Missing,
severity: Severity::Low,
external_ref: Some("WCAG 3.3.5"),
external_level: Some("AAA"),
axe_id: Some("help"),
title: "Fehlende Ausfüllhilfe (AAA)",
title_en: "Missing input help (AAA)",
description: "Für Eingabefelder gibt es keine kontextsensitive Ausfüllhilfe.",
user_impact: "Nutzer wissen nicht, welches Format oder welche Daten in ein Feld eingegeben werden müssen.",
user_impact_en: "Users do not know what format or data to enter in a field.",
technical_impact: "Fehlendes Formular-Hilfesystem.",
technical_impact_en: "Missing form help system.",
score_impact: ScoreImpact {
base_penalty: 0.5,
max_penalty: 1.5,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.language.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::LanguageClarity,
issue_class: IssueClass::Missing,
severity: Severity::High,
external_ref: Some("WCAG 3.1.1"),
external_level: Some("A"),
axe_id: Some("html-has-lang"),
title: "Fehlende Sprachangabe",
title_en: "Missing language declaration",
description: "Kein lang-Attribut im HTML-Tag.",
user_impact: "Screenreader liest mit falscher Aussprache vor.",
user_impact_en: "Screen readers read aloud with incorrect pronunciation.",
technical_impact: "Fehlendes lang-Attribut.",
technical_impact_en: "Missing lang attribute.",
score_impact: ScoreImpact {
base_penalty: 10.0,
max_penalty: 12.5,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.on_focus.risk",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Risk,
severity: Severity::High,
external_ref: Some("WCAG 3.2.1"),
external_level: Some("A"),
axe_id: Some("focus-no-context-change"),
title: "Unerwartete Kontextänderung bei Fokus",
title_en: "Unexpected context change on focus",
description: "Fokuserhalt löst unerwartete Kontextänderung aus.",
user_impact: "Verwirrende Seitenänderungen für Tastaturnutzer.",
user_impact_en: "Confusing page changes for keyboard users.",
technical_impact: "onfocus-Handler lösen Navigation aus.",
technical_impact_en: "onfocus handlers trigger navigation.",
score_impact: ScoreImpact {
base_penalty: 3.0,
max_penalty: 8.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.on_input.risk",
dimension: Dimension::Accessibility,
subcategory: Subcategory::NavigationInteraction,
issue_class: IssueClass::Risk,
severity: Severity::Medium,
external_ref: Some("WCAG 3.2.2"),
external_level: Some("A"),
axe_id: Some("input-no-context-change"),
title: "Unerwartete Kontextänderung bei Eingabe",
title_en: "Unexpected context change on input",
description: "Formularänderung löst unangekündigte Kontextänderung aus.",
user_impact: "Verwirrende Änderungen beim Ausfüllen.",
user_impact_en: "Confusing changes while filling out forms.",
technical_impact: "Auto-submit bei onchange.",
technical_impact_en: "Auto-submit on onchange.",
score_impact: ScoreImpact {
base_penalty: 2.0,
max_penalty: 5.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.unusual_words.missing_definition",
dimension: Dimension::Accessibility,
subcategory: Subcategory::LanguageClarity,
issue_class: IssueClass::Missing,
severity: Severity::Low,
external_ref: Some("WCAG 3.1.3"),
external_level: Some("AAA"),
axe_id: Some("unusual-words"),
title: "Fehlende Erklärung unüblicher Wörter (AAA)",
title_en: "Missing explanation of unusual words (AAA)",
description: "Für ungewöhnliche Wörter, Jargon oder Redewendungen gibt es keine Definitionen.",
user_impact: "Nutzer mit kognitiven Einschränkungen oder Fremdsprachler verstehen den Text nicht.",
user_impact_en: "Users with cognitive impairments or non-native speakers do not understand the text.",
technical_impact: "Fehlende Glossar-Verlinkung oder Erklärung im Text.",
technical_impact_en: "Missing glossary linkage or in-text explanation.",
score_impact: ScoreImpact {
base_penalty: 0.5,
max_penalty: 1.5,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.abbreviations.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::LanguageClarity,
issue_class: IssueClass::Missing,
severity: Severity::Low,
external_ref: Some("WCAG 3.1.4"),
external_level: Some("AAA"),
axe_id: Some("abbreviations"),
title: "Fehlende Erklärung von Abkürzungen (AAA)",
title_en: "Missing explanation of abbreviations (AAA)",
description: "Abkürzungen werden nicht bei ihrer ersten Verwendung oder über ein Mechanismus erklärt.",
user_impact: "Nutzer verstehen den Inhalt nicht, wenn sie die Abkürzung nicht kennen.",
user_impact_en: "Users do not understand the content if they do not know the abbreviation.",
technical_impact: "Fehlendes abbr-Tag mit title-Attribut.",
technical_impact_en: "Missing abbr tag with title attribute.",
score_impact: ScoreImpact {
base_penalty: 0.5,
max_penalty: 1.5,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.contrast.weak",
dimension: Dimension::Accessibility,
subcategory: Subcategory::VisualPresentation,
issue_class: IssueClass::Weak,
severity: Severity::High,
external_ref: Some("WCAG 1.4.3"),
external_level: Some("AA"),
axe_id: Some("color-contrast"),
title: "Unzureichender Farbkontrast",
title_en: "Insufficient color contrast",
description: "Text hat nicht genügend Kontrast zum Hintergrund.",
user_impact: "Text bei Sehschwäche oder ungünstigen Lichtverhältnissen schwer lesbar.",
user_impact_en: "Text is hard to read for people with low vision or under poor lighting conditions.",
technical_impact: "Kontrastverhältnis unter 4.5:1 für normalen Text.",
technical_impact_en: "Contrast ratio below 4.5:1 for normal text.",
score_impact: ScoreImpact {
base_penalty: 5.0,
max_penalty: 12.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.resize_text.weak",
dimension: Dimension::Accessibility,
subcategory: Subcategory::VisualPresentation,
issue_class: IssueClass::Weak,
severity: Severity::High,
external_ref: Some("WCAG 1.4.4"),
external_level: Some("AA"),
axe_id: Some("meta-viewport"),
title: "Text nicht ausreichend skalierbar",
title_en: "Text not sufficiently resizable",
description: "Texte können nicht auf 200% vergrößert werden.",
user_impact: "Nutzer mit Sehschwäche verlieren Inhalte bei Vergrößerung.",
user_impact_en: "Users with low vision lose content when zooming in.",
technical_impact: "Feste Pixelwerte, overflow:hidden.",
technical_impact_en: "Fixed pixel values, overflow:hidden.",
score_impact: ScoreImpact {
base_penalty: 2.5,
max_penalty: 8.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.reflow.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::VisualPresentation,
issue_class: IssueClass::Missing,
severity: Severity::High,
external_ref: Some("WCAG 1.4.10"),
external_level: Some("AA"),
axe_id: Some("css-overflow-hidden"),
title: "Kein Reflow bei 320 CSS-Pixeln",
title_en: "No reflow at 320 CSS pixels",
description: "Seite erfordert horizontales Scrollen bei einer Ansichtsbreite von 320 CSS-Pixeln.",
user_impact: "Nutzer, die auf 400 % zoomen, müssen in zwei Richtungen scrollen — Inhalte werden unzugänglich.",
user_impact_en: "Users zooming to 400% must scroll in two directions — content becomes inaccessible.",
technical_impact: "Feste Breiten oder overflow:hidden verhindern responsives Reflow.",
technical_impact_en: "Fixed widths or overflow:hidden prevent responsive reflow.",
score_impact: ScoreImpact {
base_penalty: 3.0,
max_penalty: 10.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.non_text_contrast.weak",
dimension: Dimension::Accessibility,
subcategory: Subcategory::VisualPresentation,
issue_class: IssueClass::Weak,
severity: Severity::Medium,
external_ref: Some("WCAG 1.4.11"),
external_level: Some("AA"),
axe_id: Some("non-text-contrast"),
title: "Unzureichender Kontrast bei UI-Elementen",
title_en: "Insufficient contrast for UI elements",
description: "Bedienelemente haben nicht genügend Kontrast.",
user_impact: "Interaktive Elemente schwer erkennbar.",
user_impact_en: "Interactive elements are hard to recognize.",
technical_impact: "Kontrastverhältnis UI-Elemente unter 3:1.",
technical_impact_en: "Contrast ratio of UI elements below 3:1.",
score_impact: ScoreImpact {
base_penalty: 1.5,
max_penalty: 5.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.color.link_indicator",
dimension: Dimension::Accessibility,
subcategory: Subcategory::VisualPresentation,
issue_class: IssueClass::Weak,
severity: Severity::Medium,
external_ref: Some("WCAG 1.4.1"),
external_level: Some("A"),
axe_id: Some("link-in-text-block"),
title: "Link nur durch Farbe unterscheidbar",
title_en: "Link distinguishable by color only",
description: "Inline-Link hebt sich vom Fließtext nur durch Farbe ab, ohne weitere visuelle Unterscheidung.",
user_impact: "Nutzer mit Farbsehschwäche können Links im Text nicht erkennen.",
user_impact_en: "Users with color vision deficiency cannot recognize in-text links.",
technical_impact: "Kein Unterstrich, Fettschrift oder anderes nicht-farbliches Merkmal vorhanden.",
technical_impact_en: "No underline, bold weight or other non-color cue present.",
score_impact: ScoreImpact {
base_penalty: 3.0,
max_penalty: 8.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.motion.reduced_motion",
dimension: Dimension::Accessibility,
subcategory: Subcategory::VisualPresentation,
issue_class: IssueClass::Missing,
severity: Severity::Low,
external_ref: Some("WCAG 2.3.3"),
external_level: Some("AAA"),
axe_id: Some("prefers-reduced-motion"),
title: "Animationen ohne prefers-reduced-motion",
title_en: "Animations without prefers-reduced-motion",
description: "Seite definiert Animationen oder Übergänge, berücksichtigt aber nicht das Systemsetting prefers-reduced-motion.",
user_impact: "Nutzer mit vestibulären Störungen oder Photosensitivität können Bewegung nicht deaktivieren.",
user_impact_en: "Users with vestibular disorders or photosensitivity cannot disable motion.",
technical_impact: "Kein @media (prefers-reduced-motion) in den Stylesheets.",
technical_impact_en: "No @media (prefers-reduced-motion) in the stylesheets.",
score_impact: ScoreImpact {
base_penalty: 1.0,
max_penalty: 4.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.hover.content_visibility",
dimension: Dimension::Accessibility,
subcategory: Subcategory::VisualPresentation,
issue_class: IssueClass::Weak,
severity: Severity::Medium,
external_ref: Some("WCAG 1.4.13"),
external_level: Some("AA"),
axe_id: Some("content-on-hover-focus"),
title: "Unzugängliche Hover- oder Fokus-Inhalte (Tooltips)",
title_en: "Inaccessible hover or focus content (tooltips)",
description: "Zusatzinhalte bei Hover/Fokus (Tooltips) sind unzugänglich, nicht steuerbar oder unvollständig verknüpft.",
user_impact: "Nutzer können Tooltip-Inhalte nicht lesen, diese nicht per Tastatur schließen, oder sie werden von Screenreadern ignoriert.",
user_impact_en: "Users cannot read tooltip content, cannot dismiss it via keyboard, or it is ignored by screen readers.",
technical_impact: "Nutzung des nativen title-Attributs als einzige Beschriftung, unvollständige ARIA-Verknüpfung (aria-describedby) oder fehlende Steuerungsmöglichkeiten.",
technical_impact_en: "Use of the native title attribute as the only label, incomplete ARIA linkage (aria-describedby), or missing controls.",
score_impact: ScoreImpact {
base_penalty: 2.0,
max_penalty: 6.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.background_audio.uncontrolled",
dimension: Dimension::Accessibility,
subcategory: Subcategory::VisualPresentation,
issue_class: IssueClass::Risk,
severity: Severity::Medium,
external_ref: Some("WCAG 1.4.7"),
external_level: Some("AAA"),
axe_id: Some("background-audio"),
title: "Nicht regulierbare Hintergrundgeräusche (AAA)",
title_en: "Uncontrollable background audio (AAA)",
description: "Hintergrundgeräusche können nicht abgeschaltet oder leiser gestellt werden.",
user_impact: "Nutzer mit Hörgeräten oder kognitiven Einschränkungen werden stark abgelenkt.",
user_impact_en: "Users with hearing aids or cognitive impairments are strongly distracted.",
technical_impact: "Automatisch abspielendes Audio ohne Steuerungselemente.",
technical_impact_en: "Auto-playing audio without controls.",
score_impact: ScoreImpact {
base_penalty: 0.5,
max_penalty: 1.5,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.visual_presentation.weak",
dimension: Dimension::Accessibility,
subcategory: Subcategory::VisualPresentation,
issue_class: IssueClass::Weak,
severity: Severity::Low,
external_ref: Some("WCAG 1.4.8"),
external_level: Some("AAA"),
axe_id: Some("visual-presentation"),
title: "Eingeschränkte visuelle Darstellung von Text (AAA)",
title_en: "Limited visual presentation of text (AAA)",
description: "Der Text hat unzureichenden Zeilenabstand oder ist im Blocksatz formatiert.",
user_impact: "Nutzer mit Sehbehinderungen oder Legasthenie haben Probleme beim Lesen.",
user_impact_en: "Users with visual impairments or dyslexia have difficulty reading.",
technical_impact: "Unzureichendes CSS-Styling für Zeilenabstand oder Blocksatz-Formatierung.",
technical_impact_en: "Insufficient CSS styling for line spacing or justified-text formatting.",
score_impact: ScoreImpact {
base_penalty: 0.5,
max_penalty: 1.5,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.aria_hidden_focus.invalid",
dimension: Dimension::Accessibility,
subcategory: Subcategory::TechnicalRobustness,
issue_class: IssueClass::Invalid,
severity: Severity::High,
external_ref: Some("WCAG 4.1.2"),
external_level: Some("A"),
axe_id: Some("aria-hidden-focus"),
title: "Fokussierbares Element versteckt vor Screenreadern",
title_en: "Focusable element hidden from screen readers",
description: "Interaktives Element ist über aria-hidden=\"true\" vor AT versteckt, aber per Tastatur erreichbar.",
user_impact: "Tastaturnutzer erreichen Elemente, die Screenreader nicht ankündigen — unsichtbare Stolperfallen.",
user_impact_en: "Keyboard users reach elements that screen readers do not announce — invisible traps.",
technical_impact: "aria-hidden=\"true\" auf fokussierbarem Element oder seinem Vorfahren.",
technical_impact_en: "aria-hidden=\"true\" on a focusable element or its ancestor.",
score_impact: ScoreImpact {
base_penalty: 6.0,
max_penalty: 15.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.status_messages.broken",
dimension: Dimension::Accessibility,
subcategory: Subcategory::TechnicalRobustness,
issue_class: IssueClass::Invalid,
severity: Severity::Medium,
external_ref: Some("WCAG 4.1.3"),
external_level: Some("AA"),
axe_id: Some("aria-live-region-role"),
title: "Statusmeldungen nicht programmatisch bestimmbar",
title_en: "Status messages not programmatically determinable",
description: "ARIA-Live-Regionen haben eine inkonsistente Kombination aus role und aria-live.",
user_impact: "Screenreader kündigt Statusmeldungen nicht korrekt an.",
user_impact_en: "Screen readers do not announce status messages correctly.",
technical_impact: "Konflikt zwischen role (alert/status/log/timer) und aria-live-Wert.",
technical_impact_en: "Conflict between role (alert/status/log/timer) and aria-live value.",
score_impact: ScoreImpact {
base_penalty: 3.0,
max_penalty: 8.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.identify_purpose.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::TechnicalRobustness,
issue_class: IssueClass::Missing,
severity: Severity::Low,
external_ref: Some("WCAG 1.3.6"),
external_level: Some("AAA"),
axe_id: None,
title: "Landmark ohne unterscheidbaren Namen",
title_en: "Landmark without distinguishable name",
description: "Mehrere gleichartige Landmarks ohne aria-label zur Unterscheidung.",
user_impact: "Screenreader-Nutzer können identische Landmarks nicht voneinander unterscheiden.",
user_impact_en: "Screen reader users cannot tell identical landmarks apart.",
technical_impact: "Fehlende aria-label/aria-labelledby auf doppelten Landmarks.",
technical_impact_en: "Missing aria-label/aria-labelledby on duplicate landmarks.",
score_impact: ScoreImpact {
base_penalty: 1.0,
max_penalty: 4.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "a11y.aria_required_attr.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::TechnicalRobustness,
issue_class: IssueClass::Missing,
severity: Severity::Critical,
external_ref: Some("WCAG 4.1.2"),
external_level: Some("A"),
axe_id: Some("aria-required-attr"),
title: "Pflicht-ARIA-Attribut fehlt",
title_en: "Required ARIA attribute missing",
description:
"Ein Element mit ARIA-Rolle besitzt nicht alle fuer diese Rolle erforderlichen Attribute.",
user_impact: "Assistive Technologien koennen Zustand oder Wert des Elements nicht verlaesslich vermitteln.",
user_impact_en:
"Assistive technologies cannot reliably expose the element state or value.",
technical_impact: "Rolle und ARIA-Attributsatz sind unvollstaendig.",
technical_impact_en: "Role and ARIA attribute set are incomplete.",
score_impact: ScoreImpact {
base_penalty: 6.0,
max_penalty: 15.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.name_role.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::TechnicalRobustness,
issue_class: IssueClass::Missing,
severity: Severity::High,
external_ref: Some("WCAG 4.1.2"),
external_level: Some("A"),
axe_id: None,
title: "Fehlende Name/Rolle bei Bedienelementen",
title_en: "Missing name/role on controls",
description: "Interaktive Elemente ohne zugänglichen Namen oder erkennbare Rolle.",
user_impact: "Screenreader kann Funktion nicht vermitteln.",
user_impact_en: "Screen readers cannot convey the function.",
technical_impact: "Fehlende ARIA-Labels und -Rollen.",
technical_impact_en: "Missing ARIA labels and roles.",
score_impact: ScoreImpact {
base_penalty: 5.0,
max_penalty: 12.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.modern_attributes.invalid",
dimension: Dimension::Accessibility,
subcategory: Subcategory::TechnicalRobustness,
issue_class: IssueClass::Invalid,
severity: Severity::High,
external_ref: Some("WCAG 4.1.2"),
external_level: Some("A"),
axe_id: Some("modern-attribute-misuse"),
title: "Ungültige moderne Interaktionsattribute",
title_en: "Invalid modern interaction attributes",
description: "popover-, inert- oder Dialog-Attribute erzeugen eine nicht programmatisch bestimmbare Interaktion.",
user_impact: "Screenreader- und Tastaturnutzer erreichen Dialoge, Popover oder gesperrte Bereiche nicht zuverlässig.",
user_impact_en: "Screen reader and keyboard users cannot reliably access dialogs, popovers, or disabled regions.",
technical_impact: "Defekte popovertarget-Bezüge, unbenannte Oberflächen oder aktiver Fokus innerhalb inert markierter Bereiche.",
technical_impact_en: "Broken popovertarget references, unnamed surfaces, or active focus inside inert regions.",
score_impact: ScoreImpact {
base_penalty: 4.0,
max_penalty: 10.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.aria_prohibited_attr.invalid",
dimension: Dimension::Accessibility,
subcategory: Subcategory::TechnicalRobustness,
issue_class: IssueClass::Invalid,
severity: Severity::High,
external_ref: Some("WCAG 4.1.2"),
external_level: Some("A"),
axe_id: Some("aria-prohibited-attr"),
title: "Verbotene ARIA-Attribute",
title_en: "Prohibited ARIA attributes",
description: "ARIA-Attribute werden auf einer Rolle verwendet, die sie nicht erlaubt.",
user_impact: "Screenreader erhalten widersprüchliche oder ungültige Semantik.",
user_impact_en: "Screen readers receive contradictory or invalid semantics.",
technical_impact: "ARIA-Attribute passen nicht zur impliziten oder expliziten Rolle.",
technical_impact_en: "ARIA attributes do not match the implicit or explicit role.",
score_impact: ScoreImpact {
base_penalty: 4.0,
max_penalty: 10.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.frame_title.missing",
dimension: Dimension::Accessibility,
subcategory: Subcategory::ContentAlternatives,
issue_class: IssueClass::Missing,
severity: Severity::High,
external_ref: Some("WCAG 2.4.1"),
external_level: Some("A"),
axe_id: Some("frame-title"),
title: "Iframe ohne zugänglichen Namen",
title_en: "Iframe without accessible name",
description: "Iframe oder Frame hat keinen title, aria-label oder aria-labelledby.",
user_impact: "Screenreader-Nutzer erkennen Zweck und Inhalt des eingebetteten Frames nicht.",
user_impact_en: "Screen reader users cannot identify the purpose and content of the embedded frame.",
technical_impact: "Fehlender zugänglicher Name auf iframe/frame.",
technical_impact_en: "Missing accessible name on iframe/frame.",
score_impact: ScoreImpact {
base_penalty: 4.0,
max_penalty: 10.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.frame_tested.cross_origin",
dimension: Dimension::Accessibility,
subcategory: Subcategory::TechnicalRobustness,
issue_class: IssueClass::Informational,
severity: Severity::Medium,
external_ref: Some("WCAG 4.1.2"),
external_level: Some("A"),
axe_id: Some("frame-tested"),
title: "Cross-origin Iframe: manuell prüfen",
title_en: "Cross-origin iframe: review manually",
description: "Cross-origin iframes können nicht automatisch auf Barrierefreiheit geprüft werden.",
user_impact: "Eingebettete Inhalte (Karten, Videos, Consent-Widgets) sind möglicherweise für Screenreader-Nutzer unzugänglich.",
user_impact_en: "Embedded content (maps, videos, consent widgets) may be inaccessible to screen reader users.",
technical_impact: "Iframe-Inhalt ist über CDP nicht erreichbar — automatische Regelprüfung nicht möglich.",
technical_impact_en: "Iframe content is unreachable via CDP — automatic rule checking is not possible.",
score_impact: ScoreImpact {
base_penalty: 0.0,
max_penalty: 0.0,
occurrence_scaling: Scaling::Linear,
},
report_visibility: VIS_ALL,
},
Rule {
id: "a11y.parsing.invalid",
dimension: Dimension::Accessibility,
subcategory: Subcategory::TechnicalRobustness,
issue_class: IssueClass::Invalid,
severity: Severity::Critical,
external_ref: Some("WCAG 4.1.1"),
external_level: Some("A"),
axe_id: Some("duplicate-id"),
title: "Fehlerhaftes Markup (Parsing)",
title_en: "Malformed markup (parsing)",
description: "HTML-Code weist schwerwiegende Syntaxfehler auf, wie z. B. doppelte IDs.",
user_impact: "Screenreader können den AX-Tree nicht korrekt aufbauen und überspringen Elemente.",
user_impact_en: "Screen readers cannot build the accessibility tree correctly and skip elements.",
technical_impact: "Doppelte IDs in der DOM-Struktur oder unvollständige HTML-Tags.",
technical_impact_en: "Duplicate IDs in the DOM structure or incomplete HTML tags.",
score_impact: ScoreImpact {
base_penalty: 5.0,
max_penalty: 15.0,
occurrence_scaling: Scaling::Logarithmic,
},
report_visibility: VIS_ALL,
},
Rule {
id: "perf.lcp.slow",
dimension: Dimension::Performance,
subcategory: Subcategory::LoadBehavior,
issue_class: IssueClass::Weak,
severity: Severity::High,
external_ref: Some("CWV:LCP"),
external_level: None,
axe_id: None,
title: "Langsamer Largest Contentful Paint",
title_en: "Slow Largest Contentful Paint",
description: "Hauptinhalt wird zu spät sichtbar.",
user_impact: "Nutzer sieht lange leere oder unvollständige Seite.",
user_impact_en: "Users see a blank or incomplete page for a long time.",
technical_impact: "LCP über 2500ms — Netzwerk, Rendering oder Ressourcen zu langsam.",
technical_impact_en: "LCP above 2500ms — network, rendering or resources too slow.",
score_impact: ScoreImpact {
base_penalty: 25.0,
max_penalty: 25.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
Rule {
id: "perf.fcp.slow",
dimension: Dimension::Performance,
subcategory: Subcategory::LoadBehavior,
issue_class: IssueClass::Weak,
severity: Severity::Medium,
external_ref: Some("CWV:FCP"),
external_level: None,
axe_id: None,
title: "Langsamer First Contentful Paint",
title_en: "Slow First Contentful Paint",
description: "Erster sichtbarer Inhalt erscheint zu spät.",
user_impact: "Nutzer wartet zu lange auf erste Rückmeldung.",
user_impact_en: "Users wait too long for the first feedback.",
technical_impact: "FCP über 1800ms — Server oder Rendering-Blockade.",
technical_impact_en: "FCP above 1800ms — server or rendering blockage.",
score_impact: ScoreImpact {
base_penalty: 25.0,
max_penalty: 25.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
Rule {
id: "perf.cls.unstable",
dimension: Dimension::Performance,
subcategory: Subcategory::VisualStability,
issue_class: IssueClass::Weak,
severity: Severity::Medium,
external_ref: Some("CWV:CLS"),
external_level: None,
axe_id: None,
title: "Layout-Verschiebungen während des Ladens",
title_en: "Layout shifts during loading",
description: "Seitenelemente springen während des Ladens.",
user_impact: "Nutzer klickt versehentlich auf falsches Element.",
user_impact_en: "Users accidentally click the wrong element.",
technical_impact: "CLS über 0.1 — fehlende Dimensionen, nachladende Inhalte.",
technical_impact_en: "CLS above 0.1 — missing dimensions, late-loading content.",
score_impact: ScoreImpact {
base_penalty: 25.0,
max_penalty: 25.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
Rule {
id: "perf.tbt.high",
dimension: Dimension::Performance,
subcategory: Subcategory::Interactivity,
issue_class: IssueClass::Weak,
severity: Severity::High,
external_ref: Some("CWV:TBT"),
external_level: None,
axe_id: None,
title: "Hohe Blocking-Zeit",
title_en: "High blocking time",
description: "Hauptthread blockiert zu lange durch JavaScript.",
user_impact: "Seite reagiert verzögert auf Nutzereingaben.",
user_impact_en: "The page responds to user input with delay.",
technical_impact: "TBT/INP über 200ms — zu viel JavaScript-Arbeit im Hauptthread.",
technical_impact_en: "TBT/INP above 200ms — too much JavaScript work on the main thread.",
score_impact: ScoreImpact {
base_penalty: 25.0,
max_penalty: 25.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
Rule {
id: "perf.ttfb.slow",
dimension: Dimension::Performance,
subcategory: Subcategory::LoadBehavior,
issue_class: IssueClass::Risk,
severity: Severity::Medium,
external_ref: Some("CWV:TTFB"),
external_level: None,
axe_id: None,
title: "Langsame Server-Antwortzeit",
title_en: "Slow server response time",
description: "Server reagiert zu langsam auf die Anfrage.",
user_impact: "Gesamte Ladezeit wird verzögert.",
user_impact_en: "The overall load time is delayed.",
technical_impact: "TTFB über 800ms — Server-Performance oder Netzwerk-Latenz.",
technical_impact_en: "TTFB above 800ms — server performance or network latency.",
score_impact: ScoreImpact {
base_penalty: 5.0,
max_penalty: 10.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "perf.resources.heavy",
dimension: Dimension::Performance,
subcategory: Subcategory::ResourceUsage,
issue_class: IssueClass::Risk,
severity: Severity::Medium,
external_ref: None,
external_level: None,
axe_id: None,
title: "Hoher Ressourcenverbrauch",
title_en: "High resource consumption",
description: "Seite lädt zu viele oder zu große Ressourcen.",
user_impact: "Langsame Ladezeiten, hoher Datenverbrauch auf Mobilgeräten.",
user_impact_en: "Slow load times, high data usage on mobile devices.",
technical_impact: "Gesamtgröße > 3MB, zu viele Requests, unkomprimiert.",
technical_impact_en: "Total size > 3MB, too many requests, uncompressed.",
score_impact: ScoreImpact {
base_penalty: 5.0,
max_penalty: 10.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "perf.dom.large",
dimension: Dimension::Performance,
subcategory: Subcategory::TechnicalComplexity,
issue_class: IssueClass::Risk,
severity: Severity::Low,
external_ref: None,
external_level: None,
axe_id: None,
title: "Großes DOM",
title_en: "Large DOM",
description: "Übermäßig viele DOM-Elemente.",
user_impact: "Seite reagiert langsam auf Interaktionen.",
user_impact_en: "The page responds slowly to interactions.",
technical_impact: "DOM-Größe belastet Browser-Rendering und Speicher.",
technical_impact_en: "DOM size burdens browser rendering and memory.",
score_impact: ScoreImpact {
base_penalty: 3.0,
max_penalty: 5.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_TECHNICAL,
},
Rule {
id: "seo.title.missing",
dimension: Dimension::Seo,
subcategory: Subcategory::SnippetMetadata,
issue_class: IssueClass::Missing,
severity: Severity::Critical,
external_ref: None,
external_level: None,
axe_id: None,
title: "Fehlender Seitentitel",
title_en: "Missing page title",
description: "Kein <title>-Tag vorhanden.",
user_impact: "Seite wird in Suchergebnissen ohne aussagekräftigen Titel angezeigt.",
user_impact_en: "The page appears in search results without a meaningful title.",
technical_impact: "Leeres oder fehlendes <title>-Element.",
technical_impact_en: "Empty or missing <title> element.",
score_impact: ScoreImpact {
base_penalty: 10.0,
max_penalty: 10.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
Rule {
id: "seo.title.weak",
dimension: Dimension::Seo,
subcategory: Subcategory::SnippetMetadata,
issue_class: IssueClass::Weak,
severity: Severity::Medium,
external_ref: None,
external_level: None,
axe_id: None,
title: "Ungünstige Titellänge",
title_en: "Suboptimal title length",
description: "Titel zu kurz (<30 Zeichen) oder zu lang (>60 Zeichen).",
user_impact: "Snippet wird in Suchergebnissen suboptimal dargestellt.",
user_impact_en: "The snippet is displayed suboptimally in search results.",
technical_impact: "Title-Tag außerhalb des empfohlenen Zeichenbereichs.",
technical_impact_en: "Title tag outside the recommended character range.",
score_impact: ScoreImpact {
base_penalty: 5.0,
max_penalty: 5.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "seo.description.missing",
dimension: Dimension::Seo,
subcategory: Subcategory::SnippetMetadata,
issue_class: IssueClass::Missing,
severity: Severity::High,
external_ref: None,
external_level: None,
axe_id: None,
title: "Fehlende Meta-Description",
title_en: "Missing meta description",
description: "Keine Meta-Description vorhanden.",
user_impact: "Suchmaschine generiert eigene Beschreibung, oft suboptimal.",
user_impact_en: "The search engine generates its own description, often suboptimal.",
technical_impact: "Fehlendes <meta name=\"description\">-Element.",
technical_impact_en: "Missing <meta name=\"description\"> element.",
score_impact: ScoreImpact {
base_penalty: 10.0,
max_penalty: 10.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
Rule {
id: "seo.description.weak",
dimension: Dimension::Seo,
subcategory: Subcategory::SnippetMetadata,
issue_class: IssueClass::Weak,
severity: Severity::Low,
external_ref: None,
external_level: None,
axe_id: None,
title: "Ungünstige Description-Länge",
title_en: "Suboptimal description length",
description: "Meta-Description zu kurz (<120) oder zu lang (>160 Zeichen).",
user_impact: "Snippet wird abgeschnitten oder ist zu knapp.",
user_impact_en: "The snippet is truncated or too brief.",
technical_impact: "Description außerhalb des empfohlenen Zeichenbereichs.",
technical_impact_en: "Description outside the recommended character range.",
score_impact: ScoreImpact {
base_penalty: 5.0,
max_penalty: 5.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "seo.viewport.missing",
dimension: Dimension::Seo,
subcategory: Subcategory::SnippetMetadata,
issue_class: IssueClass::Missing,
severity: Severity::High,
external_ref: None,
external_level: None,
axe_id: None,
title: "Fehlende Viewport-Meta-Tag",
title_en: "Missing viewport meta tag",
description: "Kein Viewport-Meta-Tag vorhanden.",
user_impact: "Seite wird auf Mobilgeräten nicht korrekt dargestellt.",
user_impact_en: "The page is not rendered correctly on mobile devices.",
technical_impact: "Fehlendes <meta name=\"viewport\">-Element.",
technical_impact_en: "Missing <meta name=\"viewport\"> element.",
score_impact: ScoreImpact {
base_penalty: 10.0,
max_penalty: 10.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
Rule {
id: "seo.h1.missing",
dimension: Dimension::Seo,
subcategory: Subcategory::ContentStructure,
issue_class: IssueClass::Missing,
severity: Severity::High,
external_ref: None,
external_level: None,
axe_id: None,
title: "Fehlende H1-Überschrift",
title_en: "Missing H1 heading",
description: "Seite hat keine H1-Überschrift.",
user_impact: "Suchmaschine kann Hauptthema der Seite schlechter erkennen.",
user_impact_en: "Search engines can less easily identify the main topic of the page.",
technical_impact: "Kein <h1>-Element vorhanden.",
technical_impact_en: "No <h1> element present.",
score_impact: ScoreImpact {
base_penalty: 15.0,
max_penalty: 15.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
Rule {
id: "seo.h1.multiple",
dimension: Dimension::Seo,
subcategory: Subcategory::ContentStructure,
issue_class: IssueClass::Weak,
severity: Severity::Low,
external_ref: None,
external_level: None,
axe_id: None,
title: "Mehrere H1-Überschriften",
title_en: "Multiple H1 headings",
description: "Seite hat mehr als eine H1-Überschrift.",
user_impact: "Thematische Fokussierung der Seite unklar.",
user_impact_en: "The thematic focus of the page is unclear.",
technical_impact: "Mehrere <h1>-Elemente im DOM.",
technical_impact_en: "Multiple <h1> elements in the DOM.",
score_impact: ScoreImpact {
base_penalty: 5.0,
max_penalty: 5.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "seo.headings.issue",
dimension: Dimension::Seo,
subcategory: Subcategory::ContentStructure,
issue_class: IssueClass::Weak,
severity: Severity::Low,
external_ref: None,
external_level: None,
axe_id: None,
title: "Heading-Probleme",
title_en: "Heading issues",
description: "Übersprungene Ebenen, leere Headings oder zu lange Headings.",
user_impact: "Strukturelle Signale für Suchmaschine geschwächt.",
user_impact_en: "Structural signals for search engines are weakened.",
technical_impact: "Heading-Hierarchie nicht korrekt.",
technical_impact_en: "Heading hierarchy is not correct.",
score_impact: ScoreImpact {
base_penalty: 3.0,
max_penalty: 9.0,
occurrence_scaling: Scaling::Linear,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "seo.canonical.missing",
dimension: Dimension::Seo,
subcategory: Subcategory::Indexability,
issue_class: IssueClass::Opportunity,
severity: Severity::Low,
external_ref: None,
external_level: None,
axe_id: None,
title: "Fehlende Canonical-URL",
title_en: "Missing canonical URL",
description: "Kein Canonical-Link vorhanden.",
user_impact: "Mögliche Duplicate-Content-Probleme.",
user_impact_en: "Possible duplicate content issues.",
technical_impact: "Fehlendes <link rel=\"canonical\">-Element.",
technical_impact_en: "Missing <link rel=\"canonical\"> element.",
score_impact: ScoreImpact {
base_penalty: 5.0,
max_penalty: 5.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "seo.lang.missing",
dimension: Dimension::Seo,
subcategory: Subcategory::SemanticSignals,
issue_class: IssueClass::Missing,
severity: Severity::Medium,
external_ref: None,
external_level: None,
axe_id: None,
title: "Fehlende Sprachangabe",
title_en: "Missing language declaration",
description: "Kein lang-Attribut im HTML-Tag.",
user_impact: "Suchmaschine kann Sprache nicht automatisch zuordnen.",
user_impact_en: "Search engines cannot automatically assign the language.",
technical_impact: "Fehlendes lang-Attribut im <html>-Element.",
technical_impact_en: "Missing lang attribute on the <html> element.",
score_impact: ScoreImpact {
base_penalty: 3.0,
max_penalty: 3.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "seo.og.missing",
dimension: Dimension::Seo,
subcategory: Subcategory::SemanticSignals,
issue_class: IssueClass::Opportunity,
severity: Severity::Low,
external_ref: None,
external_level: None,
axe_id: None,
title: "Fehlende Open Graph Tags",
title_en: "Missing Open Graph tags",
description: "Keine Open Graph Meta-Tags vorhanden.",
user_impact: "Social-Media-Vorschau der Seite ist unformatiert.",
user_impact_en: "The social media preview of the page is unformatted.",
technical_impact: "Fehlende og:-Meta-Tags.",
technical_impact_en: "Missing og: meta tags.",
score_impact: ScoreImpact {
base_penalty: 5.0,
max_penalty: 5.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "seo.twitter.missing",
dimension: Dimension::Seo,
subcategory: Subcategory::SemanticSignals,
issue_class: IssueClass::Opportunity,
severity: Severity::Low,
external_ref: None,
external_level: None,
axe_id: None,
title: "Fehlende Twitter Card Tags",
title_en: "Missing Twitter Card tags",
description: "Keine Twitter Card Meta-Tags vorhanden.",
user_impact: "Twitter-Vorschau der Seite ist unformatiert.",
user_impact_en: "The Twitter preview of the page is unformatted.",
technical_impact: "Fehlende twitter:-Meta-Tags.",
technical_impact_en: "Missing twitter: meta tags.",
score_impact: ScoreImpact {
base_penalty: 5.0,
max_penalty: 5.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_TECHNICAL,
},
Rule {
id: "seo.https.missing",
dimension: Dimension::Seo,
subcategory: Subcategory::Indexability,
issue_class: IssueClass::Missing,
severity: Severity::Critical,
external_ref: None,
external_level: None,
axe_id: None,
title: "Kein HTTPS",
title_en: "No HTTPS",
description: "Seite wird nicht über HTTPS ausgeliefert.",
user_impact: "Browser zeigt Sicherheitswarnung, Ranking-Nachteil.",
user_impact_en: "The browser shows a security warning; ranking disadvantage.",
technical_impact: "Fehlende TLS-Verschlüsselung.",
technical_impact_en: "Missing TLS encryption.",
score_impact: ScoreImpact {
base_penalty: 10.0,
max_penalty: 10.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
Rule {
id: "sec.https.missing",
dimension: Dimension::Security,
subcategory: Subcategory::Transport,
issue_class: IssueClass::Missing,
severity: Severity::Critical,
external_ref: None,
external_level: None,
axe_id: None,
title: "Kein HTTPS",
title_en: "No HTTPS",
description: "Seite wird nicht über HTTPS ausgeliefert.",
user_impact: "Daten werden unverschlüsselt übertragen.",
user_impact_en: "Data is transmitted unencrypted.",
technical_impact: "Fehlende TLS-Verschlüsselung.",
technical_impact_en: "Missing TLS encryption.",
score_impact: ScoreImpact {
base_penalty: 25.0,
max_penalty: 25.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
Rule {
id: "sec.hsts.missing",
dimension: Dimension::Security,
subcategory: Subcategory::Headers,
issue_class: IssueClass::Missing,
severity: Severity::High,
external_ref: None,
external_level: None,
axe_id: None,
title: "HSTS Header fehlt",
title_en: "HSTS header missing",
description: "Strict-Transport-Security Header nicht gesetzt.",
user_impact: "Downgrade-Angriffe auf HTTP möglich.",
user_impact_en: "Downgrade attacks to HTTP are possible.",
technical_impact: "Fehlender HSTS-Header.",
technical_impact_en: "Missing HSTS header.",
score_impact: ScoreImpact {
base_penalty: 15.0,
max_penalty: 15.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
Rule {
id: "sec.csp.missing",
dimension: Dimension::Security,
subcategory: Subcategory::Headers,
issue_class: IssueClass::Missing,
severity: Severity::High,
external_ref: None,
external_level: None,
axe_id: None,
title: "Content Security Policy fehlt",
title_en: "Content Security Policy missing",
description: "Kein Content-Security-Policy Header gesetzt.",
user_impact: "Erhöhtes Risiko für XSS-Angriffe.",
user_impact_en: "Increased risk of XSS attacks.",
technical_impact: "Fehlender CSP-Header.",
technical_impact_en: "Missing CSP header.",
score_impact: ScoreImpact {
base_penalty: 15.0,
max_penalty: 15.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
Rule {
id: "sec.xcto.missing",
dimension: Dimension::Security,
subcategory: Subcategory::BrowserProtection,
issue_class: IssueClass::Missing,
severity: Severity::Medium,
external_ref: None,
external_level: None,
axe_id: None,
title: "X-Content-Type-Options Header fehlt",
title_en: "X-Content-Type-Options header missing",
description: "Browser kann MIME-Typ-Sniffing durchführen.",
user_impact: "Potenzielle Sicherheitslücke durch falsche MIME-Typen.",
user_impact_en: "Potential security gap through incorrect MIME types.",
technical_impact: "Fehlender X-Content-Type-Options: nosniff Header.",
technical_impact_en: "Missing X-Content-Type-Options: nosniff header.",
score_impact: ScoreImpact {
base_penalty: 10.0,
max_penalty: 10.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "sec.xfo.missing",
dimension: Dimension::Security,
subcategory: Subcategory::BrowserProtection,
issue_class: IssueClass::Missing,
severity: Severity::Medium,
external_ref: None,
external_level: None,
axe_id: None,
title: "X-Frame-Options Header fehlt",
title_en: "X-Frame-Options header missing",
description: "Seite kann in fremden iframes eingebettet werden.",
user_impact: "Clickjacking-Angriffe möglich.",
user_impact_en: "Clickjacking attacks are possible.",
technical_impact: "Fehlender X-Frame-Options Header.",
technical_impact_en: "Missing X-Frame-Options header.",
score_impact: ScoreImpact {
base_penalty: 10.0,
max_penalty: 10.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "sec.referrer.missing",
dimension: Dimension::Security,
subcategory: Subcategory::BrowserProtection,
issue_class: IssueClass::Missing,
severity: Severity::Low,
external_ref: None,
external_level: None,
axe_id: None,
title: "Referrer-Policy Header fehlt",
title_en: "Referrer-Policy header missing",
description: "Keine Kontrolle über Referrer-Informationen.",
user_impact: "Private URLs können an Dritte weitergegeben werden.",
user_impact_en: "Private URLs may be passed on to third parties.",
technical_impact: "Fehlender Referrer-Policy Header.",
technical_impact_en: "Missing Referrer-Policy header.",
score_impact: ScoreImpact {
base_penalty: 5.0,
max_penalty: 5.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_TECHNICAL,
},
Rule {
id: "mob.viewport.missing",
dimension: Dimension::Mobile,
subcategory: Subcategory::Viewport,
issue_class: IssueClass::Missing,
severity: Severity::Critical,
external_ref: None,
external_level: None,
axe_id: None,
title: "Fehlende Viewport-Konfiguration",
title_en: "Missing viewport configuration",
description: "Kein Viewport-Meta-Tag vorhanden.",
user_impact: "Seite wird auf Mobilgeräten nicht korrekt dargestellt.",
user_impact_en: "The page is not rendered correctly on mobile devices.",
technical_impact: "Fehlendes <meta name=\"viewport\">-Element.",
technical_impact_en: "Missing <meta name=\"viewport\"> element.",
score_impact: ScoreImpact {
base_penalty: 20.0,
max_penalty: 20.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
Rule {
id: "mob.viewport.improper",
dimension: Dimension::Mobile,
subcategory: Subcategory::Viewport,
issue_class: IssueClass::Weak,
severity: Severity::Medium,
external_ref: None,
external_level: None,
axe_id: None,
title: "Unvollständige Viewport-Konfiguration",
title_en: "Incomplete viewport configuration",
description: "Viewport-Tag fehlt width=device-width oder initial-scale=1.",
user_impact: "Seite wird möglicherweise nicht optimal skaliert.",
user_impact_en: "The page may not scale optimally.",
technical_impact: "Viewport-Meta-Tag mit unvollständiger Konfiguration.",
technical_impact_en: "Viewport meta tag with incomplete configuration.",
score_impact: ScoreImpact {
base_penalty: 10.0,
max_penalty: 10.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "mob.zoom.disabled",
dimension: Dimension::Mobile,
subcategory: Subcategory::Viewport,
issue_class: IssueClass::Risk,
severity: Severity::Critical,
external_ref: None,
external_level: None,
axe_id: None,
title: "Zoom deaktiviert",
title_en: "Zoom disabled",
description: "user-scalable=no verhindert das Zoomen.",
user_impact: "Nutzer mit Sehschwäche können Inhalte nicht vergrößern.",
user_impact_en: "Users with low vision cannot enlarge content.",
technical_impact: "Viewport-Meta mit user-scalable=no.",
technical_impact_en: "Viewport meta with user-scalable=no.",
score_impact: ScoreImpact {
base_penalty: 20.0,
max_penalty: 20.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
Rule {
id: "mob.touch_targets.small",
dimension: Dimension::Mobile,
subcategory: Subcategory::TouchUsability,
issue_class: IssueClass::Weak,
severity: Severity::Medium,
external_ref: None,
external_level: None,
axe_id: None,
title: "Zu kleine Touch-Targets",
title_en: "Touch targets too small",
description: "Interaktive Elemente kleiner als 44x44 Pixel.",
user_impact: "Schwer bedienbar auf Touchscreens.",
user_impact_en: "Hard to operate on touchscreens.",
technical_impact: "Bounding Box unter 44x44px.",
technical_impact_en: "Bounding box below 44x44px.",
score_impact: ScoreImpact {
base_penalty: 10.0,
max_penalty: 10.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "mob.fonts.small",
dimension: Dimension::Mobile,
subcategory: Subcategory::Readability,
issue_class: IssueClass::Weak,
severity: Severity::Medium,
external_ref: None,
external_level: None,
axe_id: None,
title: "Zu kleine Schriftgröße",
title_en: "Font size too small",
description: "Kleinste Schriftgröße unter 12px.",
user_impact: "Text auf Mobilgeräten schwer lesbar.",
user_impact_en: "Text is hard to read on mobile devices.",
technical_impact: "Font-size unter 12px detektiert.",
technical_impact_en: "Font size below 12px detected.",
score_impact: ScoreImpact {
base_penalty: 10.0,
max_penalty: 10.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_STANDARD,
},
Rule {
id: "mob.horizontal_scroll",
dimension: Dimension::Mobile,
subcategory: Subcategory::ResponsiveLayout,
issue_class: IssueClass::Weak,
severity: Severity::High,
external_ref: None,
external_level: None,
axe_id: None,
title: "Horizontales Scrollen erforderlich",
title_en: "Horizontal scrolling required",
description: "Seite ist breiter als der Viewport.",
user_impact: "Nutzer muss horizontal scrollen, Inhalte abgeschnitten.",
user_impact_en: "Users must scroll horizontally; content is cut off.",
technical_impact: "scrollWidth > innerWidth.",
technical_impact_en: "scrollWidth > innerWidth.",
score_impact: ScoreImpact {
base_penalty: 20.0,
max_penalty: 20.0,
occurrence_scaling: Scaling::Fixed,
},
report_visibility: VIS_ALL,
},
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rule_lookup_by_id() {
let rule = RuleLookup::by_id("a11y.alt_text.missing");
assert!(rule.is_some());
let rule = rule.unwrap();
assert_eq!(rule.dimension, Dimension::Accessibility);
assert_eq!(rule.severity, Severity::High);
}
#[test]
fn test_legacy_wcag_lookup() {
let rule = RuleLookup::by_legacy_wcag_id("1.1.1");
assert!(rule.is_some());
assert_eq!(rule.unwrap().id, "a11y.alt_text.missing");
}
#[test]
fn test_frame_title_taxonomy_uses_wcag_241() {
let rule = RuleLookup::by_legacy_wcag_id("frame-title")
.expect("frame-title axe id should resolve");
assert_eq!(rule.id, "a11y.frame_title.missing");
assert_eq!(rule.external_ref, Some("WCAG 2.4.1"));
assert_eq!(rule.axe_id, Some("frame-title"));
}
#[test]
fn test_dom_parity_rules_resolve_to_specific_taxonomy() {
let form =
RuleLookup::by_legacy_wcag_id("form-no-submit").expect("form-no-submit should resolve");
assert_eq!(form.id, "a11y.form_no_submit.missing");
assert_eq!(form.external_ref, Some("WCAG 3.2.2"));
let presentation = RuleLookup::by_legacy_wcag_id("presentation-semantic-children")
.expect("presentation-semantic-children should resolve");
assert_eq!(
presentation.id,
"a11y.presentation_semantic_children.invalid"
);
assert_eq!(presentation.external_ref, Some("WCAG 1.3.1"));
let landmark_main = RuleLookup::by_legacy_wcag_id("landmark-main-present")
.expect("landmark-main-present should resolve");
assert_eq!(landmark_main.id, "a11y.landmark_main.missing");
assert_eq!(landmark_main.external_ref, Some("WCAG 1.3.1"));
let landmark_unique = RuleLookup::by_legacy_wcag_id("landmark-unique")
.expect("landmark-unique should resolve");
assert_eq!(landmark_unique.id, "a11y.landmark_unique.invalid");
assert_eq!(landmark_unique.external_ref, Some("WCAG 1.3.1"));
let modern = RuleLookup::by_legacy_wcag_id("modern-attribute-misuse")
.expect("modern-attribute-misuse should resolve");
assert_eq!(modern.id, "a11y.modern_attributes.invalid");
assert_eq!(modern.external_ref, Some("WCAG 4.1.2"));
assert_eq!(modern.axe_id, Some("modern-attribute-misuse"));
}
#[test]
fn test_all_rules_have_dimension() {
for rule in RULES {
assert!(
rule.subcategory.dimension() == rule.dimension,
"Rule {} has subcategory {:?} which belongs to {:?}, not {:?}",
rule.id,
rule.subcategory,
rule.subcategory.dimension(),
rule.dimension
);
}
}
#[test]
fn test_all_legacy_wcag_ids_resolve() {
for (wcag_id, rule_id) in LEGACY_WCAG_MAP {
assert!(
RuleLookup::by_id(rule_id).is_some(),
"Legacy WCAG ID {} maps to rule {} which doesn't exist",
wcag_id,
rule_id
);
}
}
#[test]
fn test_unique_rule_ids() {
let mut ids: Vec<&str> = RULES.iter().map(|r| r.id).collect();
ids.sort();
let len_before = ids.len();
ids.dedup();
assert_eq!(len_before, ids.len(), "Duplicate rule IDs found");
}
}