| Version | Tag | Status | API |
|---|---|---|---|
| v1 | <oc-auth-field-v1> |
Stable, allowed for generation | AuthFieldV1 |
Overview (v1)
Source: ./src/components/auth-field/v1/Overview.mdx
Auth field
The auth field component is an alternative to the auth field component to support password managers. This component is designed to be used in a login form or any other form where credentials are required.
Note: Not all password managers are able to handle shadow DOM, so this component is a workaround to support them.
Default variation
Story Default:
<oc-auth-field-v1>
<input
type="text"
aria-describedby="hintXYZ"
maxlength="250"
id="inputXYZ"
aria-required="true"
spellcheck="false"
autocorrect="off"
/>
<label for="inputXYZ">Label</label>
</oc-auth-field-v1>
Configuration
The auth field component offers styling variants such as default and error.
This component is configurable, allowing you to tailor its features and appearance to your specific needs.
To explore all the available options and adjust the component, use the component configurator and see the changes affect the component in real-time.
Usage guidelines
Before integrating the auth field component into your project, make sure you have correctly installed the OTTO components package. Look through the variations page for examples of possible component variations. Here, you can discover both common and specific variations that address different use cases.
Info
See the Auth field UX documentation for detailed user experience guidelines.
Layout considerations
This component has visual overflow.
It extends beyond its bounding box and is clipped by parent containers with overflow: hidden.
Ensure the parent container has sufficient padding to accommodate the component's full visual area.
Accessibility
The auth field component comes with a set of built-in accessibility features to ensure a seamless experience for all users.
Use aria-label
To make the auth field recognizable for screen readers, use the default slot to provide a clear and descriptive label.
Use the aria-label attribute on the label to provide additional context if the visible label is not sufficient.
See the general accessibility documentation for guidance on using oc-aria-label, including how it works with link and masked link behavior.
Invalidate the auth field
If the auth field is invalid, the aria-invalid attribute must be set to true on the input element.
You should also provide an error message to the user by using the error slot and reference it in the aria-describedby attribute on the input element.
Further reading
Configuration (v1)
Source: ./src/components/auth-field/v1/Configuration.mdx
Auth field configuration
Configure the auth field component with the controls below and see the changes live in the preview canvas. Click the Show code button within the preview canvas to see the source code for the current component configuration.
Story Default:
<oc-auth-field-v1>
<input
type="text"
aria-describedby="hintXYZ"
maxlength="250"
id="inputXYZ"
aria-required="true"
spellcheck="false"
autocorrect="off"
/>
<label for="inputXYZ">Label</label>
</oc-auth-field-v1>
Interactive configurator (Storybook controls); every option is listed in the API section of this file.
API v1
Source: ./src/components/auth-field/v1/AuthFieldV1.API.g.mdx
Auth Field v1 API
API: <oc-auth-field-v1> (AuthFieldV1)
The auth field component provides a single-line input field for user text input. It is mainly used for login forms or registration forms. The component consists of an input field and a label. The input field can be of type text, email, or password. The component can be styled in a default or error state.
Attributes / properties
| Attribute | Type | Default | Required | Description |
|---|---|---|---|---|
variant |
"default" | "error" |
"default" |
no | Sets the main styling and behavior of the auth field. |
password-visible |
boolean |
false |
no | Allows to programmatically synchronize the toggle state of the password visibility for multiple password fields, e.g. for password confirmation. As a result, both toggle buttons show and hide the password value of both password fields simultaneously. This attribute is only available for auth field components with type="password". |
show-counter |
boolean |
false |
no | Indicates whether a counter for minlength or maxlength is shown. Note: minlength and maxlength attributes need to be set on the input element. |
without-optional-label |
boolean |
false |
no | Excludes the automatic "(optional)" label suffix even if the feature toggle is active. |
Slots
| Slot | Required | Description |
|---|---|---|
default |
yes | The input and label elements for the auth field. Both elements need to be present and should be linked by the for attribute of the label element.Note: Order is important. The input element needs to be before the label element. |
hint |
no | Hint for the input field. The hint needs to be referenced by the aria-describedby attribute of the input element. |
error |
no | The error message for the input field. The error needs to be referenced by the aria-describedby attribute of the input element.aria-invalid should set to true on the input field. |
Events
| Event | Detail type | Description |
|---|---|---|
oc-property-change |
OcAuthFieldV1Events["oc-property-change"] |
Whenever a property value changes, this event triggers. Use this event to track all property changes within the component. Refer to the Events documentation for more information. |
oc-mount |
{ component: string; } |
Fired when the component is mounted to the DOM. The event is fired when the onMount hook of the component is called by the runtime. |
oc-unmount |
{ component: string; } |
Fired when the component is unmounted from the DOM. The event is fired when the function returned by the onMount hook of the component is called by the runtime. |
Variations (v1)
Source: ./src/components/auth-field/v1/Variations.mdx
Variations
Listed below are the most common variations of the auth field component as well as specific component variations for different use cases.
You can explore all available options using the component configurator, adjust the component to your needs, and see the changes live in a preview canvas.
Default
Story: components-auth-field-variations--default · tags: components
The default configuration uses the default variant and the default slot to render the input field and the label.
Args: defaultSlot=(see snippet)
<oc-auth-field-v1>
<input
type="text"
aria-describedby="hintXYZ"
maxlength="250"
id="inputXYZ"
aria-required="true"
spellcheck="false"
autocorrect="off"
/>
<label for="inputXYZ">Label</label>
</oc-auth-field-v1>
Error
Story: components-auth-field-variations--invalid · tags: components
The error variant uses the error slot to render the error message, and the hint slot to render a hint.
Args: variant=error, defaultSlot=(see snippet), errorSlot=<div slot="error" id="errorXYZ">Your input is not valid.</div>, hintSlot=<div slot="hint" id="hintXYZ">Some hint</div>
<oc-auth-field-v1 variant="error">
<div slot="error" id="errorXYZ">Your input is not valid.</div>
<div slot="hint" id="hintXYZ">Some hint</div>
<input
type="email"
aria-describedby="errorXYZ hintXYZ"
maxlength="250"
id="inputXYZ"
aria-required="true"
aria-invalid="true"
autocomplete=""
/>
<label for="inputXYZ">E-Mail</label>
</oc-auth-field-v1>
Max length
Story: components-auth-field-variations--max-length · tags: components
Variation that uses a text input field in the default slot, limiting the number of characters that can be entered.
Args: show-counter=true, defaultSlot=(see snippet)
<oc-auth-field-v1 show-counter>
<input
type="text"
aria-describedby="hintXYZ"
maxlength="250"
id="inputXYZ"
aria-required="true"
spellcheck="false"
autocorrect="off"
/>
<label for="inputXYZ">Label</label>
</oc-auth-field-v1>
Password
Story: components-auth-field-variations--password · tags: components
Variation that uses a password input field in the default slot, with a hint in the hint slot.
Args: defaultSlot=(see snippet), hintSlot=<div slot="hint" id="hintXYZ">Some hint</div>
<oc-auth-field-v1>
<div slot="hint" id="hintXYZ">Some hint</div>
<input
type="password"
aria-describedby="hintXYZ"
minlength="8"
id="inputXYZ"
aria-required="true"
autocomplete=""
/>
<label for="inputXYZ">Password</label>
</oc-auth-field-v1>
Demo: login form
Story: components-auth-field-variations--demo-login · tags: components
A demo showcasing how to use the oc-auth-field-v1 component in a login form.
<form novalidate>
<oc-auth-field-v1>
<input
name="email"
type="email"
aria-describedby="errorEmail hintEmail"
maxlength="250"
id="inputEmail"
aria-required="true"
spellcheck="false"
autocorrect="off"
aria-invalid="false"
/>
<label for="inputEmail">Username</label>
<div slot="error" id="errorEmail"></div>
<div slot="hint" id="hintEmail">Enter username</div>
</oc-auth-field-v1>
<oc-auth-field-v1>
<input
name="password"
type="password"
aria-describedby="errorPassword hintPassword"
minlength="8"
id="inputPassword"
aria-required="true"
aria-invalid="false"
autocomplete="current-password"
/>
<label for="inputPassword">Password</label>
<div slot="error" id="errorPassword"></div>
<div slot="hint" id="hintPassword">Enter password</div>
</oc-auth-field-v1>
<!-- Workaround for Safari < 18. -->
<!-- Fixed with Safari 18: https://bugs.webkit.org/show_bug.cgi?id=279595 -->
<input type="hidden" />
<div class="buttons">
<oc-button-v1 type="reset" variant="secondary">Reset</oc-button-v1>
<oc-button-v1 type="submit">Submit</oc-button-v1>
</div>
</form>
<script>
(() => {
const [form] = document.getElementsByTagName("form");
const [resetButton] = document.getElementsByTagName("oc-button-v1");
const [ocEmail, ocPassword] = document.getElementsByTagName("oc-auth-field-v1");
const inputEmail = document.getElementById("inputEmail");
const inputPassword = document.getElementById("inputPassword");
resetButton.addEventListener("click", () => {
inputEmail.ariaInvalid = "false";
ocEmail.variant = "default";
ocEmail.querySelector("[slot=error]").textContent = "";
inputPassword.ariaInvalid = "false";
ocPassword.variant = "default";
ocPassword.querySelector("[slot=error]").textContent = "";
});
const validateEmail = () => {
if (!inputEmail.value) {
inputEmail.ariaInvalid = "true";
ocEmail.querySelector("[slot=error]").textContent =
"Please enter your email address.";
} else if (inputEmail.validity.typeMismatch) {
inputEmail.ariaInvalid = "true";
ocEmail.querySelector("[slot=error]").textContent =
"Please enter a valid email address.";
} else {
inputEmail.ariaInvalid = "false";
ocEmail.querySelector("[slot=error]").textContent = "";
}
ocEmail.variant = inputEmail.ariaInvalid === "true" ? "error" : "default";
};
const validatePassword = () => {
if (!inputPassword.value) {
inputPassword.ariaInvalid = "true";
ocPassword.querySelector("[slot=error]").textContent = "Please enter your password.";
} else if (inputPassword.value.length < 8) {
inputPassword.ariaInvalid = "true";
ocPassword.querySelector("[slot=error]").textContent =
"Password must be at least 8 characters long.";
} else {
inputPassword.ariaInvalid = "false";
ocPassword.querySelector("[slot=error]").textContent = "";
}
ocPassword.variant = inputPassword.ariaInvalid === "true" ? "error" : "default";
};
form.addEventListener("submit", (ev) => {
validateEmail();
validatePassword();
// focus the first invalid field
if (inputEmail.ariaInvalid === "true") {
inputEmail.focus();
} else if (inputPassword.ariaInvalid === "true") {
inputPassword.focus();
}
if (inputEmail.ariaInvalid !== "true" && inputPassword.ariaInvalid !== "true") {
let formData = new FormData(form);
formData.set("password", "********"); // just in case
let data = JSON.stringify(Object.fromEntries(formData));
alert("sending " + data);
}
ev.preventDefault();
});
})();
</script>
${safariObscureHint}
<style>
form {
display: flex;
flex-direction: column;
gap: 24px;
}
.buttons {
display: flex;
gap: 16px;
}
</style>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: login form",
parameters: {
controls: {
disabled: true
}
},
argTypes: hideControlsBadge(Metadata),
render() {
return html`
<form novalidate>
<oc-auth-field-v1>
<input
name="email"
type="email"
aria-describedby="errorEmail hintEmail"
maxlength="250"
id="inputEmail"
aria-required="true"
spellcheck="false"
autocorrect="off"
aria-invalid="false"
/>
<label for="inputEmail">Username</label>
<div slot="error" id="errorEmail"></div>
<div slot="hint" id="hintEmail">Enter username</div>
</oc-auth-field-v1>
<oc-auth-field-v1>
<input
name="password"
type="password"
aria-describedby="errorPassword hintPassword"
minlength="8"
id="inputPassword"
aria-required="true"
aria-invalid="false"
autocomplete="current-password"
/>
<label for="inputPassword">Password</label>
<div slot="error" id="errorPassword"></div>
<div slot="hint" id="hintPassword">Enter password</div>
</oc-auth-field-v1>
<!-- Workaround for Safari < 18. -->
<!-- Fixed with Safari 18: https://bugs.webkit.org/show_bug.cgi?id=279595 -->
<input type="hidden" />
<div class="buttons">
<oc-button-v1 type="reset" variant="secondary">Reset</oc-button-v1>
<oc-button-v1 type="submit">Submit</oc-button-v1>
</div>
</form>
<script>
(() => {
const [form] = document.getElementsByTagName("form");
const [resetButton] = document.getElementsByTagName("oc-button-v1");
const [ocEmail, ocPassword] = document.getElementsByTagName("oc-auth-field-v1");
const inputEmail = document.getElementById("inputEmail");
const inputPassword = document.getElementById("inputPassword");
resetButton.addEventListener("click", () => {
inputEmail.ariaInvalid = "false";
ocEmail.variant = "default";
ocEmail.querySelector("[slot=error]").textContent = "";
inputPassword.ariaInvalid = "false";
ocPassword.variant = "default";
ocPassword.querySelector("[slot=error]").textContent = "";
});
const validateEmail = () => {
if (!inputEmail.value) {
inputEmail.ariaInvalid = "true";
ocEmail.querySelector("[slot=error]").textContent =
"Please enter your email address.";
} else if (inputEmail.validity.typeMismatch) {
inputEmail.ariaInvalid = "true";
ocEmail.querySelector("[slot=error]").textContent =
"Please enter a valid email address.";
} else {
inputEmail.ariaInvalid = "false";
ocEmail.querySelector("[slot=error]").textContent = "";
}
ocEmail.variant = inputEmail.ariaInvalid === "true" ? "error" : "default";
};
const validatePassword = () => {
if (!inputPassword.value) {
inputPassword.ariaInvalid = "true";
ocPassword.querySelector("[slot=error]").textContent = "Please enter your password.";
} else if (inputPassword.value.length < 8) {
inputPassword.ariaInvalid = "true";
ocPassword.querySelector("[slot=error]").textContent =
"Password must be at least 8 characters long.";
} else {
inputPassword.ariaInvalid = "false";
ocPassword.querySelector("[slot=error]").textContent = "";
}
ocPassword.variant = inputPassword.ariaInvalid === "true" ? "error" : "default";
};
form.addEventListener("submit", (ev) => {
validateEmail();
validatePassword();
// focus the first invalid field
if (inputEmail.ariaInvalid === "true") {
inputEmail.focus();
} else if (inputPassword.ariaInvalid === "true") {
inputPassword.focus();
}
if (inputEmail.ariaInvalid !== "true" && inputPassword.ariaInvalid !== "true") {
let formData = new FormData(form);
formData.set("password", "********"); // just in case
let data = JSON.stringify(Object.fromEntries(formData));
alert("sending " + data);
}
ev.preventDefault();
});
})();
</script>
${safariObscureHint}
<style>
form {
display: flex;
flex-direction: column;
gap: 24px;
}
.buttons {
display: flex;
gap: 16px;
}
</style>
`;
}
}
Demo: password repeat
Story: components-auth-field-variations--demo-password-repeat · tags: components
A demo showcasing how to use the oc-auth-field-v1 component in a registration form with a password repeat field.
<form novalidate>
<oc-auth-field-v1>
<input
name="password"
type="password"
aria-describedby="errorPassword1 hintPassword1"
minlength="8"
id="inputPassword1"
aria-required="true"
aria-invalid="false"
autocomplete="new-password"
/>
<label for="inputPassword1">Password</label>
<div slot="error" id="errorPassword1"></div>
<div slot="hint" id="hintPassword1">Enter password</div>
</oc-auth-field-v1>
<oc-auth-field-v1>
<input
name="password"
type="password"
aria-describedby="errorPassword2 hintPassword2"
minlength="8"
id="inputPassword2"
aria-required="true"
aria-invalid="false"
autocomplete="new-password"
/>
<label for="inputPassword2">Repeat</label>
<div slot="error" id="errorPassword2"></div>
<div slot="hint" id="hintPassword2">Repeat password</div>
</oc-auth-field-v1>
</form>
${safariObscureHint}
<script>
(() => {
const [ocPassword1, ocPassword2] = document.getElementsByTagName("oc-auth-field-v1");
ocPassword1.addEventListener("oc-property-change", (ev) => {
if (ev.detail.passwordVisible) {
ocPassword2.passwordVisible = ev.detail.passwordVisible.current;
}
});
ocPassword2.addEventListener("oc-property-change", (ev) => {
if (ev.detail.passwordVisible) {
ocPassword1.passwordVisible = ev.detail.passwordVisible.current;
}
});
})();
</script>
<style>
form {
display: flex;
flex-direction: column;
gap: 24px;
}
.buttons {
display: flex;
gap: 16px;
}
</style>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: password repeat",
parameters: {
controls: {
disabled: true
}
},
argTypes: hideControlsBadge(Metadata),
render() {
return html`
<form novalidate>
<oc-auth-field-v1>
<input
name="password"
type="password"
aria-describedby="errorPassword1 hintPassword1"
minlength="8"
id="inputPassword1"
aria-required="true"
aria-invalid="false"
autocomplete="new-password"
/>
<label for="inputPassword1">Password</label>
<div slot="error" id="errorPassword1"></div>
<div slot="hint" id="hintPassword1">Enter password</div>
</oc-auth-field-v1>
<oc-auth-field-v1>
<input
name="password"
type="password"
aria-describedby="errorPassword2 hintPassword2"
minlength="8"
id="inputPassword2"
aria-required="true"
aria-invalid="false"
autocomplete="new-password"
/>
<label for="inputPassword2">Repeat</label>
<div slot="error" id="errorPassword2"></div>
<div slot="hint" id="hintPassword2">Repeat password</div>
</oc-auth-field-v1>
</form>
${safariObscureHint}
<script>
(() => {
const [ocPassword1, ocPassword2] = document.getElementsByTagName("oc-auth-field-v1");
ocPassword1.addEventListener("oc-property-change", (ev) => {
if (ev.detail.passwordVisible) {
ocPassword2.passwordVisible = ev.detail.passwordVisible.current;
}
});
ocPassword2.addEventListener("oc-property-change", (ev) => {
if (ev.detail.passwordVisible) {
ocPassword1.passwordVisible = ev.detail.passwordVisible.current;
}
});
})();
</script>
<style>
form {
display: flex;
flex-direction: column;
gap: 24px;
}
.buttons {
display: flex;
gap: 16px;
}
</style>
`;
}
}
Interaction tests (AuthFieldV1.interactions.stories.ts)
Interaction test stories (automated tests, not usage patterns).
Label Should Be On The Top
Story: components-auth-field-interaction-tests--label-should-be-on-the-top · tags: components, auth-field, v1, play-fn
<oc-auth-field-v1>
<input
name="email"
type="email"
aria-describedby="hintEmail"
maxlength="250"
id="inputEmail"
aria-required="true"
spellcheck="false"
autocorrect="off"
/>
<label for="inputEmail">Username</label>
<div slot="hint" id="hintEmail">Enter username</div>
</oc-auth-field-v1>
Story source (TypeScript, verbatim from Storybook)
{
render: () => html`
<oc-auth-field-v1>
<input
name="email"
type="email"
aria-describedby="hintEmail"
maxlength="250"
id="inputEmail"
aria-required="true"
spellcheck="false"
autocorrect="off"
/>
<label for="inputEmail">Username</label>
<div slot="hint" id="hintEmail">Enter username</div>
</oc-auth-field-v1>
`,
play: async ({
canvasElement
}) => {
const input = canvasElement.querySelector("input")!;
const label = canvasElement.querySelector("label")!;
await expect(label.getBoundingClientRect().toJSON(), "should initially be not on the top").toMatchObject({
bottom: 52,
height: 24,
left: 28,
top: 28,
x: 28,
y: 28
});
await userEvent.type(input, "foo@bar.de");
await waitFor(() => expect(label.getBoundingClientRect().toJSON(), "should be on the top").toMatchObject({
bottom: 24,
height: 16,
left: 28,
top: 8,
x: 28,
y: 8
}));
}
}
Password Toggle Should Show Password
Story: components-auth-field-interaction-tests--password-toggle-should-show-password · tags: components, auth-field, v1, play-fn
<oc-auth-field-v1>
<input
name="password"
type="password"
aria-describedby="hintPassword"
maxlength="250"
id="inputPassword"
aria-required="true"
/>
<label for="inputPassword">Password</label>
<div slot="hint" id="hintPassword">Enter password</div>
</oc-auth-field-v1>
Story source (TypeScript, verbatim from Storybook)
{
render: () => html`
<oc-auth-field-v1>
<input
name="password"
type="password"
aria-describedby="hintPassword"
maxlength="250"
id="inputPassword"
aria-required="true"
/>
<label for="inputPassword">Password</label>
<div slot="hint" id="hintPassword">Enter password</div>
</oc-auth-field-v1>
`,
play: async ({
canvasElement
}) => {
const input = canvasElement.querySelector("input")!;
const passwordToggle = canvasElement.querySelector("oc-auth-field-v1")!.shadowRoot!.querySelector(".auth-field__password-toggle")! as HTMLElement;
await expect(passwordToggle).toBeVisible();
await expect(input.getAttribute("type"), "password should be visible").toBe("password");
await userEvent.type(input, "MyTestPassword123");
await click(passwordToggle);
await expect(input.getAttribute("type"), "password should be visible").toBe("text");
}
}
Hint And Error Should Have Correct Colors
Story: components-auth-field-interaction-tests--hint-and-error-should-have-correct-colors · tags: components, auth-field, v1, play-fn
<oc-auth-field-v1 variant="error">
<input
type="email"
aria-describedby="errorXYZ hintXYZ"
maxlength="250"
id="inputXYZ"
aria-required="true"
aria-invalid="true"
autocomplete=""
/>
<label for="inputXYZ">E-Mail</label>
<div slot="error" id="errorXYZ">Your input is not valid.</div>
<div slot="hint" id="hintXYZ">Some hint</div>
</oc-auth-field-v1>
Story source (TypeScript, verbatim from Storybook)
{
render: () => html`
<oc-auth-field-v1 variant="error">
<input
type="email"
aria-describedby="errorXYZ hintXYZ"
maxlength="250"
id="inputXYZ"
aria-required="true"
aria-invalid="true"
autocomplete=""
/>
<label for="inputXYZ">E-Mail</label>
<div slot="error" id="errorXYZ">Your input is not valid.</div>
<div slot="hint" id="hintXYZ">Some hint</div>
</oc-auth-field-v1>
`,
play: async ({
canvasElement
}) => {
const hint = canvasElement.querySelector("[slot=hint]")!;
const error = canvasElement.querySelector("[slot=error]")!;
await expect(getComputedStyle(hint).color).toBe("rgb(109, 109, 109)");
await expect(getComputedStyle(error).color).toBe("rgb(179, 0, 35)");
}
}
Should Submit Form On Enter
Story: components-auth-field-interaction-tests--should-submit-form-on-enter · tags: components, auth-field, v1, play-fn
<form>
<oc-auth-field-v1>
<input
name="email"
type="email"
aria-describedby="hintEmail"
maxlength="250"
id="inputEmail"
aria-required="true"
spellcheck="false"
autocorrect="off"
/>
<label for="inputEmail">Username</label>
<div slot="hint" id="hintEmail">Enter username</div>
</oc-auth-field-v1>
<br />
<oc-auth-field-v1>
<input
name="password"
type="password"
aria-describedby="hintPassword"
maxlength="250"
id="inputPassword"
aria-required="true"
/>
<label for="inputPassword">Password</label>
<div slot="hint" id="hintPassword">Enter password</div>
</oc-auth-field-v1>
<br />
<button type="submit">Submit</button>
</form>
Story source (TypeScript, verbatim from Storybook)
{
render: () => html`
<form>
<oc-auth-field-v1>
<input
name="email"
type="email"
aria-describedby="hintEmail"
maxlength="250"
id="inputEmail"
aria-required="true"
spellcheck="false"
autocorrect="off"
/>
<label for="inputEmail">Username</label>
<div slot="hint" id="hintEmail">Enter username</div>
</oc-auth-field-v1>
<br />
<oc-auth-field-v1>
<input
name="password"
type="password"
aria-describedby="hintPassword"
maxlength="250"
id="inputPassword"
aria-required="true"
/>
<label for="inputPassword">Password</label>
<div slot="hint" id="hintPassword">Enter password</div>
</oc-auth-field-v1>
<br />
<button type="submit">Submit</button>
</form>
`,
play: async ({
canvasElement
}) => {
const form = canvasElement.querySelector("form")!;
const button = canvasElement.querySelector("button")!;
const [emailInput, passwordInput] = Array.from(canvasElement.querySelectorAll("input"));
let formData;
form.addEventListener("submit", event => {
event.preventDefault();
formData = Object.fromEntries(new FormData(form).entries());
});
await userEvent.type(emailInput, "foo@bar.de");
await userEvent.type(passwordInput, "MyTestPassword123");
await click(button);
await expect(formData).toStrictEqual({
email: "foo@bar.de",
password: "MyTestPassword123"
});
}
}