In a previous post, I wrote about the steps I followed to create a Keycloak theme.

In this post, we'll use Material Components for the Web to change the look and feel of Keycloak's Login templates.

Material Components for the Web

I installed Material (Design) Components for the Web (MDC Web) using npm:

npm install -P material-components-web

Themes

A Keycloak theme consists of:

  • FreeMarker templates
  • Stylesheets
  • Scripts
  • Theme properties
  • Images
  • Message bundles

And each theme is comprised of the following categories (types):

  • Account - Account management
  • Admin - Administration console
  • Email - Email templates
  • Login - Login forms
  • Welcome - Welcome page

The directory structure:

├── /serendipity-keycloak-theme
     └── /theme
           └── /account
           └── /admin
           └── /email
           └── /login
                 └── /messages
                 └── /resources
                       └── /css
                       └── /img
                       └── /js
                 ├── login.ftl
                 ├── template.ftl
                 ├── theme.properties
           └── /welcome

Stylesheets

Material Components for the Web uses Sass. I installed Sass globally:

npm install -g sass

Now we'll be able to run the sass executable (from the command line) to compile .sass and .scss files to .css files. For example:

sass --load-path=/Users/robferguson/workspace/Robinyo/serendipity-keycloak-theme/node_modules login.scss:login.css

You can also setup a File Watcher in your IDE, for example:

I created partials (a Sass file named with a leading underscore that contains little snippets of CSS that you can include in other Sass files) for the theme's fonts, icons, palette and typography, for example:

# _typography.scss

$mdc-typography-font-family: "Open Sans", sans-serif;

$mdc-typography-styles-headline4: (
  font-weight: 200
);

I created a Sass file called login.scss that imports the partials and the Sass files for the MDC Web components:

@import "./fonts";
@import "./icons";
@import "./palette";
@import "./typography";
@import "@material/button/mdc-button";
@import "@material/card/mdc-card";
@import "@material/checkbox/mdc-checkbox";
@import "@material/form-field/mdc-form-field";
@import "@material/list/mdc-list";
@import "@material/textfield/mdc-text-field";
@import "@material/typography/mdc-typography";

html, body {
  width: 100vw;
  height: 100vh;
}

...

Scripts

I created a JavaScipt file called login.js that initialises the MDC Web components:

window.onload = function() {

  // Initialise all MDC text fields
  document.querySelectorAll('.mdc-text-field').forEach(function(e) {
    new mdc.textField.MDCTextField(e);
  });

  // Initialise all MDC text field icons
  document.querySelectorAll('.mdc-text-field__icon').forEach(function(e) {
    new mdc.textField.MDCTextFieldIcon(e);
  });

  // Add a ripple effect to all MDC buttons
  document.querySelectorAll('.mdc-button').forEach(function(e) {
    mdc.ripple.MDCRipple.attachTo(e);
  });  

};

Theme properties

Each category (type) has a configuration file named theme.properties.

I updated the Login theme.properties as follows:

...

styles=css/login.css
scripts=js/material-components-web.min.js js/login.js

...

Message bundles

I copied the English language properties file (messages_en.properties) from the base theme and updated it as follows:

doLogIn=Sign in
doRegister=Sign up

noAccount=Don''t have an Account?

...

FreeMarker templates

Keycloak uses templates written in the FreeMarker Template Language (FTL) to generate HTML.

Keycloak's Server Developer guide recommends that when you create a custom template, that you copy the template from the base theme to your own theme, then apply the modifications you need.

I copied the category template (template.ftl) and the Login template (login.ftl) from the base theme and updated them to use MDC Web components, for example:

<#-- login.ftl  -->

...

<div class="${properties.kcFormGroupClass!}">

    <div class="mdc-text-field mdc-text-field--with-leading-icon ${properties.kcLabelClass!} <#if usernameEditDisabled??>mdc-text-field--disabled</#if>">
        <i class="material-icons mdc-text-field__icon" role="button">person</i>
        <input tabindex="0" required id="username" class="mdc-text-field__input ${properties.kcInputClass!}" name="username" value="${(login.username!'')}" type="text" autofocus autocomplete="off" <#if usernameEditDisabled??>disabled</#if>>
        <div class="mdc-line-ripple"></div>
        <label for="username" class="mdc-floating-label ${properties.kcLabelClass!}">
            <#if !realm.loginWithEmailAllowed>
                ${msg("username")}
            <#elseif !realm.registrationEmailAsUsername>
                ${msg("usernameOrEmail")}
            <#else>
                ${msg("email")}
            </#if>
        </label>
    </div>

</div>

...

The Login page:

Source Code:
References: