diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html
index 38380b3fa11c3c6e7443830e764c94b1a041728c..f410d028556ac30087622f7a09660cdad8e6dadb 100644
--- a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html
+++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html
@@ -1,5 +1,6 @@
 <h2 class="heading-2 mt-4">Rollen für OZG-Cloud *</h2>
 <ods-validation-error
+  [id]="validationErrorId"
   [invalidParams]="invalidParams$ | async"
   label="Rollen"
   data-test-id="rollen-error"
diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.ts b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.ts
index 70a8bdfdccc7b1c6f7b3e55f0fd8cd74d6a2ca55..37336816f4fcff3a8d95ffcfe977c541794a75a8 100644
--- a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.ts
+++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.ts
@@ -1,4 +1,4 @@
-import { InvalidParam } from '@alfa-client/tech-shared';
+import { generateValidationErrorId, InvalidParam } from '@alfa-client/tech-shared';
 import { AsyncPipe } from '@angular/common';
 import { Component, Input, OnInit } from '@angular/core';
 import { AbstractControl, FormControlStatus, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms';
@@ -26,6 +26,7 @@ export class UserFormRolesComponent implements OnInit {
   public invalidParams$: Observable<InvalidParam[]> = of([]);
 
   public readonly UserFormService = UserFormService;
+  public readonly validationErrorId: string = generateValidationErrorId();
 
   ngOnInit(): void {
     const control: AbstractControl = this.formGroupParent.controls[UserFormService.CLIENT_ROLES];
diff --git a/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.html b/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.html
index 2533dc1ed4b63eec0da6aac3d7969a8b34172740..1cc8b07470d8086854cee7b310342c8697b4eed5 100644
--- a/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.html
+++ b/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.html
@@ -29,9 +29,11 @@
   [label]="label"
   [disabled]="control.disabled"
   [hasError]="hasError"
+  [ariaDescribedBy]="validationErrorId"
 >
   <ods-validation-error
     error
+    [id]="validationErrorId"
     [invalidParams]="invalidParams"
     [label]="label"
     [attr.data-test-id]="(label | convertForDataTest) + '-checkbox-editor-error'"
diff --git a/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.ts b/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.ts
index dfab859671455c3354bd14297d069ffde5474cde..bbb9f693fc54a9760f0bb33b006848b12aba6554 100644
--- a/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.ts
+++ b/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.ts
@@ -21,7 +21,7 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-import { ConvertForDataTestPipe } from '@alfa-client/tech-shared';
+import { ConvertForDataTestPipe, generateValidationErrorId } from '@alfa-client/tech-shared';
 import { Component, Input } from '@angular/core';
 import { ReactiveFormsModule } from '@angular/forms';
 import { CheckboxComponent } from '@ods/system';
@@ -38,6 +38,8 @@ export class CheckboxEditorComponent extends FormControlEditorAbstractComponent
   @Input() inputId: string;
   @Input() label: string;
 
+  public readonly validationErrorId: string = generateValidationErrorId();
+
   get hasError(): boolean {
     return this.invalidParams.length > 0;
   }
diff --git a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html
index c4c009107436b4b7a8837c0fa9c31c4add1008d6..3c116fb0b27c3f1b3b24807e7a0e26affb514b00 100644
--- a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html
+++ b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html
@@ -34,9 +34,11 @@
   [required]="isRequired"
   [focus]="focus"
   [showLabel]="showLabel"
+  [ariaDescribedBy]="validationErrorId"
 >
   <ods-validation-error
     error
+    [id]="validationErrorId"
     [invalidParams]="invalidParams"
     [label]="label"
     [attr.data-test-id]="dataTestId + '-text-editor-error'"
diff --git a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts
index 45f7c42c8f3f609567e89d3075411ac652eb8882..19ae4d4d435edb97ec43d2512eda5f6ab371c7aa 100644
--- a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts
+++ b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts
@@ -21,7 +21,7 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-import { ConvertForDataTestPipe } from '@alfa-client/tech-shared';
+import { ConvertForDataTestPipe, generateValidationErrorId } from '@alfa-client/tech-shared';
 import { CommonModule } from '@angular/common';
 import { Component, Input } from '@angular/core';
 import { ReactiveFormsModule } from '@angular/forms';
@@ -44,6 +44,8 @@ export class TextEditorComponent extends FormControlEditorAbstractComponent {
   @Input() showLabel: boolean = true;
   @Input() dataTestId: string;
 
+  public readonly validationErrorId: string = generateValidationErrorId();
+
   get variant(): string {
     return this.invalidParams.length > 0 ? 'error' : 'default';
   }
diff --git a/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.html b/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.html
index 39ac3109643ea6771e5199ba3e9bba49cef69c37..753a2c385a768d890c272847d220b89f8de34c9e 100644
--- a/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.html
+++ b/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.html
@@ -34,9 +34,11 @@
   [focus]="focus"
   [isResizable]="isResizable"
   [showLabel]="showLabel"
+  [ariaDescribedBy]="validationErrorId"
 >
   <ods-validation-error
     error
+    [id]="validationErrorId"
     [invalidParams]="invalidParams"
     [label]="label"
     [attr.data-test-id]="(label | convertForDataTest) + '-textarea-editor-error'"
diff --git a/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.ts b/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.ts
index c7032ef03b58627369f4857d0b2bda4546d092b5..9203da053f1a13a071244c009c6516c213de5434 100644
--- a/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.ts
+++ b/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.ts
@@ -21,7 +21,7 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-import { ConvertForDataTestPipe } from '@alfa-client/tech-shared';
+import { ConvertForDataTestPipe, generateValidationErrorId } from '@alfa-client/tech-shared';
 import { CommonModule } from '@angular/common';
 import { Component, Input } from '@angular/core';
 import { ReactiveFormsModule } from '@angular/forms';
@@ -44,6 +44,8 @@ export class TextareaEditorComponent extends FormControlEditorAbstractComponent
   @Input() isResizable: boolean = true;
   @Input() showLabel: boolean = true;
 
+  public readonly validationErrorId: string = generateValidationErrorId();
+
   get variant(): string {
     return this.invalidParams.length > 0 ? 'error' : 'default';
   }
diff --git a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.html b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.html
index 224dbe8cc5d73df87e45d77700b0ffca1efdb03e..cb566bafe0c5a5a804ae3e10491c0ca19faf4c40 100644
--- a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.html
+++ b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.html
@@ -24,5 +24,5 @@
 
 -->
 <ng-container *ngFor="let invalidParam of invalidParams"
-  ><ods-error-message [text]="message(invalidParam)"></ods-error-message
+  ><ods-error-message [id]="id" [text]="message(invalidParam)"></ods-error-message
 ></ng-container>
diff --git a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.ts b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.ts
index 8844b8075d1e66c181c05cbddf1da8669c698a06..fca8d2a2ecfbec56310a3ec835f03459edb4ec27 100644
--- a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.ts
+++ b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.ts
@@ -33,6 +33,7 @@ import { ErrorMessageComponent } from '@ods/system';
   templateUrl: './validation-error.component.html',
 })
 export class ValidationErrorComponent {
+  @Input({ required: true }) id: string;
   @Input() label: string;
   @Input() invalidParams: InvalidParam[];
 
diff --git a/alfa-client/libs/design-system/src/lib/form/checkbox/checkbox.component.ts b/alfa-client/libs/design-system/src/lib/form/checkbox/checkbox.component.ts
index ffadf796c9031418fda5463896a1452351f2e109..3b3750bd3174bfaeecf2821d4dc29d9e5c6a4d4e 100644
--- a/alfa-client/libs/design-system/src/lib/form/checkbox/checkbox.component.ts
+++ b/alfa-client/libs/design-system/src/lib/form/checkbox/checkbox.component.ts
@@ -21,7 +21,7 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-import { ConvertForDataTestPipe } from '@alfa-client/tech-shared';
+import { ConvertForDataTestPipe, EMPTY_STRING } from '@alfa-client/tech-shared';
 import { CommonModule } from '@angular/common';
 import { Component, Input } from '@angular/core';
 import { FormControl, ReactiveFormsModule } from '@angular/forms';
@@ -45,6 +45,7 @@ import { FormControl, ReactiveFormsModule } from '@angular/forms';
           [attr.id]="inputId"
           [attr.disabled]="disabled ? true : null"
           [attr.data-test-id]="(label | convertForDataTest) + '-checkbox-editor'"
+          [attr.aria-describedby]="ariaDescribedBy"
         />
         <label class="leading-5 text-text" [attr.for]="inputId">{{ label }}</label>
         <svg
@@ -69,4 +70,5 @@ export class CheckboxComponent {
   @Input() label: string;
   @Input() disabled: boolean = false;
   @Input() hasError: boolean = false;
+  @Input() ariaDescribedBy: string = EMPTY_STRING;
 }
diff --git a/alfa-client/libs/design-system/src/lib/form/error-message/error-message.component.ts b/alfa-client/libs/design-system/src/lib/form/error-message/error-message.component.ts
index 66ed08e7f973dad14e092c899de2880ff7d8f515..8c33dd8452e8f8bc07db1cfff2a2910dbe279da1 100644
--- a/alfa-client/libs/design-system/src/lib/form/error-message/error-message.component.ts
+++ b/alfa-client/libs/design-system/src/lib/form/error-message/error-message.component.ts
@@ -21,6 +21,7 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
+import { EMPTY_STRING } from '@alfa-client/tech-shared';
 import { CommonModule } from '@angular/common';
 import { Component, Input } from '@angular/core';
 import { ExclamationIconComponent } from '../../icons/exclamation-icon/exclamation-icon.component';
@@ -31,13 +32,14 @@ import { ExclamationIconComponent } from '../../icons/exclamation-icon/exclamati
   imports: [CommonModule, ExclamationIconComponent],
   styles: [':host {@apply flex text-error my-2 text-sm items-center font-medium}'],
   template: `<ods-exclamation-icon class="mr-1"></ods-exclamation-icon>
-    <div class="flex-grow break-all">
+    <div class="flex-grow break-all" [id]="id">
       {{ text }}
       <br *ngIf="subText" aria-hidden="true" />
       {{ subText }}
     </div> `,
 })
 export class ErrorMessageComponent {
+  @Input() id: string = EMPTY_STRING;
   @Input({ required: true }) text!: string;
   @Input() subText: string = '';
 }
diff --git a/alfa-client/libs/design-system/src/lib/form/text-input/text-input.component.ts b/alfa-client/libs/design-system/src/lib/form/text-input/text-input.component.ts
index cd01ba0190c68b4f9fec34f3e94496e6d86f73aa..4a8ddff09949312893bf0faa30b59918d5461ce5 100644
--- a/alfa-client/libs/design-system/src/lib/form/text-input/text-input.component.ts
+++ b/alfa-client/libs/design-system/src/lib/form/text-input/text-input.component.ts
@@ -62,6 +62,7 @@ type TextInputVariants = VariantProps<typeof textInputVariants>;
           <ng-content select="[prefix]" />
         </div>
         <input
+          #inputElement
           type="text"
           [id]="id"
           [formControl]="fieldControl"
@@ -71,8 +72,8 @@ type TextInputVariants = VariantProps<typeof textInputVariants>;
           [attr.aria-required]="required"
           [attr.aria-invalid]="variant === 'error'"
           [attr.data-test-id]="_dataTestId + '-text-input'"
+          [attr.aria-describedby]="ariaDescribedBy"
           (click)="clickEmitter.emit()"
-          #inputElement
         />
         <div *ngIf="withSuffix" class="absolute bottom-2 right-2 flex size-6 items-center justify-center">
           <ng-content select="[suffix]" />
@@ -102,6 +103,7 @@ export class TextInputComponent implements AfterViewInit {
   @Input() set dataTestId(value: string) {
     if (isNotUndefined(value)) this._dataTestId = value;
   }
+  @Input() ariaDescribedBy: string = EMPTY_STRING;
 
   @Output() clickEmitter: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
 
diff --git a/alfa-client/libs/design-system/src/lib/form/textarea/textarea.component.ts b/alfa-client/libs/design-system/src/lib/form/textarea/textarea.component.ts
index f3eb84af9d638567a3bcbdc644b400abf344643e..d3c04b6c82bb491b5f25652bd89e6af37595dd85 100644
--- a/alfa-client/libs/design-system/src/lib/form/textarea/textarea.component.ts
+++ b/alfa-client/libs/design-system/src/lib/form/textarea/textarea.component.ts
@@ -57,6 +57,7 @@ type TextareaVariants = VariantProps<typeof textareaVariants>;
         {{ inputLabel }}<ng-container *ngIf="required"><i aria-hidden="true">*</i></ng-container>
       </label>
       <textarea
+        #textAreaElement
         [id]="id"
         [formControl]="fieldControl"
         [rows]="rows"
@@ -66,7 +67,7 @@ type TextareaVariants = VariantProps<typeof textareaVariants>;
         [attr.aria-required]="required"
         [attr.aria-invalid]="variant === 'error'"
         [attr.data-test-id]="(inputLabel | convertForDataTest) + '-textarea'"
-        #textAreaElement
+        [attr.aria-describedby]="ariaDescribedBy"
       ></textarea>
       <ng-content select="[error]"></ng-content>
     </div>
@@ -94,6 +95,7 @@ export class TextareaComponent {
       this.textAreaElement.nativeElement.focus();
     }
   }
+  @Input() ariaDescribedBy: string = EMPTY_STRING;
 
   inputLabel: string;
   id: string;
diff --git a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts
index b0cb78f8a1d59efdddeb25e26c4deda6a746d7fa..7f8f416bf9b8a56b3f17bf41d79572d012bdbdcb 100644
--- a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts
+++ b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts
@@ -22,7 +22,7 @@
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
 import { AbstractControl, UntypedFormGroup } from '@angular/forms';
-import { isEmpty, isNil } from 'lodash-es';
+import { isEmpty, isNil, uniqueId } from 'lodash-es';
 import { ApiError, InvalidParam, Issue, IssueParam, ProblemDetail } from '../tech.model';
 import { replacePlaceholder } from '../tech.util';
 import { VALIDATION_MESSAGES, ValidationMessageCode } from './tech.validation.messages';
@@ -31,28 +31,18 @@ export function isValidationError(issue: Issue): boolean {
   return issue.messageCode.includes('javax.validation.constraints');
 }
 
-export function setIssueValidationError(
-  form: UntypedFormGroup,
-  issue: Issue,
-  pathPrefix?: string,
-): void {
+export function setIssueValidationError(form: UntypedFormGroup, issue: Issue, pathPrefix?: string): void {
   const control: AbstractControl = getControlForIssue(form, issue, pathPrefix);
 
   control.setErrors({ [issue.messageCode]: issue });
   control.markAsTouched();
 }
 
-export function getControlForIssue(
-  form: UntypedFormGroup,
-  issue: Issue,
-  pathPrefix?: string,
-): AbstractControl {
+export function getControlForIssue(form: UntypedFormGroup, issue: Issue, pathPrefix?: string): AbstractControl {
   const fieldPath: string = getFieldPath(issue.field, pathPrefix);
 
   let curControl: AbstractControl = form;
-  fieldPath
-    .split('.')
-    .forEach((field) => (curControl = (<UntypedFormGroup>curControl).controls[field]));
+  fieldPath.split('.').forEach((field) => (curControl = (<UntypedFormGroup>curControl).controls[field]));
 
   return curControl;
 }
@@ -66,9 +56,7 @@ export function getMessageForIssue(label: string, issue: Issue): string {
   }
 
   msg = replacePlaceholder(msg, 'field', label);
-  issue.parameters.forEach(
-    (param: IssueParam) => (msg = replacePlaceholder(msg, param.name, param.value)),
-  );
+  issue.parameters.forEach((param: IssueParam) => (msg = replacePlaceholder(msg, param.name, param.value)));
   return msg;
 }
 
@@ -84,11 +72,7 @@ export function getMessageCode(apiError: ApiError): string {
   return apiError.issues[0].messageCode;
 }
 
-export function setInvalidParamValidationError(
-  form: UntypedFormGroup,
-  invalidParam: InvalidParam,
-  pathPrefix?: string,
-): void {
+export function setInvalidParamValidationError(form: UntypedFormGroup, invalidParam: InvalidParam, pathPrefix?: string): void {
   const control: AbstractControl = getControlForInvalidParam(form, invalidParam, pathPrefix);
 
   control.setErrors({ [invalidParam.reason]: invalidParam });
@@ -112,9 +96,7 @@ export function getMessageForInvalidParam(label: string, invalidParam: InvalidPa
   }
 
   msg = replacePlaceholder(msg, 'field', label);
-  invalidParam.constraintParameters.forEach(
-    (param: IssueParam) => (msg = replacePlaceholder(msg, param.name, param.value)),
-  );
+  invalidParam.constraintParameters.forEach((param: IssueParam) => (msg = replacePlaceholder(msg, param.name, param.value)));
   return msg;
 }
 
@@ -126,3 +108,7 @@ export function getFieldPath(name: string, pathPrefix: string): string {
   const indexOfField = name.lastIndexOf(pathPrefix) + pathPrefix.length + 1;
   return name.slice(indexOfField);
 }
+
+export function generateValidationErrorId(): string {
+  return `${uniqueId()}-validation-error`;
+}