# UI Customization

The web components have extensive theming and customization capabilities, you can fine tune all the colors and tweak most of the relevant CSS styles of every element, and if that’s not enough you can completely replace the single elements through slots, making the components truly 100% customizable.

The theme can be customized through the `theme-options` or `themeOptions` attribute, it accepts an object with the following structure:

```typescript
interface ThemeOptions {
  colors: {
    dark: {
      primary: string
      onPrimary: string
      secondary: string
      onSecondary: string
      secondaryContainer: string
      onSecondaryContainer: string
      surface: string
      onSurface: string
      surfaceVariant: string
      onSurfaceVariant: string
    }
    light: {
      primary: string
      onPrimary: string
      secondary: string
      onSecondary: string
      secondaryContainer: string
      onSecondaryContainer: string
      surface: string
      onSurface: string
      surfaceVariant: string
      onSurfaceVariant: string
    }
  }
  elements: {
    button: {
      host: {
        borderRadius: string
        fontSize: string
        fontWeight: string
        padding: string
      }
      size: {
        small: {
          host: {
            fontSize: string
            padding: string
          }
        }
      }
      variant: {
        text: {
          host: {
            borderBottom: string
            hover: {
              opacity: string
            }
          }
        }
      }
    }
    camera: {
      host: {
        after: {
          background: string
          height: string
        }
        before: {
          background: string
          height: string
        }
      }
    }
    cameraCorners: {
      svg: {
        strokeWidth: string
      }
    }
    cameraInstructions: {
      host: {
        gap: string
      }
      li: {
        borderRadius: string
        gap: string
        padding: string
      }
      liText: {
        fontSize: string
      }
    }
    cameraSelect: {
      labels: {
        gap: string
        padding: string
      }
      labelsHeadline: {
        fontSize: string
        fontWeight: string
      }
      labelsText: {
        fontSize: string
        fontWeight: string
      }
      list: {
        borderRadius: string
        margin: string
        padding: string
        top: string
      }
      option: {
        borderRadius: string
        marginTop: string
        padding: string
      }
    }
    dialog: {
      host: {
        border: string
        borderRadius: string
        boxShadow: string
      }
    }
    poweredBy: {
      host: {
        gap: string
        height: string
      }
      icon: {
        height: string
        width: string
      }
      span: {
        fontSize: string
        fontWeight: string
        letterSpacing: string
      }
    }
    qrcode: {
      host: {
        borderRadius: string
        padding: string
      }
    }
    root: {
      buttonCameraSelect: {
        right: string
        top: string
      }
      buttonCancel: {
        left: string
        top: string
      }
      buttonClose: {
        left: string
        top: string
      }
      buttonFlash: {
        right: string
        top: string
      }
      buttonPin: {
        height: string
        width: string
      }
      buttonsSwitchToMobileChoice: {
        gap: string
      }
      cameraBiometric: {
        width: string
      }
      cameraTip: {
        backdropFilter: string
        borderRadius: string
        fontSize: string
        fontWeight: string
        height: string
        padding: string
        top: string
      }
      headline: {
        fontSize: string
        fontWeight: string
        marginTop: string
      }
      host: {
        borderRadius: string
        gap: string
        padding: string
      }
      poweredBy: {
        bottom: string
      }
      text: {
        fontSize: string
        fontWeight: string
      }
      texts: {
        gap: string
      }
    }
  }
}
```

Let’s quickly explain how this interface is structured, the two main blocks are `colors` and `elements`, as for colors it is quite simple to grasp, two palettes are exposed which are `dark` and `light`, making the components able to support dark and light themes.

To better understand the semantics behind the name of the colors please refer to this illustration:

<figure><img src="https://775767064-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMGtQOGrkVZymnfrV3C8r%2Fuploads%2Fpc5JPaRKXDuEaK2wy5gu%2Fimage.png?alt=media&#x26;token=96d606d8-4651-4dfc-bba7-2d80c710cba2" alt=""><figcaption></figcaption></figure>

Regarding the `elements` block it grants you the capability of changing the styles of all elements, the names are quite straightforward with just one exception: `ae`, this name is a codename that we reserve for the abstraction layer behind the Auth and Enroll elements, you can probably already tell that the `a` stand for Auth and the `e` stands for Enroll.

The `host` key in each element block refers to the root of that element, meaning that the style is applied directly to the target element.

The other names are quite easy to associate to each element on screen, if unsure please try to tweak the values and test if the expected style change was applied, otherwise please ask Keyless for further info.

Finally, here’s the default values that Keyless uses:

```typescript
{
  colors: {
    dark: {
      primary: '#1833B8',
      onPrimary: '#FFFFFF',
      secondary: '#FFD900',
      onSecondary: '#1A1A1A',
      error: '#BA3B1B',
      secondaryContainer: '#2B2B2B',
      onSecondaryContainer: '#F8F8F8',
      surface: '#14161C',
      onSurface: '#F8F8F8',
      surfaceVariant: '#2B2B2B',
      onSurfaceVariant: '#808080'
    },
    light: {
      primary: '#151E74',
      onPrimary: '#FFFFFF',
      secondary: '#FFDE33',
      onSecondary: '#1A1A1A',
      error: '#BA3B1B',
      secondaryContainer: '#F5F5F5',
      onSecondaryContainer: '#1A1A1A',
      surface: '#FFFFFF',
      onSurface: '#1A1A1A',
      surfaceVariant: '#F5F5F5',
      onSurfaceVariant: '#808080'
    }
  },
  elements: {
    button: {
      host: {
        borderRadius: '8px',
        fontSize: '14px',
        fontWeight: '500',
        padding: '14px 16px'
      },
      size: {
        small: {
          host: {
            fontSize: '12px',
            padding: '6px 8px'
          }
        }
      },
      variant: {
        text: {
          host: {
            borderBottom: '1px solid',
            hover: {
              opacity: '0.1'
            }
          }
        }
      }
    },
    camera: {
      host: {
        after: {
          background: 'linear-gradient(0deg, rgba(0, 0, 0, 0.25) 0%, rgba(0, 0, 0, 0) 100%)',
          height: '25%'
        },
        before: {
          background: 'linear-gradient(180deg, rgba(0, 0, 0, 0.25) 0%, rgba(0, 0, 0, 0) 100%)',
          height: '25%'
        }
      }
    },
    cameraCorners: {
      svg: {
        strokeWidth: '3px'
      }
    },
    cameraInstructions: {
      host: {
        gap: '8px'
      },
      li: {
        borderRadius: '8px',
        gap: '16px',
        padding: '16px'
      },
      liText: {
        fontSize: '14px'
      }
    },
    cameraSelect: {
      labels: {
        gap: '12px',
        padding: '24px 0 calc(24px - 8px) 0'
      },
      labelsHeadline: {
        fontSize: '24px',
        fontWeight: '500'
      },
      labelsText: {
        fontSize: '16px',
        fontWeight: '500'
      },
      list: {
        borderRadius: '16px',
        margin: '16px',
        padding: '16px',
        top: '32px !important'
      },
      option: {
        borderRadius: '8px',
        marginTop: '8px',
        padding: '20px 16px'
      }
    },
    dialog: {
      host: {
        border: '1px solid',
        borderRadius: '16px',
        boxShadow: '0px 4px 16px rgba(0, 0, 0, 0.25)'
      }
    },
    poweredBy: {
      host: {
        gap: '6px',
        height: '12px'
      },
      icon: {
        height: '7px',
        width: '55px'
      },
      span: {
        fontSize: '9px',
        fontWeight: '500',
        letterSpacing: '2px'
      }
    },
    qrcode: {
      host: {
        borderRadius: '8px',
        padding: '4px'
      }
    },
    root: {
      buttonCameraSelect: {
        right: 'calc(16px + 18px + 16px)',
        top: '18px'
      },
      buttonCancel: {
        left: '16px',
        top: '16px'
      },
      buttonClose: {
        left: '16px',
        top: '16px'
      },
      buttonFlash: {
        right: '18px',
        top: '18px'
      },
      buttonPin: {
        height: '16px',
        width: '16px'
      },
      buttonsSwitchToMobileChoice: {
        gap: '4px'
      },
      cameraBiometric: {
        width: '100%'
      },
      cameraTip: {
        backdropFilter: 'blur(4px)',
        borderRadius: '4px',
        fontSize: '12px',
        fontWeight: '500',
        height: '24px',
        padding: '0px 8px',
        top: '14px'
      },
      headline: {
        fontSize: '24px',
        fontWeight: '600',
        marginTop: '32px'
      },
      host: {
        borderRadius: '16px',
        gap: '32px',
        padding: '16px 16px 28px 16px'
      },
      poweredBy: {
        bottom: '8px'
      },
      text: {
        fontSize: '16px',
        fontWeight: '400'
      },
      texts: {
        gap: '8px'
      }
    }
  }
}
```

Here’s an example of changing the primary and surface colors:

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Auth</title>
    <style>
      * {
        box-sizing: border-box;
      }

      body {
        align-items: center;
        display: flex;
        justify-content: center;
        margin: 0;
        min-height: 100vh;
        padding: 8px;
      }

      kl-auth {
        border: 1px solid lightgray;
      }
    </style>
  </head>
  <body>
    <kl-auth
      customer="CUSTOMER_NAME"
      enable-camera-instructions
      key="IMAGE_ENCRYPTION_PUBLIC_KEY"
      key-id="IMAGE_ENCRYPTION_KEY_ID"
      lang="en"
      size="375"
      theme="light"
      transaction-data='DATA_FROM_CUSTOMER_SERVER_TO_BE_SIGNED'
      username="USERNAME"
      ws-url="KEYLESS_AUTHENTICATION_SERVICE_URL"
    ></kl-auth>
    <script src="./node_modules/@keyless/sdk-web-components/index.js" type="module"></script>
    <script>
      const auth = document.querySelector('kl-auth')

      auth.themeOptions = {
        colors: {
          light: {
            primary: '#000',
            onPrimary: '#fff',
            surface: '#fff',
            onSurface: '#000'
          }
        }
      }
    </script>
  </body>
</html>
```

### Slots

The slot element is a powerful tool that allows extensive customization in cases like the usage of this package, please refer to the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot) to read more about this technology.

The `kl-auth`, `kl-auth-dialog`, `kl-enroll` and `kl-enroll-dialog` web components support the following slots:

| **Slot**                    | **Description**                                                                                                                                                                                       |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `button-cancel`             | The circle button with the left arrow icon on the top left of the component, its action is reconnecting to the WebSocket and restarting the whole flow from zero.                                     |
| `button-close`              | The circle button with the x icon on the top right of the component, its action is closing the WebSocket connection and emitting the `close` event used by the dialog components to close the dialog. |
| `lottie-spinner`            | The spinner animation.                                                                                                                                                                                |
| `lottie-done`               | The checkmark animation.                                                                                                                                                                              |
| `lottie-error`              | The error animation.                                                                                                                                                                                  |
| `texts`                     | The title and description block.                                                                                                                                                                      |
| `camera-instructions`       | The list of camera instructions that the user should follow to perform a successful authentication or enrollment.                                                                                     |
| `button-camera-permissions` | The button that checks and potentially requests the camera permission to the user.                                                                                                                    |
| `camera-tip`                | The text tip on the top of the component when the cameras are on that suggests to the user how to better frame themself, only shown if camera checks are enabled.                                     |
| `camera-select`             | The select that is shown when the user is on a desktop device and has multiple cameras, grants the user the capability of picking a different camera than the default one.                            |
| `button-flash`              | The circle button with the flash icon on the top right of the component, its action is to force a white background on the screen.                                                                     |
| `camera-backdrop`           | The blurry camera stream that stays behind the main camera stream, its purpose is mainly for design.                                                                                                  |
| `camera-biometric`          | The main camera stream, useful for the user to adjust their camera quality in realtime.                                                                                                               |
| `buttons-stm-choice`        | The switch to mobile buttons, the primary button leads to a qrcode that lets the user continue the flow on their phone. The secondary button lets the user continue the flow on their current device. |
| `stm-qrcode`                | The qrcode that allows the user to continue the flow on their phone.                                                                                                                                  |
| `button-retry`              | The retry button, reconnects to the WebSocket and restarts the whole flow from zero.                                                                                                                  |
| `powered-by`                | The powered by Keyless element, always visible when enabled at the bottom of the component.                                                                                                           |

Let’s look at a few examples on how to use the slots, for example how to change the spinner:

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Auth</title>
    <style>
      * {
        box-sizing: border-box;
      }

      body {
        align-items: center;
        display: flex;
        justify-content: center;
        margin: 0;
        min-height: 100vh;
        padding: 8px;
      }

      kl-auth {
        border: 1px solid lightgray;
      }
      
      #custom-spinner {
        width: 48px;
        height: 48px;
        border: 5px solid black;
        border-bottom-color: transparent;
        border-radius: 50%;
        display: inline-block;
        box-sizing: border-box;
        animation: rotation 1s linear infinite;
      }

      @keyframes rotation {
        0% {
            transform: rotate(0deg);
        }
        100% {
            transform: rotate(360deg);
        }
      }
    </style>
  </head>
  <body>
    <kl-auth
      customer="CUSTOMER_NAME"
      enable-camera-instructions
      key="IMAGE_ENCRYPTION_PUBLIC_KEY"
      key-id="IMAGE_ENCRYPTION_KEY_ID"
      lang="en"
      size="375"
      theme="light"
      transaction-data='DATA_FROM_CUSTOMER_SERVER_TO_BE_SIGNED'
      username="USERNAME"
      ws-url="KEYLESS_AUTHENTICATION_SERVICE_URL"
    >
      <div id="custom-spinner" slot="spinner"></div>
    </kl-auth>
    <script src="./node_modules/@keyless/sdk-web-components/index.js" type="module"></script>
  </body>
</html>
```

Another example that shows how the visibility logic of the retry button could be customized, for example showing the retry button only when the liveness fails:

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Auth</title>
    <style>
      * {
        box-sizing: border-box;
      }

      body {
        align-items: center;
        display: flex;
        justify-content: center;
        margin: 0;
        min-height: 100vh;
        padding: 8px;
      }

      kl-auth {
        border: 1px solid lightgray;
      }

      #custom-button-retry {
        display: none;
      }
    </style>
  </head>
  <body>
    <kl-auth
      customer="CUSTOMER_NAME"
      enable-camera-instructions
      key="IMAGE_ENCRYPTION_PUBLIC_KEY"
      key-id="IMAGE_ENCRYPTION_KEY_ID"
      lang="en"
      size="375"
      theme="light"
      transaction-data='DATA_FROM_CUSTOMER_SERVER_TO_BE_SIGNED'
      username="USERNAME"
      ws-url="KEYLESS_AUTHENTICATION_SERVICE_URL"
    >
      <kl-button async="" id="custom-button-retry" slot="button-retry">Retry</kl-button>
    </kl-auth>
    <script src="./node_modules/@keyless/sdk-web-components/index.js" type="module"></script>
    <script>
      const auth = document.querySelector('kl-auth')
      const customButtonRetry = document.getElementById('custom-button-retry')

      customButtonRetry.addEventListener('button-click', async (event) => {
        await auth.onClickRetry(event)
        customButtonRetry.style.display = 'none'
      })

      auth.addEventListener('liveness-failed', () => {
        customButtonRetry.style.display = 'flex'
      })
    </script>
  </body>
</html>

```
