Skip to content

Migrate kc themes to vaadin - #33

Draft
Ekhorn wants to merge 9 commits into
mainfrom
28-migrate-kc-themes-to-vaadin
Draft

Migrate kc themes to vaadin#33
Ekhorn wants to merge 9 commits into
mainfrom
28-migrate-kc-themes-to-vaadin

Conversation

@Ekhorn

@Ekhorn Ekhorn commented Aug 7, 2026

Copy link
Copy Markdown

Description

Checklist

  • 1. Acceptance criteria of the linked issue(s) are met
  • 2. Tests are written and all tests pass
  • 3. Changes are manually tested by you and the reviewer
  • 4. Documentation is written or updated

@Ekhorn Ekhorn self-assigned this Aug 7, 2026
@Ekhorn
Ekhorn force-pushed the 28-migrate-kc-themes-to-vaadin branch from 7b2e9fe to 733a4af Compare August 7, 2026 12:05
@Ekhorn
Ekhorn force-pushed the 28-migrate-kc-themes-to-vaadin branch from 733a4af to 49f1d39 Compare August 7, 2026 14:31
@Ekhorn
Ekhorn force-pushed the 28-migrate-kc-themes-to-vaadin branch from 49f1d39 to 0343e75 Compare August 7, 2026 14:34
Vaadin's InputControlMixin manages the slotted <input>: it replaces the id
and silently drops name, value, required and autocomplete, re-delegating
its own from the host element. Setting them on the input rendered a
correct-looking but unnamed field, so every form posted nothing but its
submit button (login=).

Move them onto the component in field() and on the two hand-written
checkboxes, and hide Lumo's required indicator, which the design does not
show.

@wborn wborn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was AI-assisted. The implementation and relevant Keycloak/OpenRemote templates were compared to identify behavioral and compatibility differences.

Thanks for the work on this migration. The overall approach looks promising: using the OpenRemote Vaadin design system, keeping the page implementations typed and relatively small, retrieving branding from the manager configuration, and falling back to Keycloak's parent theme for pages OpenRemote does not customize should make the theme considerably easier to maintain.

The implementation was also compared with the old OpenRemote FreeMarker theme and the Keycloak 26.7 templates. The OTP/TOTP implementation in particular looks good and preserves the important behavior from the old theme.

There are, however, a few compatibility gaps that should be addressed before merging:

  • The login page no longer handles Keycloak's hidden/preselected username state and loses the selected credential state.
  • Some shared authentication controls are missing, including the locale selector and "try another way".
  • Registration uses the Keycloak user profile, but currently treats essentially every custom attribute as a text field and doesn't handle terms acceptance or the full reCAPTCHA behavior.
  • Global message suppression only knows about a small hard-coded set of fields and can therefore duplicate validation errors.
  • The old login page's self-registration link has intentionally been removed. If that is a product decision, that is fine, but it does mean the new theme is not fully feature-equivalent to the old one.

These compatibility issues should therefore either be addressed or explicitly documented as intended changes before merging.

<form id="kc-form-login" action=${url.loginAction} method="post">
${field({
kcContext,
name: "username",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we preserve Keycloak's username-hidden flow here? The old OpenRemote template already handled the equivalent usernameEditDisabled state, and Keycloak 26.7 now uses usernameHidden: it omits the username field and Remember me in that state. The stock template also carries auth.selectedCredential as credentialId.

As written, a flow where Keycloak has already established the username will render and submit an editable username again, and selected-credential state is lost. Could we mirror the relevant Keycloak 26.7 login semantics here?

`
: null}
`
// No "New user? Register" footer: the design's login card ends at "Forgot password?" and

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old OpenRemote theme exposed self-registration here when the realm allowed it, and Keycloak 26.7 still does the same. Removing the link means a realm with self-registration enabled no longer exposes that capability from its login page.

If dropping this is an intentional product decision that's fine, but otherwise the conditional registration link should probably be retained.

Comment thread theme/ui/src/layout.ts
<span>${message.summary}</span>
</div>`
: null}
${content}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shared layout should preserve the common Keycloak flow controls rather than only the page body. At minimum the old OpenRemote theme had the locale selector, and Keycloak 26.7's shared template also renders auth.showTryAnotherWayLink() and the organization-switch action when enabled.

Without tryAnotherWay, a user can lose the UI for switching to another configured authentication method. Could we add these shared controls to the new layout, including the locale selector when multiple languages are available?

and free text for anything a realm has added; advancedMsgStr handles both,
and falls back to the attribute name when there is neither. */
label: advancedMsgStr(attribute.displayName ?? attribute.name),
type: attribute.name === "email" ? "email" : "text",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this now drives registration from profile.attributesByName, the profile field metadata should also be honored rather than treating every custom attribute as text. Keycloak supports selects/multiselects, radio/checkbox options, textareas, multivalued/read-only fields and HTML5 annotations here.

There are also registration controls outside the profile, notably termsAcceptanceRequired and the visible/invisible reCAPTCHA variants. With terms required, for example, this form currently has no termsAccepted control at all, so the user cannot complete registration.

Could we either implement the supported Keycloak registration semantics or deliberately fall back to the parent registration page for configurations we don't support?

Comment thread theme/ui/src/layout.ts
*/
export function layout(options: LayoutOptions): TemplateResult {
const { kcContext, heading, intro, back, suppressWarning, content } = options;
const hasFieldError = kcContext.messagesPerField.existsError(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This global field list looks fragile now that registration is profile-driven. Errors for firstName, lastName, password-confirm, userLabel, termsAccepted, or any custom profile attribute aren't included, so field() can render the field error while the same validation failure is also shown in the global alert.

Could the page tell layout() which fields it owns, or could we follow Keycloak's distinction between global and field messages instead of maintaining a hard-coded list here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate Keycloak OpenRemote templates to Vaadin

2 participants