# Quick Start

{% embed url="<https://www.youtube.com/watch?v=S-B8Db2dStk>" %}
Introduction and Tutorial
{% endembed %}

**Keycloakify** is a tool for creating custom Keycloak themes, enabling you to modify the appearance and behavior of Keycloak's user interfaces. This includes:

* **Login Theme**: The UI for login and registration pages, displayed to users when they attempt to log in or sign up.
* **Account Theme**: The account management interface, where users can update their email, change their password, and manage other account settings.
* **Email Theme**: The templates used by Keycloak for automated emails, such as email confirmation or password reset notifications.
* **Admin Theme**: The Admin Console interface, used by administrators to configure Keycloak.

For a visual preview of these UIs as they appear with Keycloak's built-in theme, visit the Storybook demonstration:

{% embed url="<https://storybook.keycloakify.dev/>" %}

Whether you need to apply light CSS-level customizations to the built-in UIs or perform deeper redesigns at the component level using React, Angular, or Svelte, Keycloakify is the tool to achieve your goals.\
\
If you’d like to see Keycloakify in action, check out [Neon](https://neon.tech/). Clicking **Login** or **Sign Up** will take you to their Keycloakify-themed login pages.

## Why Choose Keycloakify?

You might be wondering why you would need a third-party tool like Keycloakify to create your custom UIs instead of relying solely on [Keycloak's built-in theming system](https://www.keycloak.org/docs/latest/server_development/#_themes). Here are a few reasons:

* **Leverage Modern Frontend Technologies**: Keycloakify enables you to use TypeScript, React, Angular, Svelte, and any styling solution or component library you prefer, such as Tailwind, MUI, shadcn/ui, or plain CSS.
* **Streamlined Testing**: Keycloakify makes it easy to [test your theme](/testing-your-theme) both [inside](/testing-your-theme/inside-of-keycloak) and [outside](/testing-your-theme/outside-of-keycloak) Keycloak, with hot reloading for a smoother development experience.
* **Automated Theme Bundling**: Keycloakify [bundles your theme into a JAR file](/deploying-your-theme#building-the-jar-file), ready to import directly into Keycloak.
* **Version Compatibility**: Themes generated with Keycloakify are backward compatible with Keycloak versions as far back as 11 and are [designed to remain compatible with future Keycloak updates](#user-content-fn-1)[^1].
* **Built-In Real-Time Validation**: Keycloakify includes real-time frontend validation by default. For example, users receive instant feedback, such as "The password must be at least 12 characters long," rather than waiting until they press the submit button.
* **Community Support**: We're here to help! If you're stuck or need guidance, reach out through our [Discord channel](https://discord.gg/kYFZG7fQmn) or [GitHub issues](https://github.com/keycloakify/keycloakify/issues/new). We respond quickly and are happy to assist.

If you’re still unsure or want a better understanding before committing to using Keycloakify, check out this guide:

{% content-ref url="/spaces/H3WkQf6kDTNqLCk7O5D7/pages/yRtzq2b7wxEedwsmmWfv" %}
[How does Keycloakify work?](https://doc-old.keycloakify.dev/faq/how-it-works)
{% endcontent-ref %}

## Pick Your Framework: React, Angular or Svelte

Keycloakify supports React, Angular, and Svelte, allowing you to work with the framework you're most familiar with. If you're only making CSS-level customizations to Keycloak's built-in theme, any of these frameworks will work. For a smoother experience, **React** is recommended, as it has the most complete integration.

For Angular and Svelte users, a few considerations apply:

* **Account Themes**: [The starting UI differs from Keycloak's default](#user-content-fn-2)[^2], requiring additional adjustments.
* **Admin Themes**: Only React supports custom Admin UIs. However, since the Admin UI is only seen by the Keycloak instance administrator, it is rarely customized.
* **Angular Setup**: [Keycloakify cannot be directly installed in an existing Angular project](#user-content-fn-3)[^3]. Themes must either be standalone projects or a subproject in a monorepo.

React provides the most seamless experience, but Angular and Svelte are fully supported for login and account themes with some extra effort, which covers the needs of most projects. Choose the framework that best suits your project and expertise. If you have any questions or concerns, feel free to ask us on our [Discord channel](https://discord.gg/kYFZG7fQmn).

## Quick Start

Before futher reading, some practice!

{% tabs %}
{% tab title="React" %}

```bash
git clone https://github.com/keycloakify/keycloakify-starter
```

{% endtab %}

{% tab title="Angular" %}

```bash
git clone https://github.com/keycloakify/keycloakify-starter-angular-vite keycloakify-starter
```

Credit goes to [@kathari00](https://github.com/kathari00) for taking the initiative and driving the development of Angular support.
{% endtab %}

{% tab title="Svelte" %}

```bash
git clone https://github.com/keycloakify/keycloakify-starter-svelte keycloakify-starter
```

Credit goes to [@luca-peruzzo](https://github.com/luca-peruzzo) for taking the initiative and driving the development of Svelte support.
{% endtab %}
{% endtabs %}

Let's create a story for the Login page and run Storybook[^4].

```bash
cd keycloakify-starter

yarn install              # You can use any package manager (npm, pnpm, etc.)
                          # If you do, delete the .yarn.lock file to avoid conflicts.

npx keycloakify add-story # Select login.ftl (for example).
                          # Always use `npx` to run CLI tools installed in your project.
                          # This is standard practice across all modern JS projects,
                          # do not try to adapt this command based on your package 
                          # manager.

npm run storybook         # `npm run <script>`, `yarn <script>`, and `pnpm run <script>`
                          # are strictly equivalent. We use `npm run` here for 
                          # consistency and because `npm` is always available.
```

You should now be able to see the login pages in different scenarios:

<figure><img src="/files/ycfZ07Q7Tu0nyK0doFuE" alt=""><figcaption><p>View of the storybook interface that let you preview the login page of your theme.</p></figcaption></figure>

Now, let's apply our first CSS customization.

Create the following stylesheet:

{% code title="src/login/main.css" %}

```css
.kcFormHeaderClass {
    border: 3px solid red;
}
```

{% endcode %}

And import it:

{% tabs %}
{% tab title="React" %}

<pre class="language-tsx" data-title="src/login/KcPage.tsx"><code class="lang-tsx"><strong>import "./main.css";
</strong>// ...
</code></pre>

{% endtab %}

{% tab title="Angular" %}

<pre class="language-typescript" data-title="src/login/KcPage.ts"><code class="lang-typescript"><strong>import "./main.css";
</strong>import { getDefaultPageComponent, type KcPage } from '@keycloakify/angular/login';
// ...
</code></pre>

{% endtab %}

{% tab title="Svelte" %}

<pre class="language-html" data-title="src/login/KcPage.svelte"><code class="lang-html">&#x3C;script lang="ts">
<strong>  import "./main.css";
</strong>  import Template from '@keycloakify/svelte/login/Template.svelte';
  ...
</code></pre>

{% endtab %}
{% endtabs %}

This is what you should be getting:

<figure><img src="/files/vKMsu6wvtVZJVciWv47K" alt=""><figcaption><p>Screenshot showing the red border being applied to the header section of the login card.</p></figcaption></figure>

Now let's see how CSS customization works in Keycloakify:

{% content-ref url="/pages/zgoryu64zhr2MBNGlmOi" %}
[CSS Customization](/css-customization)
{% endcontent-ref %}

[^1]: Of course, we can't guarantee that a given build of your theme will work with future major versions of Keycloak. However, our goal is to ensure that when a new Keycloak version introduces breaking changes, the only action required to make your theme compatible is updating the Keycloakify version and rebuilding your theme—no additional adjustments needed.

[^2]: The difference exists because recent Keycloak versions use a React-based Account UI by default, which has not been ported to Angular or Svelte in Keycloakify due to the complexity involved. For Angular and Svelte projects, Keycloakify provides a base UI derived from an older version of the Keycloak Account UI, used before the switch to React.

[^3]: Keycloakify cannot be integrated directly into standard Angular projects because the Keycloakify compiler is designed to work with Vite and Webpack. However, most Angular projects today use Esbuild by default, which is not currently supported.

[^4]: [Storybook](https://storybook.js.org/) is a tool that allows you to develop UI components in isolation. It is set up in the Keycloakify starter repos because it provides an good developer experience and makes it easy to quickly preview the different pages of your theme.


# CSS Customization

{% hint style="info" %}
This page is a must-read. Even if you plan to redesign the pages at the component level, you should at least understand how to remove the default CSS styles.
{% endhint %}

## Understanding the CSS class system

When you inspect the DOM in Storybook, you’ll notice most elements have at least a couple of classes applied to them:

* A class starting with `kc`, for example `kcLabelClass`.
* One or more classes starting with `pf-`, for example `pf-c-form__label`, `pf-c-form__label-text`.

<figure><img src="/files/rzn6toptOsyYyBp5sUqW" alt=""><figcaption><p>Inspecting an input label on the login page</p></figcaption></figure>

Classes beginning with `kc` don’t have any styles applied to them by default. Their sole purpose is to serve as selectors for your custom styles.

Classes beginning with `pf-` are Patternfly classes. [Patternfly](https://v5-archive.patternfly.org/) is a CSS framework created by RedHat, similar to Bootstrap, that the Keycloak team uses to build all of its UIs.

What you’ll want to do is partially or completely remove the Patternfly styles and then apply your custom ones.

## Applying your custom CSS

{% hint style="danger" %}
Do not edit any file in the `public/keycloakify-dev-resources` directory. These files are used by Storybook to simulate a Keycloak environment during development, and they aren't part of your actual theme.
{% endhint %}

To apply your custom CSS style, use the `kc` classes to target the components.

{% code title="src/login/main.css" %}

```css
.kcLabelClass {
   border: 3px solid red;
}
```

{% endcode %}

{% tabs %}
{% tab title="React" %}

<pre class="language-tsx" data-title="src/login/KcPage.tsx"><code class="lang-tsx"><strong>import "./main.css";
</strong>// ...
</code></pre>

{% endtab %}

{% tab title="Svelte" %}

<pre class="language-html" data-title="src/login/KcPage.svelte"><code class="lang-html">&#x3C;script lang="ts">
<strong>  import "./main.css";
</strong>  import Template from '@keycloakify/svelte/login/Template.svelte';
  ...
</code></pre>

{% endtab %}

{% tab title="Angular" %}

<pre class="language-typescript" data-title="src/login/KcPage.ts"><code class="lang-typescript"><strong>import "./main.css";
</strong>import { getDefaultPageComponent, type KcPage } from '@keycloakify/angular/login';
// ...
</code></pre>

{% endtab %}
{% endtabs %}

This is the result:

<figure><img src="/files/7ejd1BIjwwzwwxGHDxax" alt="" width="375"><figcaption><p>A red border has been applied to every input label</p></figcaption></figure>

<details>

<summary>Having different stylesheets for the login page, the register page, etc...</summary>

In this example, we use a global stylesheet that applies to all pages of the login theme. However, you can also assign different stylesheets on a page-by-page basis (e.g., one for the login page, another for the registration page, etc.).

If you plan to customize the pages at the component level using React, Angular, or Svelte, you can skip this section. Once you've learned about the [`npx keycloakify eject-page`](/common-use-case-examples/using-a-component-library) command, it will be straightforward to import different stylesheets for different ejected pages, and no additional instructions will be necessary.

However, if you plan to customize the theme using only CSS without ejecting the pages, the process may not be immediately clear.\
You need to be able to load different stylesheet based on the value of `kcContext.pageId`.\
Below is a snippet of React code demonstrating how you can apply separate stylesheets for different pages:

{% code title="src/login/KcPage.tsx" %}

```tsx
import {
    Suspense, 
    lazy,
    useMemo
} from "react";

export default function KcPage(props: { kcContext: KcContext }) {
    const { kcContext } = props;

    const { i18n } = useI18n({ kcContext });

    const classes = useCustomStyles(kcContext);

    return (
        <Suspense>
            {(() => {
                switch (kcContext.pageId) {
                    default:
                        return (
                            <DefaultPage
                                kcContext={kcContext}
                                i18n={i18n}
                                classes={classes}
                                Template={Template}
                                doUseDefaultCss={true}
                                UserProfileFormFields={UserProfileFormFields}
                                doMakeUserConfirmPassword={doMakeUserConfirmPassword}
                            />
                        );
                }
            })()}
        </Suspense>
    );
}

function useCustomStyles(kcContext: KcContext) {
    return useMemo(() => {
        
        // Your stylesheet that applies to all pages.
        import("./main.css");
        let classes: { [key in ClassKey]?: string } = {
            // Classes that apply to all pages
        };

        switch (kcContext.pageId) {
            case "login.ftl":
                // A login page-specific stylesheet.
                import("./pages/login.css");
                classes = {
                    ...classes,
                    // Classes that apply only to the login page
                };
                break;
            case "register.ftl":
                // A register page-specific stylesheet.
                import("./pages/register.css");
                classes = {
                    ...classes,
                    // Classes that apply only to the register page
                };
                break;
            // ...
        }

        return classes;

    }, []);
}
```

{% endcode %}

If this code doesn’t make much sense, you can watch [this video tutorial](https://www.youtube.com/watch?v=Nkoz1iD-HOA) where this approach is demonstrated in practice.

</details>

<details>

<summary>Using Tailwind</summary>

{% hint style="info" %}
If you wish to use tailwind there is a pre made Starter Theme for Shadcn UI:

[Shadcn UI (Tailwind)](/starter-themes/shadcn-ui-tailwind)
{% endhint %}

Of course, you can use Tailwind in the usual way by applying utility classes to the React/Angular/Svelte components.\
But note that you can also use Tailwind without modifying the page structure by using the `@apply` directive. This is shown in [this page](#using-tailwind).

</details>

<details>

<summary>Using <a href="https://getbootstrap.com/">Bootstrap</a> or some other CSS framework</summary>

If you want to use Bootstrap or another CSS framework that provides standardized classes, you might wonder how to apply these classes.\
\
Here’s an example with Bootstrap:

```bash
yarn add bootstrap
```

<pre class="language-tsx" data-title="src/login/KcPage.tsx"><code class="lang-tsx"><strong>import "bootstrap/dist/css/bootstrap.min.css";
</strong>import { Suspense, lazy } from "react";
import type { ClassKey } from "keycloakify/login";
import type { KcContext } from "./KcContext";
import { useI18n } from "./i18n";
import DefaultPage from "keycloakify/login/DefaultPage";
import Template from "keycloakify/login/Template";
const UserProfileFormFields = lazy(
    () => import("keycloakify/login/UserProfileFormFields")
);

const doMakeUserConfirmPassword = true;

export default function KcPage(props: { kcContext: KcContext }) {
    const { kcContext } = props;

    const { i18n } = useI18n({ kcContext });

    return (
        &#x3C;Suspense>
            {(() => {
                switch (kcContext.pageId) {
                    default:
                        return (
                            &#x3C;DefaultPage
                                kcContext={kcContext}
                                i18n={i18n}
                                classes={classes}
                                Template={Template}
                                doUseDefaultCss={true}
                                UserProfileFormFields={UserProfileFormFields}
                                doMakeUserConfirmPassword={doMakeUserConfirmPassword}
                            />
                        );
                }
            })()}
        &#x3C;/Suspense>
    );
}

const classes = {
<strong>    kcLabelClass: "form-label col-form-label",
</strong>} satisfies { [key in ClassKey]?: string };
</code></pre>

By doing this, you replace the Patternfly classes `pf-c-form__label pf-c-form__label-text` with the Bootstrap classes `form-label col-form-label`.

In practice, if you inspect the element in your browser, the form label that was previously rendered as:

```html
<label for="username" class="kcLabelClass pf-c-form__label pf-c-form__label-text">
```

Is now rendered as:

```html
<label for="username" class="kcLabelClass form-label col-form-label">
```

</details>

## Removing Some of the Default Styles

Let’s consider the **Sign In** button on the login page:

<figure><img src="/files/aCv64xmmLygvS3SSNekU" alt="" width="375"><figcaption><p>The default look of the "Sign In" button</p></figcaption></figure>

Here’s how we can “unstyle” it so that we can apply custom styles without worrying about conflicts from the default Patternfly styles:

<figure><img src="/files/WoauCsLEgpdeqPZEOngc" alt="" width="375"><figcaption><p>How the "Sign In" button looks when all Patternfly styles are removed</p></figcaption></figure>

To remove the Patternfly styles, inspect the button in your browser:

<figure><img src="/files/oxqqlbeNDWfmucbLhe9y" alt=""><figcaption><p>Inspecting the CSS classes applied to the "Sign In" button</p></figcaption></figure>

We can see which Patternfly classes are applied by default to the standardized element:

* `kcButtonClass` -> `pf-c-button`
* `kcButtonPrimaryClass` -> `pf-m-primary` and `long-pf-btn`
* `kcButtonBlockClass` -> `pf-m-block`
* `kcButtonLargeClass` -> `btn-lg`

Since we want to remove all the default styles, we can tell Keycloakify to remove all classes assigned by default to these `kc` classes:

{% tabs %}
{% tab title="React" %}

<pre class="language-tsx" data-title="src/login/KcPage.tsx"><code class="lang-tsx">// ...

const classes = {
<strong>    kcButtonClass: "",
</strong><strong>    kcButtonPrimaryClass: "",
</strong><strong>    kcButtonBlockClass: "",
</strong><strong>    kcButtonLargeClass: ""
</strong>} satisfies { [key in ClassKey]?: string };
</code></pre>

{% endtab %}

{% tab title="Angular" %}

<pre class="language-typescript" data-title="src/login/KcPage.ts"><code class="lang-typescript">const classes = {
<strong>    kcButtonClass: "",
</strong><strong>    kcButtonPrimaryClass: "",
</strong><strong>    kcButtonBlockClass: "",
</strong><strong>    kcButtonLargeClass: ""
</strong>} satisfies { [key in ClassKey]?: string };
</code></pre>

{% endtab %}

{% tab title="Svelte" %}

<pre class="language-html" data-title="src/login/KcPage.svelte"><code class="lang-html">&#x3C;script lang="ts">
// ...

const classes = {
<strong>    kcButtonClass: "",
</strong><strong>    kcButtonPrimaryClass: "",
</strong><strong>    kcButtonBlockClass: "",
</strong><strong>    kcButtonLargeClass: ""
</strong>} satisfies { [key in ClassKey]?: string };
</code></pre>

{% endtab %}
{% endtabs %}

After saving these changes, here’s the result:

<figure><img src="/files/H9YqwdYH8ptAnjIjCnYa" alt=""><figcaption><p>All Patternfly classes have been stripped out, restoring the button to its default HTML style.</p></figcaption></figure>

Now you can freely apply your own custom button styles without Patternfly interfering.

<figure><img src="/files/ToJcsY5g9H8yr3GgIPCa" alt="" width="375"><figcaption><p>Button with custom style</p></figcaption></figure>

<details>

<summary>Reveal custom CSS code for this custom button</summary>

{% code title="src/login/main.css" %}

```css
.kcButtonClass {
    padding: 10px 20px;
    font-size: 16px;
    font-weight: bold;
    text-transform: uppercase;
    color: #ffffff;
    background: linear-gradient(45deg, #6a11cb, #2575fc);
    border: none;
    border-radius: 25px;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    transition: transform 0.2s, box-shadow 0.2s;
    cursor: pointer;
    width: 100%;
}

.kcButtonClass:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 8px rgba(0, 0, 0, 0.2);
}

.kcButtonClass:active {
    transform: translateY(0);
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

.kcButtonClass:focus {
    outline: none;
    box-shadow: 0 0 0 3px rgba(37, 117, 252, 0.5);
}

.kcButtonClass:disabled {
    background: linear-gradient(45deg, #aaa, #ccc);
    color: #666;
    cursor: not-allowed;
    box-shadow: none;
    transform: none;
    opacity: 0.6;
}

.kcButtonClass:disabled:hover,
.kcButtonClass:disabled:active {
    transform: none;
    box-shadow: none;
}
```

{% endcode %}

</details>

## Remove All the Default Styles

You may prefer to remove all Patternfly styles altogether and start fresh.

<figure><img src="/files/hbtbATAa3Joul8ETSem3" alt="" width="375"><figcaption><p>The login page completely unstyled (doUseDefaultCss set to false).</p></figcaption></figure>

A benefit of this approach is that not only are all `pf-` classes stripped out in one go, but the global Patternfly stylesheet isn’t even loaded.

{% tabs %}
{% tab title="React" %}

<pre class="language-tsx" data-title="src/login/KcPages.tsx"><code class="lang-tsx">// ...
&#x3C;DefaultPage
    kcContext={kcContext}
    i18n={i18n}
    classes={classes}
    Template={Template}
<strong>    doUseDefaultCss={false}
</strong>    UserProfileFormFields={UserProfileFormFields}
    doMakeUserConfirmPassword={doMakeUserConfirmPassword}
/>
</code></pre>

{% endtab %}

{% tab title="Svelte" %}

<pre class="language-html" data-title="src/login/KcPage.svelte"><code class="lang-html">{#await page() then { default: Page }}
    &#x3C;Page
      {kcContext}
      i18n={i18n}
      {classes}
      {Template}
      {UserProfileFormFields}
<strong>      doUseDefaultCss={false}
</strong>      {doMakeUserConfirmPassword}
    >&#x3C;/Page>
{/await}
</code></pre>

{% endtab %}

{% tab title="Angular" %}

<pre class="language-typescript" data-title="src/login/KcPage.ts"><code class="lang-typescript">const classes = {} satisfies { [key in ClassKey]?: string };
<strong>const doUseDefaultCss = false;
</strong>const doMakeUserConfirmPassword = true;

export async function getKcPage(pageId: KcContext['pageId']): Promise&#x3C;KcPage> {
  switch (pageId) {
    default:
      return {
        PageComponent: await getDefaultPageComponent(pageId),
        TemplateComponent,
        UserProfileFormFieldsComponent,
        doMakeUserConfirmPassword,
        doUseDefaultCss,
        classes,
      };
  }
}
</code></pre>

{% endtab %}
{% endtabs %}

### Disabling the default styles only on some pages

A common scenario is using [`npx keycloakify eject-page`](/common-use-case-examples/using-a-component-library) to customize only certain pages of the login UI in depth.

For pages you've ejected, you’ll likely want to disable all default styles; however, you might prefer to keep the Patternfly styles on the pages you haven't redesigned.\
Below is an example where `login.ftl` has been ejected and its default styles are disabled, while the other pages remain styled:

{% tabs %}
{% tab title="React" %}

<pre class="language-tsx" data-title="src/login/KcPages.tsx"><code class="lang-tsx">switch (kcContext.pageId) {
    case "login.ftl":
        return (
            &#x3C;Login
                {...{ kcContext, i18n, classes }}
                Template={Template}
<strong>                doUseDefaultCss={false}
</strong>            />
        );
    default:
        return (
            &#x3C;DefaultPage
                kcContext={kcContext}
                i18n={i18n}
                classes={classes}
                Template={Template}
<strong>                doUseDefaultCss={true}
</strong>                UserProfileFormFields={UserProfileFormFields}
                doMakeUserConfirmPassword={doMakeUserConfirmPassword}
            />
        );
}
</code></pre>

{% endtab %}

{% tab title="Angular" %}

<pre class="language-typescript" data-title="src/login/KcPage.ts"><code class="lang-typescript">  switch (pageId) {
    case 'login.ftl':
      return {
        PageComponent: (await import('./pages/login/login.component')).LoginComponent,
        TemplateComponent,
        UserProfileFormFieldsComponent,
        doMakeUserConfirmPassword,
<strong>        doUseDefaultCss: false,
</strong>        classes,
      };
    default:
      return {
        PageComponent: await getDefaultPageComponent(pageId),
        TemplateComponent,
        UserProfileFormFieldsComponent,
        doMakeUserConfirmPassword,
<strong>        doUseDefaultCss: true,
</strong>        classes,
      };
  }
</code></pre>

{% endtab %}

{% tab title="Svelte" %}

<pre class="language-html" data-title="src/login/KcPage.svelte"><code class="lang-html">&#x3C;script lang="ts">
  // ...
<strong>  const doUseDefaultCss = (()=>{
</strong><strong>    switch(kcContext.pageId){
</strong><strong>      case "login.ftl": return false;
</strong><strong>      default: return true;
</strong><strong>    }
</strong><strong>  })();
</strong>  
  const page = async (): Promise&#x3C;{ default?: Component&#x3C;any> }> => {
    switch (kcContext.pageId) {
      case 'login.ftl':
        return import('./pages/Login.svelte"');
      default:
        return import('@keycloakify/svelte/login/DefaultPage.svelte');
    }
  };
&#x3C;/script>

{#await page() then { default: Page }}
    &#x3C;Page
      {kcContext}
      i18n={i18n}
      {classes}
      {Template}
      {UserProfileFormFields}
<strong>      {doUseDefaultCss}
</strong>      {doMakeUserConfirmPassword}
    >&#x3C;/Page>
{/await}
</code></pre>

{% endtab %}
{% endtabs %}

### Removing the classes in ejected components (kcClsx)

If you have ejected some pages with [`npx keycloakify eject-page`](/common-use-case-examples/using-a-component-library) and disabled the default styles by setting `doUseDefaultCss` to `false`, you might wonder if you need to keep the `kcClsx` in the pages. For example:

<pre class="language-tsx" data-title="src/login/pages/Login.tsx"><code class="lang-tsx">&#x3C;input
    tabIndex={7}
    disabled={isLoginButtonDisabled}
<strong>    className={kcClsx(
</strong><strong>        "kcButtonClass", 
</strong><strong>        "kcButtonPrimaryClass", 
</strong><strong>        "kcButtonBlockClass", 
</strong><strong>        "kcButtonLargeClass"
</strong><strong>    )}
</strong>    name="login"
    id="kc-login"
    type="submit"
    value={msgStr("doLogIn")}
/>
</code></pre>

The short answer is no; feel free to remove them.

Just be aware that if you have defined any custom CSS targeting those classes (for example `.kcButtonClass { /* ... */ }`), they will no longer apply once you remove the classes.


# Testing your Theme

Two important aspects for developing a good theme are how quickly you can see your changes on the screen and how easy it is to replicate a production environment locally.

Keycloakify provides two ways to test your theme:

## Testing Outside of Keycloak

Keycloakify enables you to test your theme without requiring a real Keycloak instance to be involves.

This approach let you preview the pages in different configurations using mock data.

This is great for getting realtime feedback when designing your page and having a quick overview of how your theme looks like as a whole.

{% content-ref url="/pages/7kM4kxp8FfS4h1K1nTjm" %}
[Outside of Keycloak](/testing-your-theme/outside-of-keycloak)
{% endcontent-ref %}

## Testing Inside of Keycloak

Previewing your theme with mocks is great but at some point you have to make sure that everything works as expected in real environement.

Keycloakify let you with a simple command spin up a preconfigured Keyclaok instance in a Docker container to test your theme in real conditions.

{% content-ref url="/pages/dpKxSonD9QiLaSSFR2uX" %}
[Inside of Keycloak](/testing-your-theme/inside-of-keycloak)
{% endcontent-ref %}


# Outside of Keycloak

The recommended way to preview your theme as you develop it is to use [Storybook](https://storybook.js.org/).\
Storybook is a tool that enables to test UI component in isolation. For reference the following website was generated with storybook:

{% embed url="<https://storybook.keycloakify.dev/>" %}

{% hint style="info" %}
If you prefer to avoid intoducing Storybook into your stack, it's okay, you can still preview your page in dev mode. Do do so, refer to [this guide](/testing-your-theme/outside-of-keycloak-without-storybook).
{% endhint %}

The starter template does not initially contain any story files, instead there's a keycloakify CLI command that let's you import specifically the stories for the pages you want to test into your project.

So, just run this command in the root of your Keycloakify project and select the pages you want.

```bash
npx keycloakify add-story
```

It will enables you to select the pages you want to add stories for.

<figure><img src="/files/T1m3Ma771x92X2vyEgEa" alt=""><figcaption></figcaption></figure>

Selecting login -> register.ftl will result in this file to be created in your project:

<figure><img src="/files/wO7nTc03dbFIvdKlwq0S" alt=""><figcaption></figcaption></figure>

You can run the above command multiple times to add stories for the different pages you want to develop.

Once your added a few stories you can start Storybook locally with:

```bash
npm run storybook
```

<figure><img src="/files/8A02h3GDDcIzKEUpsVI9" alt=""><figcaption></figcaption></figure>

You can see the changes you make in you code in realtime in your Storybook.

The idea of Storybook is to easily let you see the pages in different configuration without having to reproduce the full login/register process in a real Keycloak.\
Keycloakify provide a default mock context for every pages, the stories let you partially override some specific part of this default mock to reflect pages in different configurations.\
\
For example, if you want to create a story that show the register page in chinese you would add this:

{% tabs %}
{% tab title="React" %}

<pre class="language-tsx" data-title="src/login/pages/Register.stories.tsx"><code class="lang-tsx">import type { Meta, StoryObj } from "@storybook/react";
import { createKcPageStory } from "../KcPageStory";

const { KcPageStory } = createKcPageStory({ pageId: "register.ftl" });

const meta = {
    title: "login/register.ftl",
    component: KcPageStory
} satisfies Meta&#x3C;typeof KcPageStory>;

export default meta;

type Story = StoryObj&#x3C;typeof meta>;

export const Default: Story = {
    render: () => &#x3C;KcPageStory />
};

<strong>export const InChinese: Story = {
</strong><strong>    render: ()=> (
</strong><strong>        &#x3C;KcPageStory
</strong><strong>            kcContext={{
</strong><strong>                locale: {
</strong><strong>                    currentLanguageTag: "zh-CN"
</strong><strong>                }
</strong><strong>            }}
</strong><strong>        />
</strong><strong>    )
</strong><strong>};
</strong></code></pre>

{% endtab %}

{% tab title="Angular" %}
{% code title="src/login/pages/register/register.stories.ts" %}

```typescript
import { Meta, StoryObj } from '@storybook/angular';
import { decorators, KcPageStory } from '../KcPageStory';
import { Attribute } from 'keycloakify/login';

const meta: Meta<KcPageStory> = {
    title: 'login/register.ftl',
    component: KcPageStory,
    decorators: decorators,
    globals: {
        pageId: 'register.ftl'
    }
};

export default meta;

type Story = StoryObj<KcPageStory>;

export const Default: Story = {};

export const InChinese: Story = {
    globals: {
        kcContext: {
            locale: {
                    currentLanguageTag: "zh-CN"
            }
        }
    }
};
```

{% endcode %}
{% endtab %}

{% tab title="Svelte" %}
{% code title="src/login/pages/Login.stories.svelte" %}

```html
<script
  context="module"
  lang="ts"
>
  import { defineMeta } from '@storybook/addon-svelte-csf';
  import type { KcPageStoryProps } from '../KcPageStory';
  import KcPageStory from '../KcPageStory.svelte';

  const args: KcPageStoryProps = { pageId: 'login.ftl' };
  const { Story } = defineMeta({
    title: 'login/login.ftl',
    component: KcPageStory,
    args: args,
  });
</script>

<Story name="Default" />

<Story
  name="InChinese"
  args={{
    ...args,
    kcContext: {
      locale: {
        currentLanguageTag: "zh-CN"
      }
    }
  }}
/>
```

{% endcode %}
{% endtab %}
{% endtabs %}

<figure><img src="/files/ypLqqZB28RCkPvnIxo8w" alt=""><figcaption></figcaption></figure>

Now let's see how to test our theme in a real Keycloak instance:

{% content-ref url="/pages/dpKxSonD9QiLaSSFR2uX" %}
[Inside of Keycloak](/testing-your-theme/inside-of-keycloak)
{% endcontent-ref %}


# Inside of Keycloak

Testing your theme in Storybook is helpful, but eventually, you'll need to test it in a real Keycloak instance before deploying it to production.

## Prerequisites

1. **Install Docker**: Ensure [Docker Desktop](https://www.docker.com/products/docker-desktop/) (or just Docker) is installed and running on your computer.
2. **Install Maven**: Maven is required to build `.jar` files locally. Check if it's already installed by running: `mvn --version`

   If not, follow the instructions below to install Maven.

{% tabs %}
{% tab title="MacOS" %}
Using [Homebrew](https://formulae.brew.sh/formula/maven):

```bash
brew install maven
```

{% endtab %}

{% tab title="Windows" %}
On Windows, use the [Chocolatey](https://chocolatey.org/) package manager:

```bash
choco install openjdk
choco install maven
```

Or follow the [manual installation guide](https://chocolatey.org/).
{% endtab %}

{% tab title="Ubuntu/Debian" %}

```bash
sudo apt-get install maven
```

{% endtab %}

{% tab title="Fedora" %}

```bash
sudo dnf install maven
```

{% endtab %}
{% endtabs %}

## Running Keycloak in Docker

Once the prerequisites are set up, you're ready to start Keycloak. Run the following command in your Keycloakify project:

```bash
npx keycloakify start-keycloak
```

You will be prompted to select the Keycloak version to spin up:

<figure><img src="/files/LOknrNwAp4kjtD41r6Gb" alt=""><figcaption></figcaption></figure>

Keycloakify will launch the selected Keycloak version in a Docker container.

Once the container is running, you’ll see two links:

<figure><img src="/files/GXmYsabAeLbMiHc6OzRt" alt=""><figcaption></figcaption></figure>

1. **Keycloak Admin Console**: <http://localhost:8080>\
   The Keycloak instance is preconfigured with your theme, a custom realm named `myrealm`, and a test user (`testuser/password123`) for quick testing.\
   Changes to the configuration are saved in the `.keycloakify` directory so that they persist across restarts.
2. **Test Web App**: <https://my-theme.keycloakify.dev>\
   This app redirects to your login theme. Edits to your theme are auto-rebuilt and updated in the Keycloak container. Simply refresh the page to see changes live.

<figure><img src="/files/2Qui15l0gWoaHF1TzBHR" alt=""><figcaption><p>Accessing the test web app and signing in with testuser/password123</p></figcaption></figure>

<details>

<summary>Inspecting `window.kcContext`</summary>

You can open your browser’s developer tools to inspect the `kcContext` object on the page. This allows you to create new stories for pages with specific configurations.

<img src="/files/zuGAJwTBorFkyuXPLSkK" alt="" data-size="original">

</details>

Logging in with the test user redirects you to a page where you can inspect the decoded ID token (JWT) and access your custom [Account](/theme-types/account-theme) and [Admin](/theme-types/admin-theme) themes (if you implement them).

<figure><img src="/files/fBEklfgvYDgvjTXoT7JE" alt=""><figcaption></figcaption></figure>

## More Options

Want to use a custom Keycloak image? Load some extentions? Import a realm configuration?\
No problem! The start-keycloak command support many options!

{% content-ref url="/pages/rPBLd8NIg4AvdqlvxjjU" %}
[startKeycloakOptions](/features/compiler-options/startkeycloakoptions)
{% endcontent-ref %}


# Outside of Keycloak - Without Storybook

This option for testing your theme is a fallback option if you prefer avoiding introducing Storybook into your project.

To do that, just uncomment the following lines in [your entrypoint](#user-content-fn-1)[^1]:

<pre class="language-typescript" data-title="src/[main|index].tsx?"><code class="lang-typescript">import { createRoot } from "react-dom/client";
import { StrictMode } from "react";
import { KcPage } from "./kc.gen";

// The following block can be uncommented to test a specific page with `yarn dev`
// Don't forget to comment back or your bundle size will increase
<strong>// */
</strong>import { getKcContextMock } from "./login/KcPageStory";

if (import.meta.env.DEV) {
    window.kcContext = getKcContextMock({
        pageId: "register.ftl",
        overrides: {}
    });
}
<strong>// */
</strong>
...
</code></pre>

The `pageId` parameter of the `getKcContextMock` lets you decide what page you want to test.\
The overrides parameter lets you modify the default kcContext mock for the page.\
\
For example you can overwrite the `kcContext.locale.currentLanguageTag` to preview your page in a different language.

<pre class="language-tsx"><code class="lang-tsx">window.kcContext = getKcContextMock({
  pageId: "login.ftl",
<strong>  overrides: {
</strong><strong>    locale: {
</strong><strong>      currentLanguageTag: "zh-CN",
</strong><strong>    },
</strong><strong>  },
</strong>});
</code></pre>

Then start the dev server of your, project:

```bash
npm run dev
# Or 'npm run start' (CRA) or 'npm run serve' (Angular Webpack)
```

<figure><img src="/files/qw2yOevnETaSavsEXGQi" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
When you're done testing, don't forget to comment back the import of the mock. Forgetting to do so will negatively impact the bundle size of your pages.
{% endhint %}

[^1]: Your entrypoint is one of the following depending of your Framwork:

    * src/main.tsx
    * src/index.tsx
    * src/main.ts
    * src/index.ts
    * src/main.ts


# Deploying Your Theme

This section explains how to load and enable your theme in your production Keycloak instance.

{% hint style="warning" %}
**Warning:**\
If your goal is to test your theme in a Keycloak docker container for development purpose, this is NOT the correct section of the documentation.\
Refer to the [Testing Your Theme in a Keycloak Docker Container](/testing-your-theme/inside-of-keycloak) section for detailed instructions.\
This section is intended for deploying your theme to a **production** Keycloak instance, which involves a completely different process.
{% endhint %}

## Building the JAR File

Keycloak uses an extension system where themes or other custom plugins are packaged as standardized JAR files.

The first step is to build your theme into a JAR file that can be loaded into Keycloak.

```bash
npm run build-keycloak-theme
```

This command will create a **/dist\_keycloak** directory containing the necessary JAR files.

<figure><img src="/files/ndCtH0AXZ70g35z4sveN" alt="" width="375"><figcaption></figcaption></figure>

By default, Keycloakify generates multiple JAR files to support different Keycloak versions. Here’s how to choose the correct JAR file for your production environment:

• **Keycloak 11 to 21 and 26 and newer**: Use **keycloak-theme-for-kc-all-other-versions.jar**.

• **Keycloak 22 to 25**: Use **keycloak-theme-for-kc-22-to-25.jar**.

You can configure which JAR files are generated and how they are named. For details, refer to this guide:

{% content-ref url="/pages/mRBPowGEjKtlpj7o0TtF" %}
[keycloakVersionTargets](/features/compiler-options/keycloakversiontargets)
{% endcontent-ref %}

If you have an OPS team and your responsibility is limited to developing the theme, your job ends here. The JAR file is your deliverable. You can provide it to the person managing your Keycloak instance—they will know what to do with it.

If you are responsible for both development and deployment, keep reading to learn how to load and enable the theme in Keycloak.

## Loading the JAR File into Keycloak

Now that your theme is packaged as a JAR file, you can load it into your Keycloak server, just like any other Keycloak extension.

For official guidance, refer to the [Keycloak documentation on registering provider implementations](https://www.keycloak.org/docs/latest/server_development/#registering-provider-implementations).\
\
While the official documentation provides a general overview, you might wonder how to apply those instructions in practice. Below, you’ll find a few code snippets illustrating how to load your theme, depending on the method you use to deploy Keycloak in production.

{% hint style="warning" %}
Improtrant note:

**How to deploy Keycloak in production is beyond the scope of Keycloakify’s documentation**.

If you’re unfamiliar with deploying a Keycloak instance, we strongly recommend starting with [the official Keycloak deployment guides](https://www.keycloak.org/documentation).

Do **not** attempt to use these snippets directly without understanding how Keycloak deployment works.

Once you’re confident in deploying Keycloak, revisit this section to integrate your custom theme seamlessly.
{% endhint %}

{% tabs %}
{% tab title="Docker" %}
One of the most common ways to deploy Keycloak in production is by using the official Docker image.

If you are following this approach, you can use the `-v` option to mount your JAR file into the `/opt/keycloak` directory inside the container.

Here’s an example of how to run the Keycloak container with your custom theme:

<pre class="language-bash"><code class="lang-bash">docker run \
    # ...other options
<strong>    -v "./dist_keycloak/keycloak-theme-for-kc-all-other-versions.jar":/opt/keycloak/providers/keycloak-theme.jar \
</strong>    quay.io/keycloak/keycloak:26.0.7 \
    start
</code></pre>

{% endtab %}

{% tab title="Docker Compose" %}
This approach builds on the basic Docker setup, providing a more streamlined way to manage your Keycloak deployment with Docker Compose.\
Let’s assume you have the following directory structure:

```
./docker-compose.yaml
./themes/keycloak-theme-for-kc-all-other-versions.jar
```

{% code title="docker-compose.yaml" %}

```yaml
version: '3.7'

services:
  postgres:
    image: postgres:16.2
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    ports:
      - 5432:5432
    networks:
      - keycloak_network

  keycloak:

    image: quay.io/keycloak/keycloak:26.0.4
    command: start-dev

    environment:
      KC_HOSTNAME: ${KEYCLOAK_HOSTNAME}
      KC_HOSTNAME_PORT: 8080
      KC_HTTP_ENABLED: true
      KC_HEALTH_ENABLED: true
      KC_HOSTNAME_STRICT_HTTPS: false
      KC_HOSTNAME_STRICT: false

      KC_BOOTSTRAP_ADMIN_USERNAME: ${KEYCLOAK_ADMIN}
      KC_BOOTSTRAP_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://postgres/${POSTGRES_DB}
      KC_DB_USERNAME: ${POSTGRES_USER}
      KC_DB_PASSWORD: ${POSTGRES_PASSWORD}
    ports:
      - 8080:8080
    volumes:
      - ./themes:/opt/keycloak/providers/
    restart: unless-stopped
    depends_on:
      - postgres
    networks:
      - keycloak_network

volumes:
  postgres_data:
    driver: local

networks:
  keycloak_network:
    driver: bridge
```

{% endcode %}
{% endtab %}

{% tab title="Helm" %}
If you use [Bitnami's Keycloak Helm chart](https://github.com/bitnami/charts/tree/main/bitnami/keycloak) you can leverage the initContainers parameter to load your theme.

{% code title="Chart.yaml" %}

```yaml
apiVersion: v2
name: keycloak
version: 1.0.0
dependencies:
  - name: keycloak
    version: 24.4.10 # Keycloak 26.1.2
    repository: oci://registry-1.docker.io/bitnamicharts
```

{% endcode %}

Here we only list the relevant values:

{% code title="values.yaml" %}

```yaml
# OPTIONAL: Here you can define environment variables that you can access in your theme, see: https://docs.keycloakify.dev/features/environment-variables
extraEnvVars:
  - name: MY_APP_PALLET
    value: "monokai"

initContainers:
  - name: realm-ext-provider
    image: curlimages/curl
    imagePullPolicy: IfNotPresent
    command:
      - sh
    args:
      - -c
      - |
        # Replace USER and PROJECT, use the correct version of the jar for the keycloak version you are deploying
        mkdir -p /emptydir/app-providers-dir
        curl -L -f -S -o /emptydir/app-providers-dir/keycloak-theme.jar https://github.com/USER/PROJECT/releases/download/VERSION/keycloak-theme-for-kc-all-other-versions.jar

    volumeMounts:
      - name: empty-dir
        mountPath: /emptydir
```

{% endcode %}

Read [this section of the starter project readme](https://github.com/keycloakify/keycloakify-starter?tab=readme-ov-file#github-actions) to learn how to get GitHub Action to publish your theme's JAR as assets of your GitHub release.
{% endtab %}

{% tab title="Bare metal" %}
What you need to know is that your keycloak-theme.jar should be placed in the provider directory of your Keycloak (e.g: `/opt/keycloak/providers)`\
After that you should run bin/kc.sh build (e.g: `sh /opt/keycloak/bin/kc.sh build`)

Then you can start your Keycloak server, your theme should be available in it.
{% endtab %}

{% tab title="Docker - Custom Image" %}
Another common approach is to build a custom Docker image of Keycloak that extends the official Keycloak image and includes your theme.

<pre class="language-bash"><code class="lang-bash">cd ~/github
git clone https://github.com/keycloakify/keycloakify-starter
cd keycloakify-starter

cat &#x3C;&#x3C; EOF > ./.dockerignore
node_modules
dist
dist_keycloak
<strong># IMPORTANT: Make sure `.gitignore` is **not** listed
</strong><strong># in your .dockerignore file
</strong>EOF

cat &#x3C;&#x3C; EOF > ./Dockerfile
<strong>FROM node:20-alpine as build
</strong><strong>RUN apk update &#x26;&#x26; \
</strong><strong>    apk add --no-cache openjdk17 maven
</strong><strong>WORKDIR /app
</strong><strong>COPY . .
</strong><strong>RUN yarn install --frozen-lockfile
</strong><strong>RUN yarn build-keycloak-theme
</strong>
FROM quay.io/keycloak/keycloak:26.0.7
WORKDIR /opt/keycloak
<strong>COPY --from=build /app/dist_keycloak/keycloak-theme-for-kc-all-other-versions.jar /opt/keycloak/providers/
</strong>RUN /opt/keycloak/bin/kc.sh build
ENTRYPOINT ["/opt/keycloak/bin/kc.sh", "start", "--optimized"]
EOF

docker build -t my-keycloak .
docker run \
    -e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
    -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \
    -p 8080:8080 \
    my-keycloak
</code></pre>

Ref to official doc: <https://www.keycloak.org/server/containers>
{% endtab %}

{% tab title="Cloud-IAM" %}
If you have a Keycloak instance managed by [Cloud-IAM](https://cloud-iam.com/?mtm_campaign=keycloakify-deal\&mtm_source=keycloakify-doc-header), you can simply sign-in to and click on the "Upload JAR File" button.
{% endtab %}
{% endtabs %}

## Enabling Your Theme

Once your JAR file is loaded into your Keycloak instance, enable your theme in the Keycloak Admin Console:

1\. Go to **Realm Settings** in your desired realm.

2\. Under the **Themes** section, select your theme from the dropdown menus (e.g., Login Theme).

{% hint style="warning" %}
Never configure the master realm for your application. Create a separate realm for your application to ensure the master realm remains untouched.
{% endhint %}

<figure><img src="/files/feH9Ezh0eHkvp2YhItoc" alt=""><figcaption></figcaption></figure>

Note that the name that appears in the dropdown (here "keycloakify-starter") can be configured with [the themeName option](/features/compiler-options/themename). If you implement [theme variants](/features/theme-variants) you'll have more than one option.

<details>

<summary>Enabling diffrent theme for diffrent client</summary>

The login theme can be applied at the client level. You typically have one Keycloak client per web application.\
Setting the login theme at the client level means that each application of your realm can have different login/register pages. This comes in handy if you're implementing [Theme Variants](/features/theme-variants).

To enable a login theme on one of your clients:

* Select your realm in the top left corner
* -> Clients
* -> Select your client in the list
* -> Scroll down to "Login Theme" and select your theme.

The account theme can only be enabled at the realm level; however, accessing the account pages requires authentication. If you don't want your user to inadvertently come across the default login theme when navigating to the account pages after their session has expired, you might want to enable your login theme on the "account-console" client.

* Select your realm in the top left corner
* -> Clients
* -> Select "account console"
* -> Scroll down to "Login Theme" and select one of your login theme.

</details>


# Integrating Keycloakify in your Codebase

{% hint style="warning" %}
This is for advanced users. If you are just trying to get started with Keycloakify follow [the Quick Start Guide and fork the starter project.](/#quick-start)
{% endhint %}

There are two main approaches to integrate Keycloakify into your project.

## First Option: Installing Keycloakify Directly in Your SPA

{% hint style="danger" %}
🚨 **WARNING: ADVANCED USERS ONLY** 🚨

If you're unsure what this section is about, **this approach is NOT for you.** Instead, follow [the Quick Start Guide and fork the starter project.](/#quick-start)

This section is **only** for developers who already have an existing project and need to integrate a Keycloak theme **within it** to reuse existing components and styles.

🔹 If you're just trying to get started with Keycloakify, **stop here**—[the starter projects](/#quick-start) provide a much simpler and recommended path.

🔹 If you proceed without fully understanding how this approach differs from the starter project, you will likely get confused about what you’re actually doing, attempt to *simplify* things, and end up hitting a roadblock.
{% endhint %}

If you are developing a [Single Page Application (SPA)](#user-content-fn-1)[^1], you can install Keycloakify directly within your project.

The main advantage of this approach is that your theme's source files will reside inside `src/keycloak-theme`, allowing you to directly import and reuse the components from your existing codebase.

This approach is suitable if your project falls into one of the following categories:

* Vite + React
* Create-React-App
* [Vite + Svelte](#user-content-fn-2)[^2]
* Webpack + React ⚠

Follow the guide corresponding to your setup:

{% content-ref url="/pages/hDXVcIGqcE9raQUTZu1k" %}
[Vite](/integration-keycloakify-in-your-codebase/vite)
{% endcontent-ref %}

{% content-ref url="/pages/JJL4idAqYD3BpwDLUl2X" %}
[Create-React-App / Webpack](/integration-keycloakify-in-your-codebase/webpack)
{% endcontent-ref %}

## Second Option: Setting Up Keycloakify as a Subproject in Your Monorepo

If your project is structured as a monorepo, you can add your Keycloak theme as a subproject, typically located at `apps/keycloak-theme`.

Choose the guide that matches your monorepo setup:

{% content-ref url="/pages/KF63QE3ZzcjCQPyzd1TC" %}
[yarn/npm/pnpm/bun Workspaces](/integration-keycloakify-in-your-codebase/package-manager-workspaces)
{% endcontent-ref %}

{% content-ref url="/pages/HTGyO1jWWeVIpevAIXuO" %}
[Turborepo](/integration-keycloakify-in-your-codebase/turborepo)
{% endcontent-ref %}

{% content-ref url="/pages/F93yue5uBbgm9bPV8jUe" %}
[Nx](/integration-keycloakify-in-your-codebase/nx)
{% endcontent-ref %}

{% content-ref url="/pages/pQfhuBEbhs3Ivtw5DO0c" %}
[Angular Workspace](/integration-keycloakify-in-your-codebase/angular-workspace)
{% endcontent-ref %}

[^1]: If your project is build with Next.js or Remix it does not fall under this cathegory.

[^2]: Svelte projects initialized with SvelteKit 1.0.0 or later use Vite as the default build tool. If your project was created in 2023 or later, it is likely based on Vite.


# Vite

{% hint style="danger" %}
🚨 **WARNING: ADVANCED USERS ONLY** 🚨

If you're unsure what this section is about, **this approach is NOT for you.** Instead, follow [the Quick Start Guide and fork the starter project.](/#quick-start)

This section is **only** for developers who already have an existing project and need to integrate a Keycloak theme **within it** to reuse existing components and styles.

🔹 If you're just trying to get started with Keycloakify, **stop here**—[the starter projects](/#quick-start) provide a much simpler and recommended path.

🔹 If you proceed without fully understanding how this approach differs from the starter project, you will likely get confused about what you’re actually doing, attempt to *simplify* things, and end up hitting a roadblock.
{% endhint %}

If you have a Vite project you can integrate Keycloakify directly inside it.

**Svelte**: Although this guide uses React as an example it's also applicable for Svelte, you just need to adapt it when relevent.

Let's assume we're working with a freshly initialized Vite project.

<figure><img src="/files/xiofuwtxNCQp2Fgmj6VV" alt="" width="375"><figcaption><p>Creating a new vite project with yarn create vite.<br>Don't do that, use your existing project.</p></figcaption></figure>

<figure><img src="/files/4U0P06TLMYAqPULlvz25" alt="" width="368"><figcaption><p>Our codebase before installing Keycloakify</p></figcaption></figure>

{% hint style="info" %}
Before anything, make sure to commit all your pending changes so you can easily revert changes if need be.
{% endhint %}

Let's start by installing Keycloakify (and optionally Storybook) to our project:

{% tabs %}
{% tab title="yarn" %}

```bash
yarn add keycloakify
# Installing storybook is optional
yarn add --dev storybook @storybook/react @storybook/react-vite
```

{% endtab %}

{% tab title="pnpm" %}

```bash
pnpm add keycloakify
# Installing storybook is optional
pnpm add --dev storybook @storybook/react @storybook/react-vite
```

{% endtab %}

{% tab title="bun" %}

```bash
bun add keycloakify
# Installing storybook is optional
bun add --dev storybook @storybook/react @storybook/react-vite
```

{% endtab %}

{% tab title="npm" %}

```bash
npm install keycloakify
# Installing storybook is optional
npm install --save-dev storybook @storybook/react @storybook/react-vite
```

{% endtab %}
{% endtabs %}

Next we want to move the relevant files from [the starter template](https://github.com/keycloakify/keycloakify-starter) into our project:

```bash
cd my-react-app
git clone https://github.com/keycloakify/keycloakify-starter tmp
mv tmp/src src/keycloak-theme

# Note for the following command: If you already have Storybook setup
# in your project you can skip this.
# Only make sure you have `staticDirs: ["../public"]` in your .storybook/main.ts
mv tmp/.storybook .

rm -rf tmp
rm src/keycloak-theme/vite-env.d.ts
mv src/keycloak-theme/main.tsx src/main.tsx
```

<figure><img src="/files/W8iiVAf9V6CuB4fgQsqv" alt="" width="370"><figcaption><p>State of your codebase after bringing in the Keycloakify boilerplate code.<br>Note thate the keycloak-theme (or keycloak_theme) directory can be located anywhere under your src directory.</p></figcaption></figure>

Now you want to modify your entry point so that:

* If the kcContext global is defined, render your Keycloakify theme
* Else, reder your App as usual.

Let's say, for example, your **src/main.tsx** file currently looks like this:

{% code title="src/main.tsx" %}

```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import "./index.css";
import { MyProvider } from "./MyProvider";

createRoot(document.getElementById('root')!).render(
    <StrictMode>
      <MyProvider>
        <App />
      </MyProvider>
    </StrictMode>,
);
```

{% endcode %}

You want to **rename** this file to src/main.app.tsx (for example) and modify it as follows:

{% code title="src/main.app.tsx" %}

```tsx
import App from "./App.tsx";
import "./index.css";
import { MyProvider } from "./MyProvider.tsx";

export default function AppEntrypoint() {
  return (
    <MyProvider>
      <App />
    </MyProvider>
  )
}
```

{% endcode %}

{% hint style="info" %}
If you have some top level `await` and you don't know how to deal with thoses, join [the discord server](https://discord.com/invite/kYFZG7fQmn), I can help you out.
{% endhint %}

Then you want to create the following **src/main.tsx** file, you can copy and paste the following code (it does not need to be adapted):

{% code title="src/main.tsx" %}

```tsx
import { createRoot } from "react-dom/client";
import { StrictMode, lazy, Suspense } from "react";
import { KcPage, type KcContext } from "./keycloak-theme/kc.gen";
const AppEntrypoint = lazy(() => import("./main.app"));

// The following block can be uncommented to test a specific page with `yarn dev`
// Don't forget to comment back or your bundle size will increase
/*
import { getKcContextMock } from "./keycloak-theme/login/KcPageStory";

if (import.meta.env.DEV) {
    window.kcContext = getKcContextMock({
        pageId: "register.ftl",
        overrides: {}
    });
}
*/

createRoot(document.getElementById("root")!).render(
    <StrictMode>
        {window.kcContext ? (
            <KcPage kcContext={window.kcContext} />
        ) : (
            <Suspense>
                <AppEntrypoint />
            </Suspense>
        )}
    </StrictMode>
);

declare global {
    interface Window {
        kcContext?: KcContext;
    }
}
```

{% endcode %}

{% hint style="info" %}
**Question:**

Why do my main application and Keycloak theme share the same entry point?

**Answer:**

To simplify the build process. If you don't want it to negatively impact the performance of your application, it's essential to understand the following points:

* **Different Contexts:** The application (`App`) and Keycloak page (`KcPage`) are mounted in very different contexts. Avoid sharing providers between the two at the `main.tsx` file level. The true entry point of your application is the `AppEntrypoint` component defined in `main.app.tsx`, while the entry point for your Keycloak theme is the `KcPage` component. Be careful about what code is shared between them.
* **Responsibility of main.tsx:** The `main.tsx` file should only determine the context (either the application or Keycloak) and mount the appropriate component (`App` or `KcPage`). It should not contain any substantial logic or dependencies.
* **Performance Considerations:** Keep `main.tsx` as lightweight as possible to avoid increasing the initial load time of both your main application and login pages. For example, do not load any state management libraries like `redux-toolkit` at this level.
  {% endhint %}

You also need to use Keycloakify's Vite plugin. Here we don't provide any [build options](/features/compiler-options) but you probably at least want to define [keycloakVersionTargets](/features/compiler-options/keycloakversiontargets).

<pre class="language-tsx" data-title="vite.config.ts"><code class="lang-tsx">import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
<strong>import { keycloakify } from "keycloakify/vite-plugin";
</strong>
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(), 
<strong>    keycloakify({
</strong><strong>        accountThemeImplementation: "none"
</strong><strong>    })
</strong>  ],
})
</code></pre>

{% hint style="info" %}
Leave accountThemeImplementation set to "none" for now.\
To initialize the account theme refer to [this guide](https://github.com/keycloakify/docs.keycloakify.dev/blob/v10/keycloakify-in-my-codebase/in-your-react-project/broken-reference/README.md).
{% endhint %}

Finally you want to add to your `package.json` a script for building the theme and another one to start storybook.

<pre class="language-json" data-title="package.json"><code class="lang-json">{
  "name": "my-react-app",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b &#x26;&#x26; vite build",
    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview",
<strong>    "build-keycloak-theme": "npm run build &#x26;&#x26; keycloakify build",
</strong><strong>    "storybook": "storybook dev -p 6006"
</strong>  },
  // ...
</code></pre>

Last setp is to exclude from your html `<head />` things that aren't relevent in the context of Keycloak pages.

{% hint style="danger" %}
Do not blindly copy/paste, this is just an example!

You have to figure out what does and does not make sense to be in the `<head/>` of your Keycloak UI pages.
{% endhint %}

<pre class="language-html" data-title="public/index.html"><code class="lang-html">&#x3C;!doctype html>
&#x3C;html>

&#x3C;head>
    &#x3C;meta charset="UTF-8" />
    &#x3C;link rel="icon" type="image/svg+xml" href="/vite.svg" />
    &#x3C;meta name="viewport" content="width=device-width, initial-scale=1.0" />

<strong>    &#x3C;meta name="keycloakify-ignore-start">
</strong>    &#x3C;title>ACME Dashboard&#x3C;/title>
    &#x3C;script>
        window.ENV = {
            API_ADDRESS: '${API_ADDRESS}',
            SENTRY_DSN: '${SENTRY_DSN}'
        };
    &#x3C;/script>
<strong>    &#x3C;meta name="keycloakify-ignore-end">
</strong>    
    &#x3C;!-- ... -->

&#x3C;/head>

&#x3C;!-- ... -->
</code></pre>

In the above example we tell Keycloakify not to include the `<title>` because Keycloakify will set it dynamically to something like *"ACME- Login"* or *"ACME - Register"*.

We also exclude a placeholder script for injecting environnement variables at container startup.

**That's it, your project is ready to go!** :tada:

You can run `npm run build-keycloak-theme`, the JAR distribution of your Keycloak theme will be generated in `dist\_keycloak`.

You're now able to use all the Keycloakify commands (`npx keycloakify --help`) from the root of your project.

{% hint style="success" %}
If you're currently using [keycloak-js](https://www.npmjs.com/package/keycloak-js) or [react-oidc-context](https://github.com/authts/react-oidc-context) to manage user authentication in your app you might want to checkout [oidc-spa](https://www.oidc-spa.dev/), the alternative from the Keycloakify team.

If you have any issues [reach out on Discord](https://discord.gg/mJdYJSdcm4)! We're here to help!
{% endhint %}

{% content-ref url="/pages/IuA2ldFxCCAcwJXO0fiY" %}
[Testing your Theme](/testing-your-theme)
{% endcontent-ref %}

{% content-ref url="<https://github.com/keycloakify/docs.keycloakify.dev/blob/v11/integration-keycloakify-in-your-codebase/broken-reference/README.md>" %}
<https://github.com/keycloakify/docs.keycloakify.dev/blob/v11/integration-keycloakify-in-your-codebase/broken-reference/README.md>
{% endcontent-ref %}


# Create-React-App / Webpack

{% hint style="danger" %}
🚨 **WARNING: ADVANCED USERS ONLY** 🚨

If you're unsure what this section is about, **this approach is NOT for you.** Instead, follow [the Quick Start Guide and fork the starter project.](/#quick-start)

This section is **only** for developers who already have an existing project and need to integrate a Keycloak theme **within it** to reuse existing components and styles.

🔹 If you're just trying to get started with Keycloakify, **stop here**—[the starter projects](/#quick-start) provide a much simpler and recommended path.

🔹 If you proceed without fully understanding how this approach differs from the starter project, you will likely get confused about what you’re actually doing, attempt to *simplify* things, and end up hitting a roadblock.
{% endhint %}

If you have a Webpack/React/TypeScript project you can integrate Keycloakify directly inside it.

In this guide we're going to work with a vanilla [Create React App](https://create-react-app.dev/) project.

<figure><img src="/files/E3X65X7e80aLpmdb3UDo" alt="" width="375"><figcaption><p>Creating a CRA project. You don't need to do that, just use your existing codebase.</p></figcaption></figure>

<figure><img src="/files/NywhGfc2ZqOgIfA7ikBK" alt="" width="304"><figcaption><p>Our codebase before involving Keycloakify</p></figcaption></figure>

{% hint style="info" %}
Before anything make sure to commit all your pending changes so you can easily revert changes if need be.
{% endhint %}

Let's start by installing Keycloakify (and optionally Storybook) to our project:

{% tabs %}
{% tab title="yarn" %}

```bash
yarn add keycloakify
# Installing storybook is optional
yarn add --dev rimraf storybook @storybook/react @storybook/react-vite
```

{% endtab %}

{% tab title="pnpm" %}

```bash
pnpm add keycloakify
# Installing storybook is optional
pnpm add --dev rimraf storybook @storybook/react @storybook/react-vite
```

{% endtab %}

{% tab title="bun" %}

```bash
bun add keycloakify
# Installing storybook is optional
bun add --dev rimraf storybook @storybook/react @storybook/react-vite
```

{% endtab %}

{% tab title="npm" %}

```bash
npm install --save keycloakify
# Installing storybook is optional
npm install --save-dev rimraf storybook @storybook/react @storybook/react-vite
```

{% endtab %}
{% endtabs %}

Next we want to repatriate the relevant files from [the starter template](https://github.com/keycloakify/keycloakify-starter) into our project:

```bash
cd my-app
git clone https://github.com/keycloakify/keycloakify-starter-webpack tmp
mv tmp/src src/keycloak-theme

# Note for the following command: If you already have Storybook setup
# in your project you can skip this.
# Only make sure you have `staticDirs: ["../public"]` in your .storybook/main.ts
mv tmp/.storybook .

rm -rf tmp
rm src/keycloak-theme/react-app-env.d.ts
mv src/keycloak-theme/index.tsx src/index.tsx
```

<figure><img src="/files/6zM4Sz9EW8Yx7B5WOjlT" alt="" width="308"><figcaption><p>Sate of your codebase after bringing in Keycloakify's starter boilerplate code</p></figcaption></figure>

Now you want to modify your entry point so that:

* If the kcContext global is defined, render your Keycloakify theme
* Else, render your App as usual.

Let's say, for example, your **src/index.tsx** file currently looks like this:

{% code title="src/index.tsx" %}

```tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { MyProvider } from "./MyProvider";
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <MyProvider>
      <App />
    </MyProvider>
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
```

{% endcode %}

You want to **rename** this file to **src/index.app.tsx** (for example) and modify it as follow:

{% code title="src/index.app.tsx" %}

```tsx
import './index.css';
import App from './App';
import { MyProvider } from "./MyProvider";
import reportWebVitals from './reportWebVitals';

export default function AppEntrypoint(){
  return (
    <MyProvider>
      <App />
    </MyProvider>
  );
}

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
```

{% endcode %}

Then you want to create the following **src/index.tsx** file, you can copy paste the followint code, it does not need to be adapted:

{% code title="src/index.tsx" %}

```tsx
import { createRoot } from "react-dom/client";
import { StrictMode, lazy, Suspense } from "react";
import { KcPage, type KcContext } from "./keycloak-theme/kc.gen";
const AppEntrypoint = lazy(() => import("./index.app"));

// The following block can be uncommented to test a specific page with `yarn dev`
// Don't forget to comment back or your bundle size will increase
/*
import { getKcContextMock } from "./keycloak-theme/login/KcPageStory";

if (import.meta.env.DEV) {
    window.kcContext = getKcContextMock({
        pageId: "register.ftl",
        overrides: {}
    });
}
*/

createRoot(document.getElementById("root")!).render(
    <StrictMode>
        {window.kcContext ? (
            <KcPage kcContext={window.kcContext} />
        ) : (
            <Suspense>
                <AppEntrypoint />
            </Suspense>
        )}
    </StrictMode>
);

declare global {
    interface Window {
        kcContext?: KcContext;
    }
}
```

{% endcode %}

{% hint style="info" %}
**Question:**

Why do my main application and Keycloak theme share the same entry point?

**Answer:**

To simplify the build process. If you don't want it to negatively impact the performance of your application, it's essential to understand the following points:

* **Different Contexts:** The application (`App`) and Keycloak page (`KcPage`) are mounted in very different contexts. Avoid sharing providers between the two at the `index.tsx` file level. The true entry point of your application is the `AppEntrypoint` component in `index.app.tsx`, while the entry point for your Keycloak theme is the `KcPage` component. Be careful about what code is shared between them.
* **Responsibility of index.tsx:** The `index.tsx` file should only determine the context (either the application or Keycloak) and mount the appropriate component (`AppEntrypoint` or `KcPage`). It should not contain any substantial logic or dependencies.
* **Performance Considerations:** Keep `index.tsx` as lightweight as possible to avoid increasing the initial load time of both your main application and login pages. For example, do not load any state management libraries like `redux-toolkit` at this level.
  {% endhint %}

Finally you want to add some script for Keycloakify in you package.json and also let Keycloakify know about how your Webpack project is configured.

<pre class="language-json" data-title="package.json"><code class="lang-json">{
    "name": "my-app",
    "scripts": {
<strong>        "prestart": "keycloakify update-kc-gen &#x26;&#x26; keycloakify copy-keycloak-resources-to-public",
</strong>        "start": "react-scripts start",
<strong>        "prestorybook": "npm run prestart",
</strong>        "storybook": "storybook dev -p 6006",
<strong>        "prebuild": "keycloakify update-kc-gen",
</strong>        "build": "react-scripts build",
<strong>        "postbuild": "rimraf build/keycloakify-dev-resources",
</strong><strong>        "build-keycloak-theme": "npm run build &#x26;&#x26; keycloakify build",
</strong>        "format": "prettier . --write"
        // ...
    },
<strong>    "keycloakify": {
</strong><strong>        "accountThemeImplementation": "none",
</strong><strong>        "projectBuildDirPath": "build",
</strong><strong>        "staticDirPathInProjectBuildDirPath": "static",
</strong><strong>        "publicDirPath": "public"
</strong><strong>    },
</strong>    // ...
</code></pre>

{% hint style="info" %}
Leave accountThemeImplementation set to "none" for now.\
To initialize the account theme refer to [this guide](/theme-types/account-theme).
{% endhint %}

Keycloakify has many build options that you can use, however `projectBuildDirPath`, `staticDirPathInProjectBuildDirPath` and `publicDirPath` are parameters specific to the use of Keycloakify in a Webpack context.

Theses **are not preferences!** If you're not using Create React App your Webpack configuration is probably different and you want to update those values to reflect how webpack build your site in your project.

<figure><img src="/files/EAEAFqbpG0UZltpWg03r" alt="" width="209"><figcaption><p>Here you can see that in a CRA project, when we run <code>npm run build</code> the app distribution is generated in a <strong>build/</strong> directory, this is why we use <code>"projectBuildDirPath": "build"</code>. We can also see that all the assets of the app are gathered under a <code>static/</code> directory this is why we use <code>"staticDirPathInProjectBuildDirPath": "static"</code>. And finally we can see that everything we put in the <strong>public/</strong> directory is copied over to the <strong>build/</strong> directory when building so we use <code>"publicDirPath": "public"</code>.</p></figcaption></figure>

Last setp is to exclude from your html `<head />` things that aren't relevent in the context of Keycloak pages.

{% hint style="danger" %}
Do not blindely copy paste, this is just an example!

You have to figure out what does and does not make sense to be in the \<head/> of your Keycloak UI pages.
{% endhint %}

<pre class="language-html" data-title="public/index.html"><code class="lang-html">&#x3C;!DOCTYPE html>
&#x3C;html>
&#x3C;head>
  &#x3C;meta charset="utf-8" />
  &#x3C;link rel="icon" href="%PUBLIC_URL%/icon.png" />
  &#x3C;meta name="viewport" content="width=device-width, initial-scale=1" />
  &#x3C;meta name="theme-color" content="#000000" />
  &#x3C;link rel="apple-touch-icon" href="%PUBLIC_URL%/icon.png" />
  &#x3C;link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
  &#x3C;link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&#x26;display=swap" />
  &#x3C;link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />

<strong>  &#x3C;meta name="keycloakify-ignore-start">
</strong>  &#x3C;title>ACME Dashboard&#x3C;/title>
  &#x3C;script>
    window.ENV = {
      API_ADDRESS: '${API_ADDRESS}',
      SENTRY_DSN: '${SENTRY_DSN}'
    };
  &#x3C;/script>
<strong>  &#x3C;meta name="keycloakify-ignore-end">
</strong>
  &#x3C;!-- ... -->

&#x3C;/head>

&#x3C;!-- ... -->
</code></pre>

In the above example we tell keycloakify not to include the `<title>` because keycloakify will set it dynamically to something like *"ACME- Login"* or *"ACME - Register"*.

We also exclude a placeholder script for injecting environnement variables at container startup.

**That's it, your project is ready to go!** :tada:

You can run `npm run build-keycloak-theme`, the JAR distribution of your Keycloak theme will be generated in `build_keycloak` ([you can change this](/features/compiler-options/keycloakifybuilddirpath)).

You're now able to use all the Keycloakify commands (`npx keycloakify --help`) from the root of your project.

{% hint style="success" %}
If you're currently using [keycloak-js](https://www.npmjs.com/package/keycloak-js) or [react-oidc-context](https://github.com/authts/react-oidc-context) to manage user authentication in your app you might want to checkout [oidc-spa](https://www.oidc-spa.dev/), the alternative from the Keycloakify team.

If you have any issues [reach out on Discord](https://discord.gg/mJdYJSdcm4)! We're here to help!
{% endhint %}


# yarn/npm/pnpm/bun Workspaces

{% hint style="danger" %}
If you're unsure what this section is about, **this approach is NOT for you.** Instead, follow [the Quick Start Guide and fork the starter project.](/#quick-start)
{% endhint %}

Let's assume we have a monorepo project where sub applications are stored in the **apps/** directory.

{% tabs %}
{% tab title="yarn/npm/bun" %}

<pre class="language-json" data-title="package.json"><code class="lang-json">{
  "name": "my-monorepo",
<strong>  "workspaces": [
</strong><strong>    "apps/*"
</strong><strong>    "packages/*"
</strong><strong>  ],
</strong><strong>  "private": true,
</strong></code></pre>

{% endtab %}

{% tab title="pnpm" %}
{% code title="pnpm-workspace.yaml" %}

```yaml
packages:
  - "apps/*"
  - "packages/*"
```

{% endcode %}
{% endtab %}
{% endtabs %}

Then, you want to create a new app called, for example 'keycloak-theme' and initialize it with the code of the starter template:

```bash
cd my-monorepo
git clone https://github.com/keycloakify/keycloakify-starter apps/keycloak-theme
rm -rf apps/keycloak-theme/.git
rm -rf apps/keycloak-theme/.github
rm apps/keycloak-theme/yarn.lock
```

<figure><img src="/files/AmohbrIrLzxeEjUpG95R" alt="" width="375"><figcaption></figcaption></figure>

Now you want to update the name field of your apps/keycloak-theme/package.json to match the name of your sub app.

{% code title="apps/keycloak-theme/package.json" %}

```diff
 {
-    "name": "keycloakify-starter",
+    "name": "keycloak-theme",
```

{% endcode %}

You also want to provide an actual name to your theme as you want it to [appear in the Keycloak Admin UI](https://github.com/keycloakify/keycloakify/assets/6702424/7da4afe2-0f67-4f79-a3d0-bd982636ea23).

<pre class="language-typescript" data-title="apps/keycloak-theme/vite.config.ts"><code class="lang-typescript">import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { keycloakify } from "keycloakify/vite-plugin";

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [react(), keycloakify({
<strong>        themeName: "my-app"
</strong>    })]
});
</code></pre>

Now you can add a script in your root package json to build the theme and start the keycloak dev server:

{% tabs %}
{% tab title="pnpm" %}

<pre class="language-json" data-title="package.json"><code class="lang-json">{
   "name": "my-monorepo",
   "scripts": {
<strong>       "build-keycloak-theme": "pnpm --filter keycloak-theme run build-keycloak-theme",
</strong><strong>       "start-keycloak": "keycloakify start-keycloak -p apps/keycloak-theme"
</strong>   },
   // ...
}
</code></pre>

{% endtab %}

{% tab title="yarn" %}

<pre class="language-json" data-title="package.json"><code class="lang-json">{
   "name": "my-monorepo",
   "scripts": {
<strong>       "build-keycloak-theme": "yarn workspace keycloak-theme run build-keycloak-theme",
</strong><strong>       "start-keycloak": "keycloakify start-keycloak -p apps/keycloak-theme"
</strong>   },
   // ...
}
</code></pre>

{% endtab %}

{% tab title="npm" %}

<pre class="language-json" data-title="package.json"><code class="lang-json">{
   "name": "my-monorepo",
   "scripts": {
<strong>       "build-keycloak-theme": "npm run build-keycloak-theme --workspace=keycloak-theme",
</strong><strong>       "start-keycloak": "keycloakify start-keycloak -p apps/keycloak-theme"
</strong>   },
   // ...
}
</code></pre>

{% endtab %}

{% tab title="bun" %}

<pre class="language-json" data-title="package.json"><code class="lang-json">{
   "name": "my-monorepo",
   "scripts": {
<strong>       "build-keycloak-theme": "bun run --cwd apps/keycloak-theme build-keycloak-theme",
</strong><strong>       "start-keycloak": "keycloakify start-keycloak -p apps/keycloak-theme"
</strong>   },
   // ...
}
</code></pre>

{% endtab %}
{% endtabs %}

Now you can run:

{% tabs %}
{% tab title="pnpm" %}

```bash
pnpm install
pnpm run build-keycloak-theme
```

{% endtab %}

{% tab title="yarn" %}

```bash
yarn
yarn build-keycloak-theme
```

{% endtab %}

{% tab title="npm" %}

```bash
npm install
npm run build-keycloak-theme
```

{% endtab %}

{% tab title="bun" %}

```bash
bun install
bun run build-keycloak-theme
```

{% endtab %}
{% endtabs %}

<figure><img src="/files/YeuNJrZcdiDYFaMI5p03" alt=""><figcaption></figcaption></figure>

Two common thing you might want to do is [change the location of the directory where the JARs files are generated](/features/compiler-options/keycloakifybuilddirpath) and [only build the JAR for the Keycloak version you are using](/features/compiler-options/keycloakversiontargets).

<pre class="language-typescript" data-title="apps/keycloak-theme/vite.config.ts"><code class="lang-typescript">import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { keycloakify } from "keycloakify/vite-plugin";

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [react(), keycloakify({
        themeName: "my-app",
<strong>        keycloakifyBuildDirPath: "../../dist/apps/keycloak-theme",
</strong><strong>        keycloakVersionTargets: {
</strong><strong>            hasAccountTheme: true,
</strong><strong>            "21-and-below": false,
</strong><strong>            "23": false,
</strong><strong>            "24": false,
</strong><strong>            "25-and-above": "keycloak-theme.jar"
</strong><strong>        }
</strong>    })]
});
</code></pre>

In this configuration when you run `pnpm run build-keycloak-theme` from the root of your monorepo a single `keycloak-theme.jar` will be generated in **dist/apps/keycloak-theme**:

<figure><img src="/files/mNYcSeG9eS9woh0cxpSn" alt=""><figcaption></figcaption></figure>

When you want to use the keycloakify CLI commands you can either cd into your keycloakify sub app directory or use the [--project option of the Keycloakify CLI](/features/compiler-options/project).\
Like for example if you want to run add-story you can do either:

* `cd apps/keycloak-theme && npx keycloakify add-story`
* `npx keycloakify add-story -p apps/keycloak-theme` from the root of your monorepo.


# Turborepo

{% hint style="danger" %}
If you're unsure what this section is about, **this approach is NOT for you.** Instead, follow [the Quick Start Guide and fork the starter project.](/#quick-start)
{% endhint %}

First you want to create a new subproject in your monorepo, just clone the starter template into apps/keycloak-theme.

```bash
cd my-turborepo
git clone https://github.com/keycloakify/keycloakify-starter apps/keycloak-theme
rm -rf apps/keycloak-theme/.git
rm -rf apps/keycloak-theme/.github
```

Change the name field in the package.json of your keycloakify sub app.

{% code title="apps/keycloak-theme/package.json" %}

```diff
 {
-    "name": "keycloakify-starter",
+    "name": "keycloak-theme",
 }
```

{% endcode %}

Give an actual name to your theme (as you want it to apprear [in the Keycloak Admin Console](https://github.com/keycloakify/keycloakify/assets/6702424/7da4afe2-0f67-4f79-a3d0-bd982636ea23))

<pre class="language-typescript" data-title="apps/keycloak-theme/vite.config.ts"><code class="lang-typescript">import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { keycloakify } from "keycloakify/vite-plugin";

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [react(), keycloakify({
<strong>        themeName: "my-app"
</strong>    })]
});
</code></pre>

Then you want to add a new script for building your theme in your root **package.json**

<pre class="language-json" data-title="package.json"><code class="lang-json">{
  "name": "my-turborepo",
  "scripts": {
    "build": "turbo build",
    "dev": "turbo dev",
    "lint": "turbo lint",
    "format": "prettier --write \"**/*.{ts,tsx,md}\"",
<strong>    "build-keycloak-theme": "turbo run build-keycloak-theme --filter=keycloak-theme",
</strong><strong>    "start-keycloak": "keycloakify start-keycloak -p apps/keycloak-theme"
</strong>  },
  // ...
}
</code></pre>

Add a turborepo task

<pre class="language-json" data-title="turbo.json"><code class="lang-json">{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    // ... Other tasks
<strong>    "build-keycloak-theme": {
</strong><strong>        "outputs": [
</strong><strong>            "dist/**", 
</strong><strong>            "dist_keycloak/**"
</strong><strong>        ]
</strong><strong>    }
</strong>  }
}
</code></pre>

You can now build your keycloak theme at the root of your monorepo by running

```bash
npm run build-keycloak-theme
```

{% embed url="<https://youtu.be/4h9lOf-4ZIE>" %}
Building the theme, only compiling for Keycloak 25 with a custom jar file name. Demonstrating the effectiveness of turborepo cache
{% endembed %}

Optionally, if you want to change the location of the directory where the jar for your theme are created you can do:

<pre class="language-typescript" data-title="apps/keycloak-theme/vite.config.ts"><code class="lang-typescript">export default defineConfig({
    plugins: [react(), keycloakify({
        themeName: "my-app",
<strong>        keycloakifyBuildDirPath: "../../dist/apps/keycloak-theme"
</strong>    })]
});
</code></pre>

{% code title="turbo.json" %}

```diff
 {
   "$schema": "https://turbo.build/schema.json",
   "tasks": {
     // ... Other tasks
     "build-keycloak-theme": {
         "outputs": [
             "dist/**",
-            "dist_keycloak/**"
+            "../../dist/apps/keycloak-theme/**"
         ]
     }
   }
 }
```

{% endcode %}

If you applies those changes, when you'll run `npm run build-keycloak-theme` your JARs are going to be generated in `dist/keycloak-theme/`

When you want to use the keycloakify CLI commands you can either cd into your keycloakify sub app directory or use the [--project option of the Keycloakify CLI](/features/compiler-options/project).\
Like for example if you want to run add-story you can do either:

* `cd apps/keycloak-theme && npx keycloakify add-story`
* `npx keycloakify add-story -p apps/keycloak-theme` from the root of your monorepo

To go beyond the base configuration you might want to explore what [build options](/features/compiler-options) are available. Starting with with `keycloakVersionTargets` to make sure that you only generates the JARs file you need.

{% content-ref url="/pages/mRBPowGEjKtlpj7o0TtF" %}
[keycloakVersionTargets](/features/compiler-options/keycloakversiontargets)
{% endcontent-ref %}


# Nx

{% hint style="danger" %}
If you're unsure what this section is about, **this approach is NOT for you.** Instead, follow [the Quick Start Guide and fork the starter project.](/#quick-start)
{% endhint %}

Let's see how to integrate a Keycloakify theme into a Nx project with integrated monorepo.

In this example we'll start with the Nx Vite starter

```bash
npx create-nx-workspace@latest --preset=react-monorepo --bundler=vite
```

<figure><img src="/files/jePniCEOzUPHt4BK7Ijk" alt=""><figcaption></figcaption></figure>

Next up we want to repatriate the Keycloakify Starter template sources.\
We only copy over the src and .storybook directory.

```bash
cd nx-monorepo
rm -rf apps/keycloak-theme/src
git clone https://github.com/keycloakify/keycloakify-starter tmp
mv tmp/src apps/keycloak-theme
mv tmp/.storybook apps/keycloak-theme
rm -rf tmp
```

<figure><img src="/files/gr1hj8J9xOJvXYWKfViv" alt="" width="365"><figcaption><p>After moving src and .storybook to apps/keycloak-theme</p></figcaption></figure>

<pre class="language-json" data-title="package.json"><code class="lang-json">{
  "name": "@nx-monorepo/source",
  "version": "0.0.0",
  "scripts": {
<strong>    "build-keycloak-theme": "nx build keycloak-theme &#x26;&#x26; keycloakify build -p apps/keycloak-theme",
</strong><strong>    "keycloak-theme-storybook": "npx storybook dev -p 6006 -c apps/keycloak-theme/.storybook",
</strong><strong>    "start-keycloak": "keycloakify start-keycloak -p apps/keycloak-theme"
</strong>  },
  "dependencies": {
    "react": "18.3.1",
    "react-dom": "18.3.1",
    "tslib": "^2.3.0",
<strong>    "keycloakify": "^10.0.0"
</strong>  },
  "devDependencies": {
<strong>      "storybook": "^8.1.10",
</strong><strong>      "@storybook/react": "^8.1.10",
</strong><strong>      "@storybook/react-vite": "^8.1.10"
</strong>  // ...
</code></pre>

```bash
npm install # or `pnpm install` or `yarn`...
```

<pre class="language-typescript" data-title="apps/keycloak-theme/vite.config.ts"><code class="lang-typescript">/// &#x3C;reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
<strong>import { keycloakify } from "keycloakify/vite-plugin";
</strong>
export default defineConfig({
  root: __dirname,
  cacheDir: '../../node_modules/.vite/apps/keycloak-theme',

  server: {
    port: 4200,
    host: 'localhost',
  },

  preview: {
    port: 4300,
    host: 'localhost',
  },

  plugins: [react(), nxViteTsPaths(), 
<strong>      keycloakify({
</strong><strong>          themeName: "my-project",
</strong><strong>          themeVersion: "1.0.0",
</strong><strong>          keycloakifyBuildDirPath: '../../dist/apps/keycloak-theme'
</strong><strong>       })
</strong>   ],

  // Uncomment this if you are using workers.
  // worker: {
  //  plugins: [ nxViteTsPaths() ],
  // },

  build: {
<strong>    outDir: 'dist',
</strong>    emptyOutDir: true,
    reportCompressedSize: true,
    commonjsOptions: {
      transformMixedEsModules: true,
    },
  },
});
</code></pre>

Now if you run `npm run build-keycloak-theme` it will generate the JAR in dist/apps/keycloak-theme.

<figure><img src="/files/iLgCBCyL6SRsZ7iRL3J4" alt=""><figcaption></figcaption></figure>

When you want to use the keycloakify CLI commands you can either cd into your keycloakify sub app directory or use the [--project option of the Keycloakify CLI](/features/compiler-options/project).\
Like for example if you want to run [add-story](/testing-your-theme/outside-of-keycloak) you can do either:

* `cd apps/keycloak-theme && npx keycloakify add-story`

OR

* `npx keycloakify add-story -p apps/keycloak-theme` from the root of your monorepo

To go beyond the base configuration you might want to explore what [build options](/features/compiler-options) are available. Starting with with `keycloakVersionTargets` to make sure that you only generates the JARs file you need.

{% content-ref url="/pages/mRBPowGEjKtlpj7o0TtF" %}
[keycloakVersionTargets](/features/compiler-options/keycloakversiontargets)
{% endcontent-ref %}


# Angular Workspace

{% hint style="danger" %}
If you're unsure what this section is about, **this approach is NOT for you.** Instead, follow [the Quick Start Guide and fork the starter project.](/#quick-start)
{% endhint %}

## Integrating Keycloakify into an Angular Workspace

Let's assume you have a monorepo project where sub applications are stored in the **projects/** directory.

Next up you want to repatriate the Keycloakify Starter template sources.

```bash
cd my-workspace
yarn ng generate application keycloak-theme
rm -rf projects/keycloak-theme/public
rm -rf projects/keycloak-theme/src
git clone https://github.com/keycloakify/keycloakify-starter-angular-vite tmp
mv tmp/src projects/keycloak-theme
mv tmp/public projects/keycloak-theme
mv tmp/vite.config.ts projects/keycloak-theme
mv tmp/index.html projects/keycloak-theme
rm -rf tmp
```

#### Adjust the `vite.config.ts` File

Edit the highlighted path in `vite.config.ts` file to match the following configuration:

<pre class="language-javascript" data-title="vite.config.ts"><code class="lang-javascript">/// 

import { defineConfig } from 'vite';
import angular from '@analogjs/vite-plugin-angular';
import { keycloakify } from 'keycloakify/vite-plugin';

// https://vitejs.dev/config/
export default defineConfig(({ mode }) => ({
  build: {
    target: ['es2022'],
  },
<strong>  root: 'projects/keycloak-theme',
</strong>  resolve: {
    mainFields: ['module'],
  },
  plugins: [
    angular(),
    keycloakify({
      accountThemeImplementation: 'none',
<strong>      themeName: 'keycloak-theme',
</strong><strong>      keycloakifyBuildDirPath: '../../dist/keycloak-theme'
</strong>    }),
  ],
  define: {
    'import.meta.vitest': mode !== 'production',
  },
}));

</code></pre>

after this, your project structure should look like the following:

```
my-workspace/

│
├── projects/
│   └── keycloak-theme/
│       ├── src/
│       ├── public/
│       ├── index.html
│       ├── tsconfig.app.json
│       ├── tsconfig.spec.json
│       └── vite.config.ts
│
├── angular.json
└── package.json

```

### Update `package.json` with Keycloakify Configuration

Ensure the following lines are present in your workspace's `package.json`:

<pre class="language-json" data-title="package.json"><code class="lang-json">{
  "name": "my-workspace",
  "version": "0.0.0",
<strong>  "type": "module",
</strong>  "scripts": {
    ...
<strong>    "build-keycloak-theme": "ng build --project keycloak-theme &#x26;&#x26; npx keycloakify build --project projects/keycloak-theme",
</strong>    ...
  },
  "dependencies": {
    ...
<strong>    "@keycloakify/angular": "^0.2.14",
</strong><strong>    "keycloakify": "^11.8.1",
</strong><strong>    "marked": "^5.0.2",
</strong><strong>    "marked-gfm-heading-id": "^3.0.4",
</strong><strong>    "marked-highlight": "^2.0.1",
</strong><strong>    "marked-mangle": "^1.1.7",
</strong><strong>    "prismjs": "^1.29.0",
</strong>    ...
  },
  "devDependencies": {
    ...
<strong>    "@analogjs/platform": "^1.11.0",
</strong><strong>    "@analogjs/vite-plugin-angular": "^1.9.0",
</strong><strong>    "@analogjs/vitest-angular": "^1.9.0",
</strong><strong>    "@angular-devkit/architect": "^0.1900.6",
</strong><strong>    "@nx/angular": "^20.3.0",
</strong><strong>    "@nx/devkit": "^20.3.0",
</strong><strong>    "@nx/vite": "^20.3.0",
</strong><strong>    "vite": "^5.0.0",
</strong><strong>    "vite-tsconfig-paths": "^4.2.0",
</strong><strong>    "vitest": "^2.0.0"
</strong>    ...
  }
}
</code></pre>

### Add the Keycloakify Project to `angular.json`

To integrate the **Keycloakify** project into your workspace, update the `angular.json` file by adding an entry to the `projects` section. Below is an example configuration. Important lines that may require customization based on your project’s requirements are highlighted:

<pre class="language-json" data-title="angular.json"><code class="lang-json">  ...
    "keycloak-theme": {
      "projectType": "application",
      "schematics": {},
<strong>      "root": "projects/keycloak-theme",
</strong><strong>      "sourceRoot": "projects/keycloak-theme/src",
</strong><strong>      "prefix": "kc",
</strong>      "architect": {
        "build": {
<strong>          "builder": "@analogjs/platform:vite",
</strong>          "options": {
<strong>            "configFile": "projects/keycloak-theme/vite.config.ts",
</strong><strong>            "outputPath": "projects/keycloak-theme/dist",
</strong><strong>            "main": "projects/keycloak-theme/src/main.ts",
</strong><strong>            "index": "projects/keycloak-theme/index.html",
</strong><strong>            "tsConfig": "projects/keycloak-theme/tsconfig.app.json"
</strong>          },
          "configurations": {
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "500kB",
                  "maximumError": "1MB"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "4kB",
                  "maximumError": "8kB"
                }
              ],
              "outputHashing": "all"
            },
            "development": {
              "optimization": false,
              "extractLicenses": false,
              "sourceMap": true
            }
          },
          "defaultConfiguration": "production"
        },
        "serve": {
<strong>          "builder": "@analogjs/platform:vite-dev-server",
</strong>          "configurations": {
            "production": {
              "buildTarget": "keycloak-theme:build:production",
<strong>              "port": 5173
</strong>            },
            "development": {
              "buildTarget": "keycloak-theme:build:development",
              "hmr": true
            }
          },
          "defaultConfiguration": "development"
        },
        "lint": {
          "builder": "@angular-eslint/builder:lint",
          "options": {
            "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
          }
        }
      }
    }
</code></pre>

## Use Keycloakify

The application should now be good to go. Make sure that whenever you run a `npx keycloakify` command in your workspace root you add the path to your keycloakify project like this:\
`npx keycloakify build --project projects/keycloak-theme`


# Using a Component Library

The Keycloakify starter repository may initially seem sparse in terms of React/Angular/Svelete components, which might be confusing. However, there's no need to worry—this design choice will soon make sense.

By default, Keycloakify internalizes all the components that make up the default UI, exposing only the `DefaultPage` component.

The idea behind this approach is to let you have in your project only the pages that you have modified and don't overwhelm people that only want to apply CSS customization.

If you want to customize any component from the default theme, you can easily do so by running the following command:

```bash
npx keycloakify eject-page
```

This command allows you to select specific components from Keycloakify's source code, which will then be copied into your own codebase for further customization.

{% embed url="<https://youtu.be/PhNE-3EwwP8>" %}
Video tutorial on how to use MUI to customize the login page
{% endembed %}

{% hint style="info" %}
Disabling the default styles: One thing that is touched on [only late in the video](https://youtu.be/PhNE-3EwwP8?si=s3e9DjaIlhG2uxQC\&t=1338) is how to disable all the default styles. See documentation [here](/css-customization#remove-all-the-default-styles).
{% endhint %}


# Custom Fonts

## Using a web font service

Let's see how to use, for example, [Playwrite Netherland](https://fonts.google.com/specimen/Playwrite+NL) via Google Fonts.\
\
Create the following CSS file:

{% code title="src/login/main.css" %}

```css
@import url('https://fonts.googleapis.com/css2?family=Comic+Neue:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Playwrite+NL:wght@100..400&family=Playwrite+PL:wght@100..400&display=swap');

.kcHeaderWrapperClass {
    /* NOTE: We would use `body {` if we'd like the font to be applied to everything. */
    font-family: "Playwrite NL", cursive;
}
```

{% endcode %}

Then import it as a global stylesheet:

{% tabs %}
{% tab title="React" %}

<pre class="language-tsx" data-title="src/login/KcApp.tsx"><code class="lang-tsx"><strong>import "./main.css";
</strong>// ...
</code></pre>

{% endtab %}

{% tab title="Angular" %}

<pre class="language-tsx" data-title="src/login/KcApp.ts"><code class="lang-tsx"><strong>import "./main.css";
</strong>// ...
</code></pre>

{% endtab %}

{% tab title="Svelte" %}

<pre class="language-html" data-title="src/login/KcPage.svelte"><code class="lang-html">&#x3C;script lang="ts">
<strong>  import "./main.css";
</strong>  import Template from '@keycloakify/svelte/login/Template.svelte';
  ...
</code></pre>

{% endtab %}
{% endtabs %}

That's it!

<figure><img src="/files/NfHBCxeAwuu7cq5vXVUp" alt="" width="375"><figcaption><p>Playwrite NL successfully applied to the header</p></figcaption></figure>

## Using self hosted fonts

Keycloak is often used in enterprise internal network with strict network traffic control. In this context, using a Font CDN isn't an option, you want the font to be bundled in your JAR and served directly by the Keycloak server.

Let's see how we would use a self hosted copy [Vercel's Geist](https://vercel.com/font) font.

First let's download and extract [the font files](https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/geist.zip) in `src/login/assets/fonts/geist/`:

<figure><img src="/files/9B3G6jiA5W3d1ySL7iet" alt="" width="375"><figcaption></figcaption></figure>

Now let's set Geist as the default font.

Create the following CSS file:

{% code title="src/login/main.css" %}

```css
@import url(./assets/fonts/geist/main.css);

body {
  font-family: Geist;
}
```

{% endcode %}

Then import it as a global stylesheet:

{% tabs %}
{% tab title="React" %}

<pre class="language-tsx" data-title="src/login/KcPage.tsx"><code class="lang-tsx"><strong>import "./main.css";
</strong>// ...
</code></pre>

{% endtab %}

{% tab title="Angular" %}

<pre class="language-tsx" data-title="src/login/KcApp.ts"><code class="lang-tsx"><strong>import "./main.css";
</strong>// ...
</code></pre>

{% endtab %}

{% tab title="Svelte" %}

<pre class="language-html" data-title="src/login/KcPage.svelte"><code class="lang-html">&#x3C;script lang="ts">
<strong>  import "./main.css";
</strong>  import Template from '@keycloakify/svelte/login/Template.svelte';
  ...
</code></pre>

{% endtab %}
{% endtabs %}

That's it!

<figure><img src="/files/YfK7o60PexzxOhipRLqE" alt=""><figcaption><p>Geist successfully applied</p></figcaption></figure>


# Changing the background image

Let's see, as an example, the different ways you have to change the backgrounds image of the login page using CSS only.

{% hint style="info" %}
There is the equivalent of this guide using CSS-in-JS [here](/common-use-case-examples/changing-the-background-image-css-in-js).
{% endhint %}

First let's [download a background image](https://coolbackgrounds.io/) an put it in **src/login/assets/background.png**.

Then let's apply it as background:

{% code title="src/login/main.css" %}

```css
body.kcBodyClass {
  background: url(./assets/background.png) no-repeat center center fixed;
}
```

{% endcode %}

We import the StyleSheet:

{% tabs %}
{% tab title="React" %}

<pre class="language-tsx" data-title="src/login/KcPage.tsx"><code class="lang-tsx"><strong>import "./main.css";
</strong>// ...
</code></pre>

{% endtab %}

{% tab title="Angular" %}

<pre class="language-typescript" data-title="src/login/KcPage.ts"><code class="lang-typescript"><strong>import "./main.css";
</strong>import { getDefaultPageComponent, type KcPage } from '@keycloakify/angular/login';
// ...
</code></pre>

{% endtab %}

{% tab title="Svelte" %}

<pre class="language-html" data-title="src/login/KcPage.svelte"><code class="lang-html">&#x3C;script lang="ts">
<strong>  import "./main.css";
</strong>  ...
</code></pre>

{% endtab %}
{% endtabs %}

Result:

<figure><img src="/files/WmPcIpbZqi1AzhVW33zH" alt=""><figcaption><p>Custom background successfully applied</p></figcaption></figure>

<details>

<summary>Replacing the image without re-building the theme</summary>

If you want to be able to "hot swipe" the image, without rebuilding the theme you have to import the image from a different location.

Place the file into **/public/background.png**.

Then, in your CSS code import the image with an absolute path:

{% code title="src/login/main.css" %}

```css
body.kcBodyClass {
  background: url(/background.png) no-repeat center center fixed;
}
```

{% endcode %}

Now if you want to replace the image directly in Keycloak you'll be able to find it at:

**/opt/keycloak/themes/**[**\<name of your theme>**](/features/compiler-options/themename)**/login/resources/dist/background.png**

<img src="/files/UJnsC0zb7Td1ro2i4Wbi" alt="" data-size="original">

</details>

For a more advanced example, in the following video I show how to load different background for different page and how to create [theme variant](/features/theme-variants).

{% embed url="<https://youtu.be/Nkoz1iD-HOA?si=hBXt8rw72-Pvhhnr>" %}


# Changing the background image - CSS-in-JS

{% tabs %}
{% tab title="Vite" %}
{% hint style="info" %}
TLDR: There is nothing specific to Keycloakify about importing assets. You can do it however you would in any other project.

Just if you're referencing assets that are in the public directory, use `import.meta.env.BASE_URL`
{% endhint %}
{% endtab %}

{% tab title="Webpack" %}
{% hint style="info" %}
TLDR: You can import asset like you would in any other project, one exception being: If you reference assets that are located in your public directory from within your TSX files you must use Keycloakify's polifill of the `PUBLIC_URL` environnement variable, you can't use `process.env.PUBLIC_URL` directly:

```tsx
import { PUBLIC_URL } from "keycloakify/PUBLIC_URL";
<img src={`${PUBLIC_URL}/my-image.png`} />
```

{% endhint %}
{% endtab %}
{% endtabs %}

CSS-in-JS is preferable over plain CSS as it enables for more flexibility and is easier to maintain.

Let's see, as an example, the different ways you have to change the background image of the login page.

First let's [download a background image](https://coolbackgrounds.io/) an put it in our public directory:

<figure><img src="/files/R9BGVvJbh2bJPSkRnE7w" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
If you wish to do so, you can hot swipe assets that you have placed into your public directory in your Keycloak instance files at:

**/opt/keycloak/themes/**[**\<name of your theme>**](/features/compiler-options/themename)**/\<login|account>/resources/dist**

<img src="/files/UJnsC0zb7Td1ro2i4Wbi" alt="" data-size="original">
{% endhint %}

Let's see how we can apply the image using a CSS-in-JS. In this example we'll use [@emotion/css](https://emotion.sh/docs/introduction).

```bash
yarn add @emotion/css
```

{% tabs %}
{% tab title="Vite" %}

<pre class="language-tsx" data-title="src/login/KcPage.tsx"><code class="lang-tsx"><strong>import { css } from "@emotion/css";
</strong>import { Suspense, lazy } from "react";
// ...
export default function KcPage(props: { kcContext: KcContext }) {
    const { kcContext } = props;    
    // ...
    return (
        // ...
        &#x3C;DefaultPage
            kcContext={kcContext}
            classes={classes}
            // ...
        />
    );
}

const classes = {
<strong>    kcBodyClass: css({
</strong><strong>        "&#x26;&#x26;": { // Increase specificity so our rule takes precedence over the default background.
</strong><strong>            background: `url(${import.meta.env.BASE_URL}background.png) no-repeat center center fixed`,
</strong><strong>        }
</strong><strong>    })
</strong>} satisfies { [key in ClassKey]?: string };
</code></pre>

{% endtab %}

{% tab title="Webpack" %}

<pre class="language-tsx" data-title="src/login/KcPages.tsx"><code class="lang-tsx"><strong>import { css } from "@emotion/css";
</strong><strong>import { PUBLIC_URL } from "keycloakify/PUBLIC_URL"; // You can't use process.env.PUBLIC_URL directly.
</strong>import { Suspense, lazy } from "react";
// ...
export default function KcPage(props: { kcContext: KcContext }) {
    const { kcContext } = props;    
    // ...
    return (
        // ...
        &#x3C;DefaultPage
            kcContext={kcContext}
            classes={classes}
            // ...
        />
    );
}

const classes = {
    kcBodyClass: css({
        "&#x26;&#x26;": { // Increase specificity so our rule takes precedence over the default background.
            background: `url(${PUBLIC_URL}/background.png) no-repeat center center fixed`,
        }
    })
} satisfies { [key in ClassKey]?: string };
</code></pre>

{% endtab %}
{% endtabs %}

Result (see [testing your theme](/testing-your-theme)):

<figure><img src="/files/WmPcIpbZqi1AzhVW33zH" alt=""><figcaption><p>Custom background successfully applied</p></figcaption></figure>

Now let's go a little further, it's even better to let the bundler generate url for your imports instead of manually referencing files from your public directory.\
So, let's move the background image in **src/login/assets/**:

<figure><img src="/files/BfDHzdcm5MrP6msYqQhl" alt="" width="375"><figcaption></figcaption></figure>

And in our code import it this way:

<pre class="language-tsx" data-title="src/login/KcPage.tsx"><code class="lang-tsx">import { css } from "@emotion/css";
<strong>import backgroundPngUrl from "./assets/background.png";
</strong>import { Suspense, lazy } from "react";
// ...
export default function KcPage(props: { kcContext: KcContext }) {
    const { kcContext } = props;    
    // ...
    return (
        // ...
        &#x3C;DefaultPage
            kcContext={kcContext}
            classes={classes}
            // ...
        />
    );
}

const classes = {
    kcBodyClass: css({
        "&#x26;&#x26;": {
<strong>            background: `url(${backgroundPngUrl}) no-repeat center center fixed`,
</strong>        }
    })
} satisfies { [key in ClassKey]?: string };
</code></pre>

Now let's see how we can go further and apply different background on different pages of our theme:

{% embed url="<https://youtu.be/vRPlGUD-KvE>" %}


# Adding your Logo

Practical example of how to import custom assets in ejected components.

{% hint style="info" %}
NOTE: You can very well change the logo using only CSS without having to ejecting the template. There's a demo in [this video](https://youtu.be/Nkoz1iD-HOA?si=6DLF7iAPTeX-pkNP).
{% endhint %}

Let's say you want to put the logo of your company on every pages of the theme.

First you'd eject the Template:

```bash
npx keycloakify eject-page # Select login -> Template.tsx
```

<figure><img src="/files/0amuz948co8wQavqld2l" alt=""><figcaption></figcaption></figure>

This will create a **src/login/Template.tsx** file in your project.

Let's use this placeholder for the demo: [logo.png](https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/logo.png) and save it in **src/login/assets/logo.png**.

Now we can use the asset in our component:

{% tabs %}
{% tab title="React" %}

<pre class="language-tsx" data-title="src/login/Template.tsx"><code class="lang-tsx"><strong>import logoPngUrl from "./assets/logo.png";
</strong>// ...
&#x3C;div className={kcClsx("kcLoginClass")}>
    &#x3C;div id="kc-header" className={kcClsx("kcHeaderClass")}>
        &#x3C;div id="kc-header-wrapper" className={kcClsx("kcHeaderWrapperClass")}>
<strong>            {/*{msg("loginTitleHtml", realm.displayNameHtml)}*/}
</strong><strong>            &#x3C;img src={logoPngUrl} width={500}/>
</strong>        &#x3C;/div>
    &#x3C;/div>
    {/* ... */}
</code></pre>

{% endtab %}

{% tab title="Svelte" %}
{% code title="src/login/Template.svelte" %}

```html
<script lang="ts">
  import logoPngUrl from "./assets/logo.png";
  // ...
</script>

<div
  id="kc-header-wrapper"
  class={kcClsx('kcHeaderWrapperClass')}
>
  <!--{ msgStr('loginTitleHtml', realm.displayNameHtml) }-->
  <img src={logoPngUrl} width={500} />
</div>
```

{% endcode %}
{% endtab %}

{% tab title="Angular" %}

<pre class="language-typescript" data-title="src/login/template/template.component.ts"><code class="lang-typescript"><strong>import logoPngUrl from '../assets/logo.png';
</strong>
export class TemplateComponent extends ComponentReference {
<strong>  logoPngUrl = logoPngUrl;
</strong>  // ...
</code></pre>

<pre class="language-html" data-title="src/login/template/template.component.html"><code class="lang-html">&#x3C;img
<strong>  [src]="logoPngUrl"
</strong>  alt="logo"
  width="500"
/>
</code></pre>

{% endtab %}
{% endtabs %}

Result:

<figure><img src="/files/AGGeK6ojk3iPUHwnwtFY" alt=""><figcaption></figcaption></figure>

## Optional: Updating the logo without re-building the theme

Some people want to be able to "hot swap" the asset in the Keycloak file system without having to re-build the theme and re-deploy it.

To ensure that the assets are located in a predictible location you would use the public/ directory.

Move the file to **public/img/logo.png**.

Then make an absolute import from your component:

{% tabs %}
{% tab title="React - Vite" %}
It's important that you do not simply harcode `src="/img/logo.png"`, or keycloakify won't be able to patch the URL for Keycloak. Use Vite's builtin `import.meta.env.BASE_URL`.

<pre class="language-tsx" data-title="src/login/Template.tsx"><code class="lang-tsx">&#x3C;div className={kcClsx("kcLoginClass")}>
    &#x3C;div id="kc-header" className={kcClsx("kcHeaderClass")}>
        &#x3C;div id="kc-header-wrapper" className={kcClsx("kcHeaderWrapperClass")}>
<strong>            {/*{msg("loginTitleHtml", realm.displayNameHtml)}*/}
</strong><strong>            &#x3C;img src={`${import.meta.env.BASE_URL}img/logo.png`} width={500}/>
</strong>        &#x3C;/div>
    &#x3C;/div>
    {/* ... */}
</code></pre>

Doing this is a good practice in any Vite project (not specially Keycloakify) since it ensure the correctness of your URLs even if you customize the `base` parameter in the your **vite.config.ts**. Hard coding `"/img/logo.png"` only works when base is `"/"` (which is the default)
{% endtab %}

{% tab title="Svelte" %}
It's important that you do not simply harcode `src="/img/logo.png"`, or keycloakify won't be able to patch the URL for Keycloak. Use Vite's builtin `import.meta.env.BASE_URL`.

{% code title="src/login/Template.svelte" %}

```html
<div
  id="kc-header-wrapper"
  class={kcClsx('kcHeaderWrapperClass')}
>
  <!--{ msgStr('loginTitleHtml', realm.displayNameHtml) }-->
  <img src={`${import.meta.env.BASE_URL}img/logo.png`} width={500} />
</div>
```

{% endcode %}

Doing this is a good practice in any Vite project (not specially Keycloakify) since it ensure the correctness of your URLs even if you customize the "base" parameter in the your vite.config.ts. Writing "/img/logo.png" only works when base is "/" (which is the default)
{% endtab %}

{% tab title="Angular - Vite" %}

<pre class="language-typescript" data-title="src/login/template/template.component.ts"><code class="lang-typescript">export class TemplateComponent extends ComponentReference {
<strong>  BASE_URL = import.meta.env.BASE_URL;
</strong></code></pre>

{% code title="src/login/template/template.component.html" %}

```html
<img
  [src]="BASE_URL + 'img/logo.png'"
  alt="logo"
  width="500"
/>
```

{% endcode %}
{% endtab %}

{% tab title="React - Webpack/CRA" %}
It's important that you do not simply harcode `src="/img/logo.png"`, or keycloakify won't be able to patch the URL for Keycloak. Use `PUBLIC_URL` instead.

<pre class="language-tsx" data-title="src/login/Template.tsx"><code class="lang-tsx">import { PUBLIC_URL } from "keycloakify/PUBLIC_URL";

&#x3C;div className={kcClsx("kcLoginClass")}>
    &#x3C;div id="kc-header" className={kcClsx("kcHeaderClass")}>
        &#x3C;div id="kc-header-wrapper" className={kcClsx("kcHeaderWrapperClass")}>
<strong>            {/*{msg("loginTitleHtml", realm.displayNameHtml)}*/}
</strong><strong>            &#x3C;img src={`${PUBLIC_URL}/img/logo.png`} width={500}/>
</strong>        &#x3C;/div>
    &#x3C;/div>
    {/* ... */}
</code></pre>

NOTE: You can see PUBLIC\_URL as an equivalent of `process.env.PUBLIC_URL` that will work inside and outside of Keycloak.
{% endtab %}
{% endtabs %}

If you ever need to SSH into the Keycloak server and hot swap the image you can find it at

**/opt/keycloak/themes/**[**\<name of your theme>**](/features/compiler-options/themename)**/login/resources/dist/img/logo.png**

<figure><img src="/files/WhFTwEzmNJK5XpGPJbuZ" alt=""><figcaption><p>Inspecting the Docker Keycloak docker image file system we can find the logo.png at the expected location.</p></figcaption></figure>


# Using Tailwind

This demo show basic usage of tailwind in a Keycloakify login theme.

We show how you can levrage tailwind @apply directive to customize the default style without having to modify the components, just by tagetting the standardized kc classes.

We allso show how to use tailwind the regular way, at the component level on ejected pages.

Before you start with this exemple it is strongly recommended you read the CSS Level Customization guide. This will teach you how to partially or completely disable the PatternFly[^1] stlyes inherited from the default theme.

{% content-ref url="/pages/zgoryu64zhr2MBNGlmOi" %}
[CSS Customization](/css-customization)
{% endcontent-ref %}

To use Tailwind in your Keycloakify project start by following the setup guide for Vite.

{% embed url="<https://tailwindcss.com/docs/guides/vite#react>" %}

Beyond that, here is a demo setup of light modification of the starter template to incorporate tailwind:

{% embed url="<https://github.com/keycloakify/keycloakify-starter/tree/tailwind>" %}

{% embed url="<https://github.com/user-attachments/assets/efaa3fe7-aaf6-43cb-ae55-058858bd40ec>" %}
Preview on the 'tailwind' branch for the starter template
{% endembed %}

What has been done:

* [Applying some custom tailwind utilities classes using the @apply directive](https://github.com/keycloakify/keycloakify-starter/blob/dd516e53e4dfa7c1ce02bab557420b999e87eca2/src/login/index.css#L7-L14).
* Using the [Geist](https://vercel.com/font) font, [here](https://github.com/keycloakify/keycloakify-starter/blob/dd516e53e4dfa7c1ce02bab557420b999e87eca2/tailwind.config.js#L9-L11), [here](https://github.com/keycloakify/keycloakify-starter/blob/dd516e53e4dfa7c1ce02bab557420b999e87eca2/src/login/index.css#L1) and [here](https://github.com/keycloakify/keycloakify-starter/blob/dd516e53e4dfa7c1ce02bab557420b999e87eca2/src/login/index.css#L9).
* Ejecting the [login.ftl](https://storybook.keycloakify.dev/?path=/story/login-login-ftl--default) page (`npx keycloakify eject-page` and *login -> login.ftl*) and [applying a tailwind class](https://github.com/keycloakify/keycloakify-starter/blob/dd516e53e4dfa7c1ce02bab557420b999e87eca2/src/login/pages/Login.tsx#L172).

Here is the summary of the changes:

{% embed url="<https://github.com/keycloakify/keycloakify-starter/commit/e6c71f13acbc65ccb8f57172c45e8c04a2151007>" %}

[^1]: [PatternFly](https://v5-archive.patternfly.org/) is a utility based CSS framwork by RedHat in akin to [Bootstrap CSS](https://getbootstrap.com/docs/3.4/css/).\
    It is used by the Keycloak team to build all it's UI.


# Dark Mode Persistence

If your app offers a dark/light mode, you might be wondering how to “transfer” that mode to your login theme. This ensures that if users are browsing your app in dark mode, then click “Login,” they’ll be redirected to a Keycloakify login UI that’s rendered in dark mode.

{% embed url="<https://youtu.be/EbNGhg5aNF8>" %}
Example of a Keycloakify theme implementation that carries over dark mode
{% endembed %}

This is somewhat of a niche use case, but it illustrates how you can pass state from your application to your Keycloak UIs.

## In Your Web Application

Typically, when your user clicks your “Login” button in the header, your application will redirect them to a URL that looks something like this:

**https\://\<your-keycloak-url>/realms/protocol/openid-connect/auth?client\_id=\<your-client>**

What we want to do is append, for example, `&dark=true` or `&dark=false` to that URL so it can be retrieved on the other side by your Keycloak theme.

How you do that depends on your stack. Let’s look at an example with:

* A React SPA
* [oidc-spa](https://www.oidc-spa.dev/), a modern alternative to `keycloak-js`
* [MUI](https://mui.com/material-ui/), a popular React component library

The following snippet is a React component typically placed in the header of your application for displaying Login and Register buttons.

<figure><img src="/files/QILdViDlctn3Hp1D8EvM" alt=""><figcaption></figcaption></figure>

```tsx
import { useOidc } from "oidc";
import { useTheme } from "@mui/material/styles";
import Button from "@mui/material/Button";
import { assert } from "tsafe/assert";

export function AuthButtons() {
  const { isUserLoggedIn, login } = useOidc();

  assert(
    !isUserLoggedIn,
    "If this component is rendered, the user should not be logged in"
  );

  const theme = useTheme();

  const extraQueryParams = {
    dark: theme.palette.mode === "dark" ? "true" : "false",
    // ui_locales is a special query param that Keycloak recognizes.
    // You can set it to make sure the login pages are
    // displayed in the correct language.
    ui_locales: "en"
  };

  return (
    <>
      <Button
        onClick={() =>
          login({
            doesCurrentHrefRequiresAuth: false,
            extraQueryParams
          })
        }
      >
        Login
      </Button>
      <Button
        variant="contained"
        onClick={() =>
          login({
            doesCurrentHrefRequiresAuth: false,
            transformUrlBeforeRedirect: url => {
              const urlObj = new URL(url);
              urlObj.pathname = urlObj.pathname.replace(
                /\/auth$/,
                "/registrations"
              );
              return urlObj.href;
            },
            extraQueryParams
          })
        }
      >
        Register
      </Button>
    </>
  );
}
```

## In Your Login Theme

Within your Keycloak theme, you can now create a utility to read your custom `&dark=true|false` parameter.

{% code title="src/shared/isDark.ts" %}

```typescript
const SESSION_STORAGE_KEY = "isDark";

function getIsDark(): boolean {
    from_url: {
        const url = new URL(window.location.href);

        const value = url.searchParams.get("dark");

        if (value === null) {
            // There was no &dark= query param in the URL,
            // so we check session storage next.
            break from_url;
        }

        // Remove &dark= from the URL (just to keep it clean)
        url.searchParams.delete("dark");
        window.history.replaceState({}, "", url.toString());

        const isDark = value === "true";
        
        // Persist the value in session storage so that
        // if the user navigates, for example, from login.ftl to
        // register.ftl, we don’t lose the state.
        sessionStorage.setItem(SESSION_STORAGE_KEY, `${isDark}`);

        return isDark;
    }

    from_session_storage: {
        const value = sessionStorage.getItem(SESSION_STORAGE_KEY);

        if (value === null) {
            break from_session_storage;
        }

        return value === "true";
    }

    // Return the browser preference
    return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
}
```

{% endcode %}

How you use this utility depends heavily on your framework and UI library. As an example, here’s what it might look like with React/MUI:

<pre class="language-tsx" data-title="src/login/KcPage.tsx"><code class="lang-tsx">import type { KcContext } from "./KcContext";
import { useI18n } from "./i18n";
import DefaultPage from "keycloakify/login/DefaultPage";
import Template from "keycloakify/login/Template";
import { createTheme, ThemeProvider } from "@mui/material/styles";
<strong>import { getIsDark } from "../shared/isDark";
</strong>const UserProfileFormFields = lazy(() => import("keycloakify/login/UserProfileFormFields"));

const doMakeUserConfirmPassword = true;

const theme_dark = createTheme({
    palette: {
        mode: "dark"
    }
});
const theme_light = createTheme({
    palette: {
        mode: "light"
    }
});

export default function KcPage(props: { kcContext: KcContext }) {
    return (
<strong>        &#x3C;ThemeProvider theme={getIsDark() ? theme_dark : theme_light}>
</strong>            &#x3C;KcPageContextualized {...props} />
        &#x3C;/ThemeProvider>
    );
}

function KcPageContextualized(props: { kcContext: KcContext }) {
    const { kcContext } = props;
    const { i18n } = useI18n({ kcContext });

    return (
        &#x3C;Suspense>
            {(() => {
                switch (kcContext.pageId) {
                    default:
                        return (
                            &#x3C;DefaultPage
                                kcContext={kcContext}
                                i18n={i18n}
                                classes={classes}
                                Template={Template}
                                doUseDefaultCss={true}
                                UserProfileFormFields={UserProfileFormFields}
                                doMakeUserConfirmPassword={doMakeUserConfirmPassword}
                            />
                        );
                }
            })()}
        &#x3C;/Suspense>
    );
}
</code></pre>


# Internationalization and Translations

Or i18n for short

Keycloakify provides all the tooling you need to localize you Keycloak UIs!

{% content-ref url="/pages/d7uAH9ocLK03CHka4Moz" %}
[Basic principles](/features/i18n/basic-principles)
{% endcontent-ref %}

{% content-ref url="/pages/Kj4AZDdNHzHHGacy5uFx" %}
[Adding Support for Extra Languages](/features/i18n/adding-support-for-extra-languages)
{% endcontent-ref %}

{% content-ref url="/pages/Rh2gVRyn5bvKWRwwCsf3" %}
[Previewing Your Pages in Different Languages](/features/i18n/previewing-your-pages-in-different-languages)
{% endcontent-ref %}

{% content-ref url="/pages/sCdtaYhMmNkaxeLbUdx2" %}
[Adding New Translation Messages or Changing the Default Ones](/features/i18n/adding-new-translation-messages-or-changing-the-default-ones)
{% endcontent-ref %}


# Basic principles

{% hint style="info" %}
This documentation explains how i18n works in the Login theme.\
\
In the Account Multi-Page theme, everything works the same as in the Login theme. You just need to replace **/login/** by **/account/** in the import paths.

\
In the Account Single-Page theme, things works differently. [See relevent doc](https://github.com/keycloakify/docs.keycloakify.dev/blob/v11_next/features/account-theme/single-page.md#i18n-internationalization-and-translation).
{% endhint %}

In the Keycloak Admin Console you can enable localisation by selecting a set of language that you wish to support:

<figure><img src="/files/Rc5w3IMJHifEPtySxzAO" alt=""><figcaption><p>Enabling English, French and Spanish as supported languages in a Keycloak Realm</p></figcaption></figure>

{% hint style="info" %}
Want to add languages not in the default set into Keycloakify? [See how](/features/i18n/adding-support-for-extra-languages).
{% endhint %}

When Internationalization is enabled you will see a language dropdown select in your UIs:

<figure><img src="/files/l6DyFy1VochEEwjSq8Nt" alt="" width="375"><figcaption></figcaption></figure>

{% hint style="success" %}
You shouldn't rely on the language select to let your users select their language.

Infact, I encourage you to hide or remove it.

What you should do instead is, when redirecting your user from your application to your Keycloak login page, add an extra query param to let Keycloak know in what language the page should be rendered.

The parameter to add is ?ui\_locales=fr (Example if we want the UI to be in French).

See [oidc-spa documentation](https://docs.oidc-spa.dev/documentation/usage) for more info on how to provide this parameter. (You can do the same if you use keycloak-js or NextAuth)
{% endhint %}

If you [eject some pages](https://github.com/keycloakify/docs.keycloakify.dev/blob/v11_next/features/customization-strategies/component-level-customization/README.md), you'll see in your component how the internationalization is actually implemented:

{% tabs %}
{% tab title="React" %}

<pre class="language-tsx" data-title="src/login/Register.tsx"><code class="lang-tsx">export default function Register(props: RegisterProps) {
    const { i18n } = props;
    
    const { msg, msgStr, advancedMsg, advancedMsgStr } = i18n;

    return (
        //...
        &#x3C;a href={url.loginUrl}>
<strong>            {msg("backToLogin")}
</strong>        &#x3C;/a>
        // ...
    );
}
</code></pre>

{% endtab %}

{% tab title="Svelte" %}
{% code title="src/login/pages/Register.svelte" %}

```html
<script lang="ts">
  // ...
  const props: RegisterProps = $props();
  const { i18n } = props;
  const { msg, msgStr, advancedMsg } = $i18n;
</script>

<a href={url.loginUrl}>{@render msg('backToLogin')()}</a>
```

{% endcode %}
{% endtab %}

{% tab title="Angular" %}
{% code title="src/login/pages/register/register.component.html" %}

```html
<a
  [href]="url.loginUrl"
  [innerHTML]="i18n.msgStr('backToLogin') | kcSanitize: 'html'"
></a>
```

{% endcode %}
{% endtab %}
{% endtabs %}

<figure><img src="/files/ZvI0OBqwnF5gp3tz0iAr" alt="" width="374"><figcaption><p><code>msg("backToLogin")</code> gets rendered as <strong>« Back to Login</strong></p></figcaption></figure>

If you want to see the base message translations you can navigate to the **node\_modules/keycloakify/src/login/i18n/messages\_defaultSet/** directory:

{% hint style="danger" %}
Don't edit this file directly, it's just for seeing what are the default set of i18n messages.
{% endhint %}

<figure><img src="/files/D7BKoK4MIwmlR1tWXr7s" alt=""><figcaption></figcaption></figure>

As you can see, the translation message for the key `backToLogin` in English (**en.ts**) is:

`We are <strong>sorry</strong> ...`

{% tabs %}
{% tab title="React" %}
As a result calling `<p>{msg("backToLogin")}<p>` returns the following **JSX.Eement**:

```html
<p>
  <span data-kc-msg="backToLogin">We are <strong>sorry</strong> ...</span>
</p>
```

If you need to get the litteral string `"We are <strong>sorry</strong> ..."` instead of a JSX.Element you can use `msgStr("backToLogin")`.
{% endtab %}

{% tab title="Svelte" %}
As a result calling `<p>{@render msg("backToLogin")}</p>` returns the following snippet:

```html
<span data-kc-msg="backToLogin">We are <strong>sorry</strong> ...</span>
```

{% endtab %}

{% tab title="Angular" %}
As a result calling i18n.msgStr('errorTitleHtml') return the string "We are \<strong>sorry\</strong> ..." and you need to pass it sanitarized as \[innerHTML] so that "sorry" be redered in bold:

```html
    <p
        [innerHTML]="i18n.msgStr('errorTitleHtml') | kcSanitize: 'html'"
    ></p>
```

What will actually be rendered in the DOM will be:

```html
<p>
  <span data-kc-msg="backToLogin">We are <strong>sorry</strong> ...</span>
</p>
```

{% endtab %}
{% endtabs %}

The purpose of the `data-kc-msg` attribute is to help you identify the i18n key of the text you want to change when inspecting the DOM.

<figure><img src="/files/fV3BzNPbYGorsdh40AvD" alt=""><figcaption><p>Inspecting the DOM we can see that if we want to change the "Back to Login..." we need to change the "backToLogin" key.</p></figcaption></figure>

Now that you get the main idea, let's see how to preview your pages in different languages:

{% content-ref url="/pages/Rh2gVRyn5bvKWRwwCsf3" %}
[Previewing Your Pages in Different Languages](/features/i18n/previewing-your-pages-in-different-languages)
{% endcontent-ref %}


# Previewing Your Pages in Different Languages

{% hint style="info" %}
This section assume you have read [Testing your Theme Outside of Keycloak](/testing-your-theme/outside-of-keycloak).
{% endhint %}

To preview your component in different languages, create separate stories for each language.

<details>

<summary>Note for pepoles that have opted out of using Storybook</summary>

If you're not using storybook here is how to preview your page in dev mode.

<pre class="language-tsx" data-title="src/main.tsx"><code class="lang-tsx">/* eslint-disable react-refresh/only-export-components */
import { createRoot } from "react-dom/client";
import { StrictMode } from "react";
import { KcPage } from "./kc.gen";

// The following block can be uncommented to test a specific page with `yarn dev`
// Don't forget to comment back or your bundle size will increase
import { getKcContextMock } from "./login/KcPageStory";

if (import.meta.env.DEV) {
    window.kcContext = getKcContextMock({
        pageId: "register.ftl",
        overrides: {
            locale: {
<strong>                currentLanguageTag: "es"
</strong>            }
        }
    });
}

createRoot(document.getElementById("root")!).render(
    &#x3C;StrictMode>
        {!window.kcContext ? (
            &#x3C;h1>No Keycloak Context&#x3C;/h1>
        ) : (
            &#x3C;KcPage kcContext={window.kcContext} />
        )}
    &#x3C;/StrictMode>
);
</code></pre>

</details>

Example:

<pre class="language-tsx" data-title="src/login/pages/Login.stories.tsx"><code class="lang-tsx">import type { Meta, StoryObj } from "@storybook/react";
import { createKcPageStory } from "../KcPageStory";

const { KcPageStory } = createKcPageStory({ pageId: "login.ftl" });

const meta = {
    title: "login/login.ftl",
    component: KcPageStory
} satisfies Meta&#x3C;typeof KcPageStory>;

export default meta;

type Story = StoryObj&#x3C;typeof meta>;

export const Default: Story = {
    render: () => &#x3C;KcPageStory />
};

<strong>export const French: Story = {
</strong><strong>    render: ()=> (
</strong><strong>        &#x3C;KcPageStory
</strong><strong>            kcContext={{
</strong><strong>                locale: {
</strong><strong>                    currentLanguageTag: "fr"
</strong><strong>                }
</strong><strong>            }}
</strong><strong>        />
</strong><strong>    )
</strong><strong>};
</strong>
<strong>export const Spanish: Story = {
</strong><strong>    render: ()=> (
</strong><strong>        &#x3C;KcPageStory
</strong><strong>            kcContext={{
</strong><strong>                locale: {
</strong><strong>                    currentLanguageTag: "es"
</strong><strong>                }
</strong><strong>            }}
</strong><strong>        />
</strong><strong>    )
</strong><strong>};
</strong>
// Other stories ...
</code></pre>

<figure><img src="https://github.com/keycloakify/docs.keycloakify.dev/blob/v11_next/features/.gitbook/assets/image%20(7).png" alt=""><figcaption><p>Viewing the French story of the login page</p></figcaption></figure>

If you want all your story to by by default in an other language you can edit:

{% code title="src/login/KcPageStory.tsx" %}

```tsx
export const { getKcContextMock } = createGetKcContextMock({
  kcContextExtension,
  kcContextExtensionPerPage,
  overrides: {
    locale: {
      currentLanguageTag: "de",
    },
  },
  overridesPerPage: {},
});
```

{% endcode %}

Ok now let's see how to modify the base translation to best fit your usecase or create new translation messages:

{% content-ref url="/pages/sCdtaYhMmNkaxeLbUdx2" %}
[Adding New Translation Messages or Changing the Default Ones](/features/i18n/adding-new-translation-messages-or-changing-the-default-ones)
{% endcontent-ref %}


# Adding New Translation Messages or Changing the Default Ones

Let's see firs how you can overwrite the default translation messages to best fit your usecases.

## At theme level

See, for example, by default the login page shows "Sign in to your account":

<figure><img src="/files/NFpUJ1pd9Lsud8XoiNjJ" alt="" width="375"><figcaption></figcaption></figure>

Let's say we want to change that with a message more specific to you usecase.

First setp is to identify the message key. You can usually found it just by inspecting the HTML of your page:

<figure><img src="/files/gqX9oSPAY7bb8VLVGqZt" alt=""><figcaption></figcaption></figure>

Here we can see that the *"Sign in to your account"* translation message corespond to the message key **loginAccountTitle**.

Let's change the English and French translations:

{% code title="src/login/i18n.ts" %}

```typescript
import { i18nBuilder } from "keycloakify/login or keycloakify/accont";
import type { ThemeName } from "../kc.gen";

/** @see: https://docs.keycloakify.dev/i18n */
const { useI18n, ofTypeI18n } = i18nBuilder
    .withThemeName<ThemeName>()
    .withExtraLanguages({ /* ... */ })
    .withCustomTranslations({
        // WARNING: You can't import the translation from external files
        en: {
            loginAccountTitle: "Log in to your ACME account"
        },
        // cspell: disable
        fr: {
            loginAccountTitle: "Connectez-vous a votre compte ACME"
        }
        // cspell: enable
    })
    .build();

type I18n = typeof ofTypeI18n;

export { useI18n, type I18n };
```

{% endcode %}

Here is the result that you should get:

<figure><img src="/files/QquzC8W33dDODgC6wnlY" alt="" width="375"><figcaption></figcaption></figure>

<figure><img src="/files/3l8H1NICfHdlcaY56DG2" alt="" width="375"><figcaption></figcaption></figure>

{% hint style="warning" %}
The translations that you provide to the `withCustomTranslations` must be statically valuable. You can't import from external files. All the translations must be declared inline.\
This is because Keycloakify will analyze your code at build time to make Keycloak aware of your modifications of the base messages so that server side generated feedback messages can use your translations.

\
![](/files/JIGOGjGaBlOFvjtWbOO7)

![](/files/rQ8gcGrkuqZDfrxtYDH8)\
![](/files/GucXRQqmAfRvXTm1bCoE)
{% endhint %}

If you have opted for a configuration [at the component level](https://github.com/keycloakify/docs.keycloakify.dev/blob/v11_next/features/customization-strategies/component-level-customization/README.md) it can come handy to define you own custom message keys:

<pre class="language-typescript" data-title="src/login/i18n.ts"><code class="lang-typescript">import { i18nBuilder } from "keycloakify/login or keycloakify/account";
import type { ThemeName } from "../kc.gen";

/** @see: https://docs.keycloakify.dev/i18n */
const { useI18n, ofTypeI18n } = i18nBuilder
    .withThemeName&#x3C;ThemeName>()
    .withExtraLanguages({ /* ... */ })
    .withCustomTranslations({
        en: {
            loginAccountTitle: "Log in to your ACME account",
<strong>            myCustomMessage: "This is a custom message"
</strong>        },
        // cspell: disable
        fr: {
            loginAccountTitle: "Connectez-vous a votre compte ACME",
<strong>            myCustomMessage: "Ceci est un message personnalisé"
</strong>        }
        // cspell: enable
    })
    .build();

type I18n = typeof ofTypeI18n;

export { useI18n, type I18n };
</code></pre>

You'll then be able to use the message key "myCustomMessage" in your components:

<figure><img src="/files/7KoSVb1bwB6bYggrvNKy" alt="" width="356"><figcaption><p>TypeScript knows that "myCustomMessage" is a valid key</p></figcaption></figure>

{% hint style="success" %}
If you are implementing theme variants you can provides translations on a per-theme variant basis. [See how](/features/theme-variants).
{% endhint %}

Now this is perfect for defining generale purpose text. But some other translations messages are more specific to a specific Keycloak configuration and are best configured via the Keycloak Account Console.\
I'm thinking in particular as translations related to custom user attribues (favourite pet for example) or terms and conditions.

## In the Keycloak Realm configuration

Some relevant messages, namely [`termsText`](/page-specific-guides/terms-and-conditions-page) and all the messages used in the User Profile Attributes like for example the Display name, the helper text or the select option labels can be defined at the realm level and it will work as you would expect:

<figure><img src="/files/xOeFUkG5CdM3fJOBjKjm" alt=""><figcaption><p>The custom user attribute favourite_pet has for Display Name the message key "profile.attributes.favourite_pet"</p></figcaption></figure>

<figure><img src="/files/PFHLKofdlT5kusSvOmV9" alt=""><figcaption><p>A translation for the message key "profile.attributes.favourite_pet" has been defined for the English language: "Favourite Pet"</p></figcaption></figure>

<figure><img src="/files/Eyyll1doMnnbLXwkYx81" alt="" width="312"><figcaption><p>"Favourite Pet" is correctly used as Display Name for the input field in the register page</p></figcaption></figure>

Note that if you try to use:

```tsx
msg("profile.attributes.favourite_pet");
```

It will work at runtime, you'll get `Favourite Pet` but typescript will complain because `"profile.attributes.favourite_pet"` or `string` isn't a known i18n message key, it makes sense as it's only defined on the server.

This is why you'll see in some place in the code the usage of `advancedMsg(attribute.displayName)`, `advancedMsg()` is basically equivalent to `msg()` except that TypeScript won't complain if the key isn't part of the statically defined set.\
[More details](https://github.com/keycloakify/keycloakify/blob/60aaa03202763307a82991c38997d166f8f44d65/src/login/i18n/i18n.tsx#L58-L72).

### My Realm Overrides Translation aren't applied

There is a limitation in the current version of Keycloakify: **Not all translations defined at the Keycloak realm level are pulled by the theme**.

It will be addressed in future version but as of now, here is a workarond that you can use.

Let's say you want to make sure that the message key "**doRegister**" and "**invalidUserMessage**" can be overriten at the ream level, you can edit your vite.config.ts like so:

{% code title="vite.config.ts" %}

```typescript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { keycloakify } from "keycloakify/vite-plugin";

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        react(),
        keycloakify({
            // ...
            kcContextExclusionsFtl: `
                <@addToXKeycloakifyMessagesIfMessageKey str="doRegister" />
                <@addToXKeycloakifyMessagesIfMessageKey str="invalidUserMessage" />
            `
        })
    ]
});
```

{% endcode %}


# Adding Support for Extra Languages

There are currenly 30 languages supported by default (see list below). If a language you want to support is not in the list, this section will explain how to manually add it.

1. ar - Arabic
2. ca - Catalan
3. cs - Czech
4. da - Danish
5. de - German
6. el - Greek
7. en - English
8. es - Spanish
9. fa - Persian (Farsi)
10. fi - Finnish
11. fr - French
12. hu - Hungarian
13. it - Italian
14. ja - Japanese
15. ka - Georgian
16. lt - Lithuanian
17. lv - Latvian
18. nl - Dutch
19. no - Norwegian
20. pl - Polish
21. pt-BR - Portuguese (Brazilian)
22. pt - Portuguese
23. ru - Russian
24. sk - Slovak
25. sv - Swedish
26. th - Thai
27. tr - Turkish
28. uk - Ukrainian
29. zh-CN - Chinese (Simplified)
30. zh-TW - Chinese (Traditional)

You're language is not in the list? Let's see how to add it and enable it in the Keycloak Admin UI.

In this example we're going to add Hindi (hi).

<figure><img src="/files/GkwFHerfANBBOG1xELQG" alt=""><figcaption><p>Hindi appear as a supported locales option in the Keycloak Account UI</p></figcaption></figure>

<figure><img src="/files/30buWxqYzlFu5C6oeo8E" alt=""><figcaption><p>Hindi appear as an option in the language select dropdown</p></figcaption></figure>

To acheive this first step is to create a TypeScript file containing Hindi translation of all the default message keys. I sugest creating this file as `src/login/i18n.hi.ts` but it can be located anywhere under your src directory.

{% code title="src/login/i18n.hi.ts" lineNumbers="true" %}

```typescript
import type { MessageKey_defaultSet } from "keycloakify/login or keycloakify/accont";

const messages: Record<MessageKey_defaultSet, string> = {
    // cspell: disable
    doLogIn: "साइन इन करें",
    doRegister: "रजिस्टर करें",
    doRegisterSecurityKey: "रजिस्टर करें",
    // ... translation for all the other default messages
    // cspell: enable
};

export default messages;
```

{% endcode %}

Wait before panicking realizing that there are hundreds of message keys. ChatGPT can do this for you!

Just take the English translation of the message a reference and ask it to translate for you (you'll have to press continue several times).

You can find the English translations in your repo at:

**node\_modules/keycloakify/src/login/i18n/messages\_defaultSet/en.ts**

<figure><img src="/files/0k7yG0S78hHlcMtlK6VT" alt=""><figcaption></figcaption></figure>

{% hint style="danger" %}
**Do not customize the translations messages to fit your usecase**!

This is not the place to do it.\
Theses translation should be as generic as they can be.

This process is just a temporary sollution until Keycloak add support for your language, you should be prepared to to delete the file as soon as it's no longer nessesary.\
For customizing the translation messages and adding your custom messages see [this section](/features/i18n/adding-new-translation-messages-or-changing-the-default-ones).
{% endhint %}

Next step is to index the translation file that you have created, edit the **src/login/i18n.ts** file as follow:

<pre class="language-typescript" data-title="src/login/i18n.ts"><code class="lang-typescript">import { i18nBuilder } from "keycloakify/login or keycloakify/account";
import type { ThemeName } from "../kc.gen";

/** @see: https://docs.keycloakify.dev/i18n */
const { useI18n, ofTypeI18n } = i18nBuilder
    .withThemeName&#x3C;ThemeName>()
<strong>    .withExtraLanguages({
</strong><strong>        hi: {
</strong><strong>            // cspell: disable-next-line
</strong><strong>            label: "हिन्दी",
</strong><strong>            getMessages: () => import("./i18n.hi")
</strong><strong>        }
</strong><strong>    })
</strong>    .build();

type I18n = typeof ofTypeI18n;

export { useI18n, type I18n };
</code></pre>

That's it!\
Now, you should be able to enable Hindi in the Account Console of the Keycloak where your theme is loaded.


# Theme Variants

Theme variant enables you to create multiples Keycloak theme with a single codebase.

{% tabs %}
{% tab title="Vite" %}
{% code title="vite.config.ts" %}

```typescript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { keycloakify } from "keycloakify/vite-plugin";

export default defineConfig({
  plugins: [
    react(),
    keycloakify({
      themeName: ["keycloakify-starter", "keycloakify-starter-variant-1"],
    }),
  ],
});
```

{% endcode %}
{% endtab %}

{% tab title="Webpack" %}
{% code title="package.json" %}

```json
{
  "keycloakify": {
    "themeName": ["keycloakify-starter", "keycloakify-starter-variant-1"]
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

This will make the theme variant appear in the Keycloak admin select input:

<figure><img src="/files/A7zxrfZRAuJqZPgCtB8m" alt=""><figcaption></figcaption></figure>

In your code you'll be able to load different styles based on the value of `kcContext.themeName`:

<figure><img src="/files/Yoi9X0FUPXUqICdpwU2N" alt=""><figcaption><p>NOTE: You need to <code>run npm run dev</code>, <code>npm run storybook</code> or <code>npm run build-keycloak-theme</code> for the types to be updated.</p></figcaption></figure>

{% embed url="<https://youtu.be/Nkoz1iD-HOA>" %}
Tutorial video
{% endembed %}

## Different text for each of your theme variants

Keycloakify lets you provide custom tranlations on a per-theme variant basis.

{% hint style="info" %}
Read [this](/features/i18n/adding-new-translation-messages-or-changing-the-default-ones) first for context.
{% endhint %}

Example:

<pre class="language-typescript"><code class="lang-typescript">import { i18nBuilder } from "keycloakify/login";
import type { ThemeName } from "../kc.gen";

/** @see: https://docs.keycloakify.dev/i18n */
const { useI18n, ofTypeI18n } = i18nBuilder
    .withThemeName&#x3C;ThemeName>()
    .withExtraLanguages({ /* ... */ })
    .withCustomTranslations({
        en: {
            doLogIn: "Log in!",
<strong>            loginAccountTitle: {
</strong><strong>                "my-theme-1": "Log in to your ACME1 account",
</strong><strong>                "my-theme-2": "Log in to your ACME2 account"
</strong><strong>            }
</strong>        },
        // cspell: disable
        fr: {
            doLogIn: "Se connecter!",
<strong>            loginAccountTitle: {
</strong><strong>                "my-theme-1": "Connectez-vous à votre compte ACME1",
</strong><strong>                "my-theme-2": "Connectez-vous à votre compte ACME2"
</strong><strong>            }
</strong>        }
        // cspell: enable
    })
    .build();

type I18n = typeof ofTypeI18n;

export { useI18n, type I18n };

</code></pre>

<figure><img src="/files/9ZAFtb13TaSITSkhuzsx" alt=""><figcaption><p>"my-theme-1" view</p></figcaption></figure>

<figure><img src="/files/r00BC45rBOVy0Phk8C9z" alt=""><figcaption><p>"my-theme-2" view</p></figcaption></figure>

## In Native Themes

For native Email Theme [this video timestamp](https://www.youtube.com/watch?v=IZ9LSLfWxqo\&t=684s).\
For native Theme in general [this video timestamp](https://youtu.be/OFg9RIM5hSw?si=zQ6Kdd7TgzZMsiH2\&t=139).


# Environment Variables

Environment variables defined on the Keycloak server can be transferred to the theme. This allows for a degree of theme customization without necessitating a rebuild. This approach is particularly useful if multiple parties are reusing your theme. As an example, you can distribute a single .jar file to multiple customers, enabling them to modify certain aspect of the login page by defining specific environment variables.

Let's define two environnement variable:

{% tabs %}
{% tab title="Vite" %}

<pre class="language-typescript" data-title="vite.config.ts"><code class="lang-typescript">import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { keycloakify } from "keycloakify/vite-plugin";

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        react(),
        keycloakify({
            // ...
<strong>            environmentVariables: [
</strong><strong>                { name: "MY_APP_API_URL", default: "" },
</strong><strong>                { name: "MY_APP_PALETTE", default: "dracula" }
</strong><strong>            ]
</strong>        })
    ]
});

</code></pre>

{% endtab %}

{% tab title="Webpack" %}
{% code title="package.json" %}

```json
{
    "keycloakify": {
        "environmentVariables": [
            { "name": "MY_APP_API_URL", "default": "" },
            { "name": "MY_APP_PALETTE", "default": "dracula" }
        ]
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

We can then access the runtime value of thoses variables under kcContext.properties:

<figure><img src="/files/EjPAMMvrkp88VL1TtwsE" alt=""><figcaption><p>Accessing the value of the environement variable defined.</p></figcaption></figure>

Now let's see how you can set the value of thoses environement variable on the Keycloak side:

{% tabs %}
{% tab title="Docker" %}

<pre class="language-bash"><code class="lang-bash">docker run \
    -e KEYCLOAK_ADMIN=admin \
    -e KEYCLOAK_ADMIN_PASSWORD=admin \
<strong>    --env MY_APP_API_URL='https://api.my-org.com' \
</strong><strong>    --env MY_APP_PALETTE='solaris'
</strong>    -p 8080:8080 \
    start --optimized
</code></pre>

{% endtab %}

{% tab title="Helm" %}

<pre class="language-bash" data-title="values.json"><code class="lang-bash">keycloak:
  initContainers: |
    - name: realm-ext-provider
      image: curlimages/curl
      imagePullPolicy: IfNotPresent
      command:
        - sh
      args:
        - -c
        - |
          # Replace USER and PROJECT.    
          curl -L -f -S -o /extensions/keycloak-theme.jar https://github.com/USER/PROJECT/releases/latest/download/keycloak-theme-for-kc-24.jar

      volumeMounts:
        - name: extensions
          mountPath: /extensions

  extraVolumeMounts: |
    - name: extensions
      mountPath: /opt/bitnami/keycloak/providers

  extraVolumes: |
    - name: extensions
      emptyDir: {}
      
  extraEnv: |
<strong>    - name: MY_APP_API_URL
</strong><strong>      value: 'https://api.my-org.com'
</strong><strong>    - name: MY_APP_PALETTE
</strong><strong>      value: 'solaris'
</strong></code></pre>

{% endtab %}

{% tab title="Bare Metal" %}

```bash
MY_APP_API_URL="https://api.my-org.com" MY_APP_PALETTE="solaris" /opt/keycloak/bin/kc.sh start
```

{% endtab %}
{% endtabs %}

To test locally, you can pass the environement variable to the start-keycloak CLI command:

```bash
MY_APP_PALETTE="solaris" MY_APP_API_URL="..." npx keycloakify start-keycloak
```

You can also create stories with specific ENV values:

```tsx
export const Solaris: Story = {
    render: () => (
        <KcPageStory
            kcContext={{
                properties: {
                    MY_APP_PALETTE: "solaris"
                },
            }}
        />
    )
};
```


# Styling a Custom Page Not Included in Base Keycloak

{% hint style="info" %}
If you are looking to implement the pages featured by the [PhaseTwo](https://phasetwo.io/) plugins they are implemented [in this repo](https://github.com/p2-inc/keycloakify-starter/tree/p2/magic-link-extension-templates).
{% endhint %}

Sometimes certain extensions will add new functionality that requires an additional page not originally shipped with Keycloak. Keycloakify out-of-the-box will only provide customization to base pages, so if a new page is introduced by an extension, there is a good chance the page will not be styled correctly.

To account for these cases, Keycloakify supports the ability to add custom pages and configure them such that style preservation is maintained.

For our example on how to customize this, we will be using Phase Two's `otp-form.ftl` page. Phase Two provides email OTP codes for logging in and as a result has a special page if OTP codes are enabled in the authorization flow.

{% hint style="success" %}
You can load the extension that you are using in Keycloak container that is started when running `npx keycloakify start-keycloak`. Use [the `extensionJars` option](/features/compiler-options/startkeycloakoptions).
{% endhint %}

{% embed url="<https://github.com/p2-inc/keycloak-magic-link/blob/main/src/main/resources/theme-resources/templates/otp-form.ftl>" %}
You can find the original .ftl file on Phase Two's github
{% endembed %}

The first thing we want to do is to let Keycloakify know that we are adding a new page:

<pre class="language-typescript" data-title="src/login/KcPageStory.tsx"><code class="lang-typescript">const kcContextExtensionPerPage: KcContextExtensionPerPage = {
<strong>    "otp-form.ftl": { /* We will delare propreties that we need later */ }
</strong>};
</code></pre>

Then, we want to create the page under the pages directory, our file name in this case will be `OtpForm.tsx` and paste in some starter code including the template.

{% code title="src/login/pages/OtpForm.tsx" %}

```tsx
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";

export default function OtpForm(props: PageProps<Extract<KcContext, { pageId: "otp-form.ftl" }>, I18n>) {
    const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;

    const { kcClsx } = getKcClsx({
        doUseDefaultCss,
        classes
    });

    const { msg, msgStr } = i18n;

    const { url } = kcContext;


    return (
        <Template
            kcContext={kcContext}
            i18n={i18n}
            doUseDefaultCss={doUseDefaultCss}
            classes={classes}
            displayInfo={false}
            headerNode={
                // Header code goes here
            }
        >
            // Page code goes here
        </Template>
    );
}
```

{% endcode %}

Note the `pageId` variable specified `otp-form.ftl`, that should match the exact name of the page file you are trying to implement. Additionally, we will also need to modify the `kcContext` values to account for certain custom variables, but we will get to that later. For now the last new file we need to add would be the story file for this page:

{% code title="src/login/pages/OtpForm.stories.tsx" %}

```tsx
import type { Meta, StoryObj } from "@storybook/react";
import { createKcPageStory } from "../KcPageStory";

const { KcPageStory } = createKcPageStory({ pageId: "otp-form.ftl" });

const meta = {
    title: "login/otp-form.ftl",
    component: KcPageStory
} satisfies Meta<typeof KcPageStory>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
    render: () => <KcPageStory />
};
```

{% endcode %}

Next the easiest thing is to just paste the default code for the custom page right into the template and begin modifying it for Keycloakify. In our case, here is the code for that page at the time of writing:

<details>

<summary>otp-form.ftl</summary>

```xml
<#import "template.ftl" as layout>
<@layout.registrationLayout displayInfo=true; section>
    <#if section = "title">
        ${msg("doLogIn")}

    <#elseif section = "header">
      <div id="kc-username" class="${properties.kcFormGroupClass!}">
        <label id="kc-attempted-username">${auth.attemptedUsername}</label>
        <a id="reset-login" href="${url.loginRestartFlowUrl}" aria-label="${msg("restartLoginTooltip")}">
          <div class="kc-login-tooltip">
            <i class="${properties.kcResetFlowIcon!}"></i>
            <span class="kc-tooltip-text">${msg("restartLoginTooltip")}</span>
          </div>
        </a>
      </div>

    <#elseif section = "form">
      <p>Enter access code</p>
      <form id="kc-otp-login-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
        <div class="${properties.kcFormGroupClass!}">
          <div class="${properties.kcLabelWrapperClass!}">
            <label for="otp" class="${properties.kcLabelClass!}">${msg("loginOtpOneTime")}</label>
          </div>

          <div class="${properties.kcInputWrapperClass!}">
            <input id="otp" name="otp" autocomplete="off" type="text" class="${properties.kcInputClass!}" autofocus aria-invalid="<#if messagesPerField.existsError('totp')>true</#if>"/>
            <#if messagesPerField.existsError('totp')>
              <span id="input-error-otp-code" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">${kcSanitize(messagesPerField.get('totp'))?no_esc}</span>
            </#if>
          </div>
        </div>

        <div class="${properties.kcFormGroupClass!}">
          <div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
            <div class="${properties.kcFormOptionsWrapperClass!}">
            </div>
          </div>

          <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
            <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submit" id="kc-submit" type="submit" value="${msg("doSubmit")}" />
            <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="resend" id="kc-resend" type="submit" value="${msg("doResend")}" />
          </div>
        </div>
      </form>
    </#if>
</@layout.registrationLayout>
```

</details>

Breaking down this code:

1. The freemarker, dynamic variables/messages, and classnames will need to be converted to React.
2. The content in the header section will go in the `headerNode` prop of `<Template>` and the form section will be the child of the `<Template>` element.
3. `@layout.registrationLayout` has the prop `displayInfo=true` which means we need to set that prop in the `<Template>` element.
4. The `auth` and `messagesPerField` variables and their attributes which need to be provided in kcContext.

1, 2, and 3 require converting code to JSX. The converted code for the page can be found at the bottom. Here are some tips:

* Any classname provided as a variable will use `kcClsx` to resolve, so `${properties.kcFormClass!}` would turn into `{kcClsx("kcFormGroupClass")}`
* When dealing with message values, `msg` may return full blown HTML so it can be used as a child element and `msgStr` will return straight text.
  * Example 1, `aria-label="${msg("restartLoginTooltip")}"` would turn into `aria-label={msgStr("restartLoginTooltip")}`.
  * Example 2, `msg` variables they can inject HTML as a variable, when this happens we need to dangerously set inner html. Specifcally with a piece of code like this:

    ```html
    <#if messagesPerField.existsError('totp')>
      <span id="input-error-otp-code" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
        ${kcSanitize(messagesPerField.get('totp'))?no_esc}
      </span>
    </#if>
    ```

    would turn into this:

    ```jsx
    {
      messagesPerField.existsError("totp") && (
        <span
          id="input-error-otp-code"
          className={kcClsx("kcInputErrorMessageClass")}
          aria-live="polite"
          dangerouslySetInnerHTML={{ __html: messagesPerField.get("totp") }}
        />
      );
    }
    ```

    Unfortunately, a lot of it is up to you to decide with the extension you might be using, but there may be some trial and error.

4 on the other hand requires changing some code in other files.

{% code title="src/login/KcContext.ts" %}

```tsx
/* eslint-disable @typescript-eslint/ban-types */
import type { ExtendKcContext } from "keycloakify/login";
import type { KcEnvName, ThemeName } from "../kc.gen";

export type KcContextExtension = {
    themeName: ThemeName;
    properties: Record<KcEnvName, string> & {};
};

// added for otp form page, required for the types
export type KcContextExtensionPerPage = {
    "otp-form.ftl": {
        auth: {
            attemptedUsername: string;
        };
        url: {
            loginRestartFlowUrl: string;
            loginAction: string;
        };
    };
};

export type KcContext = ExtendKcContext<KcContextExtension, KcContextExtensionPerPage>;
```

{% endcode %}

As seen above, kcContext is where we can add the type definitions for the props passed into the page. In the freemarker we also see `msg("doResend")` value which is not in the base keycloak i18 library. We would also need add this for mocking purposes.

<pre class="language-typescript" data-title="src/login/i18n.ts"><code class="lang-typescript">import { i18nBuilder } from "keycloakify/login";
import type { ThemeName } from "../kc.gen";

/** @see: https://docs.keycloakify.dev/i18n */
const { useI18n, ofTypeI18n } = i18nBuilder
    .withThemeName&#x3C;ThemeName>()
    .withExtraLanguages({ /* ... */ })
    .withCustomTranslations({
<strong>        en: {
</strong><strong>            doResend: "Resend"
</strong><strong>        },
</strong><strong>        fr: {
</strong><strong>            doResend: "Renvoyer"
</strong><strong>        }
</strong>    })
    .build();

type I18n = typeof ofTypeI18n;

export { useI18n, type I18n };
</code></pre>

The last two things we need to do now would be adding the story to the `KcPageStory.tsx`

<pre class="language-typescript" data-title="src/login/KcPageStory.tsx"><code class="lang-typescript">const kcContextExtensionPerPage: KcContextExtensionPerPage = {
<strong>    "otp-form.ftl": {
</strong><strong>        auth: {
</strong><strong>            attemptedUsername: "user@user.com"
</strong><strong>        },
</strong><strong>        url: {
</strong><strong>            loginRestartFlowUrl: "#",
</strong><strong>            loginAction: "#"
</strong><strong>        }
</strong><strong>    }
</strong>};
</code></pre>

and adding the page to the `KcPage.tsx`

{% code title="src/login/KcPage.tsx" %}

```tsx
case "otp-form.ftl":
    return (
        <OtpForm
            {...{ kcContext, i18n, classes }}
            Template={Template}
            doUseDefaultCss={true}
        />
    );
```

{% endcode %}

After all that you should be done! You can view the new component in storybook and check everything looks right and then the next time you bundle and build it, it should be deployed.

<details>

<summary>Completed code for OtpForm.tsx:</summary>

```jsx
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";

export default function OtpForm(props: PageProps<Extract<KcContext, { pageId: "otp-form.ftl" }>, I18n>) {
    const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;

    const { kcClsx } = getKcClsx({
        doUseDefaultCss,
        classes
    });

    const { msg, msgStr } = i18n;

    const { auth, url, messagesPerField } = kcContext;

    return (
        <Template
            kcContext={kcContext}
            i18n={i18n}
            doUseDefaultCss={doUseDefaultCss}
            classes={classes}
            displayInfo={false}
            headerNode={
                <div id="kc-username" className={kcClsx("kcFormGroupClass")} style={{ fontSize: "16px" }}>
                    <label id="kc-attempted-username">{auth.attemptedUsername}</label>
                    <a id="reset-login" href={url.loginRestartFlowUrl} aria-label={msgStr("restartLoginTooltip")}>
                        <div className="kc-login-tooltip">
                            <i className={kcClsx("kcResetFlowIcon")}></i>
                            <span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
                        </div>
                    </a>
                </div>
            }
        >
            <p>Enter access code</p>
            <form id="kc-otp-login-form" className={kcClsx("kcFormClass")} action={url.loginAction} method="post">
                <div className={kcClsx("kcFormGroupClass")}>
                    <div className={kcClsx("kcLabelWrapperClass")}>
                        <label htmlFor="otp" className={kcClsx("kcLabelClass")}>
                            {msg("loginOtpOneTime")}
                        </label>
                    </div>

                    <div className={kcClsx("kcInputWrapperClass")}>
                        <input
                            id="otp"
                            name="otp"
                            autoComplete="off"
                            type="text"
                            className={kcClsx("kcInputClass")}
                            autoFocus
                            aria-invalid={messagesPerField.existsError("totp") ? "true" : undefined}
                        />
                        {messagesPerField.existsError("totp") && (
                            <span
                                id="input-error-otp-code"
                                className={kcClsx("kcInputErrorMessageClass")}
                                aria-live="polite"
                                dangerouslySetInnerHTML={{ __html: messagesPerField.get("totp") }}
                            />
                        )}
                    </div>
                </div>

                <div className={kcClsx("kcFormGroupClass")}>
                    <div id="kc-form-options" className={kcClsx("kcFormOptionsClass")}>
                        <div className={kcClsx("kcFormOptionsWrapperClass")} />
                    </div>

                    <div id="kc-form-buttons" className={kcClsx("kcFormButtonsClass")}>
                        <input
                            className={kcClsx("kcButtonClass", "kcButtonPrimaryClass", "kcButtonLargeClass")}
                            name="submit"
                            id="kc-submit"
                            type="submit"
                            value={msgStr("doSubmit")}
                        />
                        <input
                            className={kcClsx("kcButtonClass", "kcButtonPrimaryClass", "kcButtonLargeClass")}
                            name="resend"
                            id="kc-resend"
                            type="submit"
                            value={msgStr("doResend")}
                        />
                    </div>
                </div>
            </form>
        </Template>
    );
}
```

</details>


# Integrating an Existing Theme into Your Keycloakify Project

If you have already created a Keycloak theme using the native theming system, you can easily import it into your Keycloakify project.

This process is straightforward and requires no special configuration. Simply copy the source files of your existing theme and place them into the `src` directory of your Keycloakify project.

<figure><img src="/files/7DxUDHAUG51wU1tYSJjm" alt="Native login theme in a Keycloakify project"><figcaption><p>Screenshot showing a native login theme inside a Keycloakify project</p></figcaption></figure>

Below is a live demonstration using a login theme, but the same process applies to any type of theme.

{% embed url="<https://youtu.be/OFg9RIM5hSw>" %}

## Theme Variants Support

If your project includes theme variants, they will also work seamlessly with your imported native theme.

You can leverage a special FreeMarker variable in your `.ftl` files to display the active theme variant dynamically:

{% code title="src/login/login.ftl" %}

```ftl
<h1>${xKeycloakify.themeName}</h1>
```

{% endcode %}

To customize translations based on the active theme variant, create property files with the following naming pattern:

```
messages/messages_<language>_override_<theme name>.properties
```

For example, if you have theme variants named `vanilla` and `chocolate`, you can override the `loginAccountTitle` message key for each variant. Here's an example project structure:

<figure><img src="/files/AngWpqpVlkV38bJNle1k" alt="Theme variants: vanilla and chocolate"><figcaption><p>A project with two theme variants, "vanilla" and "chocolate," where the <code>loginAccountTitle</code> message key is overridden for each variant.</p></figcaption></figure>


# Compiler Options

In this folder are listed the different configuration options you can use with Keycloakify.

{% content-ref url="/pages/sAn1CMPugevymopvISTP" %}
[--project](/features/compiler-options/project)
{% endcontent-ref %}

{% content-ref url="/pages/mRBPowGEjKtlpj7o0TtF" %}
[keycloakVersionTargets](/features/compiler-options/keycloakversiontargets)
{% endcontent-ref %}

{% content-ref url="/pages/fSXIuJAeXG6zS0Dngrwl" %}
[environmentVariables](/features/compiler-options/environmentvariables)
{% endcontent-ref %}

{% content-ref url="/pages/OsiqwCQ78zVFKYIPoetx" %}
[themeName](/features/compiler-options/themename)
{% endcontent-ref %}

{% content-ref url="/pages/So587wI5rxNzE4tgjM2n" %}
[themeVersion](/features/compiler-options/themeversion-1)
{% endcontent-ref %}

{% content-ref url="/pages/JiFpOnhbLCtsgqiWGks5" %}
[postBuild](/features/compiler-options/postbuild)
{% endcontent-ref %}

{% content-ref url="/pages/SlMwmrVoJvUH6p7gEFlD" %}
[XDG\_CACHE\_HOME](/features/compiler-options/xdg_cache_home)
{% endcontent-ref %}

{% content-ref url="/pages/Xoja0PWa8hbPBCKgumXN" %}
[kcContextExclusionsFtl](/features/compiler-options/kccontextexclusionsftl)
{% endcontent-ref %}

{% content-ref url="/pages/F5AqEk6mLLrO2j9EIyV3" %}
[keycloakifyBuildDirPath](/features/compiler-options/keycloakifybuilddirpath)
{% endcontent-ref %}

{% content-ref url="/pages/Oiz3abwTVeRWWu66Raz1" %}
[groupId](/features/compiler-options/groupid)
{% endcontent-ref %}

{% content-ref url="/pages/TEGfZNaxkhQ1KWjnZpcL" %}
[artifactId](/features/compiler-options/artifactid)
{% endcontent-ref %}

{% content-ref url="/pages/8mWTNLW8DTWqJvc78IzS" %}
[Webpack specific options](/features/compiler-options/webpack-specific-options)
{% endcontent-ref %}


# --project

This option is for Monorepos. More specifically, monorepo system that works with a single package.json at the root of the project.

You can run every subcommand of the `keycloakify` CLI tool from the root of your Keycloakify project using the `--project` (or `-p`) option. Example with the `build` command:

```bash
npx keycloakify build -p <path>
```

`<path>` would be typically something like `packages/keycloak-theme`


# keycloakVersionTargets

{% embed url="<https://youtu.be/WiiG42jn5T0>" %}

By default, Keycloakify generates diffrent jar files, each one meant to be used with a given Keycloak version range.

{% hint style="info" %}
Yes, **Keycloak 26 is supported**. Use the *keycloak-theme-for-kc-all-other-version.jar*.
{% endhint %}

<figure><img src="/files/ZemcrIAzlzSsBnQ40jDj" alt="" width="375"><figcaption></figcaption></figure>

However you might want to customize this behavior. If you know ahead of time what Keycloak you theme will using you can build only for this version using the `keycloakVersionTargets` build option.

{% tabs %}
{% tab title="Vite" %}

<pre class="language-typescript" data-title="vite.config.ts"><code class="lang-typescript">import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { keycloakify } from "keycloakify/vite-plugin";

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [react(), keycloakify({
        // ...
<strong>        keycloakVersionTargets: {
</strong><strong>            // It depends of your configuration
</strong><strong>            // Watch the video to learn more
</strong><strong>        }
</strong>    })]
});
</code></pre>

{% endtab %}

{% tab title="Webpack" %}

<pre class="language-json" data-title="package.json"><code class="lang-json">{
    "keycloakify": {
        // ...
<strong>        "keycloakVersionTargets": {
</strong><strong>           // It depends of your configuration, if you are implementing
</strong><strong>           // an Multi-Page account theme or not and of the version
</strong><strong>           // of keycloakify you are using.  
</strong><strong>           // Since TypeScript can't help you here the best option 
</strong><strong>           // to know what ranges are available is to clone the vite
</strong><strong>           // starter, pin your Keycloakify specific version and 
</strong><strong>           // set the account implementation that you have in your project.
</strong><strong>           // Watch the video to learn more.
</strong><strong>           // The vite starter: https://github.com/keycloakify/keycloakify-starter
</strong>    }
}
</code></pre>

{% endtab %}
{% endtabs %}


# environmentVariables

{% content-ref url="/pages/akVZjbaYVuAkHfiXUOwT" %}
[Environment Variables](/features/environment-variables)
{% endcontent-ref %}


# themeName

This is the name that will appear in the select input of the Keycloak Admin UI that let's you select the theme.

<figure><img src="/files/OCv3rLo8843c4D0ZDgy6" alt="" width="375"><figcaption><p>Here the theme name is "my-react-app"</p></figcaption></figure>

By default it's `package.json["name"]`

{% tabs %}
{% tab title="Vite" %}

<pre class="language-typescript" data-title="vite.config.ts"><code class="lang-typescript">import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { keycloakify } from "keycloakify/vite-plugin";

export default defineConfig({
  plugins: [
    react(), 
    keycloakify({
<strong>      themeName: "my-custom-name"
</strong>    })
  ],
})
</code></pre>

{% endtab %}

{% tab title="Webpack" %}
{% code title="package.json" %}

```json
{
    "keycloakify": {
        "themeName": "my-custom-name"
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

The theme name is also in `kcContext.themeName`

Providing an array enables you to implement theme variant. See:

{% content-ref url="/pages/gvUwHzE1TuQBb81U6brg" %}
[Theme Variants](/features/theme-variants)
{% endcontent-ref %}


# startKeycloakOptions

You can configure the Keycloak testing container using options in your build tool configuration.

{% tabs %}
{% tab title="Vite" %}
{% code title="vite.config.ts" %}

```typescript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { keycloakify } from "keycloakify/vite-plugin";

// https://vitejs.dev/config/
export default defineConfig({
 plugins: [
  react(),
  keycloakify({
   accountThemeImplementation: "none",
   startKeycloakOptions: {
    // All the options are optional

    /*
    By default Keycloakify uses the official Keycloak Docker Image (quay.io/keycloak/keycloak)
    and lets you select the tag when you run the command by asking you to 
    select a Keycloak version.
    Using this option you can use an alternative Docker Image like the one 
    of Phase2 (https://quay.io/repository/phasetwo/phasetwo-keycloak) and a 
    specific tag to use.
    This option can also be used to pin a specific version of the official 
    Keycloak Docker Image.
    Example: `dockerImage: "quay.io/keycloak/keycloak:25.0.2"`
    */
    dockerImage: "quay.io/phasetwo/phasetwo-keycloak:25.0.2.1721752809",

    /*
    This option allows you to pass extra docker arguments to the 
    `docker run` command.
    */
    dockerExtraArgs: [
     "-e", "KC_HTTP_RELATIVE_PATH=/auth"
    ],

    /*
    This option allows you to start Keycloak with extra arguments.
    */
    keycloakExtraArgs: [
     "--spi-email-template-provider=freemarker-plus-mustache",
     "--spi-email-template-freemarker-plus-mustache-enabled=true",
     "--spi-theme-cache-themes=false"
    ],

    /*
    This option allow you to load custom Keycloak extensions in the 
    Keycloak instance running in the Docker container.
    In this example we load two extensions:
    - https://github.com/InseeFr/Keycloak-FranceConnect
    - https://github.com/micedre/keycloak-mail-whitelisting
    
    (NOTE: ./keycloak-resources/ is just an example, you can use any directory)
    */
    extensionJars: [
     "https://github.com/InseeFr/Keycloak-FranceConnect/releases/download/6.2.0/keycloak-franceconnect-6.2.0.jar",
     "./keycloak-resources/keycloak-mail-whitelisting-2.0.jar"
    ],
    /*
    By default, the Keycloak instance is loaded with a pre-configured 
    realm so you do not have to create a realm, a client, a user, etc.  
    However you might want to edit this base realm configuration and 
    persist the changes that you made.  
    I explain it in this video: https://www.youtube.com/watch?v=lMOLrdqilqE&t=991s
    */
    realmJsonFilePath: "./keycloak-resources/myrealm-realm.json",
    /*
     * By default the Keycloak instance will run on port 8080.
     * This option allows you to change it to another port.
     */
    port: 8081
   }
  })
 ]
});
```

{% endcode %}
{% endtab %}

{% tab title="Webpack" %}
{% code title="package.json" %}

```json
{
  "keycloakify": {
    "startKeycloakOptions": {
      "dockerImage": "quay.io/phasetwo/phasetwo-keycloak:25.0.2.1721752809",
      "dockerExtraArgs": [
        "-e",
        "KC_HTTP_RELATIVE_PATH=/auth"
      ],
      "keycloakExtraArgs": [
        "--spi-email-template-provider=freemarker-plus-mustache",
        "--spi-email-template-freemarker-plus-mustache-enabled=true",
        "--spi-theme-cache-themes=false"
      ],
      "extensionJars": [
        "https://github.com/InseeFr/Keycloak-FranceConnect/releases/download/6.2.0/keycloak-franceconnect-6.2.0.jar",
        "./keycloak-resources/keycloak-mail-whitelisting-2.0.jar"
      ],
      "realmJsonFilePath": "./keycloak-resources/myrealm-realm.json",
      "port": 8081
    }
  }
}
```

{% endcode %}

See the **Vite** tab for explanations of the options.
{% endtab %}
{% endtabs %}

Configured as above, the resulting Docker command will resemble the following:

<figure><img src="/files/2YhlYmPuRs7Adqmq42Wz" alt=""><figcaption></figcaption></figure>

dd


# themeVersion

Configure the version that will appear in the `pom.xml` file within the jar file of your theme and in the `kcContext.themeVersion`.

This is purely indicative, it's a way for you to quickly see what version of your theme is in production.

<figure><img src="/files/s5cCJJasan2xpyGyuB2W" alt=""><figcaption></figcaption></figure>

By default the version that is used is the one in the package.json of your project

{% code title="package.json" %}

```json
{
  "version": "1.3.4"
}
```

{% endcode %}

But you can overwrite this value using an environment variable:

{% tabs %}
{% tab title="Vite" %}

<pre class="language-typescript" data-title="vite.config.ts"><code class="lang-typescript">import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { keycloakify } from "keycloakify/vite-plugin";

export default defineConfig({
  plugins: [
    react(), 
    keycloakify({
<strong>      themeVersion: "1.2.3"
</strong>    })
  ],
})
</code></pre>

{% endtab %}

{% tab title="Webpack" %}
{% code title="package.json" %}

```json
{
  "keycloakify": {
    "themeVersion": "1.2.3"
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}


# accountThemeImplementation

Possible values: `"none" | "Single-Page" | "Multi-Page"`

This option is set automatically for you when you run the `npx keycloakify initialize-account-theme` command. **Do not edit the vallue manually!**

You might wonder why this option exists when it could be possible to infer it from the source code.\
The reason is that the available [keycloakVersionTargets](/features/compiler-options/keycloakversiontargets) changes when you implement a Multi-Page account theme and when you don't. We want you to get a type error if the version target you specified are incorrect.

{% tabs %}
{% tab title="Vite" %}

<pre class="language-typescript" data-title="vite.config.ts"><code class="lang-typescript">import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { keycloakify } from "keycloakify/vite-plugin";

export default defineConfig({
  plugins: [
    react(), 
    keycloakify({
<strong>      accountThemeImplementation: "none"
</strong>    })
  ],
})
</code></pre>

{% endtab %}

{% tab title="Webpack" %}
{% code title="package.json" %}

```json
{
  "keycloakify": {
    "accountThemeImplementation": "none"
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}


# postBuild

{% hint style="info" %}
Only available in Vite projects, not in Webpack
{% endhint %}

The postBuild hook is called just before Keycloakify bundles the themes resources into the jar.

This gives you the ability to implement some custom transformation.

Let's say, for example, we have a big `material-icons` in our `public` directory and those icons are used in the main app but not in the Keycloak theme. We can use the postBuild hook to make sure that those icons are not bundled in the generated jar files.

<pre class="language-typescript" data-title="vite.config.ts"><code class="lang-typescript">import * as fs from "fs/promises";
import * as path from "path";

export default defineConfig({
    plugins: [
        react(),
        keycloakify({
<strong>            postBuild: async (buildContext) => {
</strong><strong>                await fs.rm(
</strong><strong>                    path.join(
</strong><strong>                        "theme",
</strong><strong>                        buildContext.themeNames[0], // keycloakify-starter
</strong><strong>                        "login", // Note: We assume we only have an login theme, if we had an account theme we would have to remove it there as well.
</strong><strong>                        "resources",
</strong><strong>                        "dist", // Your Vite dist/ or Webpack build/ is here.
</strong><strong>                        "material-icons"
</strong><strong>                    ),
</strong><strong>                    { recursive: true }
</strong><strong>                );
</strong><strong>            }
</strong>        })
    ]
});
</code></pre>

When this function is invoked the current working directory (process.cwd()) is the root of the directory of the files about to be archived.

You can get an idea of how is structured the files inside the jar by extracting it manually:

```bash
mkdir dist_keycloak/extracted
cd dist_keycloak/extracted
jar -xf ../keycloak-theme-for-kc-25-and-above.jar
```

<figure><img src="/files/gQOCXAb1M7Kx8ZcKS6Mo" alt=""><figcaption><p>Overview of the content of the jar extracted</p></figcaption></figure>

### Debugging

Note that the script is executed in a different thread. console.log() won't work.\
If you want to debug you can write your logs into a file. Example: \\

{% code title="vite.config.ts" %}

```typescript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { keycloakify } from "keycloakify/vite-plugin";

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        react(),
        keycloakify({
            accountThemeImplementation: "Single-Page",
            postBuild: async (buildContext) => {

                const fs = await import("fs");
                const path = await import("path");

                const logFilePath = path.join(buildContext.projectDirPath, "postBuildLog.txt");

                fs.rmSync(logFilePath, { force: true });

                const log= (msg: string) => {
                    fs.appendFileSync(
                        logFilePath,
                        Buffer.from(msg + "\n", "utf8")
                    );
                };

                // This will be logged into postBuildLog.txt at the root of your project.
                log("Hello World");

            }
        })
    ]
});

```

{% endcode %}


# XDG\_CACHE\_HOME

If this environnement variable is defined this cache directory will be used instead of the default `node_modules/.cache/keycloakify` example:

```bash
export XDG_CACHE_HOME=/home/runner/.cache/yarn
npx keycloakify build
# /home/runner/.cache/yarn/keycloakify will contain various resources
```

This option is mainly useful if you need to be able to build your theme offline, in a context with network restriction polices.

The Keycloakify caches the default Keycloak theme resources to avoid having to download them over and over.


# kcContextExclusionsFtl

[Keycloakify shifts page generation from the backend to the client](https://github.com/keycloakify/keycloakify/discussions/346#discussioncomment-5889791). To achieve this, Keycloakify creates a global `kcContext` object, which holds the necessary information for generating HTML pages.

This object contains no sensitive data—only the information that the Keycloak team considers essential for rendering the various login pages. Additionally, if you have custom plugins, such as [keycloak-email-whitelisting](https://github.com/micedre/keycloak-mail-whitelisting), they may introduce additional values into this object.

<figure><img src="/files/rOUX25XxN3dBPZbh501i" alt=""><figcaption><p>A typical kcContext for the register.ftl page</p></figcaption></figure>

If you'd like to prevent some values of the FreeMarker context from being forwarded to the client you can do it with the `kcContextExclusionsFtl` option.

Let's say in this example that we would like to exclude:

* `kcContext.keycloakifyVersion`
* `kcContext.realm.actionTokenGeneratedByUserLifespanMinutes` in the register.ftl page
* `kcContext.realm.idpVerifyAccountLinkActionTokenLifespanMinutes` in the register.ftl page

This is how you would do it:

{% tabs %}
{% tab title="Vite" %}

<pre class="language-typescript" data-title="vite.config.ts"><code class="lang-typescript">import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { keycloakify } from "keycloakify/vite-plugin";

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        react(),
        keycloakify({
            // ...
<strong>            kcContextExclusionsFtl: `
</strong>
<strong>                &#x3C;#if (
</strong><strong>                    xKeycloakify.pageId == "register.ftl" &#x26;&#x26;
</strong><strong>                    [
</strong><strong>                        "actionTokenGeneratedByUserLifespanMinutes", 
</strong><strong>                        "idpVerifyAccountLinkActionTokenLifespanMinutes"
</strong><strong>                    ]?seq_contains(key) &#x26;&#x26;
</strong><strong>                    areSamePath(path, ["realm"])
</strong><strong>                )>
</strong><strong>                    &#x3C;#continue>
</strong><strong>                &#x3C;/#if>
</strong>
<strong>                &#x3C;#if xKeycloakify.keycloakifyVersion != "__hidden__">
</strong><strong>                    &#x3C;#assign xKeycloakify = xKeycloakify + { "keycloakifyVersion": "__hidden__" }>
</strong><strong>                &#x3C;/#if>
</strong>
<strong>            `
</strong>        })
    ]
});
</code></pre>

{% hint style="info" %}
You can also provide a path to a .ftl file instead of inlining the ftl code in your vite.config.ts file.
{% endhint %}
{% endtab %}

{% tab title="Webpack" %}
{% code title="kcContextExclusions.ftl" %}

```ftl
<#if (
    xKeycloakify.pageId == "register.ftl" &&
    [
        "actionTokenGeneratedByUserLifespanMinutes", 
        "idpVerifyAccountLinkActionTokenLifespanMinutes"
    ]?seq_contains(key) &&
    areSamePath(path, ["realm"])
)>
    <#continue>
</#if>

<#if xKeycloakify.keycloakifyVersion != "__hidden__">
    <#assign xKeycloakify = xKeycloakify + { "keycloakifyVersion": "__hidden__" }>
</#if>
```

{% endcode %}

<pre class="language-json" data-title="package.json"><code class="lang-json">{
    "keycloakify": {
        // ...
<strong>        "kcContextExclusionsFtl": "./kcContextExclusions.ftl"
</strong>    }
}
</code></pre>

{% endtab %}
{% endtabs %}

To test/debug your exclusion when you are running `npx keycloakify start-keycloak` you can open the file

`dist_keycloak/theme/<theme_name>/login/login.ftl`

Your custom exclusion are injected around line 300. You can edit this code and reload the page immediately.

## Taking full control of how the KcContext is generated

If you feel limited by this option you can take ownership of the FreeMarker template that generates the kcContext. Let's see how it can be done through an example.\
\
Some values, like for example the realm attributes (kcContext.realm.attributes) are explicitely excluded from the KcContext.

In the following video we explore how to include them back.

{% embed url="<https://www.youtube.com/watch?v=WdSPrpFObhg>" %}

Note that in the video we includes **all** the realm attributes. We might want to expose only a specific set of values. For this we could do:

{% code title="node\_modules/keycloakify/src/bin/keycloakify/generateFtl/kcContextDeclarationTemplate.ftl" %}

```diff
-    ) || (
-        key == "attributes" &&
-        areSamePath(path, ["realm"])
-    ) || (
+    ) || (
+        areSamePath(path, ["realm", "attributes"]) &&
+        !["myFirstAttribute", "mySecondAttribute"]?seq_contains(key)
+    ) || (
```

{% endcode %}

We could also chose to include only the realm attributes with a specific prefix, for example `theme_`:

{% code title="node\_modules/keycloakify/src/bin/keycloakify/generateFtl/kcContextDeclarationTemplate.ftl" %}

```diff
-    ) || (
-        key == "attributes" &&
-        areSamePath(path, ["realm"])
-    ) || (
+    ) || (
+        areSamePath(path, ["realm", "attributes"]) &&
+        !key?starts_with("theme_")
+    ) || (
```

{% endcode %}

## Setting up patch-package

As explained in the video:

Add [patch-package](https://www.npmjs.com/package/patch-package) add dev dependency

```bash
yarn add --dev patch-package
```

Edit the FreeMarker template that generates the KcContext in:

**node\_modules/keycloakify/src/bin/keycloakify/generateFtl/kcContextDeclarationTemplate.ftl**

You can then create a diff for your changes by running:

```bash
npx patch-package keycloakify
```

Then add a postinstall script to your package.json:

<pre class="language-json" data-title="package.json"><code class="lang-json">{
    "name": "keycloakify-starter",
    "scripts": {
<strong>        "postinstall": "patch-package",
</strong>        "dev": "vite",
    ...
}
</code></pre>

Commit the **patch/** directory that have been created by patch-package.


# keycloakifyBuildDirPath

This option enables you to configure in which directory the .jar files should be created.

{% tabs %}
{% tab title="Vite" %}
By default it's **./dist\_keycloak**

<pre class="language-typescript" data-title="vite.config.ts"><code class="lang-typescript">import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { keycloakify } from "keycloakify/vite-plugin";

export default defineConfig({
  plugins: [
    react(), 
    keycloakify({
<strong>      keycloakifyBuildDirPath: "./keycloak-theme-dist"
</strong>    })
  ],
})
</code></pre>

{% endtab %}

{% tab title="Webpack" %}
By default it's **./build\_keycloak**

{% code title="package.json" %}

```json
{
    "keycloakify": {
        "keycloakifyBuildDirPath": "./keycloak-theme-jars"
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}


# groupId

Configure the `groupId` that will appear in the `pom.xml` file.

<figure><img src="/files/LuiRykuTl3d0xa59iaNy" alt=""><figcaption></figcaption></figure>

{% tabs %}
{% tab title="Vite" %}

<pre class="language-typescript" data-title="vite.config.ts"><code class="lang-typescript">import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { keycloakify } from "keycloakify/vite-plugin";

export default defineConfig({
  plugins: [
    react(), 
    keycloakify({
<strong>      groupId: "dev.keycloakify.demo-app-advanced.keycloak"
</strong>    })
  ],
})
</code></pre>

{% endtab %}

{% tab title="Webpack" %}
{% code title="package.json" %}

```json
{
    "keycloakify": {
        "groupId": "dev.keycloakify.demo-app-advanced.keycloak"
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

By default it's the package.json homepage field at reverse with .keycloak at the end.

You can overwrite this using an environment variable:

```bash
KEYCLOAKIFY_GROUP_ID="com.your-company.your-project.keycloak" npx keycloakify
```


# artifactId

{% hint style="info" %}
NOTE: For changing the name of the jar file that is generated by Keycloakify see this option instead: [keycloakVersionTargets](/features/compiler-options/keycloakversiontargets).
{% endhint %}

Configure the `artifactId` that will appear in the `pom.xml` file.

<figure><img src="/files/cGoLTg9HsA7wqLMTko8D" alt=""><figcaption></figcaption></figure>

{% tabs %}
{% tab title="Vite" %}

<pre class="language-typescript" data-title="vite.config.ts"><code class="lang-typescript">import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { keycloakify } from "keycloakify/vite-plugin";

export default defineConfig({
  plugins: [
    react(), 
    keycloakify({
<strong>      artifactId: "keycloakify-advanced-starter-keycloak-theme"
</strong>    })
  ],
})
</code></pre>

{% endtab %}

{% tab title="Webpack" %}
{% code title="package.json" %}

```json
{
    "keycloakify": {
        "artifactId": "keycloakify-advanced-starter-keycloak-theme"
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

By default it's `<themeName>-keycloak-theme` See, [`keycloak.themeName`](#keyclokify.themename) option.

You can overwrite this using an environment variable:

```bash
KEYCLOAKIFY_ARTIFACT_ID="my-cool-theme" npx keycloakify build
```


# Webpack specific options

In this sub folder are listed the few build options that are only relevant in Webpack project.

Be aware, theses are not preferences, they have to reflect your webpack configuration!

{% content-ref url="/pages/6FbZUlPKFhYxjIlxwzWf" %}
[projectBuildDirPath](/features/compiler-options/webpack-specific-options/projectbuilddirpath)
{% endcontent-ref %}

{% content-ref url="/pages/vaTbGbqpTp6w7QxSyIHq" %}
[staticDirPathInProjectBuildDirPath](/features/compiler-options/webpack-specific-options/staticdirpathinprojectbuilddirpath)
{% endcontent-ref %}

{% content-ref url="/pages/207XkFI3UafMX62gqJQ5" %}
[publicDirPath](/features/compiler-options/webpack-specific-options/publicdirpath)
{% endcontent-ref %}


# projectBuildDirPath

In the Create React App setup the, when you run yarn build, a build/ directory is generated.\
If, in your setup it's an other directory you can use this option:

{% code title="package.json" %}

```json
{
  "keycloakify": {
    "projectBuildDirPath": "a/b/c"
  }
}
```

{% endcode %}

By default it's `"build"`.


# staticDirPathInProjectBuildDirPath

In the Creact React App setup the, when you run yarn build, a build/ directory is generated.\
I this directory there's a static directory/.\
If, in your setup it's an other directory you can use this option:

{% code title="package.json" %}

```json
{
    "keycloakify": {
        "staticDirPathInProjectBuildDirPath": "a/b/c"
    }
}
```

{% endcode %}

By default it's `"static"`.


# publicDirPath

To enable to test your theme locally, in storybook or with yarn start Keycloakify copies the default theme resources, primarily constituted of PatternFly, the CSS framework used for the default theme.

This option allows you to customize what's the public directory in your case. By default it's `public/` but in angular for example it's `src/assets/`.

{% code title="package.json" %}

```json
{
  "keycloakify": {
    "publicDirPath": "./public"
  }
}
```

{% endcode %}

Default: `~/public`

You can also use the `PUBLIC_DIR_PATH` environnement variable. Example:

```bash
npx PUBLIC_DIR_PATH=./src/assets keycloakify copy-keycloak-resources-to-public
```


# Registration Page

<details>

<summary>Angular</summary>

The register page can be customized in Angular. As soon as you run `npx keycloakify eject-page` and pick the register page all its sub components are also ejected into your project:\
![](/files/AmpI8PURRNpFp36ASHoP)

</details>

In this video, I explain how to customize the register page of Keycloak, both at the Keycloak configuration level and at the theme level.

{% embed url="<https://youtu.be/lMOLrdqilqE>" %}

## Timestamps

* [01:28](https://www.youtube.com/watch?v=lMOLrdqilqE\&t=88s) - User Profile Attributes Configuration
* [11:05](https://www.youtube.com/watch?v=lMOLrdqilqE\&t=665s) - Password Policies Configuration
* [13:27](https://www.youtube.com/watch?v=lMOLrdqilqE\&t=807s) - Email Domain Accept List
* [15:10](https://www.youtube.com/watch?v=lMOLrdqilqE\&t=910s) - Adding Custom User Attributes to the JWT of the ID and Access Token
* [16:31](https://www.youtube.com/watch?v=lMOLrdqilqE\&t=991s) - Exporting the Realm Configuration
* [19:14](https://www.youtube.com/watch?v=lMOLrdqilqE\&t=1154s) - Creating Storybook Stories for the Register Page
* [23:53](https://www.youtube.com/watch?v=lMOLrdqilqE\&t=1433s) Customizing the Register Page with CSS
* [26:35](https://www.youtube.com/watch?v=lMOLrdqilqE\&t=1595s) - Customizing the Register Page with React


# Terms and Conditions Page

The Tems and Condition feature of Keycloak enables you to make new users of your service consent to the terms of use of your services upon regisering.

{% embed url="<https://storybook.keycloakify.dev/?path=/story/login-terms-ftl--long-message>" %}
Preview of the default terms.ftl page
{% endembed %}

<figure><img src="/files/k11AOVkTWSRltktiIjvv" alt=""><figcaption><p>Preview of the the term and condition page.</p></figcaption></figure>

## Enabling the feature

If you want you show your terms and condition page when they create an account you have to enable it in your realm configuration.

This is how to do it in the Keycloak Admin Console:

* Navigat to your realm
* Left bar item: Authentication
* Tab: Required Actions
* Terms and condition: Enabled & Set as Default Action

## Defining your Terms and Conditions text

The way of defining your terms of services in Keycloak is to provide a message bundle for your realm that overrides the `termsText` key for the different languages that you have enabled.

In recent Keycloak's versions this can be acheived directly via the Keycloak Admin Console as shown in this video:

{% embed url="<https://youtu.be/naW2TxwJZsA>" %}

{% hint style="info" %}
If you want to use an iframe as text [read this](https://github.com/keycloakify/keycloakify/discussions/687).
{% endhint %}

## Customizing how the terms are rendered

If you want to customize the page that display the terms and condition you have to eject the [terms.ftl](https://storybook.keycloakify.dev/?path=/story/login-terms-ftl--default) page.

```bash
npx keycloakify eject-page
# Select login -> terms.ftl
```

This will create `src/login/Terms.tsx` in your project.

You also probably want to add a story for the Term page:

```bash
npx keycloakify add-story
# Select login -> terms.ftl
```

In `src/login/Terms.tsx`, note that `msg("termsText")` returns a `JSX.Element`. It's because the `msg()` function renders the string message as HTML text. You can't work directly with that.

If you want to apply transformation to the text, you should use `msgStr("termsText")` instead. This returns the original string as defined in your realm configuration.


# Differences Between Login Themes and Other Types of Themes

Here are the types of themes that exist:

* **Login Theme**: The UI for login and registration pages, displayed to users when they attempt to sign in or sign up.
* **Account Theme**: The account management interface, where users can update their email, change their password, and manage other account settings.
* **Email Theme**: Templates for automated emails (e.g., email confirmations or password reset notifications).
* **Admin Theme**: The Admin Console interface used by administrators to configure Keycloak.

Most of this documentation focuses on the Login Theme.

If you choose to implement a **Multi-Page Account Theme**, note that it works exactly like the Login Theme.

Here are the features that apply to all theme types:

* ✅ [Testing your theme inside Keycloak](/testing-your-theme/inside-of-keycloak)
* ✅ [Theme Variants](/features/theme-variants)
* ✅ [Environment Variables](/features/compiler-options/environmentvariables) (except email theme)

Below are the documentation pages that apply **only** to the Login Theme **and** the Multi-Page Account Theme but are handled differently in the other types of themes:

* ❌ [Testing your theme outside of Keycloak](/testing-your-theme/outside-of-keycloak) (using `npx keycloakify add-story` and Storybook)
* ❌ [`npx keycloakify eject-page`](/common-use-case-examples/using-a-component-library)
* ❌ [Internationalization](/features/i18n) — the other theme types handle translations differently.

In this section, you’ll find all the information you need to create the other types of themes with Keycloakify:

{% content-ref url="/pages/t2MZIH8IDMSQzIIcD8IM" %}
[Account Theme](/theme-types/account-theme)
{% endcontent-ref %}

{% content-ref url="/pages/Ya434hPcmwUhKb5zJTr1" %}
[Email Theme](/theme-types/email-theme)
{% endcontent-ref %}

{% content-ref url="/pages/jPipYosg0IVICWToM3La" %}
[Admin Theme](/theme-types/admin-theme)
{% endcontent-ref %}


# Account Theme

## Deciding if you need an account theme or not

The first question you want to ask yourself is: "Do I really need an account theme?"

If you're looking to create an account theme just to allow your users to change their password, update account information such as email, phone number, favorite pets, etc., or delete their account, then you do **not** need an account theme.

There are pages in the login theme for those functions. You only need to add a button to redirect your user to the appropriate page in your main app. Here is how to do it with oidc-spa: [Documentation](https://docs.oidc-spa.dev/documentation/user-account-management).

An added benefit of not having an account theme is that you will reuse the exact same form that you created for the registration page in the `login-update-profile.ftl` page.

Consider creating an account theme only if you need to provide advanced account management features to your users, such as connection logs, management of active sessions, file uploads, etc.

Here is a video that explains this in detail:

{% embed url="<https://youtu.be/PiTUPdpmueA>" %}

## Choosing an account theme type

Keycloakify provide you two ways to create your own account theme.

You can chose between two implementation of the account theme:

### Single Page

<figure><img src="/files/WqM1AFImn87OeKcMbN7l" alt=""><figcaption><p>Screenshot of the Single Page Account theme</p></figcaption></figure>

The Single Page theme also refered as account v3 is this the default theme that comes with Keycloak 25. [But thanks to Keycloakify's compatiblity layer it works with older Keycloak versions down to 19](https://youtu.be/HWiWHpF5mY0).

#### Pros

* Get's all the latest features.
* The base code is maintained by the Keycloak team and automatically integrated into Keycloakify. You're using the real thing, not a fork.
* If you're a React developper you'll feel right at home. It's uing i18n-next and react-router

#### Cons

* Opting for this option will add a lot of dependencies to your project (i18n-next, react-router-dom, patenrnfly and more).
* CSS level customization beyond [overidding the Paternfly CSS variables](https://www.patternfly.org/components/button/html/#css-variables) is not practical, you'll have to customize at the React component level.
* No Storybook integration.
* Only available with React (Angular and Svelte not supported)

To get started with the Single-Page account theme:

{% content-ref url="/pages/s8qtD61ZfME9GX7WgP65" %}
[Single-Page](/theme-types/account-theme/single-page)
{% endcontent-ref %}

### Multi Page

<figure><img src="/files/Eya1tP3ugkKHv9MnnKcO" alt=""><figcaption></figcaption></figure>

This is a fork of the Account v1 maintained by the Keycloakify team.

#### Pros

* Works exactly the same as the login theme, nothing new to learn.
* Available in React, Angular and Svelte
* Storybook support
* CSS level customization support like in the login theme.
* As it's maintained by us, we can guarenty a certain level of stability in future version of Keycloak.
* Compatible with all Keycloak version.
* Does not add any dependency to your project.

#### Cons

* Don't come with all the feature out of the box yet. You'll have to use the [Keycloak Account REST API if you want to implement the missing features](/theme-types/account-theme/multi-page).
* It relies on Java code maintained by us, this code uses Keycloak internal API, you have to trust us to keep maintaining it.
* The default look is a bit dated.

To get started with the Multi-Page account theme:

{% content-ref url="/pages/kvWG0r5wgKSR6WqbEBJ0" %}
[Multi-Page](/theme-types/account-theme/multi-page)
{% endcontent-ref %}


# Single-Page

Customizing the Single Page Account UI

To initialize a Single-Page account theme run the following command:

```bash
npx keycloakify initialize-account-theme # Select 'Sigle-Page'
```

{% embed url="<https://youtu.be/UKU6zGCH-CY>" %}
Video Tutorial
{% endembed %}

📌 Timestamps:\
[00:00](https://www.youtube.com/watch?v=UKU6zGCH-CY\&t=0s) – Intro\
[03:33](https://www.youtube.com/watch?v=UKU6zGCH-CY\&t=213s) – Changing the logo\
[07:46](https://www.youtube.com/watch?v=UKU6zGCH-CY\&t=466s) – Using a custom button component\
[14:13](https://www.youtube.com/watch?v=UKU6zGCH-CY\&t=853s) – Update process\
[16:55](https://www.youtube.com/watch?v=UKU6zGCH-CY\&t=1015s) – Translations (i18n)\
[18:23](https://www.youtube.com/watch?v=UKU6zGCH-CY\&t=1103s) – Enabling your account theme in the Keycloak Admin Console

## Initialize the Account

After initialization, there will be many files that are part of the theme but are currently git-ignored. To customize a file and ensure it's tracked, run the own command included at the top of the file. For instance, at the top of `KcPage.tsx` you'll see `npx keycloakify own --path "account/KcPage.tsx"`.

When a new person clones the project and runs the installation script, their project will populate with all the necessary files while continuing to be git-ignored.

## Running the Server

To preview the application use `npx keycloakify start-keycloak`. This will run your application within a Keycloak container. Open the local realm version and select the Account theme link. Once that is open, you'll be able to work like you normally would on any React application with instant refresh and reload.

**Note**

If you have a Login theme where you are forcing a specific template to render, make sure to comment that out. Otherwise, you will never be able to log in.

## Updating the Logo

1. Own the `Header.tsx` (`src/account/root/Header.tsx`) file.
2. Add your own logo to the `Assets` (`src/account/assets`) folder.
3. Update the logo import to your new logo. `import logoSvgUrl from "../assets/logo.svg";` becomes `import logoSvgUrl from "../assets/your-logo.svg";` or whatever.
4. Run the server with `npx keycloakify start-keycloak`.

## Updating a Custom Component

Updating a component that comes from PatternFly, means that we need to create a new component in some fashion. In order to make it possible to edit the components that are used from PatternFly, those are re-exported from the core library of PatternFly.

1. In the project structure, locate `/src/shared/@patternfly/.../index.tsx` of whatever component or item you want to edit. Run the own command for that specific file.
2. For the sake of this example, let's create a "CustomButton" component from the `@patternfly/react-core` package. Create a new file `CustomButton.tsx` at the same level as the index file (e.g., `/src/shared/@patternfly/react-core/CustomButton.tsx`).
3. Inside that component, you can create it however you want. For example, you could do

```tsx
import { type ButtonProps, Button } from "@patternfly/react-core";
import { css } from "@patternfly/react-styles";
import styles from "./CustomButton.module.css"; // Import the CSS module

export function CustomButton(props: ButtonProps) {
  const { children, variant = "primary", className: className_props, ...rest } = props;

  const className = css(styles.button, styles[variant], className_props);

  if (variant === "link") {
    return (
      <Button {...rest} className={className} variant="link">
        {children}
      </Button>
    );
  }

  return (
    <button {...rest} className={className}>
      {children}
    </button>
  );
}
```

4. You'll notice that it imports a CSS module file. Create `CustomButton.module.css` right next to it and style however you want.
5. In the `index.tsx` file then export the component. This will then override the button exported by Patternfly and apply it to every page that imports that button.

```tsx
export * from "@patternfly/react-core";
export { CustomButton as Button } from "./CustomButton";
```

## Update Instances

The core module for the account is `@keycloakify/keycloak-account-ui`. You can always update the minor versions without issues (simply update them and run the installation script).

## Translations

Each file has instructions at the top of it. Generally, just create an override file and not own the base translation file. This will help to make sure only those changes take effect.

Read more in the [email theme](/theme-types/email-theme).


# Multi-Page

## Initializing the Multi-Page Account Theme

<figure><img src="/files/Qe2zztN6sgbpbY9tUTR8" alt=""><figcaption><p>The Multi-Page Account theme before customization</p></figcaption></figure>

You've made your mind and opted for the Multi-Page Account theme?\
Great, let's start by initializing you theme:

```bash
npx keycloakify initialize-account-theme
```

When asked, select "Multi-Page".

This command will create the nessesary boilerplate for you.

Beyond that there isn't much thing you need to be aware of, things works exactly as in the login theme. You'll be able to use the keycloakify`add-story` and `eject-page` CLI command just select account when asked.

## Internationalization and translation (i18n)

The i18n system of the Multi-Page Account theme is similar in every way to the one of the login theme, you just need to replace **/login/** by **/account/** everywhere.

## Using the REST API

Even if you're using the Multi-Page theme you can still consume the REST API the Single-Page Account is build on top of. So, if some information you need are missing from the `kcContext` you can fetch them dynamically.

{% embed url="<https://youtu.be/FrFr-hqyjb4>" %}

{% tabs %}
{% tab title="React" %}
{% embed url="<https://github.com/keycloakify/keycloakify-starter/tree/account_api_poc>" %}
Branch of the starter template modified to call the Account REST API
{% endembed %}
{% endtab %}

{% tab title="Angualr" %}
{% embed url="<https://github.com/keycloakify/keycloakify-starter-angular-vite/tree/account_api_poc>" %}
Branch of the starter template modified to call the Account REST API
{% endembed %}
{% endtab %}

{% tab title="Svelte" %}
TODO
{% endtab %}
{% endtabs %}

You can find the code for the Account v3 theme [here](https://github.com/keycloak/keycloak/tree/main/js/apps/account-ui/src/api). This will help you infer all the available endpoints. You can also enable the Account v3 theme in your Keycloak and use the network tab to see the available endpoints.


# Email Theme

There are two ways you can create a Keycloak Email theme with Keycloakify

## Using keycloakify-emails

[keycloakify-email](https://github.com/timofei-iatsenko/keycloakify-emails) is a Keycloakify plugin that enable to create an email theme using [jsx-email](https://jsx.email/) or any other email templating solution.

{% embed url="<https://github.com/timofei-iatsenko/keycloakify-emails>" %}

*This plugin will evenutally be integrated to Keycloakify core.*

{% hint style="warning" %}
This approach only works in Vite project. So not with Webpack/Create-React-App
{% endhint %}

{% tabs %}
{% tab title="React" %}
For react, there is a working example in the /example directory of keycloakify-emails that uses jsx-email\
[https://github.com/timofei-iatsenko/keycloakify-emails](https://github.com/timofei-iatsenko/keycloakify-emails/tree/main/example)
{% endtab %}

{% tab title="Svelte" %}
{% embed url="<https://github.com/keycloakify/svelte-email>" %}
{% endtab %}

{% tab title="Angular" %}
{% embed url="<https://github.com/keycloakify/angular-email>" %}
{% endtab %}
{% endtabs %}

## Using FreeMarker

`npx keycloakify initialize-email-theme`, select the `native` option.

Running this command will initialize a native email theme in the `src/email` directory.

{% embed url="<https://youtu.be/IZ9LSLfWxqo>" %}

### Using assets in native email theme

To use images you can put them into **src/email/resources/** example:

<figure><img src="/files/UoCv5ofeYN4Zj6GNeINY" alt=""><figcaption><p>kc-logo.png in src/email/resources</p></figcaption></figure>

And then you can import them using the FreeMarker varialble `url.resourcesUrl`. Example:

<figure><img src="/files/9JSh9eJVumfnwUHOQZEd" alt=""><figcaption><p>Using kc-logo.png</p></figcaption></figure>


# Admin Theme

To initialize an Admin theme run the following command:

```bash
npx keycloakify initialize-admin-theme
```

The [Single-Page Account](/theme-types/account-theme/single-page) and Admin themes work the same way!\
You can refer to this video, which covers everything you need to know.\
The specific timestamp where the Admin theme is addressed is [19:55](https://www.youtube.com/watch?v=UKU6zGCH-CY\&t=1195s).

{% embed url="<https://youtu.be/UKU6zGCH-CY>" %}
Video Tutorial
{% endembed %}

📌 Timestamps:\
[00:00](https://www.youtube.com/watch?v=UKU6zGCH-CY\&t=0s) – Intro\
[03:33](https://www.youtube.com/watch?v=UKU6zGCH-CY\&t=213s) – Changing the logo\
[07:46](https://www.youtube.com/watch?v=UKU6zGCH-CY\&t=466s) – Using a custom button component\
[14:13](https://www.youtube.com/watch?v=UKU6zGCH-CY\&t=853s) – Update process\
[16:55](https://www.youtube.com/watch?v=UKU6zGCH-CY\&t=1015s) – Translations (i18n)\
[18:23](https://www.youtube.com/watch?v=UKU6zGCH-CY\&t=1103s) – Enabling your account theme in the Keycloak Admin Console\
[19:55](https://www.youtube.com/watch?v=UKU6zGCH-CY\&t=1195s) – Admin Theme


# Quick start

Keycloakify is a tool that enables to create keycloak themes for customizing of the look and feel of Keycloak's user-facing pages. You can preview these pages here:

{% embed url="<https://storybook.keycloakify.dev/>" %}

Why would I chose to use this third party tool insted of directly implementing [the theming system featured by Keycloak](https://www.keycloak.org/docs/latest/server_development/#_themes)?

* Keycloakify lets you use modern frontend technology: **TypeScript**, **React**, and **any styling solution or component library** you'd like, such as **Tailwind**, **MUI**, **shadcn/ui**, or just **plain CSS**. With the base theming system you have to write [FreeMarker](https://freemarker.apache.org/index.html) and integrating frontend technologies into a Java Stack isn't straight forward.
* Keycloakify makes it very easy to test your theme [inside](/v10/testing-your-theme/in-a-keycloak-docker-container) and [outside](/v10/testing-your-theme/in-storybook) Keycloak with hot reloading enabled.
* Keycloakify bundles the theme for you into a JAR that you can simply [import into Keycloak](/v10/importing-your-theme-in-keycloak).
* The Keycloak themes generated with Keycloakify are compatible with all Keycloak versions down to Keycloak version 11, by opposition to regular themes that must be updated to target a specific Keycloak version.
* Keycloakify themes implement real-time frontend validation out of the box. For example, when a user chooses a password that is too weak, they see the feedback message like *"the password must be at least 12 character long"* immediately and not after they have pressed the submit button.
* We're here to help! Either via [our Discord](https://discord.gg/kYFZG7fQmn) channel or [GitHub issues](https://github.com/keycloakify/keycloakify/issues/new).

First thing you want to do is to fork/clone the Keycloakify Vite[^1] starter template.

{% embed url="<https://github.com/keycloakify/keycloakify-starter>" %}

Then you can move on to the next section of the documentation:

{% content-ref url="/pages/6zsjToyFtGwfJpF6IgrG" %}
[Testing your Theme](/v10/testing-your-theme)
{% endcontent-ref %}

[^1]: There's also a Webpack based starter that you can find [here](https://github.com/keycloakify/keycloakify-starter-webpack).


# Testing your Theme

The name of the game for developing a good theme is how quickly you can see your changes on the screen and how easy it is to replicate a production environment locally.

Keycloakify help you with that providing you three ways to test your theme on your machine.

{% content-ref url="/pages/M0xHW2Aark4N3MkSMLgQ" %}
[In Storybook](/v10/testing-your-theme/in-storybook)
{% endcontent-ref %}

{% content-ref url="/pages/cVCigTvwKjj8vK7vEHSb" %}
[In a Keycloak Docker Container](/v10/testing-your-theme/in-a-keycloak-docker-container)
{% endcontent-ref %}

{% content-ref url="/pages/2JTVpGkJ7Kbc1xJKuvOC" %}
[With Vite or Webpack in dev mode](/v10/testing-your-theme/with-vite-or-webpack-in-dev-mode)
{% endcontent-ref %}


# In Storybook

{% hint style="info" %}
TLDR:

```bash
npx keycloakify add-story
npm run storybook
```

{% endhint %}

[Storybook](https://storybook.js.org/) is a tool that enables to test UI component in isolation. For reference, the component showcase Keycloakify website is a website generated with Storybook.

{% embed url="<https://storybook.keycloakify.dev/?path=/story/introduction--page>" %}

The starter template does not initially contain any story files, instead there's a keycloakify CLI command that let's you import specifically the stories for the pages you want to test into your project.

So, just run this command in the root of your Keycloakify project and select the pages you want.

```bash
npx keycloakify add-story
```

It will enables you to select the pages you want to add stories for.

<figure><img src="/files/mqjfG85EV98KuKojT3NE" alt=""><figcaption></figcaption></figure>

Selecting login -> register.ftl will result in this file to be created in your project:

<figure><img src="/files/i2QcvJDLBahrAdTVK57W" alt=""><figcaption></figcaption></figure>

You can run the above command multiple times to add stories for the different pages you want to develop.

Once your added a few stories you can start Storybook locally with:

```bash
npm run storybook
```

<figure><img src="/files/NMioZFXdsBE0HqoJJHQa" alt=""><figcaption></figcaption></figure>

You can see the changes you make in you code in realtime in your Storybook.

The idea of Storybook is to easily let you see the pages in different configuration without having to reproduce the full login/register process in a real Keycloak.\
Keycloakify provide a default mock context for every pages, the stories let you partially override some specific part of this default mock to reflect pages in different configurations.\
\
For example, if you want to create a story that show the register page in chinese you would add this:

<pre class="language-tsx" data-title="src/login/pages/Register.stories.tsx"><code class="lang-tsx">import type { Meta, StoryObj } from "@storybook/react";
import { createKcPageStory } from "../KcPageStory";

const { KcPageStory } = createKcPageStory({ pageId: "register.ftl" });

const meta = {
    title: "login/register.ftl",
    component: KcPageStory
} satisfies Meta&#x3C;typeof KcPageStory>;

export default meta;

type Story = StoryObj&#x3C;typeof meta>;

export const Default: Story = {
    render: () => &#x3C;KcPageStory />
};

<strong>export const InChinese: Story = {
</strong><strong>    render: ()=> (
</strong><strong>        &#x3C;KcPageStory
</strong><strong>            kcContext={{
</strong><strong>                locale: {
</strong><strong>                    currentLanguageTag: "zh-CN"
</strong><strong>                }
</strong><strong>            }}
</strong><strong>        />
</strong><strong>    )
</strong><strong>};
</strong></code></pre>

<figure><img src="/files/M1BJIgPozWliggXmsQea" alt=""><figcaption></figcaption></figure>

That's really nice, however this approach has it's limits. At some point you'll want to test in a real Keycloak to make sure everything works. Also you don't necessary know the kcContext values to provides in order to replicate a desired configuration.

Don't worry! Keycloakify got you covered by letting you test in a local Keycloak Docker container.

{% content-ref url="/pages/cVCigTvwKjj8vK7vEHSb" %}
[In a Keycloak Docker Container](/v10/testing-your-theme/in-a-keycloak-docker-container)
{% endcontent-ref %}


# In a Keycloak Docker Container

{% hint style="info" %}
TLDR:

```bash
npx keycloakify start-keycloak
```

{% endhint %}

Testing your theme in Storybook is nice, but at some point you'll want to test your theme in a real Keycloak before shipping it in production!

First you want to install and launch [Docker Desktop](https://www.docker.com/products/docker-desktop/) (or just Docker) on your computer, if you haven't done it already.

You'll also need Maven to build the .jar locally. Try running `mvn --version` to see if you have it already. If you don't install it with:

{% tabs %}
{% tab title="MacOS" %}
Using [Homebrew](https://formulae.brew.sh/formula/maven):

```bash
brew install maven
```

{% endtab %}

{% tab title="Ubuntu/Debian" %}

```bash
sudo apt-get install maven
```

{% endtab %}

{% tab title="Fedora" %}

```bash
sudo dnf install maven
```

{% endtab %}

{% tab title="Windows" %}
On Windows you can use the [Chocolatery](https://chocolatey.org/) package manager:

```bash
choco install openjdk
choco install maven
```

Or [install it manually](https://chocolatey.org/).
{% endtab %}
{% endtabs %}

\
You are ready! In your Keycloakify just run:

```bash
npx keycloakify start-keycloak
```

You'll be invited to chose the Keycloak version you want to spin up:

<figure><img src="/files/IWBhKQ7FHkxQql1Ru5Jc" alt=""><figcaption></figcaption></figure>

Keycloakify will preconfigure a realm and client for your theme so you don't necessarily need to go in the the Keycloak admin console you can simply navigate to **<https://my-theme.keycloakify.dev>** it will redirect to your local Keycloak login pages!

<figure><img src="/files/3icjEiDIwpRYIpQ2CAkp" alt=""><figcaption></figcaption></figure>

{% hint style="success" %}
If you makes changes in your theme while the Keycloak container is running **your theme will be automatically recompiled** and updated in Keycloak. After a few seconds you'll just have to refresh the page you see them live.
{% endhint %}

Clicking on the **<https://my-theme.keycloakify.dev>** link will redirect you to the login page of your theme:

<figure><img src="/files/ST5hCJJI6vPub2BVX9Wr" alt=""><figcaption></figcaption></figure>

With the developer tool of your brower you'll be able to explore the kcContext of the page. You can use it to creates new stories of your pages in specific configuration.

<figure><img src="/files/3eC0nyjs34PWEzGaPYu0" alt=""><figcaption></figcaption></figure>

Loggin in with the test user (testuser/password123) will redirect you to a page where you'll be able to inspect the decoded id token JWT beside other things.

<figure><img src="/files/t2C1Pvjsm5jfEVY57ycS" alt=""><figcaption></figcaption></figure>

## Configuration options

There are many options available to you to configure the Keycloak testing container.

{% tabs %}
{% tab title="Vite" %}

<pre class="language-typescript" data-title="vite.config.ts"><code class="lang-typescript">import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { keycloakify } from "keycloakify/vite-plugin";

// https://vitejs.dev/config/
export default defineConfig({
 plugins: [
  react(),
  keycloakify({
   accountThemeImplementation: "none",
<strong>   startKeycloakOptions: {
</strong><strong>    // All the options are optional
</strong>
<strong>    /*
</strong><strong>    By default Keycloakify uses the official Keycloak Docker Image (quay.io/keycloak/keycloak)
</strong><strong>    and lets you select the tag when you run the command by asking you to 
</strong><strong>    select a Keycloak version.
</strong><strong>    Using this option you can use an alternative Docker Image like the one 
</strong><strong>    of Phase2 (https://quay.io/repository/phasetwo/phasetwo-keycloak) and a 
</strong><strong>    specific tag to use.
</strong><strong>    This option can also be used to pin a specific version of the official 
</strong><strong>    Keycloak Docker Image.
</strong><strong>    Example: `dockerImage: "quay.io/keycloak/keycloak:25.0.2"`
</strong><strong>    */
</strong><strong>    dockerImage: "quay.io/phasetwo/phasetwo-keycloak:25.0.2.1721752809",
</strong>
<strong>    /*
</strong><strong>    This option allows you to pass extra docker arguments to the 
</strong><strong>    `docker run` command.
</strong><strong>    */
</strong><strong>    dockerExtraArgs: [
</strong><strong>     "-e", "KC_HTTP_RELATIVE_PATH=/auth"
</strong><strong>    ],
</strong>
<strong>    /*
</strong><strong>    This option allows you to start Keycloak with extra arguments.
</strong><strong>    */
</strong><strong>    keycloakExtraArgs: [
</strong><strong>     "--spi-email-template-provider=freemarker-plus-mustache",
</strong><strong>     "--spi-email-template-freemarker-plus-mustache-enabled=true",
</strong><strong>     "--spi-theme-cache-themes=false"
</strong><strong>    ],
</strong>
<strong>    /*
</strong><strong>    This option allow you to load custom Keycloak extensions in the 
</strong><strong>    Keycloak instance running in the Docker container.
</strong><strong>    In this example we load two extensions:
</strong><strong>    - https://github.com/InseeFr/Keycloak-FranceConnect
</strong><strong>    - https://github.com/micedre/keycloak-mail-whitelisting
</strong><strong>    */
</strong><strong>    extensionJars: [
</strong><strong>     "https://github.com/InseeFr/Keycloak-FranceConnect/releases/download/6.2.0/keycloak-franceconnect-6.2.0.jar",
</strong><strong>     "./keycloak-resources/keycloak-mail-whitelisting-2.0.jar"
</strong><strong>    ],
</strong><strong>    /*
</strong><strong>    By default, the Keycloak instance is loaded with a pre-configured 
</strong><strong>    realm so you do not have to create a realm, a client, a user, etc.  
</strong><strong>    However you might want to edit this base realm configuration and 
</strong><strong>    persist the changes that you made.  
</strong><strong>    I explain it in this video: https://www.youtube.com/watch?v=lMOLrdqilqE&#x26;t=991s
</strong><strong>    */
</strong><strong>    realmJsonFilePath: "./keycloak-resources/myrealm-realm.json",
</strong><strong>    /*
</strong><strong>     * By default the Keycloak instance will run on port 8080.
</strong><strong>     * This option allows you to change it to another port.
</strong><strong>     */
</strong><strong>    port: 8081
</strong><strong>   }
</strong><strong>  })
</strong><strong> ]
</strong><strong>});
</strong></code></pre>

{% endtab %}

{% tab title="Webpack" %}
{% code title="package.json" %}

```json
{
  "keycloakify": {
    "startKeycloakOptions": {
      "dockerImage": "quay.io/phasetwo/phasetwo-keycloak:25.0.2.1721752809",
      "dockerExtraArgs": [
        "-e",
        "KC_HTTP_RELATIVE_PATH=/auth"
      ],
      "keycloakExtraArgs": [
        "--spi-email-template-provider=freemarker-plus-mustache",
        "--spi-email-template-freemarker-plus-mustache-enabled=true",
        "--spi-theme-cache-themes=false"
      ],
      "extensionJars": [
        "https://github.com/InseeFr/Keycloak-FranceConnect/releases/download/6.2.0/keycloak-franceconnect-6.2.0.jar",
        "./keycloak-resources/keycloak-mail-whitelisting-2.0.jar"
      ],
      "realmJsonFilePath": "./keycloak-resources/myrealm-realm.json",
      "port": 8081
    }
  }
}
```

{% endcode %}

See the Vite tab for explaination of the different options.
{% endtab %}
{% endtabs %}

Configured as the example above, the docker run command will be the following one:

<figure><img src="/files/iWtuGjZysIDxFQp5zucu" alt=""><figcaption></figcaption></figure>


# With Vite or Webpack in dev mode

{% tabs %}
{% tab title="Vite" %}
{% hint style="info" %}
TLDR:

* Uncomment the `getKcContextMock()` in **src/main.tsx**
* **npm run dev**
* Don't forget to comment again when you're done testing.
  {% endhint %}
  {% endtab %}

{% tab title="Webpack" %}
{% hint style="info" %}
TLDR:

* Uncomment the `getKcContextMock()` in **src/index.tsx**
* **npm run start**
* Don't forget to comment again when you're done testing.
  {% endhint %}
  {% endtab %}
  {% endtabs %}

If you don't have Storybook in your project you can also test your theme with the dev server.

To do that, just uncomment some line in your entrypoint:

{% tabs %}
{% tab title="Vite" %}

<pre class="language-tsx" data-title="src/main.tsx"><code class="lang-tsx">import { createRoot } from "react-dom/client";
import { StrictMode, lazy, Suspense } from "react";

<strong>import { getKcContextMock } from "./login/KcPageStory";
</strong>
<strong>if (import.meta.env.DEV) {
</strong><strong>    window.kcContext = getKcContextMock({
</strong><strong>        pageId: "register.ftl",
</strong><strong>        overrides: {}
</strong><strong>    });
</strong><strong>}
</strong>
const KcLoginThemePage = lazy(() => import("./login/KcPage"));
const KcAccountThemePage = lazy(() => import("./account/KcPage"));

createRoot(document.getElementById("root")!).render(
    &#x3C;StrictMode>
        &#x3C;Suspense>
            {(() => {
                switch (window.kcContext?.themeType) {
                    case "login":
                        return &#x3C;KcLoginThemePage kcContext={window.kcContext} />;
                    case "account":
                        return &#x3C;KcAccountThemePage kcContext={window.kcContext} />;
                }
                return &#x3C;h1>No Keycloak Context&#x3C;/h1>;
            })()}
        &#x3C;/Suspense>
    &#x3C;/StrictMode>
);
</code></pre>

{% endtab %}

{% tab title="Webpack" %}

<pre class="language-tsx" data-title="src/index.tsx"><code class="lang-tsx">import { createRoot } from "react-dom/client";
import { StrictMode, lazy, Suspense } from "react";

<strong>import { getKcContextMock } from "./login/KcPageStory";
</strong>
<strong>if (process.env.NODE_ENV === "development") {
</strong><strong>    window.kcContext = getKcContextMock({
</strong><strong>        pageId: "register.ftl",
</strong><strong>        overrides: {}
</strong><strong>    });
</strong><strong>}
</strong>
const KcLoginThemePage = lazy(() => import("./login/KcPage"));
const KcAccountThemePage = lazy(() => import("./account/KcPage"));

createRoot(document.getElementById("root")!).render(
    &#x3C;StrictMode>
        &#x3C;Suspense>
            {(() => {
                switch (window.kcContext?.themeType) {
                    case "login":
                        return &#x3C;KcLoginThemePage kcContext={window.kcContext} />;
                    case "account":
                        return &#x3C;KcAccountThemePage kcContext={window.kcContext} />;
                }
                return &#x3C;h1>No Keycloak Context&#x3C;/h1>;
            })()}
        &#x3C;/Suspense>
    &#x3C;/StrictMode>
);
</code></pre>

{% endtab %}
{% endtabs %}

The pageId parameter of the getKcContextMock let you decide what page you want to test.\
The overrides parameter let you modify the the default kcContext mock for the page.\
\
For example you can set:

```tsx
window.kcContext = getKcContextMock({
  pageId: "login.ftl",
  overrides: {
    locale: {
      currentLanguageTag: "zh-CN",
    },
  },
});
```

For rendering the Login page in Chinese.

You can then run the development server with:

{% tabs %}
{% tab title="Vite" %}

```bash
npm run dev
```

{% endtab %}

{% tab title="Webpack (Create React App)" %}

```bash
npm run start
```

{% endtab %}
{% endtabs %}

<figure><img src="/files/xLWP0O8HAqzjVls5lKvY" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
When you're done testing, don't forget to comment back the import of the mock. Forgetting to do so will negatively impact the bundle size of your pages.
{% endhint %}


# Integrating Keycloakify in your Codebase

{% hint style="info" %}
**Before You Start**:

This documentation section is relevant only if **you already have a project** and want to add a Keycloak theme as one of its deliverables.

One of the powerful aspects of Keycloakify is its ability to let you reuse components and styles from your main application in your Keycloak theme. However, if you don't have an existing codebase, it’s easier to fork [the starter project](https://github.com/keycloakify/keycloakify-starter) and develop your Keycloak theme as a standalone project.
{% endhint %}

There is two main approach to integrate Keycloakify into your project, pick the one that you think will work best for you.

{% tabs %}
{% tab title="Collocation" %}
If you happen to be devlopping a React Single Page Application with Vite or Webpack you can install Keycloakify directly within your project!

This approach make it easy to reuse style and component of your main application in your Keycloakify theme if you are not in a mono-repository setup.

{% content-ref url="/pages/bcI4VfgrHJ00EEwVsZOK" %}
[In your React Project](/v10/keycloakify-in-my-codebase/in-your-react-project)
{% endcontent-ref %}
{% endtab %}

{% tab title="Monorepo" %}
There are many cases where the colocation apprach is not feasable, for example:

* You are using Next.js or another meta framwork that involves server side rendering.
* You are using a framework other than React (Vue, Angular, Svelt ...)

Beside, you might prefer to to keep your Keycloak theme as an isolated component of your app.

In this case you can integrate Keycloakify...

{% content-ref url="/pages/8BjaGP7OJVRdSWnMVvKz" %}
[As a Subproject of your Monorepo](/v10/keycloakify-in-my-codebase/as-a-subproject-of-your-monorepo)
{% endcontent-ref %}
{% endtab %}
{% endtabs %}


# In your React Project

Keycloakify support two bundler: Vite and Webpack (Mainly because there are still using [Create React App](https://create-react-app.dev/)).

Pick the guide applicable to you:

{% tabs %}
{% tab title="Vite" %}
{% content-ref url="/pages/Sz53S40cFG504nKVVqhk" %}
[In your Vite Project](/v10/keycloakify-in-my-codebase/in-your-react-project/in-your-vite-project)
{% endcontent-ref %}
{% endtab %}

{% tab title="Webpack (CRA projects are Webpack projects)" %}
{% content-ref url="/pages/73IGhO6kRLI9cqQJ7XCM" %}
[In your Webpack Project](/v10/keycloakify-in-my-codebase/in-your-react-project/in-your-webpack-project)
{% endcontent-ref %}
{% endtab %}
{% endtabs %}


# In your Vite Project

If you have a Vite/React/TypeScript project you can integrate Keycloakify directly inside it.

In this guide we're going to work with a vanilla Vite project.

<figure><img src="/files/l5cfTJrAS456dvi38EUa" alt="" width="375"><figcaption><p>Creating a new vite project with yarn create vite. You don't need to create a new project. Just use your existing codebase.</p></figcaption></figure>

<figure><img src="/files/IK18FRt0Lz1qZ9vGagBP" alt="" width="368"><figcaption><p>Our codebase before installing Keycloakify</p></figcaption></figure>

{% hint style="info" %}
Before anything make sure to commit all your pending changes so you can easily revert changes if need be.
{% endhint %}

Let's start by installing Keycloakify (and optionally Storybook) to our project:

{% tabs %}
{% tab title="yarn" %}

```bash
yarn add keycloakify
yarn add --dev storybook @storybook/react @storybook/react-vite
```

{% endtab %}

{% tab title="pnpm" %}

```bash
pnpm add keycloakify
pnpm add --dev storybook @storybook/react @storybook/react-vite
```

{% endtab %}

{% tab title="bun" %}

```bash
bun add keycloakify
bun add --dev storybook @storybook/react @storybook/react-vite
```

{% endtab %}

{% tab title="npm" %}

```bash
npm install --save keycloakify
npm install --save-dev storybook @storybook/react @storybook/react-vite
```

{% endtab %}
{% endtabs %}

Next we want to repatriate the relevant files from [the starter template](https://github.com/keycloakify/keycloakify-starter) into our project:

```bash
cd my-react-app
git clone https://github.com/keycloakify/keycloakify-starter tmp
mv tmp/src src/keycloak-theme
mv tmp/.storybook .
rm -rf tmp
rm src/keycloak-theme/vite-env.d.ts
mv src/keycloak-theme/main.tsx src/main.tsx
```

<figure><img src="/files/wcgo7g4fg3VjreSlEyrb" alt="" width="370"><figcaption><p>State of your codebase after bringin in the Keycloakify boilerplate code.<br>Note thate the keycloak-theme (or keycloak_theme) directory can be located anywhere under your src directory.</p></figcaption></figure>

Now you want to modify your entry point so that:

* If the kcContext global is defined, render your Keycloakify theme
* Else, reder your App as usual.

<pre class="language-tsx" data-title="src/main.tsx"><code class="lang-tsx">/* eslint-disable react-refresh/only-export-components */
import { createRoot } from "react-dom/client";
import { 
    StrictMode,
<strong>    lazy,
</strong><strong>    Suspense
</strong>} from "react";
<strong>import { KcPage, type KcContext } from "./keycloak-theme/kc.gen";
</strong><strong>const App = lazy(() => import("./App"));
</strong>
<strong>// The following block can be uncommented to test a specific page with `yarn dev`
</strong><strong>// Don't forget to comment back or your bundle size will increase
</strong><strong>/*
</strong><strong>import { getKcContextMock } from "./keycloak-theme/login/KcPageStory";
</strong>
<strong>if (import.meta.env.DEV) {
</strong><strong>    window.kcContext = getKcContextMock({
</strong><strong>        pageId: "register.ftl",
</strong><strong>        overrides: {}
</strong><strong>    });
</strong><strong>}
</strong><strong>*/
</strong>
createRoot(document.getElementById("root")!).render(
    &#x3C;StrictMode>
<strong>        {window.kcContext ? (
</strong><strong>            &#x3C;KcPage kcContext={window.kcContext} />
</strong><strong>        ) : (
</strong><strong>            &#x3C;Suspense>
</strong><strong>                &#x3C;App />
</strong><strong>            &#x3C;/Suspense>
</strong><strong>        )}
</strong>    &#x3C;/StrictMode>
);

<strong>declare global {
</strong><strong>    interface Window {
</strong><strong>        kcContext?: KcContext;
</strong><strong>    }
</strong><strong>}
</strong></code></pre>

{% hint style="info" %}
**Question:**

Why do my main application and Keycloak theme share the same entry point?

**Answer:**

To simplify the build process. If you don't want it to negatively impact the performance of your application, it's essential to understand the following points:

* **Different Contexts:** The application (`App`) and Keycloak page (`KcPage`) are mounted in very different contexts. Avoid sharing providers between the two at the `main.tsx` file level. The true entry point of your application is the `App` component, while the entry point for your Keycloak theme is the `KcPage` component. Be careful about what code is shared between them.
* **Responsibility of main.tsx:** The `main.tsx` file should only determine the context (either the application or Keycloak) and mount the appropriate component (`App` or `KcPage`). It should not contain any substantial logic or dependencies.
* **Performance Considerations:** Keep `main.tsx` as lightweight as possible to avoid increasing the initial load time of both your main application and login pages. For example, do not load any state management libraries like `redux-toolkit` at this level.
  {% endhint %}

You also need to use Keycloakify's Vite plugin. Here we don't provide any [build options](/v10/configuration-options) but you probably at least want to define [keycloakVersionTargets](/v10/configuration-options/keycloakversiontargets).

<pre class="language-tsx" data-title="vite.config.ts"><code class="lang-tsx">import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
<strong>import { keycloakify } from "keycloakify/vite-plugin";
</strong>
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(), 
<strong>    keycloakify({
</strong><strong>        accountThemeImplementation: "none"
</strong><strong>    })
</strong>  ],
})
</code></pre>

{% hint style="info" %}
Leave accountThemeImplementation set to "none" for now.\
To initialize the account theme refer to [this guide](https://github.com/keycloakify/docs.keycloakify.dev/blob/v10/keycloakify-in-my-codebase/in-your-react-project/broken-reference/README.md).
{% endhint %}

Finally you want to add to your package.json a script for building the theme and another one to start storybook.

<pre class="language-json" data-title="package.json"><code class="lang-json">{
  "name": "my-react-app",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b &#x26;&#x26; vite build",
    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview",
<strong>    "build-keycloak-theme": "npm run build &#x26;&#x26; keycloakify build",
</strong><strong>    "storybook": "storybook dev -p 6006"
</strong>  },
  // ...
</code></pre>

That's it, your project is ready to go!

You can run npm run build-keycloak-theme, the JAR distribution of your Keycloak theme will be generated in dist\_keycloak.

You're now able to use all the Keycloakify commands (`npx keycloakify --help`) from the root of your project.

{% hint style="success" %}
If you're currently using [keycloak-js](https://www.npmjs.com/package/keycloak-js) or [react-oidc-context](https://github.com/authts/react-oidc-context) to manage user authentication in your app you might want to checkout [oidc-spa](https://www.oidc-spa.dev/), the alternative from the Keycloakify team.

If you have any issues [reach out on Discord](https://discord.gg/mJdYJSdcm4)! We're here to help!
{% endhint %}

{% content-ref url="/pages/6zsjToyFtGwfJpF6IgrG" %}
[Testing your Theme](/v10/testing-your-theme)
{% endcontent-ref %}

{% content-ref url="/pages/YK0LL0dGU1QbQCeJQo1X" %}
[Customization Strategies](/v10/customization-strategies)
{% endcontent-ref %}


# In your Webpack Project

If you have a Webpack/React/TypeScript project you can integrate Keycloakify directly inside it.

In this guide we're going to work with a vanilla [Create React App](https://create-react-app.dev/) project.

<figure><img src="/files/1dWaBAVWLQXvRVCest2J" alt="" width="375"><figcaption><p>Creating a CRA project. You don't need to do that, just use your existing codebase.</p></figcaption></figure>

<figure><img src="/files/GmsM8Y657ScLx7svyBgV" alt="" width="304"><figcaption><p>Our codebase before involving Keycloakify</p></figcaption></figure>

{% hint style="info" %}
Before anything make sure to commit all your pending changes so you can easily revert changes if need be.
{% endhint %}

Let's start by installing Keycloakify (and optionally Storybook) to our project:

{% tabs %}
{% tab title="yarn" %}

```bash
yarn add keycloakify
yarn add --dev rimraf storybook @storybook/react @storybook/react-vite
```

{% endtab %}

{% tab title="pnpm" %}

```bash
pnpm add keycloakify
pnpm add --dev rimraf storybook @storybook/react @storybook/react-vite
```

{% endtab %}

{% tab title="bun" %}

```bash
bun add keycloakify
bun add --dev rimraf storybook @storybook/react @storybook/react-vite
```

{% endtab %}

{% tab title="npm" %}

```bash
npm install --save keycloakify
npm install --save-dev rimraf storybook @storybook/react @storybook/react-vite
```

{% endtab %}
{% endtabs %}

Next we want to repatriate the relevant files from [the starter template](https://github.com/keycloakify/keycloakify-starter) into our project:

```bash
cd my-app
git clone https://github.com/keycloakify/keycloakify-starter-webpack tmp
mv tmp/src src/keycloak-theme
mv tmp/.storybook .
rm -rf tmp
rm src/keycloak-theme/react-app-env.d.ts
mv src/keycloak-theme/index.tsx src/index.tsx
```

<figure><img src="/files/qYpdbSXOJqPCjyqpGNaG" alt="" width="308"><figcaption><p>Sate of your codebase after bringing in Keycloakify's starter boilerplate code</p></figcaption></figure>

Now you want to modify your entry point so that:

* If the kcContext global is defined, render your Keycloakify theme
* Else, render your App as usual.

<pre class="language-tsx" data-title="src/index.tsx"><code class="lang-tsx">/* eslint-disable react-refresh/only-export-components */
import { createRoot } from "react-dom/client";
import { 
    StrictMode,
<strong>    lazy,
</strong><strong>    Suspense
</strong>} from "react";
<strong>import { KcPage, type KcContext } from "./keycloak-theme/kc.gen";
</strong><strong>const App = lazy(()=> import("./App"));
</strong>
<strong>// The following block can be uncommented to test a specific page with `yarn dev`
</strong><strong>// Don't forget to comment back or your bundle size will increase
</strong><strong>/*
</strong><strong>import { getKcContextMock } from "./keycloak-theme/login/KcPageStory";
</strong>
<strong>if (process.env.NODE_ENV === "development") {
</strong><strong>    window.kcContext = getKcContextMock({
</strong><strong>        pageId: "register.ftl",
</strong><strong>        overrides: {}
</strong><strong>    });
</strong><strong>}
</strong><strong>*/
</strong>
createRoot(document.getElementById("root")!).render(
    &#x3C;StrictMode>
<strong>        {window.kcContext ? (
</strong><strong>            &#x3C;KcPage kcContext={window.kcContext} />
</strong><strong>        ) : (
</strong><strong>            &#x3C;Suspense>
</strong><strong>                &#x3C;App />
</strong><strong>            &#x3C;/Suspense>
</strong><strong>        )}
</strong>    &#x3C;/StrictMode>
);

<strong>declare global {
</strong><strong>    interface Window {
</strong><strong>        kcContext?: KcContext;
</strong><strong>    }
</strong><strong>}
</strong></code></pre>

{% hint style="info" %}
**Question:**

Why do my main application and Keycloak theme share the same entry point?

**Answer:**

To simplify the build process. If you don't want it to negatively impact the performance of your application, it's essential to understand the following points:

* **Different Contexts:** The application (`App`) and Keycloak page (`KcPage`) are mounted in very different contexts. Avoid sharing providers between the two at the `main.tsx` file level. The true entry point of your application is the `App` component, while the entry point for your Keycloak theme is the `KcPage` component. Be careful about what code is shared between them.
* **Responsibility of main.tsx:** The `main.tsx` file should only determine the context (either the application or Keycloak) and mount the appropriate component (`App` or `KcPage`). It should not contain any substantial logic or dependencies.
* **Performance Considerations:** Keep `main.tsx` as lightweight as possible to avoid increasing the initial load time of both your main application and login pages. For example, do not load any state management libraries like `redux-toolkit` at this level.
  {% endhint %}

Finally you want to add some script for Keycloakify in you package.json and also let Keycloakify know about how your Webpack project is configured.

<pre class="language-json" data-title="package.json"><code class="lang-json">{
    "name": "my-app",
    "scripts": {
<strong>        "prestart": "keycloakify update-kc-gen &#x26;&#x26; keycloakify copy-keycloak-resources-to-public",
</strong>        "start": "react-scripts start",
<strong>        "prestorybook": "npm run prestart",
</strong>        "storybook": "storybook dev -p 6006",
<strong>        "prebuild": "keycloakify update-kc-gen",
</strong>        "build": "react-scripts build",
<strong>        "postbuild": "rimraf build/keycloakify-dev-resources",
</strong><strong>        "build-keycloak-theme": "npm run build &#x26;&#x26; keycloakify build",
</strong>        "format": "prettier . --write"
        // ...
    },
<strong>    "keycloakify": {
</strong><strong>        "accountThemeImplementation": "none",
</strong><strong>        "projectBuildDirPath": "build",
</strong><strong>        "staticDirPathInProjectBuildDirPath": "static",
</strong><strong>        "publicDirPath": "public"
</strong><strong>    },
</strong>    // ...
</code></pre>

{% hint style="info" %}
Leave accountThemeImplementation set to "none" for now.\
To initialize the account theme refer to [this guide](/v10/account-theme).
{% endhint %}

Keycloakify has many build options that you can use, however `projectBuildDirPath`, `staticDirPathInProjectBuildDirPath` and `publicDirPath` are parameters specific to the use of Keycloakify in a Webpack context.

Theses **are not preferences!** If you're not using Create React App your Webpack configuration is probably different and you want to update those values to reflect how webpack build your site in your project.

<figure><img src="/files/U9etZ2NtghDc1h72ssQa" alt="" width="209"><figcaption><p>Here you can see that in a CRA project, when we run <code>npm run build</code> the app distribution is generated in a <strong>build/</strong> directory, this is why we use <code>"projectBuildDirPath": "build"</code>. We can also see that all the assets of the app are gathered under a <code>static/</code> directory this is why we use <code>"staticDirPathInProjectBuildDirPath": "static"</code>. And finally we can see that everything we put in the <strong>public/</strong> directory is copied over to the <strong>build/</strong> directory when building so we use <code>"publicDirPath": "public"</code>.</p></figcaption></figure>

That's it, your project is ready to go!

You can run `npm run build-keycloak-theme`, the JAR distribution of your Keycloak theme will be generated in `build_keycloak` ([you can change this](/v10/configuration-options/keycloakifybuilddirpath)).

You're now able to use all the Keycloakify commands (`npx keycloakify --help`) from the root of your project.

{% hint style="success" %}
If you're currently using [keycloak-js](https://www.npmjs.com/package/keycloak-js) or [react-oidc-context](https://github.com/authts/react-oidc-context) to manage user authentication in your app you might want to checkout [oidc-spa](https://www.oidc-spa.dev/), the alternative from the Keycloakify team.

If you have any issues [reach out on Discord](https://discord.gg/mJdYJSdcm4)! We're here to help!
{% endhint %}

{% content-ref url="/pages/6zsjToyFtGwfJpF6IgrG" %}
[Testing your Theme](/v10/testing-your-theme)
{% endcontent-ref %}

{% content-ref url="/pages/YK0LL0dGU1QbQCeJQo1X" %}
[Customization Strategies](/v10/customization-strategies)
{% endcontent-ref %}


# As a Subproject of your Monorepo

{% tabs %}
{% tab title="Turborepo" %}
{% content-ref url="/pages/lm2Wyv1LaG9nXjZgK0b1" %}
[Turborepo](/v10/keycloakify-in-my-codebase/as-a-subproject-of-your-monorepo/turborepo)
{% endcontent-ref %}
{% endtab %}

{% tab title="Nx integrated Monorepo" %}
{% content-ref url="/pages/H4C78BXcCEznHBXQfU2Y" %}
[Nx Integrated Monorepo](/v10/keycloakify-in-my-codebase/as-a-subproject-of-your-monorepo/nx-integrated-monorepo)
{% endcontent-ref %}
{% endtab %}

{% tab title="pnpm/yarn/npm/bun Workspaces" %}
{% content-ref url="/pages/OryTUazkupOZJtJD3xcn" %}
[Package Manager Workspaces](/v10/keycloakify-in-my-codebase/as-a-subproject-of-your-monorepo/package-manager-workspaces)
{% endcontent-ref %}
{% endtab %}
{% endtabs %}


# Turborepo

First you want to create a new subproject in your monorepo, just clone the starter template into apps/keycloak-theme.

```bash
cd my-turborepo
git clone https://github.com/keycloakify/keycloakify-starter apps/keycloak-theme
rm -rf apps/keycloak-theme/.git
rm -rf apps/keycloak-theme/.github
```

Change the name field in the package.json of your keycloakify sub app.

{% code title="apps/keycloak-theme/package.json" %}

```diff
 {
-    "name": "keycloakify-starter",
+    "name": "keycloak-theme",
 }
```

{% endcode %}

Give an actual name to your theme (as you want it to apprear [in the Keycloak Admin Console](https://github.com/keycloakify/keycloakify/assets/6702424/7da4afe2-0f67-4f79-a3d0-bd982636ea23))

<pre class="language-typescript" data-title="apps/keycloak-theme/vite.config.ts"><code class="lang-typescript">import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { keycloakify } from "keycloakify/vite-plugin";

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [react(), keycloakify({
<strong>        themeName: "my-app"
</strong>    })]
});
</code></pre>

Then you want to add a new script for building your theme in your root **package.json**

<pre class="language-json" data-title="package.json"><code class="lang-json">{
  "name": "my-turborepo",
  "scripts": {
    "build": "turbo build",
    "dev": "turbo dev",
    "lint": "turbo lint",
    "format": "prettier --write \"**/*.{ts,tsx,md}\"",
<strong>    "build-keycloak-theme": "turbo run build-keycloak-theme --filter=keycloak-theme",
</strong><strong>    "start-keycloak": "keycloakify start-keycloak -p apps/keycloak-theme"
</strong>  },
  // ...
}
</code></pre>

Add a turborepo task

<pre class="language-json" data-title="turbo.json"><code class="lang-json">{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    // ... Other tasks
<strong>    "build-keycloak-theme": {
</strong><strong>        "outputs": [
</strong><strong>            "dist/**", 
</strong><strong>            "dist_keycloak/**"
</strong><strong>        ]
</strong><strong>    }
</strong>  }
}
</code></pre>

You can now build your keycloak theme at the root of your monorepo by running

```bash
npm run build-keycloak-theme
```

{% embed url="<https://youtu.be/4h9lOf-4ZIE>" %}
Building the theme, only compiling for Keycloak 25 with a custom jar file name. Demonstrating the effectiveness of turborepo cache
{% endembed %}

Optionally, if you want to change the location of the directory where the jar for your theme are created you can do:

<pre class="language-typescript" data-title="apps/keycloak-theme/vite.config.ts"><code class="lang-typescript">export default defineConfig({
    plugins: [react(), keycloakify({
        themeName: "my-app",
<strong>        keycloakifyBuildDirPath: "../../dist/apps/keycloak-theme"
</strong>    })]
});
</code></pre>

{% code title="turbo.json" %}

```diff
 {
   "$schema": "https://turbo.build/schema.json",
   "tasks": {
     // ... Other tasks
     "build-keycloak-theme": {
         "outputs": [
             "dist/**",
-            "dist_keycloak/**"
+            "../../dist/apps/keycloak-theme/**"
         ]
     }
   }
 }
```

{% endcode %}

If you applies those changes, when you'll run `npm run build-keycloak-theme` your JARs are going to be generated in `dist/keycloak-theme/`

When you want to use the keycloakify CLI commands you can either cd into your keycloakify sub app directory or use the [--project option of the Keycloakify CLI](/v10/configuration-options/project).\
Like for example if you want to run add-story you can do either:

* `cd apps/keycloak-theme && npx keycloakify add-story`
* `npx keycloakify add-story -p apps/keycloak-theme` from the root of your monorepo

To go beyond the base configuration you might want to explore what [build options](/v10/configuration-options) are available. Starting with with `keycloakVersionTargets` to make sure that you only generates the JARs file you need.

{% content-ref url="/pages/UY54L7bRtl3LTcI1bHkN" %}
[Targetting Specific Keycloak Versions](/v10/targeting-specific-keycloak-versions)
{% endcontent-ref %}


# Nx Integrated Monorepo

Let's see how to integrate a Keycloakify theme into a Nx project with integrated monorepo.

In this example we'll start with the Nx Vite starter

```bash
npx create-nx-workspace@latest --preset=react-monorepo --bundler=vite
```

<figure><img src="/files/ERT2cVGTicJYrIhaEOxM" alt=""><figcaption></figcaption></figure>

Next up we want to repatriate the Keycloakify Starter template sources.\
We only copy over the src and .storybook directory.

```bash
cd nx-monorepo
rm -rf apps/keycloak-theme/src
git clone https://github.com/keycloakify/keycloakify-starter tmp
mv tmp/src apps/keycloak-theme
mv tmp/.storybook apps/keycloak-theme
rm -rf tmp
```

<figure><img src="/files/yCnk4xb8uxWnyHWIhOWK" alt="" width="365"><figcaption><p>After moving src and .storybook to apps/keycloak-theme</p></figcaption></figure>

<pre class="language-json" data-title="package.json"><code class="lang-json">{
  "name": "@nx-monorepo/source",
  "version": "0.0.0",
  "scripts": {
<strong>    "build-keycloak-theme": "nx build keycloak-theme &#x26;&#x26; keycloakify build -p apps/keycloak-theme",
</strong><strong>    "keycloak-theme-storybook": "npx storybook dev -p 6006 -c apps/keycloak-theme/.storybook",
</strong><strong>    "start-keycloak": "keycloakify start-keycloak -p apps/keycloak-theme"
</strong>  },
  "dependencies": {
    "react": "18.3.1",
    "react-dom": "18.3.1",
    "tslib": "^2.3.0",
<strong>    "keycloakify": "^10.0.0"
</strong>  },
  "devDependencies": {
<strong>      "storybook": "^8.1.10",
</strong><strong>      "@storybook/react": "^8.1.10",
</strong><strong>      "@storybook/react-vite": "^8.1.10"
</strong>  // ...
</code></pre>

```bash
npm install # or `pnpm install` or `yarn`...
```

<pre class="language-typescript" data-title="apps/keycloak-theme/vite.config.ts"><code class="lang-typescript">/// &#x3C;reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
<strong>import { keycloakify } from "keycloakify/vite-plugin";
</strong>
export default defineConfig({
  root: __dirname,
  cacheDir: '../../node_modules/.vite/apps/keycloak-theme',

  server: {
    port: 4200,
    host: 'localhost',
  },

  preview: {
    port: 4300,
    host: 'localhost',
  },

  plugins: [react(), nxViteTsPaths(), 
<strong>      keycloakify({
</strong><strong>          themeName: "my-project",
</strong><strong>          themeVersion: "1.0.0",
</strong><strong>          keycloakifyBuildDirPath: '../../dist/apps/keycloak-theme'
</strong><strong>       })
</strong>   ],

  // Uncomment this if you are using workers.
  // worker: {
  //  plugins: [ nxViteTsPaths() ],
  // },

  build: {
<strong>    outDir: 'dist',
</strong>    emptyOutDir: true,
    reportCompressedSize: true,
    commonjsOptions: {
      transformMixedEsModules: true,
    },
  },
});
</code></pre>

Now if you run `npm run build-keycloak-theme` it will generate the JAR in dist/apps/keycloak-theme.

<figure><img src="/files/2cGoAvUGWGYhiJSiyvWi" alt=""><figcaption></figcaption></figure>

When you want to use the keycloakify CLI commands you can either cd into your keycloakify sub app directory or use the [--project option of the Keycloakify CLI](/v10/configuration-options/project).\
Like for example if you want to run [add-story](/v10/testing-your-theme/in-storybook) you can do either:

* `cd apps/keycloak-theme && npx keycloakify add-story`

OR

* `npx keycloakify add-story -p apps/keycloak-theme` from the root of your monorepo

To go beyond the base configuration you might want to explore what [build options](/v10/configuration-options) are available. Starting with with `keycloakVersionTargets` to make sure that you only generates the JARs file you need.

{% content-ref url="/pages/UY54L7bRtl3LTcI1bHkN" %}
[Targetting Specific Keycloak Versions](/v10/targeting-specific-keycloak-versions)
{% endcontent-ref %}


# Package Manager Workspaces

Let's assume we have a monorepo project where sub applications are stored in the **apps/** directory.

{% tabs %}
{% tab title="yarn/npm/bun" %}

<pre class="language-json" data-title="package.json"><code class="lang-json">{
  "name": "my-monorepo",
<strong>  "workspaces": [
</strong><strong>    "apps/*"
</strong><strong>    "packages/*"
</strong><strong>  ],
</strong><strong>  "private": true,
</strong></code></pre>

{% endtab %}

{% tab title="pnpm" %}
{% code title="pnpm-workspace.yaml" %}

```yaml
packages:
  - "apps/*"
  - "packages/*"
```

{% endcode %}
{% endtab %}
{% endtabs %}

Then, you want to create a new app called, for example 'keycloak-theme' and initialize it with the code of the starter template:

```bash
cd my-monorepo
git clone https://github.com/keycloakify/keycloakify-starter apps/keycloak-theme
rm -rf apps/keycloak-theme/.git
rm -rf apps/keycloak-theme/.github
rm apps/keycloak-theme/.yarn.lock
```

<figure><img src="/files/izEh07LT1o2nnz2Ip7Q8" alt="" width="375"><figcaption></figcaption></figure>

Now you want to update the name field of your apps/keycloak-theme/package.json to match the name of your sub app.

{% code title="apps/keycloak-theme/package.json" %}

```diff
 {
-    "name": "keycloakify-starter",
+    "name": "keycloak-theme",
```

{% endcode %}

You also want to provide an actual name to your theme as you want it to [appear in the Keycloak Admin UI](https://github.com/keycloakify/keycloakify/assets/6702424/7da4afe2-0f67-4f79-a3d0-bd982636ea23).

<pre class="language-typescript" data-title="apps/keycloak-theme/vite.config.ts"><code class="lang-typescript">import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { keycloakify } from "keycloakify/vite-plugin";

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [react(), keycloakify({
<strong>        themeName: "my-app"
</strong>    })]
});
</code></pre>

Now you can add a script in your root package json to build the theme and start the keycloak dev server:

{% tabs %}
{% tab title="pnpm" %}

<pre class="language-json" data-title="package.json"><code class="lang-json">{
   "name": "my-monorepo",
   "scripts": {
<strong>       "build-keycloak-theme": "pnpm --filter keycloak-theme run build-keycloak-theme",
</strong><strong>       "start-keycloak": "keycloakify start-keycloak -p apps/keycloak-theme"
</strong>   },
   // ...
}
</code></pre>

{% endtab %}

{% tab title="yarn" %}

<pre class="language-json" data-title="package.json"><code class="lang-json">{
   "name": "my-monorepo",
   "scripts": {
<strong>       "build-keycloak-theme": "yarn workspace keycloak-theme run build-keycloak-theme",
</strong><strong>       "start-keycloak": "keycloakify start-keycloak -p apps/keycloak-theme"
</strong>   },
   // ...
}
</code></pre>

{% endtab %}

{% tab title="npm" %}

<pre class="language-json" data-title="package.json"><code class="lang-json">{
   "name": "my-monorepo",
   "scripts": {
<strong>       "build-keycloak-theme": "npm run build-keycloak-theme --workspace=keycloak-theme",
</strong><strong>       "start-keycloak": "keycloakify start-keycloak -p apps/keycloak-theme"
</strong>   },
   // ...
}
</code></pre>

{% endtab %}

{% tab title="bun" %}

<pre class="language-json" data-title="package.json"><code class="lang-json">{
   "name": "my-monorepo",
   "scripts": {
<strong>       "build-keycloak-theme": "bun run --cwd apps/keycloak-theme build-keycloak-theme",
</strong><strong>       "start-keycloak": "keycloakify start-keycloak -p apps/keycloak-theme"
</strong>   },
   // ...
}
</code></pre>

{% endtab %}
{% endtabs %}

Now you can run:

{% tabs %}
{% tab title="pnpm" %}

```bash
pnpm install
pnpm run build-keycloak-theme
```

{% endtab %}

{% tab title="yarn" %}

```bash
yarn
yarn build-keycloak-theme
```

{% endtab %}

{% tab title="npm" %}

```bash
npm install
npm run build-keycloak-theme
```

{% endtab %}

{% tab title="bun" %}

```bash
bun install
bun run build-keycloak-theme
```

{% endtab %}
{% endtabs %}

<figure><img src="/files/eritT6hOg6VeOGCtAWJt" alt=""><figcaption></figcaption></figure>

Two common thing you might want to do is [change the location of the directory where the JARs files are generated](/v10/configuration-options/keycloakifybuilddirpath) and [only build the JAR for the Keycloak version you are using](/v10/targeting-specific-keycloak-versions).

<pre class="language-typescript" data-title="apps/keycloak-theme/vite.config.ts"><code class="lang-typescript">import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { keycloakify } from "keycloakify/vite-plugin";

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [react(), keycloakify({
        themeName: "my-app",
<strong>        keycloakifyBuildDirPath: "../../dist/apps/keycloak-theme",
</strong><strong>        keycloakVersionTargets: {
</strong><strong>            hasAccountTheme: true,
</strong><strong>            "21-and-below": false,
</strong><strong>            "23": false,
</strong><strong>            "24": false,
</strong><strong>            "25-and-above": "keycloak-theme.jar"
</strong><strong>        }
</strong>    })]
});
</code></pre>

In this configuration when you run `pnpm run build-keycloak-theme` from the root of your monorepo a single `keycloak-theme.jar` will be generated in **dist/apps/keycloak-theme**:

<figure><img src="/files/W9Fbr6GzZPBwWKuwq4iW" alt=""><figcaption></figcaption></figure>

When you want to use the keycloakify CLI commands you can either cd into your keycloakify sub app directory or use the [--project option of the Keycloakify CLI](/v10/configuration-options/project).\
Like for example if you want to run add-story you can do either:

* `cd apps/keycloak-theme && npx keycloakify add-story`
* `npx keycloakify add-story -p apps/keycloak-theme` from the root of your monorepo.


# Customization Strategies

There is two main way to create your Keycloak theme.

## CSS Level Customization

This is the recomended approach as it is easy to implement and easy to maintain. Even non web developer should be able to pull it off.

In this approach you'll use your favorite styling solution it can be:

* Plain CSS
* [Tailwind](https://tailwindcss.com/docs/reusing-styles#extracting-classes-with-apply)
* A language superset of CSS lile [Sass](https://sass-lang.com/) or [Less](https://lesscss.org/)
* An utility class based CSS framework like [Bootstrap](https://getbootstrap.com/) or [Fundation](https://get.foundation/)

{% content-ref url="/pages/8QvCEcHqtalyqFo9zEKF" %}
[CSS Level Customization](/v10/customization-strategies/css-level-customization)
{% endcontent-ref %}

## React Component Level Customization

If you want to use your React component library like [Shadecn/UI](https://ui.shadcn.com/), [MUI](https://mui.com/) or [Ant](https://ant.design/) you have to go down at the component level.

{% content-ref url="/pages/DKyTa9bcRDgRhXFRYeqf" %}
[Component Level Customization](/v10/customization-strategies/component-level-customization)
{% endcontent-ref %}


# CSS Level Customization

Customize the theme without touching the React components

Keycloakify enables you to customize the pages without changing the React component by using CSS, SASS LESS Tailwind or a [CSS in JS solution](#user-content-fn-1)[^1].

If you can make it work, CSS Level Customization is preferable over component level customization since it's much easier to maintain.

If you want to use a component library like MUI, ShadeCN/UI or Antlr, this is not the approach that you should favor you should instead checkout [Component Level customization](/v10/customization-strategies/component-level-customization).

{% content-ref url="/pages/7UsQLoug27by7bEllN1N" %}
[Basic example](/v10/customization-strategies/css-level-customization/basic-example)
{% endcontent-ref %}

[^1]: Example: Emotion or tss-react.


# Basic example

As you can see in the screenshot below most DOM Element get assigned a class starting by kcSomething. Example kcFormHeaderClass.

No styles rules get assigned to those classes they are only here for you to use as target for your custom CSS.

<figure><img src="/files/NT8TLP4TbpC59cqyNxHP" alt=""><figcaption><p>Inspecting the login.ftl page in chrome dev tools</p></figcaption></figure>

So if you're not very interested in all the bells and whistles Keycloakify offers, you can just create a CSS[^1] file and just start customizing the page:

<figure><img src="/files/A7XjOTIkE9kgzB8xw2OB" alt=""><figcaption><p>Applying a red border to all the DOM element with the kcFormHeaderClass class</p></figcaption></figure>

<figure><img src="/files/6IzIGbxF1rpVyIpFc4Hq" alt="" width="375"><figcaption><p>The red border gets applied</p></figcaption></figure>

Up next:

{% content-ref url="/pages/u9uURKCWrEvhExws0OYR" %}
[Removing the default styles](/v10/customization-strategies/css-level-customization/removing-the-default-styles)
{% endcontent-ref %}

[^1]: ...or LESS or SASS


# Removing the default styles

## Case by case

You may notice that beside the the kcSomething classes other classes are applied to the components.

<figure><img src="/files/ncs7hF1BMA1mDyODm3eK" alt=""><figcaption><p>The &#x3C;header> element has an extra class beside kcFormHeaderClass: login-pf-header</p></figcaption></figure>

This other classes, non prefixed with kc actually have styles rules that target them. Let's as an example remove the login-pf-header class:

<pre class="language-tsx" data-title="src/login/KcPage.tsx"><code class="lang-tsx">import "./main.css";
// ...

export default function KcPage(props: { kcContext: KcContext }) {
  // ...
}

const classes = {
<strong>    kcFormHeaderClass: ""
</strong>} satisfies { [key in ClassKey]?: string };
</code></pre>

<figure><img src="/files/1vxLph1jQO45UEZbkRz8" alt=""><figcaption><p>We can see that now, the &#x3C;header> element only have the kcFormHeaderClass, the login-pf-header class has been removed. As a result the text is now aligned to the left (default layout)</p></figcaption></figure>

On some components, multiples utility classes are applied, you may want to keep some of them and remove others. Example:

<figure><img src="/files/cJiDki01tzgg48ksHIEt" alt=""><figcaption><p>Here we can see that a bunch of col-* classes gets applied to the element with kcInputWrapperClass</p></figcaption></figure>

Let's say, for example, that we would like to remove keep only the col-md and lg classes. To do that that we would write:

{% code title="src/login/KcPage.tsx" %}

```tsx
import "./main.css";
// ...

export default function KcPage(props: { kcContext: KcContext }) {
  // ...
}

const classes = {
  kcInputWrapperClass: "col-md-12 col-lg-12",
} satisfies { [key in ClassKey]?: string };
```

{% endcode %}

Result:

<figure><img src="/files/ENnF7ifoETd6PcJO3fP1" alt="" width="375"><figcaption></figcaption></figure>

## Removing all the default styles at once

Maybe you'd prefer to remove all default styles at once you can do that by setting doUseDefaultCss to false.

<pre class="language-tsx" data-title="src/login/KcPages.tsx"><code class="lang-tsx">export default function KcPage(props: { kcContext: KcContext }) {

    return (
        &#x3C;Suspense>
            {(() => {
                switch (kcContext.pageId) {
                    default:
                        return (
                            &#x3C;DefaultPage
                                kcContext={kcContext}
                                i18n={i18n}
                                classes={classes}
                                Template={Template}
<strong>                                doUseDefaultCss={false}
</strong>                                UserProfileFormFields={UserProfileFormFields}
                                doMakeUserConfirmPassword={doMakeUserConfirmPassword}
                            />
                        );
                }
            })()}
        &#x3C;/Suspense>
    );
}
</code></pre>

However be aware that re-styling everything involves quite a bit of work:

<figure><img src="/files/NLw8S2vAf0B7xstpmszr" alt="" width="375"><figcaption><p>The login page with doUseDefaultCss set to false</p></figcaption></figure>

Up next:

{% content-ref url="/pages/ngO4Ccs4QpvgwCBAqzxW" %}
[Applying your own classes](/v10/customization-strategies/css-level-customization/applying-your-own-classes)
{% endcontent-ref %}


# Applying your own classes

{% hint style="info" %}
If you're porting a pre-existing, non Keycloakify theme to Keycloakify this is the approach you would implement.
{% endhint %}

If you have an utility stylesheet that defines standardized classes you can apply them using the class object like so:

<figure><img src="/files/7fr2e7OANjfFnOPQyTRp" alt=""><figcaption></figcaption></figure>

Result:

<figure><img src="/files/7UcBX1rRJEZPKPscPkar" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
If you want to implement this approach with **Tailwind** read [this](https://github.com/keycloakify/keycloakify-starter/blob/dd516e53e4dfa7c1ce02bab557420b999e87eca2/src/login/KcPage.tsx#L53-L65). But there's a section dedicated to Tailwind [here](/v10/customization-strategies/css-level-customization/using-tailwind).
{% endhint %}

Up next:

{% content-ref url="/pages/mHnTNMfeZsitVQDSbOgU" %}
[Page specific styles](/v10/customization-strategies/css-level-customization/page-specific-styles)
{% endcontent-ref %}


# Page specific styles

So far the customization we have made applies to all the pages however you might want to have stylesheet specific to certain pages.

You can do that by loading different stylesheet and applying different classes depending on the `kcContext.pageId`.

Implementation example, instead of importing our stylesheet at the top of the KcPage.tsx component file we import them dynamically:

<pre class="language-tsx" data-title="src/login/KcPage.tsx"><code class="lang-tsx">import {
    Suspense, 
    lazy,
<strong>    useMemo
</strong>} from "react";

export default function KcPage(props: { kcContext: KcContext }) {
    const { kcContext } = props;

    const { i18n } = useI18n({ kcContext });

<strong>    const classes = useCustomStyles(kcContext);
</strong>
    return (
        &#x3C;Suspense>
            {(() => {
                switch (kcContext.pageId) {
                    default:
                        return (
                            &#x3C;DefaultPage
                                kcContext={kcContext}
                                i18n={i18n}
                                classes={classes}
                                Template={Template}
                                doUseDefaultCss={true}
                                UserProfileFormFields={UserProfileFormFields}
                                doMakeUserConfirmPassword={doMakeUserConfirmPassword}
                            />
                        );
                }
            })()}
        &#x3C;/Suspense>
    );
}

<strong>function useCustomStyles(kcContext: KcContext) {
</strong><strong>    return useMemo(() => {
</strong><strong>        
</strong><strong>        // You stylesheet that applies to all pages.
</strong><strong>        import("./main.css");
</strong><strong>        let classes: { [key in ClassKey]?: string } = {
</strong><strong>            // Your classes that applies to all pages
</strong><strong>        };
</strong>
<strong>        switch (kcContext.pageId) {
</strong><strong>            case "login.ftl":
</strong><strong>                // You login page specific stylesheet.
</strong><strong>                import("./pages/login.css");
</strong><strong>                classes = {
</strong><strong>                    ...classes,
</strong><strong>                    // Your classes that applies only to the login page
</strong><strong>                };
</strong><strong>                break;
</strong><strong>            case "register.ftl":
</strong><strong>                // Your account page specific stylesheet
</strong><strong>                import("./pages/register.css");
</strong><strong>                classes = {
</strong><strong>                    ...classes,
</strong><strong>                    // Your classes that applies only to the register page
</strong><strong>                };
</strong><strong>                break;
</strong><strong>            // ...
</strong><strong>        }
</strong>
<strong>        return classes;
</strong>
<strong>    }, []);
</strong><strong>}
</strong></code></pre>

## What's next?

At this point of the documentation, if you're looking for using tailwind you can go to this page:

{% content-ref url="/pages/odEwRcRmpO50UGfysv29" %}
[Using Tailwind](/v10/customization-strategies/css-level-customization/using-tailwind)
{% endcontent-ref %}

Else you can skip directly to the next section:

{% content-ref url="/pages/jwbsGFEnYCSwTyS6xfMB" %}
[Using custom assets](/v10/customization-strategies/css-level-customization/using-custom-assets)
{% endcontent-ref %}


# Using Tailwind

{% hint style="info" %}
Even if you're only interested by Tailwind you should still read the other section of the [CSS Level Customization](/v10/customization-strategies/css-level-customization) first as it gives important context.
{% endhint %}

To use Tailwind in your Keycloakify project start by following the setup guide for Vite.

{% embed url="<https://tailwindcss.com/docs/guides/vite#react>" %}

Beyond that, here is a demo setup of light modification of the starter template to incorporate tailwind:

{% embed url="<https://github.com/keycloakify/keycloakify-starter/tree/tailwind>" %}

{% embed url="<https://private-user-images.githubusercontent.com/6702424/345903122-3f06a287-03e3-4441-a4e9-e4ea6b76f388.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjA5ODk4NDIsIm5iZiI6MTcyMDk4OTU0MiwicGF0aCI6Ii82NzAyNDI0LzM0NTkwMzEyMi0zZjA2YTI4Ny0wM2UzLTQ0NDEtYTRlOS1lNGVhNmI3NmYzODgucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDcxNCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA3MTRUMjAzOTAyWiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZGI5ZjU5MzkxY2MzYmQ3MTNkZWM5NWUwOWM3MDg1MGJlYjQ4ODdhZjVlMTRlOGZlMDllMmM3MzQwYTJmNTgyYyZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.4EdrQ-RKBP3SDeZ865IYyO2wb2ctkHEkdV_JAqquFQI>" %}
Preview on the 'tailwind' branch for the starter template
{% endembed %}

What has been done:

* [Applying some custom tailwind utilities classes using the @apply directive](https://github.com/keycloakify/keycloakify-starter/blob/dd516e53e4dfa7c1ce02bab557420b999e87eca2/src/login/index.css#L7-L14).
* Using the [Geist](https://vercel.com/font) font, [here](https://github.com/keycloakify/keycloakify-starter/blob/dd516e53e4dfa7c1ce02bab557420b999e87eca2/tailwind.config.js#L9-L11), [here](https://github.com/keycloakify/keycloakify-starter/blob/dd516e53e4dfa7c1ce02bab557420b999e87eca2/src/login/index.css#L1) and [here](https://github.com/keycloakify/keycloakify-starter/blob/dd516e53e4dfa7c1ce02bab557420b999e87eca2/src/login/index.css#L9).
* Ejecting the [login.ftl](https://storybook.keycloakify.dev/?path=/story/login-login-ftl--default) page (`npx keycloakify eject-page` and *login -> login.ftl*) and [applying a tailwind class](https://github.com/keycloakify/keycloakify-starter/blob/dd516e53e4dfa7c1ce02bab557420b999e87eca2/src/login/pages/Login.tsx#L172).

Here is the summary of the changes:

{% embed url="<https://github.com/keycloakify/keycloakify-starter/commit/e6c71f13acbc65ccb8f57172c45e8c04a2151007>" %}


# Using custom assets

Let's see how to import custom asset:

{% content-ref url="/pages/HMm2D1YX3DxvdfU6bg7e" %}
[.css, .sass or .less](/v10/customization-strategies/css-level-customization/using-custom-assets/plain-css)
{% endcontent-ref %}

{% content-ref url="/pages/N4Ck22jBYqwXELEfV6G2" %}
[CSS-in-JS](/v10/customization-strategies/css-level-customization/using-custom-assets/css-in-js)
{% endcontent-ref %}


# .css, .sass or .less

Let's see, as an example, the different ways you have to change the backgrounds image of the login page.

First let's [download a background image](https://coolbackgrounds.io/) an put it in our public directory:

<figure><img src="/files/u1i0bRZ7q9BOzEiRwwYL" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
If you wish to do so, you can hot swipe assets that you have placed into your public directory in your Keycloak instance files at:

**/opt/keycloak/themes/**[**\<name of your theme>**](/v10/configuration-options/themename)**/\<login|account>/resources/dist**

<img src="/files/czJJrujItRzbTYN4KMZn" alt="" data-size="original">
{% endhint %}

Let's apply this image to the body using plain CSS

{% code title="src/login/main.css" %}

```css
body.kcBodyClass {
  background: url(/background.png) no-repeat center center fixed;
}
```

{% endcode %}

We import the StyleSheet:

<pre class="language-tsx" data-title="src/login/KcPage.tsx"><code class="lang-tsx"><strong>import "./main.css";
</strong>import { Suspense, lazy } from "react";
// ...
</code></pre>

Result (see [testing your theme](/v10/testing-your-theme)):

<figure><img src="/files/SaemSmtkCGGgq8DcjNG0" alt=""><figcaption><p>Custom background successfully applied</p></figcaption></figure>

If you prefer, you can also move the background.png image from `public/` to, for examples, `src/login/assets/background.png` and reference the image with a path relative to the CSS file, in this case it would be:

{% code title="src/login/main.css" %}

```css
body.kcBodyClass {
  background: url(./assets/background.png) no-repeat center center fixed;
}
```

{% endcode %}

In the following video I show how to load different background for different page and how to create [theme variant](/v10/theme-variants).

{% embed url="<https://youtu.be/Nkoz1iD-HOA?si=hBXt8rw72-Pvhhnr>" %}


# CSS-in-JS

{% tabs %}
{% tab title="Vite" %}
{% hint style="info" %}
TLDR: There is nothing specific to Keycloakify about importing assets. You can do it however you would in any other project.

Just if you're referencing assets that are in the public directory, use `import.meta.env.BASE_URL`
{% endhint %}
{% endtab %}

{% tab title="Webpack" %}
{% hint style="info" %}
TLDR: You can import asset like you would in any other project, one exception being: If you reference assets that are located in your public directory from within your TSX files you must use Keycloakify's polifill of the `PUBLIC_URL` environnement variable, you can't use `process.env.PUBLIC_URL` directly:

```tsx
import { PUBLIC_URL } from "keycloakify/PUBLIC_URL";
<img src={`${PUBLIC_URL}/my-image.png`} />
```

{% endhint %}
{% endtab %}
{% endtabs %}

CSS-in-JS is preferable over plain CSS as it enables for more flexibility and is easier to maintain.

Let's see, as an example, the different ways you have to change the background image of the login page.

First let's [download a background image](https://coolbackgrounds.io/) an put it in our public directory:

<figure><img src="/files/u1i0bRZ7q9BOzEiRwwYL" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
If you wish to do so, you can hot swipe assets that you have placed into your public directory in your Keycloak instance files at:

**/opt/keycloak/themes/**[**\<name of your theme>**](/v10/configuration-options/themename)**/\<login|account>/resources/dist**

<img src="/files/czJJrujItRzbTYN4KMZn" alt="" data-size="original">
{% endhint %}

Let's see how we can apply the image using a CSS-in-JS. In this example we'll use [@emotion/css](https://emotion.sh/docs/introduction).

```bash
yarn add @emotion/css
```

{% tabs %}
{% tab title="Vite" %}

<pre class="language-tsx" data-title="src/login/KcPage.tsx"><code class="lang-tsx"><strong>import { css } from "@emotion/css";
</strong>import { Suspense, lazy } from "react";
// ...
export default function KcPage(props: { kcContext: KcContext }) {
    const { kcContext } = props;    
    // ...
    return (
        // ...
        &#x3C;DefaultPage
            kcContext={kcContext}
            classes={classes}
            // ...
        />
    );
}

const classes = {
<strong>    kcBodyClass: css({
</strong><strong>        "&#x26;&#x26;": { // Increase specificity so our rule takes precedence over the default background.
</strong><strong>            background: `url(${import.meta.env.BASE_URL}background.png) no-repeat center center fixed`,
</strong><strong>        }
</strong><strong>    })
</strong>} satisfies { [key in ClassKey]?: string };
</code></pre>

{% endtab %}

{% tab title="Webpack" %}

<pre class="language-tsx" data-title="src/login/KcPages.tsx"><code class="lang-tsx"><strong>import { css } from "@emotion/css";
</strong><strong>import { PUBLIC_URL } from "keycloakify/PUBLIC_URL"; // You can't use process.env.PUBLIC_URL directly.
</strong>import { Suspense, lazy } from "react";
// ...
export default function KcPage(props: { kcContext: KcContext }) {
    const { kcContext } = props;    
    // ...
    return (
        // ...
        &#x3C;DefaultPage
            kcContext={kcContext}
            classes={classes}
            // ...
        />
    );
}

const classes = {
    kcBodyClass: css({
        "&#x26;&#x26;": { // Increase specificity so our rule takes precedence over the default background.
            background: `url(${PUBLIC_URL}/background.png) no-repeat center center fixed`,
        }
    })
} satisfies { [key in ClassKey]?: string };
</code></pre>

{% endtab %}
{% endtabs %}

Result (see [testing your theme](/v10/testing-your-theme)):

<figure><img src="/files/SaemSmtkCGGgq8DcjNG0" alt=""><figcaption><p>Custom background successfully applied</p></figcaption></figure>

Now let's go a little further, it's even better to let the bundler generate url for your imports instead of manually referencing files from your public directory.\
So, let's move the background image in **src/login/assets/**:

<figure><img src="/files/r8bMAuqRxQ546XAdnI7g" alt="" width="375"><figcaption></figcaption></figure>

And in our code import it this way:

<pre class="language-tsx" data-title="src/login/KcPage.tsx"><code class="lang-tsx">import { css } from "@emotion/css";
<strong>import backgroundPngUrl from "./assets/background.png";
</strong>import { Suspense, lazy } from "react";
// ...
export default function KcPage(props: { kcContext: KcContext }) {
    const { kcContext } = props;    
    // ...
    return (
        // ...
        &#x3C;DefaultPage
            kcContext={kcContext}
            classes={classes}
            // ...
        />
    );
}

const classes = {
    kcBodyClass: css({
        "&#x26;&#x26;": {
<strong>            background: `url(${backgroundPngUrl}) no-repeat center center fixed`,
</strong>        }
    })
} satisfies { [key in ClassKey]?: string };
</code></pre>

Now let's see how we can go further and apply different background on different pages of our theme:

{% embed url="<https://youtu.be/vRPlGUD-KvE>" %}


# Component Level Customization

The Keycloakify starter repository may initially seem sparse in terms of React components, which might be confusing. However, there's no need to worry—this design choice will soon make sense.

By default, Keycloakify internalizes all the React components that make up the default theme, exposing only the `<DefaultPage />` component.

If you want to customize any component from the default theme, you can easily do so by running the following command:

```bash
npx keycloakify eject-page
```

This command allows you to select specific components from Keycloakify's source code, which will then be copied into your own codebase for further customization.

{% embed url="<https://youtu.be/PhNE-3EwwP8>" %}
Video tutorial on how to use MUI to customize the login page
{% endembed %}

{% hint style="info" %}
Disabling the default styles:\
One thing that is touched on [only late in the video](https://youtu.be/PhNE-3EwwP8?si=s3e9DjaIlhG2uxQC\&t=1338) is how to disable all the default styles.\
See documentation [here](/v10/customization-strategies/css-level-customization/removing-the-default-styles).
{% endhint %}

Following is a step by step guide on how to import and use custom assets in your react components:

{% content-ref url="/pages/ikVVRbkbwfcHY7jhXevb" %}
[Using custom assets](/v10/customization-strategies/component-level-customization/in-react-components)
{% endcontent-ref %}


# Using custom assets

{% tabs %}
{% tab title="Vite" %}
{% hint style="info" %}
TLDR: There is nothing specific to Keycloakify about importing assets. You can do it however you would in any other project.

Just if you're referencing assets that are in the public directory, use `import.meta.env.BASE_URL`
{% endhint %}
{% endtab %}

{% tab title="Webpack" %}
{% hint style="info" %}
TLDR: You can import asset like you would in any other project, one exception being: If you reference assets that are located in your public directory from within your TSX files you must use Keycloakify's polifill of the `PUBLIC_URL` environnement variable, you can't use `process.env.PUBLIC_URL` directly:

```tsx
import { PUBLIC_URL } from "keycloakify/PUBLIC_URL";
<img src={`${PUBLIC_URL}/my-image.png`} />
```

{% endhint %}
{% endtab %}
{% endtabs %}

Let's say you want to put te logo of your company on every pages of the theme.

First you'd eject the Template:

```bash
npx keycloakify eject-page # Select login -> Template.tsx
```

<figure><img src="/files/BEqyXEGzptHF8mk5VeGd" alt=""><figcaption></figcaption></figure>

This will create a src/login/Template.tsx file in your project.

## Import from the public directory

Let's use this placeholder for the demo: [logo.png](https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/logo.png).

We put the file in public/img/logo.png

<div align="center" data-full-width="false"><figure><img src="/files/CO6wHaQ8wsKM7RGlXIzc" alt="" width="563"><figcaption></figcaption></figure></div>

Now let's edit the template to import the file:

<pre class="language-tsx" data-title="src/login/Template.tsx"><code class="lang-tsx">export default function Template(props: TemplateProps&#x3C;KcContext, I18n>) {

    return (
        &#x3C;div className={kcClsx("kcLoginClass")}>
            &#x3C;div id="kc-header" className={kcClsx("kcHeaderClass")}>
                &#x3C;div id="kc-header-wrapper" className={kcClsx("kcHeaderWrapperClass")}>
<strong>                    {/*{msg("loginTitleHtml", realm.displayNameHtml)}*/}
</strong><strong>                    &#x3C;img src={`${import.meta.env.BASE_URL}img/logo.png`} width={500}/>
</strong>                &#x3C;/div>
            &#x3C;/div>
            {/* ... */}
</code></pre>

You can see the result by running `npx keycloakify start-keycloak`

<figure><img src="/files/iEyH2pRD5uMXOPwlvEPv" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
If you ever need to SSH into the Keycloak server and hot swipe the image you can find it at

**/opt/keycloak/themes/**[**\<name of your theme>**](/v10/configuration-options/themename)**/login/resources/dist/img/logo.png**

<img src="/files/LlBBjseEsncyMndB4Kcs" alt="" data-size="original">
{% endhint %}

## Letting the bundle handle your import

Importing your asset from the public directory has the drawback that you won't get a compilation error if you made a mistake, like for example if you rename a file and forget to update the imports.\
A nice solution for this is to let Vite or Webpack handle the import.

Let's move our logo.png to **/src/login/assets/logo.png**

<figure><img src="/files/DELqzWqixX92Eqk8jYHa" alt="" width="336"><figcaption></figcaption></figure>

Now let's update the imports:

<pre class="language-tsx" data-title="src/login/Template.tsx"><code class="lang-tsx">import logoPngUrl from "./assets/logo.png";

export default function Template(props: TemplateProps&#x3C;KcContext, I18n>) {

    return (
        &#x3C;div className={kcClsx("kcLoginClass")}>
            &#x3C;div id="kc-header" className={kcClsx("kcHeaderClass")}>
                &#x3C;div id="kc-header-wrapper" className={kcClsx("kcHeaderWrapperClass")}>
<strong>                    {/*{msg("loginTitleHtml", realm.displayNameHtml)}*/}
</strong><strong>                    &#x3C;img src={logoPngUrl} width={500}/>
</strong>                &#x3C;/div>
            &#x3C;/div>
            {/* ... */}
</code></pre>

This will yield the same result except that now if you delete, move or rename the logo.png file you'll get a compilation error letting you know that you must also update your **Template.tsx** file.


# Custom Fonts

{% hint style="info" %}
TLDR: This is just a general purpose tutorial on how to import fonts in a web project.\
If you already know how to do it you can skip this page since there is nothing specific to Keycloakify about importing fonts. You can import them just as you would in any other web project.

Only, if you import the your fonts in the `index.html` don't forget to import them as well in `.storybook/preview-head.html` for Storybook.
{% endhint %}

## Using a web font service

Let's see how to use, for example, [Playwrite Netherland](https://fonts.google.com/specimen/Playwrite+NL) via Google Fonts.

First we need to add the few links tag we got from from Google Fonts in our HTML \<head>:

<pre class="language-html" data-title="index.html (or public/index.html in Webpack)"><code class="lang-html">&#x3C;!doctype html>
&#x3C;html>
    &#x3C;head>
        &#x3C;meta charset="utf-8" />
        &#x3C;meta name="viewport" content="width=device-width, initial-scale=1" />

        &#x3C;link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />

<strong>        &#x3C;link rel="preconnect" href="https://fonts.googleapis.com">
</strong><strong>        &#x3C;link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
</strong><strong>        &#x3C;link href="https://fonts.googleapis.com/css2?family=Playwrite+NL:wght@100..400&#x26;display=swap" rel="stylesheet">
</strong>
    &#x3C;/head>

    &#x3C;body>
        &#x3C;div id="root">&#x3C;/div>
        &#x3C;script type="module" src="/src/main.tsx">&#x3C;/script>
    &#x3C;/body>
&#x3C;/html>
</code></pre>

The fonts must also be imported in Storybook, so we add the links in the .storybook/preview-head.html as well:

<pre class="language-html" data-title=".storybook/preview-head.html"><code class="lang-html">&#x3C;style>
    body.sb-show-main.sb-main-padded {
        padding: 0;
    }
    
    /* ... */
&#x3C;/style>

<strong>&#x3C;link rel="preconnect" href="https://fonts.googleapis.com">
</strong><strong>&#x3C;link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
</strong><strong>&#x3C;link href="https://fonts.googleapis.com/css2?family=Playwrite+NL:wght@100..400&#x26;display=swap" rel="stylesheet">
</strong></code></pre>

Then all we have to do is apply the Font font family, in this example we will use vanilla CSS but you can of course use your favorite styling solution.

{% code title="src/login/main.css" %}

```css
.kcHeaderWrapperClass {
  /* NOTE: We would use `body {` if we'd like the font to be applied to everything. */
  font-family: "Playwrite NL", cursive;
}
```

{% endcode %}

<pre class="language-tsx" data-title="src/login/KcPage.tsx"><code class="lang-tsx"><strong>import "./main.css";
</strong>import { Suspense, lazy } from "react";
// ...
</code></pre>

That's it!

<figure><img src="/files/mg4oF6xKNdowHXqChbjF" alt="" width="375"><figcaption><p>Playwrite NL successfully applied to the header</p></figcaption></figure>

## Using self hosted fonts

Keycloak is often used in enterprise internal network with strict network traffic control. In this context, using a Font CDN isn't an option, you want the font to be bundled in your jar and served directly by the Keycloak server.

Let's see how we would use a self hosted copy [Vercel's Geist](https://vercel.com/font) font.

First let's download and extract [the font files](https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/geist.zip) in `src/login/assets/fonts/geist/`:

<figure><img src="/files/FP8TuPxEJb54yORQBGTI" alt="" width="375"><figcaption></figcaption></figure>

Now let's set Geist as the default font.

{% code title="src/login/main.css" %}

```css
@import url(./assets/fonts/geist/main.css);

body {
  font-family: Geist;
}
```

{% endcode %}

<pre class="language-tsx" data-title="src/login/KcPage.tsx"><code class="lang-tsx"><strong>import "./main.css";
</strong>import { Suspense, lazy } from "react";
// ...
</code></pre>

Result:

<figure><img src="/files/Bunnj8Be5Gxd4PDsC02T" alt=""><figcaption><p>Geist successfully applied</p></figcaption></figure>


# Internationalization and Translations

Or i18n for short

Internationalization and Translation, referred as i18n is the set of feature that enables you to make your pages available in multiple languages.

## Base principles

When in your components you see instructions like:

<pre class="language-tsx" data-title="src/login/Register.tsx"><code class="lang-tsx">export default function Register(props: RegisterProps) {
    const { i18n } = props;
    
    const { msg, msgStr, advancedMsg, advancedMsgStr } = i18n;

    return (
        //...
        &#x3C;a href={url.loginUrl}>
<strong>            {msg("backToLogin")}
</strong>        &#x3C;/a>
        // ...
    );
}
</code></pre>

<figure><img src="/files/P9VWLNRVJYhSYflsVMhB" alt="" width="374"><figcaption><p><code>msg("backToLogin")</code> gets rendered as <strong>« Back to Login</strong></p></figcaption></figure>

## Overriding the base message or adding custom ones

### In the theme

If you want to see the base message translations you can navigate to the **node\_modules/keycloakify/src/login/i18n/messages\_defaultSet/** directory:

<figure><img src="/files/EaUtva0BuGDnv7r4s1I7" alt=""><figcaption><p>Don't edit this file directly, it's just for seeing what are the default set of i18n messages.</p></figcaption></figure>

As you can see, the translation message for the key backToLogin in English (**en.ts**) is:

`We are <strong>sorry</strong> ...`

\
As a result:

{% code title="msg(" %}

```html
<div data-kc-msg="backToLogin">We are <strong>sorry</strong> ...</div>
```

{% endcode %}

{% hint style="info" %}
The `data-kc-msg` attribute is only here to help you find the source code that generates this node when inspecting with the browser dev tools.\
Note also that the text that is going to be rendered is:

We are **sorry** ... (with sorry in bold)
{% endhint %}

{% code title="msgStr(" %}

```javascript
"We are <strong>sorry</strong> ...";
```

{% endcode %}

Keycloakify let's you overwrite the values of the translations messages and define new ones.\
This is how to do it:

{% code title="src/login/i18n.tsx" %}

```tsx
import { createUseI18n } from "keycloakify/login";

export const { useI18n, ofTypeI18n } = createUseI18n({
  en: {
    backToLogin: "⏪ Back to <strong>Login page</strong>",
    myCustomKey: "My custom message",
  },
  fr: {
    backToLogin: "⏪ Retour à la <strong>page de Login</strong>",
    myCustomKey: "Mon message personalisé",
  },
});

export type I18n = typeof ofTypeI18n;
```

{% endcode %}

{% hint style="warning" %}
The messageBundle that you provide as argument of the `createUseI18n` function must be statically valuable. You can't import from external files. All the translations must be declared inline.\
This is because Keycloakify will analyze your code at build time to make Keycloak aware of your modifications of the base messages so that server side generated feedback messages can use your translations.

![](/files/oao2ydkTD8tOC3eaP0JF)\
![](/files/pFvpIia596WIpnWP0mCT)

<img src="/files/gTOaEaAPxGFWxjCP0DML" alt="" data-size="original">
{% endhint %}

If you don't provide translation for all the language that are enabled in the realm configuration it will fallback to english (or your first translation declared).

### In the Keycloak Realm configuration

Some relevant messages, namely `termsText` and all the messages used in the User Profile Attributes like for example the Display name, the helper text or the select option labels can be defined at the realm level and it will work as you would expect:

<figure><img src="/files/GnGutSTs9LVSDjNGbxvq" alt=""><figcaption><p>The custom user attribute favourite_pet has for Display Name the message key "profile.attributes.favourite_pet"</p></figcaption></figure>

<figure><img src="/files/d3WvH2lqMjF6ugld18Pk" alt=""><figcaption><p>A translation for the message key "profile.attributes.favourite_pet" has been defined for the English language: "Favourite Pet"</p></figcaption></figure>

<figure><img src="/files/bsINRIDdtajSJBIjXcmY" alt="" width="312"><figcaption><p>"Favourite Pet" is correctly used as Display Name for the input field in the register page</p></figcaption></figure>

Note that if you try to use:

```tsx
msg("profile.attributes.favourite_pet");
```

It will work at runtime, you'll get `Favourite Pet` but typescript will complain because `"profile.attributes.favourite_pet"` or `string` isn't a known i18n message key, it makes sense as it's only defined on the server.

This is why you'll see in some place in the code the usage of `advancedMsg(attribute.displayName)`, `advancedMsg()` is basically equivalent to `msg()` except that TypeScript won't complain if the key isn't part of the statically defined set.\
[More details](https://github.com/keycloakify/keycloakify/blob/60aaa03202763307a82991c38997d166f8f44d65/src/login/i18n/i18n.tsx#L58-L72).\
\
See also:

{% content-ref url="/pages/icN8VmF6sv3X7bxdsSWp" %}
[Terms and conditions](/v10/terms-and-conditions)
{% endcontent-ref %}

{% content-ref url="<https://github.com/keycloakify/docs.keycloakify.dev/blob/v10/broken-reference/README.md>" %}
<https://github.com/keycloakify/docs.keycloakify.dev/blob/v10/broken-reference/README.md>
{% endcontent-ref %}

## Stories in different languages

Changing the language directly using the dropdown select in Storybook isn't supported.\
To preview your component in different languages, create separate stories for each language.\
Example:

{% code title="pages/\*\*\*.stories.tsx" %}

```tsx
export const Default: Story = {
  render: () => <KcPageStory />,
};

export const French: Story = {
  render: () => (
    <KcPageStory
      kcContext={{
        locale: {
          currentLanguageTag: "fr",
        },
      }}
    />
  ),
};

export const Spanish: Story = {
  render: () => (
    <KcPageStory
      kcContext={{
        locale: {
          currentLanguageTag: "es",
        },
      }}
    />
  ),
};
```

{% endcode %}

If you want all your story to by by default in an other language you can edit:

{% code title="src/login/KcPageStory.tsx" %}

```tsx
export const { getKcContextMock } = createGetKcContextMock({
  kcContextExtension,
  kcContextExtensionPerPage,
  overrides: {
    locale: {
      currentLanguageTag: "de",
    },
  },
  overridesPerPage: {},
});
```

{% endcode %}

## What do do if my language is not in the default set

{% hint style="warning" %}
Support for adding extra language will be added soon.\
In the meantime see <https://github.com/keycloakify/keycloakify/issues/599>
{% endhint %}

As of writing theses line Keycloak support 27 languages.

<figure><img src="/files/einifm5ewmR2PxsxdzDx" alt="" width="96"><figcaption><p>The 27 languages in supported by Keycloak</p></figcaption></figure>

What to do is your language isn't one of them? Like Hebrew for example.

Unfortunately, Keycloak doesn't provide a way to easily add a new language however there's a workaround with Keycloakify. You can overrides the translation of an other language to add your translations. Here is an example:

{% embed url="<https://github.com/keycloakify/keycloakify-starter/commit/6cce74f516c25005da39e8612132712db1723894>" %}


# Theme Variants

Theme variant enables you to create multiples Keycloak theme with a single codebase.

{% tabs %}
{% tab title="Vite" %}
{% code title="vite.config.ts" %}

```typescript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { keycloakify } from "keycloakify/vite-plugin";

export default defineConfig({
  plugins: [
    react(),
    keycloakify({
      themeName: ["keycloakify-starter", "keycloakify-starter-variant-1"],
    }),
  ],
});
```

{% endcode %}
{% endtab %}

{% tab title="Webpack" %}
{% code title="package.json" %}

```json
{
  "keycloakify": {
    "themeName": ["keycloakify-starter", "keycloakify-starter-variant-1"]
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

This will make the theme variant appear in the Keycloak admin select input:

<figure><img src="/files/uW0IYfHzuzVG2OaegaNA" alt=""><figcaption></figcaption></figure>

In your code you'll be able to load different styles based on the value of `kcContext.themeName`:

<figure><img src="/files/54dcPyL1ScUuk5gV9Bf6" alt=""><figcaption><p>NOTE: You need to <code>run npm run dev</code>, <code>npm run storybook</code> or <code>npm run build-keycloak-theme</code> for the types to be updated.</p></figcaption></figure>

{% embed url="<https://youtu.be/Nkoz1iD-HOA>" %}
Tutorial video
{% endembed %}

{% embed url="<https://github.com/keycloakify/keycloakify-starter/tree/theme_variant>" %}
Branch of the starter where the changes of the video have been applied
{% endembed %}


# Customizing the Register Page

In this video, I explain how to customize the register page of Keycloak, both at the Keycloak configuration level and at the theme level.

{% embed url="<https://youtu.be/lMOLrdqilqE>" %}

## Timestamps

* [01:28](https://www.youtube.com/watch?v=lMOLrdqilqE\&t=88s) - User Profile Attributes Configuration
* [11:05](https://www.youtube.com/watch?v=lMOLrdqilqE\&t=665s) - Password Policies Configuration
* [13:27](https://www.youtube.com/watch?v=lMOLrdqilqE\&t=807s) - Email Domain Accept List
* [15:10](https://www.youtube.com/watch?v=lMOLrdqilqE\&t=910s) - Adding Custom User Attributes to the JWT of the ID and Access Token
* [16:31](https://www.youtube.com/watch?v=lMOLrdqilqE\&t=991s) - Exporting the Realm Configuration
* [19:14](https://www.youtube.com/watch?v=lMOLrdqilqE\&t=1154s) - Creating Storybook Stories for the Register Page
* [23:53](https://www.youtube.com/watch?v=lMOLrdqilqE\&t=1433s) Customizing the Register Page with CSS
* [26:35](https://www.youtube.com/watch?v=lMOLrdqilqE\&t=1595s) - Customizing the Register Page with React


# Account Theme

## Deciding if you need an account theme or not

The first question you want to ask yourself is: "Do I really need an account theme?"

If you're looking to create an account theme just to allow your users to change their password, update account information such as email, phone number, favorite pets, etc., or delete their account, then you do **not** need an account theme.

There are pages in the login theme for those functions. You only need to add a button to redirect your user to the appropriate page in your main app. Here is how to do it with oidc-spa: [Documentation](https://docs.oidc-spa.dev/documentation/user-account-management).

An added benefit of not having an account theme is that you will reuse the exact same form that you created for the registration page in the `login-update-profile.ftl` page.

Consider creating an account theme only if you need to provide advanced account management features to your users, such as connection logs, management of active sessions, file uploads, etc.

Here is a video that explains this in detail:

{% embed url="<https://youtu.be/PiTUPdpmueA>" %}

## Choosing an account theme type

Keycloakify provide you two ways to create your own account theme.

You can chose between two implementation of the account theme:

### Single Page

<figure><img src="/files/cXJhtXlfPMTOpxk12sW9" alt=""><figcaption><p>Screenshot of the Single Page Account theme</p></figcaption></figure>

The Single Page theme also refered as account v3 is this the default theme that comes with Keycloak 25. [But thanks to Keycloakify's compatiblity layer it works with older Keycloak versions down to 19](https://youtu.be/HWiWHpF5mY0).

#### Pros

* Get's all the latest features.
* The base code is maintained by the Keycloak team and automatically integrated into Keycloakify. You're using the real thing, not a fork.
* If you're a React developper you'll feel right at home. It's uing i18n-next and react-router

#### Cons

* Opting for this option will add a lot of dependencies to your project (i18n-next, react-router-dom, patenrnfly and more).
* CSS level customization beyond [overidding the Paternfly CSS variables](https://www.patternfly.org/components/button/html/#css-variables) is not practical, you'll have to customize at the React component level.
* No Storybook support.
* No `npx keycloakify eject-page` CLI, you'll have to manually copy paste from the source the components you want to take ownership over.
* When upgrading to a future version of Keycloak, there’s a possibility that your account theme may break. If your customizations are limited to styles, updating should be straightforward—simply bump the version number of [the Account UI](https://github.com/keycloakify/keycloak-account-ui) in your dependencies. However, if you’ve made customizations at the React component level, migrating to the new version could require substantial effort due to potential extensive changes in the underlying code. The Keycloakify team cannot guarantee a specific level of stability for these modifications, as they are not part of our codebase.

To get started with the Single-Page account theme:

{% content-ref url="/pages/feNZICfdWJLR1l6ze1Jj" %}
[Single-Page](/v10/account-theme/single-page)
{% endcontent-ref %}

### Multi Page

<figure><img src="/files/mnM0Z3Rli0xn1DR1yheM" alt=""><figcaption></figcaption></figure>

This is a fork of the Account v1 maintained by the Keycloakify team.

#### Pros

* Works exactly the same as the login theme, nothing new to learn.
* Storybook support
* CSS level customization support just like in the login theme.
* As it's maintained by us, we can guarenty a certain level of stability in future version of Keycloak.
* Compatible with all Keycloak version.
* Does not add any dependency to your project.

#### Cons

* Don't come with all the feature out of the box yet. You'll have to use the [Keycloak Account REST API if you want to implement them](/v10/account-theme/multi-page).
* It relies on Java code maintained by us, this code uses Keycloak internal API, you have to trust us to keep maintaining it.
* The default look is a bit dated (as of today, we'll update it).

To get started with the Multi-Page account theme:

{% content-ref url="/pages/D8wdy90BLRWSyFfWyfrB" %}
[Multi-Page](/v10/account-theme/multi-page)
{% endcontent-ref %}


# Single-Page

Customizing the Single Page Account UI

The present documation page is a transcript of what I explain in this video:

{% embed url="<https://youtu.be/PCNd3Nso1mY>" %}
This excerpt is from a video call with the Keycloak team, where we introduced the support for Account SPA customization.
{% endembed %}

## Initializing the Single-Page Account Theme

<figure><img src="/files/c7TOoKlPczYHi4qo3k7w" alt=""><figcaption><p>The account Single Page Account theme before customization</p></figcaption></figure>

You've made your mind and opted for the Single Page Account UI?\
Great, let's start by initializing you theme:

```bash
npx keycloakify initialize-account-theme
```

<figure><img src="/files/gDZ8N4BR2RTIHXy9woci" alt=""><figcaption><p>When prompted, select the "Single-Page" option</p></figcaption></figure>

This command will create the nessesary boilerpate and add to your project dependencies the required dependencies of the latest Account UI.

<figure><img src="/files/9tf3WhiXo7slcbdb5OWo" alt=""><figcaption><p>Dependency added to your project when using initializing the Single-Page Account UI</p></figcaption></figure>

Before starting the customization, let's make sure that everything works by adding a simple console.log.

<pre class="language-tsx" data-title="src/account/KcPage.tsx"><code class="lang-tsx">import { lazy } from "react";
import { KcAccountUiLoader } from "@keycloakify/keycloak-account-ui";
import type { KcContext } from "./KcContext";

const KcAccountUi = lazy(() => import("@keycloakify/keycloak-account-ui/KcAccountUi"));

export default function KcPage(props: { kcContext: KcContext }) {
    const { kcContext } = props;
    
<strong>    console.log("This is my account theme!");
</strong>    
    return &#x3C;KcAccountUiLoader kcContext={kcContext} KcAccountUi={KcAccountUi} />;
}
</code></pre>

Then let's start Keycloak and test it live:

```bash
npx keycloakify start-keycloak
```

Selet Keycloak 25.

<figure><img src="/files/A4zba7akFbZsdmHPCsPQ" alt="" width="375"><figcaption><p>Keycloak 25 up and runing on your computer</p></figcaption></figure>

Once you get the confirmation message that Keycloak is up and running you can reach the <https://my-theme.keycloakify.dev> to get redirected to your Login theme.\
Use the test credentials to authenticate as the test user:

<figure><img src="/files/llXIguSOtmkpNH3Tsl4q" alt="" width="375"><figcaption><p>Authenticating as the test user</p></figcaption></figure>

On the next page you'll be provided with a link to the Account pages:

<figure><img src="/files/AJffNrAyEHFg7bnhSX82" alt=""><figcaption><p>Authenticated as "testuser" on the my-theme.keycloakify.dev utility app</p></figcaption></figure>

You should be able to see your log statement confirming that you are indeed running your theme.

<figure><img src="/files/TISs56IgojsUOZPubfGN" alt=""><figcaption></figcaption></figure>

Compilation of your theme is running in watch mode when using the start-keycloak command, you can eddit your console.log message, save and after a few seconds reload the page, you should see the message updated.\
\
After completing the initialization process, it's a good time to commit the changes.

```bash
git add -A
git commit -am "Initialize the Single-Page Account Theme"
```

## Basic customization

You can customize some aspect of the account theme witout having to go down at the React component level.\
If you stick to this level of customization the Keycloak team is able to guarenty that you'll be able to keep your theme compatible with upcoming version of Keycloak with minimal maintenance effort.

Let's see what we can do.

### Changing the logo

First start by adding a logo file in your src directory somewhere, example:

<figure><img src="/files/k2MiC00IdJvz9AaNt5MH" alt=""><figcaption></figcaption></figure>

And pass a reference to it as props of the `<KcAccountUiLoader />` component:

<pre class="language-tsx" data-title="src/account/KcPage.tsx"><code class="lang-tsx">import { lazy } from "react";
import { KcAccountUiLoader } from "@keycloakify/keycloak-account-ui";
import type { KcContext } from "./KcContext";
<strong>import myLogoPngUrl from "./assets/my-logo.png";
</strong>
const KcAccountUi = lazy(() => import("@keycloakify/keycloak-account-ui/KcAccountUi"));

export default function KcPage(props: { kcContext: KcContext }) {
    const { kcContext } = props;

    return (
        &#x3C;KcAccountUiLoader
            kcContext={kcContext}
            KcAccountUi={KcAccountUi}
<strong>            logoUrl={myLogoPngUrl}
</strong>        />
    );
}
</code></pre>

After saving and reloading you should be able to see that the logo has been updated:

<figure><img src="/files/FHvyJb2k0ACncBONPTuO" alt="" width="375"><figcaption><p>The Keycloak logo has been replaced by the Keycloakify logo</p></figcaption></figure>

### Customizing what sections are availables in the left pannel

Beside changing option in your Keycloak realm configuration you can define what options should be displayed and when in the left pannel.\
For that you can use the content props of the `KcAccountUiLoader` component.

You can start by copy/pasting the default content located in **node\_modules/@keycloakify/keycloak-account-ui/src/public/content.ts**

<pre class="language-tsx" data-title="src/account/KcPage.tsx"><code class="lang-tsx">import { lazy } from "react";
import { KcAccountUiLoader } from "@keycloakify/keycloak-account-ui";
import type { KcContext } from "./KcContext";
import myLogoPngUrl from "./assets/my-logo.png";

const KcAccountUi = lazy(() => import("@keycloakify/keycloak-account-ui/KcAccountUi"));

export default function KcPage(props: { kcContext: KcContext }) {
    const { kcContext } = props;

    return (
        &#x3C;KcAccountUiLoader
            kcContext={kcContext}
            KcAccountUi={KcAccountUi}
            logoUrl={myLogoPngUrl}
<strong>            content={[
</strong><strong>                {
</strong><strong>                    label: "personalInfo",
</strong><strong>                    path: ""
</strong><strong>                },
</strong><strong>                {
</strong><strong>                    label: "accountSecurity",
</strong><strong>                    children: [
</strong><strong>                        {
</strong><strong>                            label: "signingIn",
</strong><strong>                            path: "account-security/signing-in"
</strong><strong>                        },
</strong><strong>                        {
</strong><strong>                            label: "deviceActivity",
</strong><strong>                            path: "account-security/device-activity"
</strong><strong>                        },
</strong><strong>                        {
</strong><strong>                            label: "linkedAccounts",
</strong><strong>                            path: "account-security/linked-accounts",
</strong><strong>                            isVisible: "isLinkedAccountsEnabled"
</strong><strong>                        }
</strong><strong>                    ]
</strong><strong>                },
</strong><strong>                {
</strong><strong>                    label: "applications",
</strong><strong>                    path: "applications"
</strong><strong>                },
</strong><strong>                {
</strong><strong>                    label: "groups",
</strong><strong>                    path: "groups",
</strong><strong>                    isVisible: "isViewGroupsEnabled"
</strong><strong>                },
</strong><strong>                {
</strong><strong>                    label: "resources",
</strong><strong>                    path: "resources",
</strong><strong>                    isVisible: "isMyResourcesEnabled"
</strong><strong>                },
</strong><strong>                {
</strong><strong>                    label: "oid4vci",
</strong><strong>                    path: "oid4vci",
</strong><strong>                    isVisible: "isOid4VciEnabled"
</strong><strong>                }
</strong><strong>            ]}
</strong>        />
    );
}
</code></pre>

Let's for example comment out the deviceActivity section.

<figure><img src="/files/iYV6Peb7goatqUkTe2FD" alt="" width="297"><figcaption><p>Before</p></figcaption></figure>

<figure><img src="/files/JIW14YtLTveU8dUAnRCQ" alt="" width="375"><figcaption><p>Commenting out deviceActivity</p></figcaption></figure>

<figure><img src="/files/5a0uop4CYPRHOzU033ow" alt="" width="315"><figcaption><p>After, the Device Activity section has disapeared</p></figcaption></figure>

### Adding custom CSS

The official way of customizing the look of the Account UI is to overload the PaternFly CSS variables:

{% embed url="<https://www.patternfly.org/components/button/html/#css-variables>" %}

But we are aware that this isn't enough for most of you.

Beyond that you can of course import your custom CSS and use the PaternFly utility classes as target but be aware that theses classes can be changed in the future.

\
Example loading some custom CSS:

<figure><img src="/files/66e1Fu30pGNgtQQmBV0e" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/c2grbEhT3QhLWGXYMMS4" alt=""><figcaption><p>The red boder has been applied on all the element that have the pf-v5-c-page__main-secrion class</p></figcaption></figure>

## Component level customization

To customize the Account theme at the React component level you want to use the eject-page command and slect "account".

```bash
npx keycloakify eject-page
```

<figure><img src="/files/v9z0EasAgDTnJhz03qX6" alt=""><figcaption></figcaption></figure>

After running this command you'll be able to see that the following change has been automatically applied:

{% code title="src/account/KcPage.tsx" %}

```diff
 import { lazy } from "react";
 import { KcAccountUiLoader } from "@keycloakify/keycloak-account-ui";
 import type { KcContext } from "./KcContext";

-const KcAccountUi = lazy(()=> import("@keycloakify/keycloak-account-ui/KcAccountUi"));
+const KcAccountUi = lazy(() => import("./KcAccountUi"));

 export default function KcPage(props: { kcContext: KcContext }) {
     const { kcContext } = props;

     return (
         <KcAccountUiLoader
             kcContext={kcContext}
             KcAccountUi={KcAccountUi}
         />
     );
 }
```

{% endcode %}

Also the KcAccountUi component has be copied over from **node\_modules/@keycloakify/keycloak-account-ui/src/KcAccountUi.tsx** to your codebase at **src/account/KcAccountUi.tsx**.

<figure><img src="/files/2Tef2jGaq3gpJvtuwu0m" alt=""><figcaption><p>KcAccountUi.tsx has been ejected</p></figcaption></figure>

That what you'll want to each time you'll want to take ownership of some component of the Account UI: Copy the original source file into your codebase and update the absolute imports by relative imports.\
Let's see in practice how we would eject the routes:

```bash
cp node_modules/@keycloakify/keycloak-account-ui/src/routes.tsx src/account/
```

Then you want to update the absolututes import of the routes to your local routes in KcAccountUI.tsx

```diff
- import { routes } from "@keycloakify/keycloak-account-ui/routes";
+ import { router } from "./routes";
```

<figure><img src="/files/e1sUcadILTw4O5inEFKX" alt=""><figcaption></figcaption></figure>

And that's it, you now own the routes!\
Moving forward you can incrementally "eject" the components you need to take ownerhip over by followind the same process.

Be aware: The more components you eject, the more work it will represent to mainain your theme up to date with the future evolution of Keycloak.

## Updating keycloak-account-ui to a new version

Each time a new version of Keycloak is released, a new version of @keycloak/keycloak-account-ui is also released with the same version number.\
This does not nessesarely means that in order to have a custom theme that works with Keycloak 25.0.2 for example you need to use @keycloak/keycloak-account-ui\@25.0.2. Actually it's very likely that you theme will still work with Keycloak 26, however at some point your theme will break with future version of Keycloak and you'll have to upgrade.

Keycloakify does not use the NPM package @keycloak/keycloak-account-ui but an automatically repackaged distribution of it: @keycloakify/keycloak-account-ui.

So, when comes the time to upgrade you want to navigate to:\\

{% embed url="<https://github.com/keycloakify/keycloak-account-ui>" %}

And look in the README in the installation section:

<figure><img src="/files/Z9jdu0oQD3Ym7MRRwHBk" alt=""><figcaption><p>Instalation section of keycloakify/keycloak-account-ui version 25.0.2</p></figcaption></figure>

You want to copy and paste the dependencies into the package.json of your Keycloakify project.

The readme is generated automatically, you can trust that is always up do date.

You migh wonder why there's only RC releases of @keycloakify/keycloak-account-ui, it's because we want to match the version number of the upstream package @keycloak/keycloak-account-ui but still be able to publish update when minor changes on the re-packaging distribution is needed.


# Multi-Page

## Initializing the Multi-Page Account Theme

<figure><img src="/files/kccde8NlQ45qST3ULUXQ" alt=""><figcaption><p>The Multi-Page Account theme before customization</p></figcaption></figure>

You've made your mind and opted for the Multi-Page Account theme?\
Great, let's start by initializing you theme:

```bash
npx keycloakify initialize-account-theme
```

When asked, select "Multi-Page".

This command will create the nessesary boilerplate for you.

Beyond that there isn't much thing you need to be aware of, things works exactly as in the login theme. You'll be able to use the keycloakify`add-story` and `eject-page` CLI command just select account when asked.

## Using the REST API

Even if you're using the Multi-Page theme you can still consume the REST API the Single-Page Account is build on top of. So, if some information you need are missing from the `kcContext` you can fetch them dynamically.

{% embed url="<https://youtu.be/FrFr-hqyjb4>" %}

{% embed url="<https://github.com/keycloakify/keycloakify-starter/tree/account_api_poc>" %}
Branch of the starter template modified to call the Account REST API
{% endembed %}

You can find the code for the Account v3 theme [here](https://github.com/keycloak/keycloak/tree/main/js/apps/account-ui/src/api). This will help you infer all the available endpoints. You can also enable the Account v3 theme in your Keycloak and use the network tab to see the available endpoints.


# Terms and conditions

The Tems and Condition feature of Keycloak enalbes you to make new users of your service consent to the terms of use of your services upon regisering.

{% embed url="<https://storybook.keycloakify.dev/?path=/story/login-terms-ftl--default>" %}

<figure><img src="/files/BATvmLfHd0qhOjIDEeqI" alt=""><figcaption></figcaption></figure>

## Enabling the feature

If you want you show your terms and condition page when they create an account you have to enable it in your realm configuration.

This is how to do it in the Keycloak Admin Console:

TODO: Tango

## Defining your Terms and Conditions text

The way of defining your terms of services in Keycloak is to provide a message bundle for your realm that overrides the `temsText` key for the different languages that you have enabled.

In recent Keycloak's versions this can be acheived directly via the Keycloak Admin Console as shown in this video:

{% embed url="<https://youtu.be/naW2TxwJZsA>" %}

## Customizing how the terms are rendered

If you want to customize the page that display the terms and condition you have to eject the [terms.ftl](https://storybook.keycloakify.dev/?path=/story/login-terms-ftl--default) page.

```bash
npx keycloakify eject-page
# Select login -> terms.ftl
```

This will create `src/login/Terms.tsx` in your project.

You also probably want to add a story for the Term page:

```bash
npx keycloakify add-story
# Select login -> terms.ftl
```

In `src/login/Terms.tsx`, note that `msg("termsText")` returns a `JSX.Element`. It's because the `msg()` function renders the string message as HTML text. You can't work directly with that.

If you want to apply transformation to the text, you should use `msgStr("termsText")` instead. This returns the original string as defined in your realm configuration.


# Styling a Custom Page Not Included In Base Keycloak

Sometimes certain extensions will add new functionality that requires an additional page not originally shipped with Keycloak. Keycloakify out-of-the-box will only provide customization to base pages, so if a new page is introduced by an extension, there is a good chance the page will not be styled correctly.

To account for these cases, Keycloakify supports the ability to add custom pages and configure them such that style preservation is maintained.

For our example on how to customize this, we will be using Phase Two's otp-form.ftl page. Phase Two provides email OTP codes for logging in and as a result has a special page if OTP codes are enabled in the authorization flow.

{% hint style="success" %}
You can load the extention that you are using in Keycloak container that is started when running `npx keycloakify start-keycloak`. Use [the `extensionJars option`](/v10/testing-your-theme/in-a-keycloak-docker-container).
{% endhint %}

{% embed url="<https://github.com/p2-inc/keycloak-magic-link/blob/main/src/main/resources/theme-resources/templates/otp-form.ftl>" %}
You can find the original .ftl file on Phase Two's github
{% endembed %}

The first thing we will do is create the page under the pages directory, our file name in this case will be `OtpForm.tsx` and paste in some starter code including the template.

{% code title="src/login/pages/OtpForm.tsx" %}

```tsx
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";

export default function OtpForm(props: PageProps<Extract<KcContext, { pageId: "otp-form.ftl" }>, I18n>) {
    const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;

    const { kcClsx } = getKcClsx({
        doUseDefaultCss,
        classes
    });

    const { msg, msgStr } = i18n;

    const { url } = kcContext;


    return (
        <Template
            kcContext={kcContext}
            i18n={i18n}
            doUseDefaultCss={doUseDefaultCss}
            classes={classes}
            displayInfo={false}
            headerNode={
                // Header code goes here
            }
        >
            // Page code goes here
        </Template>
    );
}
```

{% endcode %}

Note the `pageId` variable specified `otp-form.ftl`, that should match the exact name of the page file you are trying to implement. Additionally, we will also need to modify the `kcContext` values to account for certain custom variables, but we will get to that later. For now the last new file we need to add would be the story file for this page:

{% code title="src/login/pages/OtpForm.stories.tsx" %}

```tsx
import type { Meta, StoryObj } from "@storybook/react";
import { createKcPageStory } from "../KcPageStory";

const { KcPageStory } = createKcPageStory({ pageId: "otp-form.ftl" });

const meta = {
    title: "login/otp-form.ftl",
    component: KcPageStory
} satisfies Meta<typeof KcPageStory>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
    render: () => <KcPageStory />
};
```

{% endcode %}

Next the easiest thing is to just paste the default code for the custom page right into the template and begin modifying it for Keycloakify. In our case, here is the code for that page at the time of writing:

<details>

<summary>otp-form.ftl</summary>

```xml
<#import "template.ftl" as layout>
<@layout.registrationLayout displayInfo=true; section>
    <#if section = "title">
        ${msg("doLogIn")}

    <#elseif section = "header">
      <div id="kc-username" class="${properties.kcFormGroupClass!}">
        <label id="kc-attempted-username">${auth.attemptedUsername}</label>
        <a id="reset-login" href="${url.loginRestartFlowUrl}" aria-label="${msg("restartLoginTooltip")}">
          <div class="kc-login-tooltip">
            <i class="${properties.kcResetFlowIcon!}"></i>
            <span class="kc-tooltip-text">${msg("restartLoginTooltip")}</span>
          </div>
        </a>
      </div>

    <#elseif section = "form">
      <p>Enter access code</p>
      <form id="kc-otp-login-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
        <div class="${properties.kcFormGroupClass!}">
          <div class="${properties.kcLabelWrapperClass!}">
            <label for="otp" class="${properties.kcLabelClass!}">${msg("loginOtpOneTime")}</label>
          </div>

          <div class="${properties.kcInputWrapperClass!}">
            <input id="otp" name="otp" autocomplete="off" type="text" class="${properties.kcInputClass!}" autofocus aria-invalid="<#if messagesPerField.existsError('totp')>true</#if>"/>
            <#if messagesPerField.existsError('totp')>
              <span id="input-error-otp-code" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">${kcSanitize(messagesPerField.get('totp'))?no_esc}</span>
            </#if>
          </div>
        </div>

        <div class="${properties.kcFormGroupClass!}">
          <div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
            <div class="${properties.kcFormOptionsWrapperClass!}">
            </div>
          </div>

          <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
            <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submit" id="kc-submit" type="submit" value="${msg("doSubmit")}" />
            <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="resend" id="kc-resend" type="submit" value="${msg("doResend")}" />
          </div>
        </div>
      </form>
    </#if>
</@layout.registrationLayout>
```

</details>

Breaking down this code:

1. The freemarker, dynamic variables/messages, and classnames will need to be converted to React.
2. The content in the header section will go in the `headerNode` prop of `<Template>` and the form section will be the child of the `<Template>` element.
3. `@layout.registrationLayout` has the prop `displayInfo=true` which means we need to set that prop in the `<Template>` element.
4. The `auth` and `messagesPerField` variables and their attributes which need to be provided in kcContext.

1, 2, and 3 require converting code to JSX. The converted code for the page can be found at the bottom. Here are some tips:

* Any classname provided as a variable will use `kcClsx` to resolve, so `${properties.kcFormClass!}` would turn into `{kcClsx("kcFormGroupClass")}`
* When dealing with message values, `msg` may return full blown HTML so it can be used as a child element and `msgStr` will return straight text.
  * Example 1, `aria-label="${msg("restartLoginTooltip")}"` would turn into `aria-label={msgStr("restartLoginTooltip")}`.
  * Example 2, `msg` variables they can inject HTML as a variable, when this happens we need to dangerously set inner html. Specifcally with a piece of code like this:

    ```html
    <#if messagesPerField.existsError('totp')>
      <span id="input-error-otp-code" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
        ${kcSanitize(messagesPerField.get('totp'))?no_esc}
      </span>
    </#if>
    ```

    would turn into this:

    ```jsx
    {
      messagesPerField.existsError("totp") && (
        <span
          id="input-error-otp-code"
          className={kcClsx("kcInputErrorMessageClass")}
          aria-live="polite"
          dangerouslySetInnerHTML={{ __html: messagesPerField.get("totp") }}
        />
      );
    }
    ```

    Unfortunately, a lot of it is up to you to decide with the extension you might be using, but there may be some trial and error.

4 on the other hand requires changing some code in other files.

{% code title="src/login/KcContext.ts" %}

```tsx
/* eslint-disable @typescript-eslint/ban-types */
import type { ExtendKcContext } from "keycloakify/login";
import type { KcEnvName, ThemeName } from "../kc.gen";

export type KcContextExtension = {
    themeName: ThemeName;
    properties: Record<KcEnvName, string> & {};
};

// added for otp form page, required for the types
export type KcContextExtensionPerPage = {
    "otp-form.ftl": {
        auth: {
            attemptedUsername: string;
        };
        url: {
            loginRestartFlowUrl: string;
            loginAction: string;
        };
    };
};

export type KcContext = ExtendKcContext<KcContextExtension, KcContextExtensionPerPage>;
```

{% endcode %}

As seen above, kcContext is where we can add the type definitions for the props passed into the page. In the freemarker we also see `msg("doResend")` value which is not in the base keycloak i18 library. We would also need add this for mocking purposes.

{% code title="src/login/i18n.ts" %}

```typescript
import { createUseI18n } from "keycloakify/login";

export const { useI18n, ofTypeI18n } = createUseI18n({
    en: {
        doResend: "Resend"
    },
    fr: {
        doResend: "Renvoyer"
    }
});

export type I18n = typeof ofTypeI18n;
```

{% endcode %}

The last two things we need to do now would be adding the story to the `KcPageStory.tsx`

{% code title="src/login/KcPageStory.tsx" %}

```typescript
const kcContextExtensionPerPage: KcContextExtensionPerPage = {
    "otp-form.ftl": {
        auth: {
            attemptedUsername: "user@user.com"
        },
        url: {
            loginRestartFlowUrl: "#",
            loginAction: "#"
        }
    }
};
```

{% endcode %}

and adding the page to the `KcPage.tsx`

{% code title="src/login/KcPage.tsx" %}

```tsx
case "otp-form.ftl":
    return (
        <OtpForm
            {...{ kcContext, i18n, classes }}
            Template={Template}
            doUseDefaultCss={true}
        />
    );
```

{% endcode %}

After all that you should be done! You can view the new component in storybook and check everything looks right and then the next time you bundle and build it, it should be deployed.

<details>

<summary>Completed code for OtpForm.tsx:</summary>

```jsx
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";

export default function OtpForm(props: PageProps<Extract<KcContext, { pageId: "otp-form.ftl" }>, I18n>) {
    const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;

    const { kcClsx } = getKcClsx({
        doUseDefaultCss,
        classes
    });

    const { msg, msgStr } = i18n;

    const { auth, url, messagesPerField } = kcContext;

    return (
        <Template
            kcContext={kcContext}
            i18n={i18n}
            doUseDefaultCss={doUseDefaultCss}
            classes={classes}
            displayInfo={false}
            headerNode={
                <div id="kc-username" className={kcClsx("kcFormGroupClass")} style={{ fontSize: "16px" }}>
                    <label id="kc-attempted-username">{auth.attemptedUsername}</label>
                    <a id="reset-login" href={url.loginRestartFlowUrl} aria-label={msgStr("restartLoginTooltip")}>
                        <div className="kc-login-tooltip">
                            <i className={kcClsx("kcResetFlowIcon")}></i>
                            <span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
                        </div>
                    </a>
                </div>
            }
        >
            <p>Enter access code</p>
            <form id="kc-otp-login-form" className={kcClsx("kcFormClass")} action={url.loginAction} method="post">
                <div className={kcClsx("kcFormGroupClass")}>
                    <div className={kcClsx("kcLabelWrapperClass")}>
                        <label htmlFor="otp" className={kcClsx("kcLabelClass")}>
                            {msg("loginOtpOneTime")}
                        </label>
                    </div>

                    <div className={kcClsx("kcInputWrapperClass")}>
                        <input
                            id="otp"
                            name="otp"
                            autoComplete="off"
                            type="text"
                            className={kcClsx("kcInputClass")}
                            autoFocus
                            aria-invalid={messagesPerField.existsError("totp") ? "true" : undefined}
                        />
                        {messagesPerField.existsError("totp") && (
                            <span
                                id="input-error-otp-code"
                                className={kcClsx("kcInputErrorMessageClass")}
                                aria-live="polite"
                                dangerouslySetInnerHTML={{ __html: messagesPerField.get("totp") }}
                            />
                        )}
                    </div>
                </div>

                <div className={kcClsx("kcFormGroupClass")}>
                    <div id="kc-form-options" className={kcClsx("kcFormOptionsClass")}>
                        <div className={kcClsx("kcFormOptionsWrapperClass")} />
                    </div>

                    <div id="kc-form-buttons" className={kcClsx("kcFormButtonsClass")}>
                        <input
                            className={kcClsx("kcButtonClass", "kcButtonPrimaryClass", "kcButtonLargeClass")}
                            name="submit"
                            id="kc-submit"
                            type="submit"
                            value={msgStr("doSubmit")}
                        />
                        <input
                            className={kcClsx("kcButtonClass", "kcButtonPrimaryClass", "kcButtonLargeClass")}
                            name="resend"
                            id="kc-resend"
                            type="submit"
                            value={msgStr("doResend")}
                        />
                    </div>
                </div>
            </form>
        </Template>
    );
}
```

</details>


# Accessing the Server Environment Variables

Environment variables defined on the Keycloak server can be transferred to the theme. This allows for a degree of theme customization without necessitating a rebuild. This approach is particularly useful if multiple parties are reusing your theme. As an example, you can distribute a single .jar file to multiple customers, enabling them to modify certain aspect of the login page by defining specific environment variables.

Let's define two environnement variable:

{% tabs %}
{% tab title="Vite" %}

<pre class="language-typescript" data-title="vite.config.ts"><code class="lang-typescript">import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { keycloakify } from "keycloakify/vite-plugin";

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        react(),
        keycloakify({
            // ...
<strong>            environmentVariables: [
</strong><strong>                { name: "MY_APP_API_URL", default: "" },
</strong><strong>                { name: "MY_APP_PALETTE", default: "dracula" }
</strong><strong>            ]
</strong>        })
    ]
});

</code></pre>

{% endtab %}

{% tab title="Webpack" %}
{% code title="package.json" %}

```json
{
    "keycloakify": {
        "environmentVariables": [
            { "name": "MY_APP_API_URL", "default": "" },
            { "name": "MY_APP_PALETTE", "default": "dracula" }
        ]
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

We can then access the runtime value of thoses variables under kcContext.properties:

<figure><img src="/files/xOLwlH5NO9pgdwkmOcHX" alt=""><figcaption><p>Accessing the value of the environement variable defined.</p></figcaption></figure>

Now let's see how you can set the value of thoses environement variable on the Keycloak side:

{% tabs %}
{% tab title="Docker" %}

<pre class="language-bash"><code class="lang-bash">docker run \
    -e KEYCLOAK_ADMIN=admin \
    -e KEYCLOAK_ADMIN_PASSWORD=admin \
<strong>    --env MY_APP_API_URL='https://api.my-org.com' \
</strong><strong>    --env MY_APP_PALETTE='solaris'
</strong>    -p 8080:8080 \
    docker-keycloak-with-theme
</code></pre>

{% endtab %}

{% tab title="Helm" %}
{% code title="values.json" %}

```bash
keycloak:
  initContainers: |
    - name: realm-ext-provider
      image: curlimages/curl
      imagePullPolicy: IfNotPresent
      command:
        - sh
      args:
        - -c
        - |
          # Replace USER and PROJECT.    
          curl -L -f -S -o /extensions/keycloak-theme.jar https://github.com/USER/PROJECT/releases/latest/download/keycloak-theme-for-kc-24.jar

      volumeMounts:
        - name: extensions
          mountPath: /extensions

  extraVolumeMounts: |
    - name: extensions
      mountPath: /opt/bitnami/keycloak/providers

  extraVolumes: |
    - name: extensions
      emptyDir: {}
      
  extraEnv: |
    - name: MY_APP_API_URL
      value: 'https://api.my-org.com'
    - name: MY_APP_PALETTE
      value: 'solaris'
```

{% endcode %}
{% endtab %}

{% tab title="Bare Metal" %}

```bash
MY_APP_API_URL="https://api.my-org.com" MY_APP_PALETTE="solaris" /opt/keycloak/bin/kc.sh start
```

{% endtab %}
{% endtabs %}

To test locally, you can pass the environement variable to the start-keycloak CLI command:

```bash
MY_APP_PALETTE="solaris" MY_APP_API_URL="..." npx keycloakify start-keycloak
```

You can also create stories with specific ENV values:

```tsx
export const Solaris: Story = {
    render: () => (
        <KcPageStory
            kcContext={{
                properties: {
                    MY_APP_PALETTE: "solaris"
                },
            }}
        />
    )
};
```


# Targetting Specific Keycloak Versions

By default, Keycloakify generates multiples jar files, each one meant to be used with a given Keycloak version range.

<figure><img src="/files/2lctYpkQhQCZrLX38QlF" alt="" width="315"><figcaption><p>files generated by 'keycloakify build' when you have a Multi Page Account theme</p></figcaption></figure>

However you might want to customize this behavior. If you know ahead of time what Keycloak you theme will using you can build only for this version using the `keycloakVersionTargets` build option.

{% tabs %}
{% tab title="Vite" %}

<pre class="language-typescript" data-title="vite.config.ts"><code class="lang-typescript">import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { keycloakify } from "keycloakify/vite-plugin";

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [react(), keycloakify({
        accountThemeImplementation: "none", 
<strong>        keycloakVersionTargets: {
</strong><strong>            "21-and-below": false,
</strong><strong>            "22-and-above": "my-keycloak-theme.jar"
</strong><strong>        }
</strong>    })]
});
</code></pre>

{% endtab %}

{% tab title="Webpack" %}

<pre class="language-json" data-title="package.json"><code class="lang-json">{
    "keycloakify": {
        "accountThemeImplementation": "none", 
<strong>        "keycloakVersionTargets": {
</strong><strong>            "21-and-below": false,
</strong><strong>            "22-and-above": "my-keycloak-theme.jar"
</strong><strong>        }
</strong>    }
}
</code></pre>

{% endtab %}
{% endtabs %}

In this configuration only the jar for Keycloak 22 and above will be generated by `npx keycloakify build`. and the file will be: dist\_keycloak/my-keycloak-theme.jar.

{% hint style="info" %}
If you have [a Multi Page account theme](/v10/account-theme/multi-page) the keycloakVersionTargets expected changes! Use TypeScript auto completion to know what ranges are required to be filled.
{% endhint %}


# Email Customization

Customize the default email template

*Introduced in* [*v4.8.0*](https://github.com/InseeFrLab/keycloakify/releases/tag/v4.8.0)

{% hint style="warning" %}
Currently, customizing emails with React is not possible, and you must use FreeMarker instead. If this poses a significant obstacle for you, please [open a new issue](https://github.com/keycloakify/keycloakify/issues/new) to discuss it further.
{% endhint %}

It is now possible to customize the emails sent to your users to confirm their email address ect.\
Just run npx keycloakify `npx keycloakify initialize-email-theme`.

For this script to work you must be in one of these scenarios:

* `src/login` or `src/account` exists, if it's the case it will assume that this is a standalone keycloak theme and create `src/email`
* There is a `keycloak-theme` directory somewhere in your `src` directory. If it's the case it will create `src/**/keycloak-theme/email`.

This directory should be tracked by Git (`yarn add -A`) You can start hacking the default template.

You can remove all the template and resource file you aren't going to customize (it will fallback to the default email theme as long as you keep a `theme.properties` with `parent=base`).\
When `npx keycloakify` (`yarn keycloak`) is run it will bundle your email theme into your `.jar` file and you will be able to select it in the Keycloak administration pages.

![Selecting your email theme in the Keycloak admin](/files/xSFPDhug6JDZsjLclTma)


# Passing URL Parameters to your Theme

Let's explore how we can pass query params to the URL before redirecting to the login page so that we can transport some values from the main app to the login page.

{% embed url="<https://github.com/keycloakify/keycloakify-starter/blob/0c56eff3b00b99fd723de1dcdb91c40a3b3478cd/src/App/oidc.ts#L23>" %}

{% embed url="<https://github.com/keycloakify/keycloakify-starter/blob/0c56eff3b00b99fd723de1dcdb91c40a3b3478cd/src/keycloak-theme/login/pages/Login.tsx#L9-L13>" %}

You might want to store the value in the local storage if otherwise you'll lost it when the user navigate from the login page to the register page. Example implementation [here](https://github.com/InseeFrLab/onyxia/blob/40d393973398f5bbcea60d7cd9a9a9e0267bd273/web/src/keycloak-theme/login/onyxiaInstancePublicUrl.ts#L6-L28).


# Admin theme

I'm working on it. It should be comming soon.


# Importing the JAR of Your Theme Into Keycloak

Now that you have your theme as a .jar file, let's see how you can import it in Keycloak so that it appears in the dropdown list for selecting theme in the Keycloak Admin console.

<figure><img src="/files/IWV0D1TdziwrKfzESoGT" alt="" width="375"><figcaption><p>Custom login and account theme selected in the Keycloak Admin console</p></figcaption></figure>

{% tabs %}
{% tab title="Docker" %}

<pre class="language-sh"><code class="lang-sh">cd ~/github
git clone https://github.com/keycloakify/keycloakify-starter
cd keycloakify-starter
# Just to make sure these instructions remain relevant in the future
# We pin the version of the starter we are using.  
git checkout 2553c38272fc76efba8f88c9add6de5ce696ba9d
yarn
yarn build-keycloak-theme

docker run \
    -p 8080:8080 \
    --name my-keycloak \
    -e KEYCLOAK_ADMIN=admin \
    -e KEYCLOAK_ADMIN_PASSWORD=admin \
<strong>    -v "./dist_keycloak/keycloak-theme-for-kc-22-and-above.jar":/opt/keycloak/providers/keycloak-theme.jar \
</strong>    quay.io/keycloak/keycloak:25.0.4 \
    start-dev
</code></pre>

{% hint style="warning" %}
Here we use `"start-dev"` but in production use `"start --optimized"`
{% endhint %}
{% endtab %}

{% tab title="Docker - Custom Image" %}
Let's see how you would go about creating a Keycloak Docker image with your theme available.

{% embed url="<https://willwill96.github.io/the-ui-dawg-static-site/en/keycloakify/#integrating-keycloak-and-keycloakify-jar>" %}
Checkout this great tutorial that explains it in great details
{% endembed %}

<pre class="language-bash"><code class="lang-bash">cd ~/github
mkdir docker-keycloak-with-theme
cd docker-keycloak-with-theme
git clone https://github.com/keycloakify/keycloakify-starter
cd keycloakify-starter
# Just to make sure these instructions remain relevant in the future
# We pin the version of the starter we are using.  
git checkout 2553c38272fc76efba8f88c9add6de5ce696ba9d
cd ..

cat &#x3C;&#x3C; EOF > ./Dockerfile
FROM node:18 as keycloakify_jar_builder
RUN apt-get update &#x26;&#x26; \
    apt-get install -y openjdk-17-jdk &#x26;&#x26; \
    apt-get install -y maven;
COPY ./keycloakify-starter/package.json ./keycloakify-starter/yarn.lock /opt/app/
WORKDIR /opt/app
RUN yarn install --frozen-lockfile
COPY ./keycloakify-starter/ /opt/app/
RUN yarn build-keycloak-theme

FROM quay.io/keycloak/keycloak:latest as builder
WORKDIR /opt/keycloak
<strong>COPY --from=keycloakify_jar_builder /opt/app/dist_keycloak/keycloak-theme-for-kc-22-and-above.jar /opt/keycloak/providers/
</strong>RUN /opt/keycloak/bin/kc.sh build

FROM quay.io/keycloak/keycloak:latest
COPY --from=builder /opt/keycloak/ /opt/keycloak/
ENV KC_HOSTNAME=localhost
ENTRYPOINT ["/opt/keycloak/bin/kc.sh", "start-dev"]
EOF

docker build -t docker-keycloak-with-theme .
docker run \
    -e KEYCLOAK_ADMIN=admin \
    -e KEYCLOAK_ADMIN_PASSWORD=admin \
    -p 8080:8080 \
    docker-keycloak-with-theme
</code></pre>

{% hint style="warning" %}
In this Docker file we use `ENTRYPOINT ["/opt/keycloak/bin/kc.sh", "start-dev"]` but in production use `ENTRYPOINT ["/opt/keycloak/bin/kc.sh", "start", "--optimized"]`
{% endhint %}
{% endtab %}

{% tab title="Docker Compose" %}

* Create `docker-compose.yml` for keycloak
* build custom theme from keycloakify and get `.jar` copy and put it some where in same `docker-compose.yml` directory
* in `docker-compose.yml` for example .jar is in themes

```
volumes: 
      - ./themes:/opt/keycloak/providers/
```

^^^ this volums .jar in themes in to `opt/keycloak/providers/` in docker container

{% code title="docker-compose.yml" %}

```yaml
version: '3.7'

services:
  postgres:
    image: postgres:16.2
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    ports:
      - 5432:5432
    networks:
      - keycloak_network

  keycloak:

    image: quay.io/keycloak/keycloak:25.0.2
    command: start-dev

    environment:
      KC_HOSTNAME: ${KEYCLOAK_HOSTNAME}
      KC_HOSTNAME_PORT: 8080
      KC_HTTP_ENABLED: true
      KC_HEALTH_ENABLED: true
      KC_HOSTNAME_STRICT_HTTPS: false
      KC_HOSTNAME_STRICT: false
      
      KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN}
      KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://postgres/${POSTGRES_DB}
      KC_DB_USERNAME: ${POSTGRES_USER}
      KC_DB_PASSWORD: ${POSTGRES_PASSWORD}
    ports:
      - 8080:8080
    volumes: 
      - ./themes:/opt/keycloak/providers/
    restart: unless-stopped
    depends_on:
      - postgres
    networks:
      - keycloak_network

volumes:
  postgres_data:
    driver: local

networks:
  keycloak_network:
    driver: bridge
```

{% endcode %}
{% endtab %}

{% tab title="Helm" %}
If you use [Bitnami's Keycloak Helm chart](https://github.com/bitnami/charts/tree/main/bitnami/keycloak) you can leverage the initContainers parameter to load your theme.

{% code title="Chart.yaml" %}

```yaml
apiVersion: v2
name: keycloak
version: 1.0.0
dependencies:
  - name: keycloak
    version: 21.4.1 # Keycloak 24
    repository: oci://registry-1.docker.io/bitnamicharts
```

{% endcode %}

Here we only list the rellevent values:

<pre class="language-yaml" data-title="values.yaml"><code class="lang-yaml">keycloak:
  initContainers: |
    - name: realm-ext-provider
      image: curlimages/curl
      imagePullPolicy: IfNotPresent
      command:
        - sh
      args:
        - -c
        - |
<strong>          # Replace USER and PROJECT.    
</strong><strong>          curl -L -f -S -o /extensions/keycloakify-starter.jar https://github.com/USER/PROJECT/releases/latest/download/keycloak-theme-for-kc-24.jar
</strong>
      volumeMounts:
        - name: extensions
          mountPath: /extensions

  extraVolumeMounts: |
    - name: extensions
      mountPath: /opt/bitnami/keycloak/providers

  extraVolumes: |
    - name: extensions
      emptyDir: {}
</code></pre>

Read [this section of the starter project readme](https://github.com/keycloakify/keycloakify-starter?tab=readme-ov-file#github-actions) to learn how to get GitHub Action to publish your theme's JAR as assets of your GitHub release.
{% endtab %}

{% tab title="Bare metal" %}
What you need to know is that your keycloak-theme.jar should be placed in the provider directory of your Keycloak (e.g: `/opt/keycloak/providers)`\
After that you should run bin/kc.sh build (e.g: `bash /opt/keycloak/bin/kc.sh build`)

Then you can start your Keycloak server, your theme should be available in it!
{% endtab %}

{% tab title="Cloud-IAM" %}
If you are utilizing a Keycloak instance managed by [Cloud-IAM](https://cloud-iam.com/?mtm_campaign=keycloakify-deal\&mtm_source=keycloakify-doc-header), importing themes and extensions is quite straightforward.

{% hint style="info" %}
Uploading custom JAR files is only available with paid plans.

If you decide to subscribe, please consider using the code `keycloakify5`.

This code will provide you with a 5% discount, and we will also receive 5%, which greatly supports our project!
{% endhint %}

{% embed url="<https://app.tango.us/app/embed/e22aec8f-d7cf-44a7-bfe7-9f92630aa7eb>" %}
{% endtab %}
{% endtabs %}


# Enabling your Theme in the Keycloak Admin Console

Let's see how to enable your theme once you have sucessfully imported it in your Keycloak instance.

<figure><img src="/files/dMjQp9B9szqSqBYKfeVC" alt=""><figcaption></figcaption></figure>

## Enabeling globaly on your realm

{% hint style="warning" %}
In any senario you should never use the Keycloak reserved realm (master) for your application.\
You should create one.
{% endhint %}

The first options is to enable the your themes at the realm level, which mean every applications that uses this realm will get this theme applied.

* Select your realm it the top left corner
* -> Realm settings
* -> "Themes" tab

Here you'll be able to select your login, account and email theme.

## Enabling a theme for a specific client

The login theme can be applied at the client level. You have typically one Keycloak client per web applications.\
Setting the login theme at the client level means that each application of your realm can have different login/register pages. This comes in handy if you're implementing [Theme Variants](/v10/theme-variants).

To enable a login theme on one of your client:

* Select your realm in the top left corner
* -> Clients
* -> Select your client in the list
* -> Scroll down to "Login Theme" and select your theme.

The account theme can only be enabled at the realm level; however, accessing the account pages requires authentication. If you don't want your user to inadvertently come across the default login theme when navigating to the account pages after their session has expired, you might want to enable your login theme on the "account-console" client.

* Select your realm in the top left corner
* -> Clients
* -> Select "account console"
* -> Scroll down to "Login Theme" and select one of your login theme.


# Taking ownership of the kcContext

This documentation explore how to finely controlls what is and isnt included in the `window.kcContext` object.

{% hint style="info" %}
If you simply want to **remove** some specific values from the kcContext you can use the [kcContextExclusionFtl](/v10/configuration-options/kccontextexclusionsftl) option.
{% endhint %}

Some values, like for example the realm attributes (kcContext.realm.attributes) are explicitely excluded from the KcContext.

In the following video we explore how to include them back.

{% embed url="<https://www.youtube.com/watch?v=WdSPrpFObhg>" %}

Note that in the video we includes **all** the realm attributes. We might want to expose only a specific set of values. For this we could do:

{% code title="node\_modules/keycloakify/src/bin/keycloakify/generateFtl/kcContextDeclarationTemplate.ftl" %}

```diff
-    ) || (
-        key == "attributes" &&
-        areSamePath(path, ["realm"])
-    ) || (
+    ) || (
+        areSamePath(path, ["realm", "attributes"]) &&
+        !["myFirstAttribute", "mySecondAttribute"]?seq_contains(key)
+    ) || (
```

{% endcode %}

We could also chose to include only the realm attributes with a specific prefix, for example `theme_`:

{% code title="node\_modules/keycloakify/src/bin/keycloakify/generateFtl/kcContextDeclarationTemplate.ftl" %}

```diff
-    ) || (
-        key == "attributes" &&
-        areSamePath(path, ["realm"])
-    ) || (
+    ) || (
+        areSamePath(path, ["realm", "attributes"]) &&
+        !key?starts_with("theme_")
+    ) || (
```

{% endcode %}

## Setting up patch-package

As explained in the video:

Add [patch-package](https://www.npmjs.com/package/patch-package) add dev dependency

```bash
yarn add --dev patch-package
```

Edit the FreeMarker template that generates the KcContext in:

**node\_modules/keycloakify/src/bin/keycloakify/generateFtl/kcContextDeclarationTemplate.ftl**

You can then create a diff for your changes by running:

```bash
npx patch-package keycloakify
```

Then add a postinstall script to your package.json:

<pre class="language-json" data-title="package.json"><code class="lang-json">{
    "name": "keycloakify-starter",
    "scripts": {
<strong>        "postinstall": "patch-package",
</strong>        "dev": "vite",
</code></pre>

Commit the **patch/** directory that have been created by patch-package.


# Configuration Options

In this folder are listed the different configuration options you can use with Keycloakify.

{% content-ref url="/pages/lyYJe1vliPIjHUwmZeQD" %}
[--project](/v10/configuration-options/project)
{% endcontent-ref %}

{% content-ref url="/pages/U98fY8vwKDRtuz1IOuvi" %}
[keycloakVersionTargets](/v10/configuration-options/keycloakversiontargets)
{% endcontent-ref %}

{% content-ref url="/pages/AvaNq0hbxCrVN81k0niI" %}
[environmentVariables](/v10/configuration-options/environmentvariables)
{% endcontent-ref %}

{% content-ref url="/pages/XDiK2Pu50xXJWew8xaQg" %}
[themeName](/v10/configuration-options/themename)
{% endcontent-ref %}

{% content-ref url="/pages/kpV0OzdDPVIW0cjNoDLd" %}
[themeVersion](/v10/configuration-options/themeversion-1)
{% endcontent-ref %}

{% content-ref url="/pages/w0o6ewUOtanfF1xn5Iln" %}
[postBuild](/v10/configuration-options/postbuild)
{% endcontent-ref %}

{% content-ref url="<https://github.com/keycloakify/docs.keycloakify.dev/blob/v10/configuration-options/broken-reference/README.md>" %}
<https://github.com/keycloakify/docs.keycloakify.dev/blob/v10/configuration-options/broken-reference/README.md>
{% endcontent-ref %}

{% content-ref url="/pages/f5fmptZpvwaTGTJqH3Nh" %}
[XDG\_CACHE\_HOME](/v10/configuration-options/xdg_cache_home)
{% endcontent-ref %}

{% content-ref url="/pages/BRdAJ9xzBonCyU7VYn72" %}
[kcContextExclusionsFtl](/v10/configuration-options/kccontextexclusionsftl)
{% endcontent-ref %}

{% content-ref url="/pages/XueQuPVuZ2Y1jaGrk7tI" %}
[keycloakifyBuildDirPath](/v10/configuration-options/keycloakifybuilddirpath)
{% endcontent-ref %}

{% content-ref url="/pages/jg1gvSUXWUD7A0x7uYs0" %}
[groupId](/v10/configuration-options/groupid)
{% endcontent-ref %}

{% content-ref url="/pages/MrbX3HTvvq0aOBXFWCHe" %}
[artifactId](/v10/configuration-options/artifactid)
{% endcontent-ref %}

{% content-ref url="/pages/vWbxJo07WCQaXCkMLadC" %}
[Webpack specific options](/v10/configuration-options/webpack-specific-options)
{% endcontent-ref %}


# --project

This option is for Monorepos. More specifically, monorepo system that works with a single package.json at the root of the project.

You can run every subcommand of the `keycloakify` CLI tool from the root of your Keycloakify project using the `--project` (or `-p`) option. Example with the `build` command:

```bash
npx keycloakify build -p <path>
```

`<path>` would be typically something like `packages/keycloak-theme`

{% content-ref url="/pages/8BjaGP7OJVRdSWnMVvKz" %}
[As a Subproject of your Monorepo](/v10/keycloakify-in-my-codebase/as-a-subproject-of-your-monorepo)
{% endcontent-ref %}




---

[Next Page](/llms-full.txt/1)

