<#import "/templates/guide.adoc" as tmpl>
<#import "/templates/links.adoc" as links>

<@tmpl.guide
title="Working with themes"
priority=20
summary="Understand how to create and configure themes.">

{project_name} provides theme support for web pages and emails. This allows customizing the look and feel of end-user facing pages so they can be
integrated with your applications.

image::ui-customization/login-sunrise.png[caption="",title="Login page with sunrise example theme"]

== Theme types

A theme can provide one or more types to customize different aspects of {project_name}. The types available are:

* Account - Account Console
* Admin - Admin Console
* Email - Emails
* Login - Login forms
* Welcome - Welcome page

== Configuring a theme

All theme types, except welcome, are configured through the Admin Console.

.Procedure

. Log into the Admin Console.
. Select your realm from the drop-down box in the top left corner.
. Click *Realm Settings* from the menu.
. Click the *Themes* tab.
+
NOTE: To set the theme for the `master` Admin Console you need to set the Admin Console theme for the `master` realm.
+
. To see the changes to the Admin Console refresh the page.

. Change the welcome theme by using the `spi-theme--welcome-theme` option.

. For example:
+
[source,bash]
----
bin/kc.[sh|bat] start --spi-theme--welcome-theme=custom-theme
----

[[_default-themes]]
== Default themes

{project_name} comes bundled with default themes in the JAR file `keycloak-themes-{project_versionMvn}.jar` inside the server distribution.
The server's root `themes` directory does not contain any themes by default, but it contains a README file with some additional details about the default themes.
To simplify upgrading, do not edit the bundled themes directly. Instead create your own theme that extends one of the bundled themes.

[[_creating-a-theme]]
== Creating a theme

A theme consists of:

* HTML templates (https://freemarker.apache.org/[Freemarker Templates])
* Images
* Message bundles
* Stylesheets
* Scripts
* Theme properties

Unless you plan to replace every single page you should extend another theme. Most likely you will want to extend some existing theme. Alternatively, if you intend to provide your own implementation of the admin or account console,
consider extending the `base` theme. The `base` theme consists of a message bundle and therefore such implementation needs to start from scratch, including implementation of the main `index.ftl` Freemarker template, but it can leverage existing translations from the message bundle.

When extending a theme you can override individual resources (templates, stylesheets, etc.). If you decide to override HTML templates bear in mind that you may
need to update your custom template when upgrading to a new release.

While creating a theme it's a good idea to disable caching as this makes it possible to edit theme resources directly from the `themes` directory without
restarting {project_name}.

.Procedure

. Run Keycloak with the following options:
+
[source,bash]
----
bin/kc.[sh|bat] start --spi-theme--static-max-age=-1 --spi-theme--cache-themes=false --spi-theme--cache-templates=false
----

. Create a directory in the `themes` directory.
+
The name of the directory becomes the name of the theme. For example to
create a theme called `mytheme` create the directory `themes/mytheme`.

. Inside the theme directory, create a directory for each of the types your theme is going to provide.
+
For example, to add the login type to the `mytheme` theme, create the directory `themes/mytheme/login`.

. For each type create a file `theme.properties` which allows setting some configuration for the theme.
+
For example, to configure the theme `themes/mytheme/login` to extend the `base` theme and import some common resources, create the file `themes/mytheme/login/theme.properties` with following contents:
+
[source]
----
parent=base
import=common/keycloak
----
+
You have now created a theme with support for the login type.

. Log into the Admin Console to check out your new theme
. Select your realm
. Click *Realm Settings* from the menu.
. Click on the *Themes* tab.
. For *Login Theme* select *mytheme* and click *Save*.
. Open the login page for the realm.
+
You can do this either by logging in through your application or by opening the Account Console (`/realms/{realm-name}/account`).

. To see the effect of changing the parent theme, set `parent=keycloak` in `theme.properties` and refresh the login page.

[NOTE]
====
Be sure to re-enable caching in production as it will significantly impact performance.
====
[NOTE]
====
If you want to manually delete the content of the themes cache, you can do so by deleting the `data/tmp/kc-gzip-cache` directory of the server distribution.
It can be useful for instance if you redeployed custom providers or custom themes without disabling themes caching in the previous server executions.
====

=== Theme properties

Theme properties are set in the file `<THEME TYPE>/theme.properties` in the theme directory.

* parent - Parent theme to extend
* import - Import resources from another theme
* common - Override the common resource path. The default value is `common/keycloak` when not specified. This value would be used as value of suffix of ${r"`${url.resourcesCommonPath}`"}, which is used typically in freemarker templates (prefix of ${r"`${url.resoucesCommonPath}`"} value is theme root uri).
* styles - Space-separated list of styles to include
* locales - Comma-separated list of supported locales
* contentHashPattern - Regex pattern of a file path in the theme where files have a content hash as part of their file name.
A content hash is usually an abbreviated hash of the file's contents. The hash will change when the contents of the file have changed, and is usually created using the bundling process of the JavaScript application bundling. When the preview feature `rolling-updates:v2` is enabled, this allows for a more seamless rolling upgrade.

There are a list of properties that can be used to change the css class used for certain element types. For a list of these properties look at the theme.properties
file in the corresponding type of the keycloak theme (`themes/keycloak/<THEME TYPE>/theme.properties`).

You can also add your own custom properties and use them from custom templates.

When doing so, you can substitute system properties or environment variables by using these formats:

* ${r"`${some.system.property}`"} - for system properties
* ${r"`${env.ENV_VAR}`"} - for environment variables.

A default value can also be provided in case the system property or the environment variable is not found with ${r"`${foo\:defaultValue}`"}.

NOTE: If no default value is provided and there's no corresponding system property or environment variable, then nothing is replaced and you end up with the format in your template.

Here's an example of what is possible:

[source,properties]
----
javaVersion=${r"${java.version}"}

unixHome=${r"${env.HOME:Unix home not found}"}
windowsHome=${r"${env.HOMEPATH:Windows home not found}"}
----

[[_theme_stylesheet]]
=== Add a stylesheet to a theme

You can add one or more stylesheets to a theme.

.Procedure

. Create a file in the `<THEME TYPE>/resources/css` directory of your theme.

. Add this file to the `styles` property in `theme.properties`.
+
For example, to add `styles.css` to the `mytheme`, create `themes/mytheme/login/resources/css/styles.css` with the following content:
+
[source,css]
----
.login-pf body {
    background: DimGrey none;
}
----

. Edit `themes/mytheme/login/theme.properties` and add:
+
[source]
----
styles=css/styles.css
----

. To see the changes, open the login page for your realm.
+
You will notice that the only styles being applied are those from your custom stylesheet.

. To include the styles from the parent theme, load the styles from that theme. Edit `themes/mytheme/login/theme.properties` and change `styles` to:
+
[source]
----
styles=css/login.css css/styles.css
----
+
NOTE: To override styles from the parent stylesheets, ensure that your stylesheet is listed last.

=== Adding a script to a theme

You can add one or more scripts to a theme.

.Procedure

. Create a file in the `<THEME TYPE>/resources/js` directory of your theme.

. Add the file to the `scripts` property in `theme.properties`.
+
For example, to add `script.js` to the `mytheme`, create `themes/mytheme/login/resources/js/script.js` with the following content:
+
[source,javascript]
----
alert('Hello');
----
+
Then edit `themes/mytheme/login/theme.properties` and add:
+
[source]
----
scripts=js/script.js
----

=== Adding an image to a theme

To make images available to the theme add them to the `<THEME TYPE>/resources/img` directory of your theme. These can be used from within stylesheets or
directly in HTML templates.

For example to add an image to the `mytheme` copy an image to `themes/mytheme/login/resources/img/image.jpg`.

You can then use this image from within a custom stylesheet with:

[source,css]
----
body {
    background-image: url('../img/image.jpg');
    background-size: cover;
}
----

Or to use directly in HTML templates add the following to a custom HTML template:

[source,html]
----
<img src="${r"${url.resourcesPath}"}/img/image.jpg" alt="My image description">
----

[[_theme_custom_footer]]
=== Adding a custom footer to a login theme

In order to use a custom footer, create a `footer.ftl` file in your custom login theme with the desired content.

An example for a custom `footer.ftl` may look like this:

[source]
----
<#noparse>
<#macro content>
<#-- footer at the end of the login box -->
<div>
    <ul id="kc-login-footer-links">
        <li><a href="#home">Home</a></li>
        <li><a href="#about">About</a></li>
        <li><a href="#contact">Contact</a></li>
    </ul>
</div>
</#macro>
</#noparse>
----

=== Adding an image to an email theme

To make images available to the theme add them to the `<THEME TYPE>/email/resources/img` directory of your theme. These can be used from within directly in HTML templates.

For example to add an image to the `mytheme` copy an image to `themes/mytheme/email/resources/img/logo.jpg`.

To use directly in HTML templates add the following to a custom HTML template:

[source,html]
----
<img src="${r"${url.resourcesUrl}"}/img/image.jpg" alt="My image description">
----

[[custom-identity-providers-icons]]
=== Adding custom Identity Providers icons

{project_name} supports adding icons for custom Identity providers, which are displayed on the login screen.

.Procedure

. Define icon classes in your login `theme.properties` file (for example, `themes/mytheme/login/theme.properties`) with key pattern `kcLogoIdP-<alias>`.

. For an Identity Provider with an alias `myProvider`, you may add a line to `theme.properties` file of your custom theme. For example:
+
[source]
----
kcLogoIdP-myProvider = fa fa-lock
----

All icons are available on the official website of PatternFly4.
Icons for social providers are already defined in `base` login theme properties (`themes/keycloak/login/theme.properties`), where you can inspire yourself.

=== Creating a custom HTML template

{project_name} uses https://freemarker.apache.org/[Apache Freemarker] templates to generate HTML and render pages.

Although it is possible to create custom templates to change completely how pages are rendered, the recommendation is to leverage the built-in
templates as much as possible. The reasons are:

* During upgrades, you might be forced to update your custom templates to get the latest updates from newer versions
* <<_theme_stylesheet,Configuring CSS styles>> to your themes allows you to adapt the UI to match your UI design standards and guidelines.
* link:{adminguide_link}#user-profile[User Profile] allows you to support custom user attributes and configure how they are rendered.

In most cases, you won't need to change templates to adapt {project_name} to your needs, but you can override individual
templates in your own theme by creating `<THEME TYPE>/<TEMPLATE>.ftl`.
Admin and account console use a single template `index.ftl` for rendering the application.

For a list of templates in other theme types look at the `theme/base/<THEME_TYPE>` directory in the JAR file at `$KEYCLOAK_HOME/lib/lib/main/org.keycloak.keycloak-themes-<VERSION>.jar`.

.Procedure

. Copy the template from the base theme to your own theme.

. Apply the modifications you need.
+
For example, to create a custom login form for the `mytheme` theme, copy `themes/base/login/login.ftl` to `themes/mytheme/login` and open it in an editor.
+
After the first line (`+<#noparse><#import ...></#noparse>+`), add `<h1>HELLO WORLD!</h1>` as shown here:
+
[source,html]
----
<#noparse>
<#import "template.ftl" as layout>
<h1>HELLO WORLD!</h1>
...
</#noparse>
----

. Back up the modified template. When upgrading to a new version of {project_name} you may need to update your custom templates to apply changes to the original template if applicable.

[role="_additional-resources"]
.Additional resources
* See the https://freemarker.apache.org/docs/index.html[FreeMarker Manual] for details on how to edit templates.

=== Emails

To edit the subject and contents for emails, for example password recovery email, add a message bundle to the `email` type of your theme. There are three messages for each email. One for the subject, one for the plain text body and one for the html body.

To see all emails available take a look at `themes/base/email/messages/messages_en.properties`.

For example to change the password recovery email for the `mytheme` theme create `themes/mytheme/email/messages/messages_en.properties` with the following
content:
[source]
----
passwordResetSubject=My password recovery
passwordResetBody=Reset password link: {0}
passwordResetBodyHtml=<a href="{0}">Reset password</a>
----

== Deploying themes

Themes can be deployed to {project_name} by copying the theme directory to `themes` or it can be deployed as an archive. During development you can copy the
theme to the `themes` directory, but in production you may want to consider using an `archive`. An `archive` makes it simpler to have a versioned copy of
the theme, especially when you have multiple instances of {project_name} for example with clustering.

WARNING: Themes contain FreeMarker templates that the server renders at runtime, so a malicious template can run code as the {project_name} process. Install themes only from trusted sources, and restrict write access to the `themes` directory and to theme JARs under `providers/` to trusted operators.


.Procedure

. To deploy a theme as an archive, create a JAR archive with the theme resources.
. Add a file `META-INF/keycloak-themes.json` to the
archive that lists the available themes in the archive as well as what types each theme provides.
+
For example for the `mytheme` theme create `mytheme.jar` with the contents:
+
* META-INF/keycloak-themes.json
* theme/mytheme/login/theme.properties
* theme/mytheme/login/login.ftl
* theme/mytheme/login/resources/css/styles.css
* theme/mytheme/login/resources/img/image.png
* theme/mytheme/login/messages/messages_en.properties
* theme/mytheme/email/messages/messages_en.properties
+
The contents of `META-INF/keycloak-themes.json` in this case would be:
+
[source,json]
----
{
    "themes": [{
        "name" : "mytheme",
        "types": [ "login", "email" ]
    }]
}
----
+
A single archive can contain multiple themes and each theme can support one or more types.

To deploy the archive to {project_name}, add it to the `providers/` directory of
{project_name} and restart the server if it is already running.

== Dark Mode
Dark mode is a feature that changes the color scheme to a dark background with lighter text.  For example, themes based on PatternFly, such as Keycloak's admin console and account console, support dark mode.  But for some themes, dark mode doesn't work well for various reasons.  So Keycloak allows you to disable dark mode from the admin console.

.Procedure to disable dark mode

. Log into the Admin Console.
. Select your realm.
. Click *Realm Settings* from the menu.
. Click on the *Themes* tab.
. Unselect *Dark mode* and click *Save*.

If enabled, the dark variant of the theme will be applied based on user preference through an operating system setting (e.g. light or dark mode) or a user agent setting. If disabled, only the light variant will be used. This setting only applies to themes that support dark and light variants. On themes that do not support this feature it will have no effect.

[role="_additional-resources"]
== Additional resources for Themes
* For more inspiration, see <<_default-themes,Default Themes>> bundled inside {project_name}.
* {quickstartRepo_link}[{quickstartRepo_name}] - Directory `extension` of the quickstarts repository contains some theme examples, which can be also used as an inspiration.
</@tmpl.guide>

== Theme SPIs

The following sections show how to achieve finer grained control over your themes.

=== Theme selector

By default the theme configured for the realm is used, with the exception of clients being able to override the login
theme. This behavior can be changed through the Theme Selector SPI.

This could be used to select different themes for desktop and mobile devices by looking at the user agent
header, for example.

To create a custom theme selector you need to implement `ThemeSelectorProviderFactory` and `ThemeSelectorProvider`.

=== Theme resources

When implementing custom providers there may often be a need to add additional templates, resources and messages bundles.

ifeval::[{project_product}!=true]
An example use-case would be a https://www.keycloak.org/docs/latest/server_development/index.html#_auth_spi[custom authenticator] that requires additional templates and resources.
endif::[]

The easiest way to load additional theme resources is to create a JAR with templates in `theme-resources/templates`
resources in `theme-resources/resources` and messages bundles in `theme-resources/messages`.

If you want a more flexible way to load templates and resources that can be achieved through the ThemeResourceSPI.
By implementing `ThemeResourceProviderFactory` and `ThemeResourceProvider` you can decide exactly how to load templates
and resources.

=== Locale selector

By default, the locale is selected using the `DefaultLocaleSelectorProvider` which implements the `LocaleSelectorProvider` interface. English is the default language when internationalization is disabled.

With internationalization enabled, the locale is resolved according to the logic described in the https://www.keycloak.org/docs/latest/server_admin/#_user_locale_selection[Server Administration documentation].

This behavior can be changed through the `LocaleSelectorSPI` by implementing the `LocaleSelectorProvider` and `LocaleSelectorProviderFactory`.

The `LocaleSelectorProvider` interface has a single method, `resolveLocale`, which must return a locale given a `RealmModel` and a nullable `UserModel`. The actual request is available from the `KeycloakSession#getContext` method.

Custom implementations can extend the `DefaultLocaleSelectorProvider` in order to reuse parts of the default behavior. For example to ignore the `Accept-Language` request header, a custom implementation could extend the default provider, override it's `getAcceptLanguageHeaderLocale`, and return a null value. As a result the locale selection will fall back on the realm's default language.

=== Additional resources for Locale selector
* For more details on creating and deploying a custom provider, see https://www.keycloak.org/docs/latest/server_development/index.html#_providers[Service Provider Interfaces].
