Claude
Skills
Sign in
Back

keycloak-theming

Included with Lifetime
$97 forever

Multi-tenant Keycloak authentication theming with realm-specific design systems

Design

What this skill does


# Keycloak Theming Skill

Multi-tenant Keycloak authentication theming with realm-specific design systems.

## Overview

This skill provides comprehensive guidance for implementing custom Keycloak themes across multiple realms with tenant-specific branding and design systems.

## Multi-Realm Configuration

### Realm Structure

```
Keycloak Instance
├── thelobbi (Realm)
│   ├── Theme: lobbi-theme
│   ├── Primary Color: #0066cc
│   ├── Logo: lobbi-logo.svg
│   └── Use Case: Main platform authentication
│
└── brooksidebi (Realm)
    ├── Theme: brookside-theme
    ├── Primary Color: #2c5282
    ├── Logo: brookside-logo.svg
    └── Use Case: BI platform authentication
```

### Realm Configuration

**thelobbi Realm:**
```json
{
  "realm": "thelobbi",
  "displayName": "The Lobbi",
  "displayNameHtml": "<div class=\"kc-logo-text\"><span>The Lobbi</span></div>",
  "loginTheme": "lobbi-theme",
  "accountTheme": "lobbi-theme",
  "adminTheme": "keycloak.v2",
  "emailTheme": "lobbi-theme"
}
```

**brooksidebi Realm:**
```json
{
  "realm": "brooksidebi",
  "displayName": "Brookside BI",
  "displayNameHtml": "<div class=\"kc-logo-text\"><span>Brookside BI</span></div>",
  "loginTheme": "brookside-theme",
  "accountTheme": "brookside-theme",
  "adminTheme": "keycloak.v2",
  "emailTheme": "brookside-theme"
}
```

## Custom Theme Structure

### Directory Layout

```
keycloak/
└── themes/
    ├── lobbi-theme/
    │   ├── login/
    │   │   ├── theme.properties
    │   │   ├── resources/
    │   │   │   ├── css/
    │   │   │   │   ├── login.css
    │   │   │   │   └── styles.css
    │   │   │   ├── img/
    │   │   │   │   ├── lobbi-logo.svg
    │   │   │   │   ├── lobbi-icon.svg
    │   │   │   │   └── background.jpg
    │   │   │   └── js/
    │   │   │       └── script.js
    │   │   └── login.ftl
    │   │
    │   ├── account/
    │   │   └── theme.properties
    │   │
    │   └── email/
    │       └── theme.properties
    │
    └── brookside-theme/
        └── [same structure as lobbi-theme]
```

### Theme Properties

**themes/lobbi-theme/login/theme.properties:**
```properties
parent=keycloak
import=common/keycloak

styles=css/login.css css/styles.css
stylesCommon=node_modules/patternfly/dist/css/patternfly.min.css

meta=viewport==width=device-width,initial-scale=1
```

## Login Page Theming Templates (FTL)

### Base Login Template

**themes/lobbi-theme/login/login.ftl:**
```ftl
<#import "template.ftl" as layout>
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','password') displayInfo=realm.password && realm.registrationAllowed && !registrationDisabled??; section>
    <#if section = "header">
        ${msg("loginAccountTitle")}
    <#elseif section = "form">
    <div id="kc-form">
      <div id="kc-form-wrapper">
        <#if realm.password>
            <form id="kc-form-login" onsubmit="login.disabled = true; return true;" action="${url.loginAction}" method="post">
                <div class="${properties.kcFormGroupClass!}">
                    <label for="username" class="${properties.kcLabelClass!}">
                        <#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if>
                    </label>

                    <input tabindex="1" id="username" class="${properties.kcInputClass!}" name="username" value="${(login.username!'')}"  type="text" autofocus autocomplete="off"
                           aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>"
                    />

                    <#if messagesPerField.existsError('username','password')>
                        <span id="input-error" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
                                ${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc}
                        </span>
                    </#if>
                </div>

                <div class="${properties.kcFormGroupClass!}">
                    <label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>

                    <input tabindex="2" id="password" class="${properties.kcInputClass!}" name="password" type="password" autocomplete="off"
                           aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>"
                    />
                </div>

                <div class="${properties.kcFormGroupClass!} ${properties.kcFormSettingClass!}">
                    <div id="kc-form-options">
                        <#if realm.rememberMe && !usernameEditDisabled??>
                            <div class="checkbox">
                                <label>
                                    <#if login.rememberMe??>
                                        <input tabindex="3" id="rememberMe" name="rememberMe" type="checkbox" checked> ${msg("rememberMe")}
                                    <#else>
                                        <input tabindex="3" id="rememberMe" name="rememberMe" type="checkbox"> ${msg("rememberMe")}
                                    </#if>
                                </label>
                            </div>
                        </#if>
                        </div>
                        <div class="${properties.kcFormOptionsWrapperClass!}">
                            <#if realm.resetPasswordAllowed>
                                <span><a tabindex="5" href="${url.loginResetCredentialsUrl}">${msg("doForgotPassword")}</a></span>
                            </#if>
                        </div>

                  </div>

                  <div id="kc-form-buttons" class="${properties.kcFormGroupClass!}">
                      <input type="hidden" id="id-hidden-input" name="credentialId" <#if auth.selectedCredential?has_content>value="${auth.selectedCredential}"</#if>/>
                      <input tabindex="4" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" name="login" id="kc-login" type="submit" value="${msg("doLogIn")}"/>
                  </div>
            </form>
        </#if>
        </div>
    </div>
    <#elseif section = "info" >
        <#if realm.password && realm.registrationAllowed && !registrationDisabled??>
            <div id="kc-registration-container">
                <div id="kc-registration">
                    <span>${msg("noAccount")} <a tabindex="6"
                                                 href="${url.registrationUrl}">${msg("doRegister")}</a></span>
                </div>
            </div>
        </#if>
    </#if>

</@layout.registrationLayout>
```

### Custom Template with Realm-Specific Branding

**themes/lobbi-theme/login/template.ftl:**
```ftl
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="robots" content="noindex, nofollow">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <#if properties.meta?has_content>
        <#list properties.meta?split(' ') as meta>
            <meta name="${meta?split('==')[0]}" content="${meta?split('==')[1]}"/>
        </#list>
    </#if>

    <title>${msg("loginTitle",(realm.displayName!''))}</title>
    <link rel="icon" href="${url.resourcesPath}/img/lobbi-icon.svg" />

    <#if properties.stylesCommon?has_content>
        <#list properties.stylesCommon?split(' ') as style>
            <link href="${url.resourcesCommonPath}/${style}" rel="stylesheet" />
        </#list>
    </#if>
    <#if properties.styles?has_content>
        <#list properties.styles?split(' ') as style>
            <link href="${url.resourcesPath}/${style}" rel="stylesheet" />
        </#list>
    </#if>
</head>

<body class="keycloak-theme ${realm.name}-realm">
    <div class="kc-container">
        <div class="kc-content">
            <

Related in Design