Login is a component that contains a log-in form. You can use it to authenticate the user with a username and password.
It’s compatible with password managers, supports internationalization, and works on all device sizes.
package com.vaadin.demo.component.login;
import com.vaadin.flow.component.login.LoginForm;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.router.Route;
@Route("login-basic")
public class LoginBasic extends Div {
public LoginBasic() {
// Demo purposes only
getStyle().set("display", "flex").set("justify-content", "center");
// tag::snippet[]
LoginForm loginForm = new LoginForm();
add(loginForm);
// end::snippet[]
// Prevent the example from stealing focus when browsing the
// documentation
loginForm.getElement().setAttribute("no-autofocus", "");
}
}
login-basic.tsx
import React from 'react';
import { LoginForm } from '@vaadin/react-components/LoginForm.js';
function Example() {
return (
<>
{/* tag::snippet[] */}
{/* no-autofocus is used to prevent the example from stealing focus when browsing the
documentation */}
<LoginForm no-autofocus />
{/* end::snippet[] */}
</>
);
}
login-basic.ts
import '@vaadin/login/vaadin-login-form.js';
import { css, html, LitElement } from 'lit';
import { customElement } from 'lit/decorators.js';
import { applyTheme } from 'Frontend/demo/theme';
@customElement('login-basic')
export class Example extends LitElement {
static override styles = css`
:host {
display: flex !important;
justify-content: center;
}
`;
protected override createRenderRoot() {
const root = super.createRenderRoot();
applyTheme(root);
return root;
}
protected override render() {
return html`
<!-- tag::snippet[] -->
<!-- no-autofocus is used to prevent the example from stealing focus when browsing the documentation -->
<vaadin-login-form no-autofocus></vaadin-login-form>
<!-- end::snippet[] -->
`;
}
}
Basic Login Component
The basic Login component consists of a title (i.e., "Log In"), two input fields ("Username" and "Password"), and two buttons ("Log In" and "Forgot Password").
You can customize the form’s title and labels using internationalization.
package com.vaadin.demo.component.login;
import com.vaadin.flow.component.login.LoginForm;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.router.Route;
@Route("login-rich-content")
public class LoginRichContent extends Div {
public LoginRichContent() {
// tag::snippet[]
// See login-rich-content.css
addClassName("login-rich-content");
LoginForm loginForm = new LoginForm();
loginForm.getElement().getThemeList().add("dark");
// end::snippet[]
add(loginForm);
// Prevent the example from stealing focus when browsing the
// documentation
loginForm.getElement().setAttribute("no-autofocus", "");
}
}
Login is incompatible with password managers if placed inside another component’s shadow root. [1]
Handle the Login Event
When the user submits the login form, the Login component fires a login event with the entered username and password. Listen for this event to authenticate the credentials and handle the result.
The log-in button is disabled after submission to prevent multiple submissions while the login attempt is being processed. If authentication fails, make the form available for another attempt.
package com.vaadin.demo.component.login;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.login.AbstractLogin;
import com.vaadin.flow.component.login.LoginForm;
import com.vaadin.flow.router.Route;
@Route("login-event")
public class LoginEventExample extends Div {
public LoginEventExample() {
// Demo purposes only
getStyle().set("display", "flex").set("justify-content", "center");
// tag::snippet[]
var loginForm = new LoginForm();
loginForm.addLoginListener(this::onLogin);
add(loginForm);
// end::snippet[]
// Prevent the example from stealing focus when browsing the
// documentation
loginForm.getElement().setAttribute("no-autofocus", "");
}
private void onLogin(AbstractLogin.LoginEvent loginEvent) {
var login = loginEvent.getSource();
try {
// Demo purposes only
Thread.sleep(1000);
} catch (InterruptedException e) {
// Do nothing
}
login.setEnabled(true); // re-enable login button
}
}
login-event.tsx
import React from 'react';
import { useSignal } from '@vaadin/hilla-react-signals';
import { LoginForm, type LoginFormDisabledChangedEvent } from '@vaadin/react-components/LoginForm.js';
import { loginHostStyles } from './login-host-styles';
function Example() {
const disabled = useSignal<boolean>(false);
return (
<>
{/* tag::snippet[] */}
{/* no-autofocus is used to prevent the example from stealing focus when browsing the
documentation */}
<LoginForm
no-autofocus
disabled={disabled.value}
onDisabledChanged={(event: LoginFormDisabledChangedEvent) => {
disabled.value = event.detail.value;
}}
onLogin={() => {
setTimeout(() => {
disabled.value = false; // Re-enable login button
}, 1000);
}}
/>
{/* end::snippet[] */}
</>
);
}
login-event.ts
import '@vaadin/login/vaadin-login-form.js';
import { css, html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import type { LoginFormDisabledChangedEvent } from '@vaadin/login/vaadin-login-form.js';
import { applyTheme } from 'Frontend/demo/theme';
@customElement('login-event')
export class Example extends LitElement {
@state()
private disabled = false;
static override styles = css`
:host {
display: flex !important;
justify-content: center;
}
`;
protected override createRenderRoot() {
const root = super.createRenderRoot();
applyTheme(root);
return root;
}
protected override render() {
return html`
<!-- tag::snippet[] -->
<!-- no-autofocus is used to prevent the example from stealing focus when browsing the documentation -->
<vaadin-login-form
no-autofocus
.disabled=${this.disabled}
@login=${this.onLogin}
@disabled-changed=${this.onDisabledChanged}
></vaadin-login-form>
<!-- end::snippet[] -->
`;
}
private onDisabledChanged(event: LoginFormDisabledChangedEvent) {
this.disabled = event.detail.value;
}
private onLogin() {
setTimeout(() => {
this.disabled = false; // Re-enable login button
}, 1000);
}
}
Modal Overlay
Login features its own modal overlay which is also modal on server side. Use it to create simple log-in pages — which are full-screen on mobile devices — or to handle authentication without a dedicated log-in page. You can also use it to handle re-authentication when the user’s session has expired.
The overlay can be opened programmatically or through user interaction (e.g., by using a log-in button).
The overlay has a header and the log-in form. By default, the header contains placeholders for the application’s title and description. Both properties are configurable.
package com.vaadin.demo.component.login;
import com.vaadin.flow.component.login.LoginOverlay;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.router.Route;
@Route("login-overlay-header")
public class LoginOverlayHeader extends Div {
public LoginOverlayHeader() {
// tag::snippet[]
LoginOverlay loginOverlay = new LoginOverlay();
loginOverlay.setTitle("TaskMob");
loginOverlay.setDescription("Built with ♥ by Vaadin");
// end::snippet[]
add(loginOverlay);
loginOverlay.setOpened(true);
// Prevent the example from stealing focus when browsing the
// documentation
loginOverlay.getElement().setAttribute("no-autofocus", "");
}
}
login-overlay-header.tsx
import React from 'react';
import { LoginOverlay } from '@vaadin/react-components/LoginOverlay.js';
function Example() {
return (
// tag::snippet[]
<LoginOverlay title="TaskMob" description="Built with ♥ by Vaadin" opened no-autofocus />
// end::snippet[]
);
}
login-overlay-header.ts
import '@vaadin/login';
import { html, LitElement } from 'lit';
import { customElement } from 'lit/decorators.js';
import { applyTheme } from 'Frontend/demo/theme';
@customElement('login-overlay-header')
export class Example extends LitElement {
protected override createRenderRoot() {
const root = super.createRenderRoot();
applyTheme(root);
return root;
}
protected override render() {
return html`
<!-- tag::snippet[] -->
<!-- no-autofocus is used to prevent the example from stealing focus when browsing the documentation -->
<vaadin-login-overlay
title="TaskMob"
description="Built with ♥ by Vaadin"
opened
no-autofocus
></vaadin-login-overlay>
<!-- end::snippet[] -->
`;
}
}
Custom Form Area
The overlay provides a custom form area for adding fields in addition to username and password. This area is placed above the "Submit" button. Use the name attribute to ensure the custom field’s value is submitted with the form.
package com.vaadin.demo.component.login;
import com.vaadin.flow.component.login.LoginOverlay;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Paragraph;
import com.vaadin.flow.router.Route;
@Route("login-overlay-footer")
public class LoginOverlayFooter extends Div {
public LoginOverlayFooter() {
// tag::snippet[]
LoginOverlay loginOverlay = new LoginOverlay();
Paragraph text = new Paragraph("Never tell your password to anyone");
text.getStyle().set("text-align", "center");
loginOverlay.getFooter().add(text);
// end::snippet[]
add(loginOverlay);
loginOverlay.setOpened(true);
// Prevent the example from stealing focus when browsing the
// documentation
loginOverlay.getElement().setAttribute("no-autofocus", "");
}
}
login-overlay-footer.tsx
import React from 'react';
import { LoginOverlay } from '@vaadin/react-components/LoginOverlay.js';
function Example() {
return (
// tag::snippet[]
<LoginOverlay opened no-autofocus>
<p slot="footer" style={{ textAlign: 'center' }}>
Never tell your password to anyone
</p>
</LoginOverlay>
// end::snippet[]
);
}
login-overlay-footer.ts
import '@vaadin/login';
import { html, LitElement } from 'lit';
import { customElement } from 'lit/decorators.js';
import { applyTheme } from 'Frontend/demo/theme';
@customElement('login-overlay-footer')
export class Example extends LitElement {
protected override createRenderRoot() {
const root = super.createRenderRoot();
applyTheme(root);
return root;
}
protected override render() {
return html`
<!-- tag::snippet[] -->
<!-- no-autofocus is used to prevent the example from stealing focus when browsing the documentation -->
<vaadin-login-overlay opened no-autofocus>
<p slot="footer" style="text-align: center">Never tell your password to anyone</p>
</vaadin-login-overlay>
<!-- end::snippet[] -->
`;
}
}
Validation
Login shows an error message when authentication fails. The error message includes a title in addition to the message. It’s displayed directly below the title of the form.
package com.vaadin.demo.component.login;
import com.vaadin.flow.component.login.LoginOverlay;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.router.Route;
@Route("login-validation")
public class LoginValidation extends Div {
public LoginValidation() {
LoginOverlay loginOverlay = new LoginOverlay();
// tag::snippet[]
loginOverlay.setError(true);
// end::snippet[]
add(loginOverlay);
loginOverlay.setOpened(true);
// Prevent the example from stealing focus when browsing the
// documentation
loginOverlay.getElement().setAttribute("no-autofocus", "");
}
}
login-validation.tsx
import React from 'react';
import { LoginOverlay } from '@vaadin/react-components/LoginOverlay.js';
// tag::snippet[]
function Example() {
return <LoginOverlay opened error no-autofocus />;
}
// end::snippet[]
login-validation.ts
import '@vaadin/login';
import { html, LitElement } from 'lit';
import { customElement } from 'lit/decorators.js';
import { applyTheme } from 'Frontend/demo/theme';
@customElement('login-validation')
export class Example extends LitElement {
protected override createRenderRoot() {
const root = super.createRenderRoot();
applyTheme(root);
return root;
}
protected override render() {
return html`
<!-- tag::snippet[] -->
<!-- no-autofocus is used to prevent the example from stealing focus when browsing the documentation -->
<vaadin-login-overlay opened error no-autofocus></vaadin-login-overlay>
<!-- end::snippet[] -->
`;
}
}
The error message is customizable using internationalization. It should contain instructions on how to resolve the problem.
More information can be provided to the user, for example, by linking to a page with helpful material or by displaying contact information.
package com.vaadin.demo.component.login;
import com.vaadin.flow.component.login.LoginI18n;
import com.vaadin.flow.component.login.LoginOverlay;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.router.Route;
@Route("login-additional-information")
public class LoginAdditionalInformation extends Div {
public LoginAdditionalInformation() {
// tag::snippet[]
LoginI18n i18n = LoginI18n.createDefault();
i18n.setAdditionalInformation(
"Contact admin@company.com if you're experiencing issues logging into your account");
LoginOverlay loginOverlay = new LoginOverlay();
loginOverlay.setI18n(i18n);
// end::snippet[]
add(loginOverlay);
loginOverlay.setOpened(true);
// Prevent the example from stealing focus when browsing the
// documentation
loginOverlay.getElement().setAttribute("no-autofocus", "");
}
}
login-additional-information.tsx
import React, { useEffect, useRef } from 'react';
import { LoginOverlay, type LoginOverlayElement } from '@vaadin/react-components/LoginOverlay.js';
function Example() {
// tag::snippet[]
const loginRef = useRef<LoginOverlayElement>(null);
useEffect(() => {
if (loginRef.current) {
loginRef.current.i18n = {
...loginRef.current.i18n,
additionalInformation: `Contact admin@company.com if you're experiencing issues logging into your account`,
};
}
}, [loginRef.current]);
return <LoginOverlay ref={loginRef} opened />;
// end::snippet[]
}
login-additional-information.ts
import '@vaadin/login';
import { html, LitElement } from 'lit';
import { customElement, query } from 'lit/decorators.js';
import type { LoginOverlay } from '@vaadin/login';
import { applyTheme } from 'Frontend/demo/theme';
@customElement('login-additional-information')
export class Example extends LitElement {
protected override createRenderRoot() {
const root = super.createRenderRoot();
applyTheme(root);
return root;
}
// tag::snippet[]
@query('vaadin-login-overlay')
private login!: LoginOverlay;
protected override firstUpdated() {
this.login.i18n = {
...this.login.i18n,
additionalInformation: `Contact admin@company.com if you're experiencing issues logging into your account`,
};
}
protected override render() {
return html`<vaadin-login-overlay opened></vaadin-login-overlay>`;
}
// end::snippet[]
}
Internationalization (i18n)
Login’s titles, descriptions, labels, and messages are all customizable using internationalization.
This token is submitted as _csrf=71dac59f-34ee-4b31-b478-2891cbd0c55d, that is, using the _csrf_parameter content as the variable name and the _csrf content as the value.
B6AA0496-4D82-4C93-B8FB-64148FF9C88C
1. When added to a web component that uses shadow root, password managers are unable to find the input fields and therefore won’t work.