User interface customization
Customizing user interface elements
Update the theme-options or themeOptions attributes with an object using the following structure:
Details
/** @public */
export interface ThemeOptions {
colors: ThemeOptionsColors
elements: ThemeOptionsElements
}
/** @public */
export interface ThemeOptionsColors {
dark: ThemeOptionsColorsValue
light: ThemeOptionsColorsValue
}
/** @public */
export interface ThemeOptionsColorsValue {
primary: string
onPrimary: string
secondary: string
onSecondary: string
error: string
onError: string
secondaryContainer: string
onSecondaryContainer: string
surface: string
onSurface: string
surfaceVariant: string
onSurfaceVariant: string
}
/** @public */
export interface ThemeOptionsElements {
button: ThemeOptionsElementsButton
camera: ThemeOptionsElementsCamera
cameraCorners: ThemeOptionsElementsCameraCorners
cameraInstructions: ThemeOptionsElementsCameraInstructions
cameraSelect: ThemeOptionsElementsCameraSelect
cameraTip: ThemeOptionsElementsCameraTip
dialog: ThemeOptionsElementsDialog
poweredBy: ThemeOptionsElementsPoweredBy
qrcode: ThemeOptionsElementsQrcode
root: ThemeOptionsElementsRoot
}
/** @public */
export interface ThemeOptionsElementsRoot {
buttonCameraSelect: ThemeOptionsElementsRootButtonCameraSelect
buttonCancel: ThemeOptionsElementsRootButtonCancel
buttonClose: ThemeOptionsElementsRootButtonClose
buttonFlash: ThemeOptionsElementsRootButtonFlash
buttonPin: ThemeOptionsElementsRootButtonPin
buttonsSwitchToMobileChoice: ThemeOptionsElementsRootButtonsSwitchToMobileChoice
cameraBiometric: ThemeOptionsElementsRootCameraBiometric
cameraTip: ThemeOptionsElementsRootCameraTip
headline: ThemeOptionsElementsRootHeadline
host: ThemeOptionsElementsRootHost
poweredBy: ThemeOptionsElementsRootPoweredBy
text: ThemeOptionsElementsRootText
texts: ThemeOptionsElementsRootTexts
}
/** @public */
export interface ThemeOptionsElementsRootButtonCameraSelect {
right: string
top: string
}
/** @public */
export interface ThemeOptionsElementsRootButtonCancel {
left: string
top: string
}
/** @public */
export interface ThemeOptionsElementsRootButtonClose {
left: string
top: string
}
/** @public */
export interface ThemeOptionsElementsRootButtonFlash {
right: string
top: string
}
/** @public */
export interface ThemeOptionsElementsRootButtonPin {
height: string
width: string
}
/** @public */
export interface ThemeOptionsElementsRootButtonsSwitchToMobileChoice {
gap: string
}
/** @public */
export interface ThemeOptionsElementsRootCameraBiometric {
width: string
}
/** @public */
export interface ThemeOptionsElementsRootCameraTip {
top: string
}
/** @public */
export interface ThemeOptionsElementsRootHeadline {
fontSize: string
fontWeight: string
marginTop: string
}
/** @public */
export interface ThemeOptionsElementsRootHost {
borderRadius: string
gap: string
padding: string
}
/** @public */
export interface ThemeOptionsElementsRootPoweredBy {
bottom: string
}
/** @public */
export interface ThemeOptionsElementsRootText {
fontSize: string
fontWeight: string
}
/** @public */
export interface ThemeOptionsElementsRootTexts {
gap: string
}
/** @public */
export interface ThemeOptionsElementsButton {
host: ThemeOptionsElementsButtonHost
size: ThemeOptionsElementsButtonSize
variant: ThemeOptionsElementsButtonVariant
}
/** @public */
export interface ThemeOptionsElementsButtonHost {
borderRadius: string
fontSize: string
fontWeight: string
padding: string
}
/** @public */
export interface ThemeOptionsElementsButtonSizeSmallHost {
fontSize: string
padding: string
}
/** @public */
export interface ThemeOptionsElementsButtonSizeSmall {
host: ThemeOptionsElementsButtonSizeSmallHost
}
/** @public */
export interface ThemeOptionsElementsButtonSize {
small: ThemeOptionsElementsButtonSizeSmall
}
/** @public */
export interface ThemeOptionsElementsButtonVariantTextHostHover {
opacity: string
}
/** @public */
export interface ThemeOptionsElementsButtonVariantTextHost {
borderBottom: string
hover: ThemeOptionsElementsButtonVariantTextHostHover
}
/** @public */
export interface ThemeOptionsElementsButtonVariantText {
host: ThemeOptionsElementsButtonVariantTextHost
}
/** @public */
export interface ThemeOptionsElementsButtonVariant {
text: ThemeOptionsElementsButtonVariantText
}
/** @public */
export interface ThemeOptionsElementsCamera {
host: ThemeOptionsElementsCameraHost
}
/** @public */
export interface ThemeOptionsElementsCameraHost {
after: ThemeOptionsElementsCameraHostAfter
before: ThemeOptionsElementsCameraHostBefore
}
/** @public */
export interface ThemeOptionsElementsCameraHostAfter {
background: string
height: string
}
/** @public */
export interface ThemeOptionsElementsCameraHostBefore {
background: string
height: string
}
/** @public */
export interface ThemeOptionsElementsCameraCorners {
svg: ThemeOptionsElementsCameraCornersSvg
}
/** @public */
export interface ThemeOptionsElementsCameraCornersSvg {
strokeWidth: string
}
/** @public */
export interface ThemeOptionsElementsCameraInstructions {
host: ThemeOptionsElementsCameraInstructionsHost
li: ThemeOptionsElementsCameraInstructionsLi
liText: ThemeOptionsElementsCameraInstructionsLiText
}
/** @public */
export interface ThemeOptionsElementsCameraInstructionsHost {
gap: string
}
/** @public */
export interface ThemeOptionsElementsCameraInstructionsLi {
borderRadius: string
gap: string
padding: string
}
/** @public */
export interface ThemeOptionsElementsCameraInstructionsLiText {
fontSize: string
}
/** @public */
export interface ThemeOptionsElementsCameraSelect {
labels: ThemeOptionsElementsCameraSelectLabels
labelsHeadline: ThemeOptionsElementsCameraSelectLabelsHeadline
labelsText: ThemeOptionsElementsCameraSelectLabelsText
list: ThemeOptionsElementsCameraSelectList
option: ThemeOptionsElementsCameraSelectOption
}
/** @public */
export interface ThemeOptionsElementsCameraSelectLabels {
gap: string
padding: string
}
/** @public */
export interface ThemeOptionsElementsCameraSelectLabelsHeadline {
fontSize: string
fontWeight: string
}
/** @public */
export interface ThemeOptionsElementsCameraSelectLabelsText {
fontSize: string
fontWeight: string
}
/** @public */
export interface ThemeOptionsElementsCameraSelectList {
borderRadius: string
margin: string
padding: string
top: string
}
/** @public */
export interface ThemeOptionsElementsCameraSelectOption {
borderRadius: string
marginTop: string
padding: string
}
/** @public */
export interface ThemeOptionsElementsCameraTip {
backdropFilter: string
borderRadius: string
fontSize: string
fontWeight: string
height: string
padding: string
}
/** @public */
export interface ThemeOptionsElementsDialog {
host: ThemeOptionsElementsDialogHost
}
/** @public */
export interface ThemeOptionsElementsDialogHost {
border: string
borderRadius: string
boxShadow: string
}
/** @public */
export interface ThemeOptionsElementsFaceScanHost {
borderRadius?: string
}
/** @public */
export interface ThemeOptionsElementsFaceScanCorners {
strokeWidth: string
}
/** @public */
export interface ThemeOptionsElementsFaceScanScanLine {
height: string
animationDuration?: string
}
/** @public */
export interface ThemeOptionsElementsFaceScanWireframe {
opacity: string
animationDuration?: string
}
/** @public */
export interface ThemeOptionsElementsFaceScanTransitions {
borderFade?: string
wireframeFade?: string
}
/** @public */
export interface ThemeOptionsElementsPoweredBy {
host: ThemeOptionsElementsPoweredByHost
icon: ThemeOptionsElementsPoweredByIcon
span: ThemeOptionsElementsPoweredBySpan
}
/** @public */
export interface ThemeOptionsElementsPoweredByHost {
gap: string
height: string
}
/** @public */
export interface ThemeOptionsElementsPoweredByIcon {
height: string
width: string
}
/** @public */
export interface ThemeOptionsElementsPoweredBySpan {
fontSize: string
fontWeight: string
letterSpacing: string
}
/** @public */
export interface ThemeOptionsElementsQrcode {
host: ThemeOptionsElementsQrcodeHost
}
/** @public */
export interface ThemeOptionsElementsQrcodeHost {
borderRadius: string
padding: string
}
Interface structure
The theme options structure includes two main properties, colors and elements.
The colors block supports dark mode settings by providing dark and light objects, which each define color settings for the corresponding display mode.
The following diagram shows how each sub-property applies to the user interface.
The properties of the elements object affect the corresponding UI elements. Each property corresponds to a specific user interface element except for the ae property, which refers to the abstraction layer behind the authentication (a) and enroll (e) operations.
Style changes applied to an element property affect the corresponding user interface element.
Default style values
The following example shows the default style values for PingOne Recognize:
Details
{
colors: {
dark: {
primary: '#1833B8',
onPrimary: '#FFFFFF',
secondary: '#FFD900',
onSecondary: '#1A1A1A',
error: '#BA3B1B',
onError: '#FFFFFF',
secondaryContainer: '#2B2B2B',
onSecondaryContainer: '#F8F8F8',
surface: '#14161C',
onSurface: '#F8F8F8',
surfaceVariant: '#2B2B2B',
onSurfaceVariant: '#808080'
},
light: {
primary: '#151E74',
onPrimary: '#FFFFFF',
secondary: '#FFDE33',
onSecondary: '#1A1A1A',
error: '#BA3B1B',
onError: '#FFFFFF',
secondaryContainer: '#F5F5F5',
onSecondaryContainer: '#1A1A1A',
surface: '#FFFFFF',
onSurface: '#1A1A1A',
surfaceVariant: '#F5F5F5',
onSurfaceVariant: '#808080'
}
},
elements: {
button: {
host: {
borderRadius: '8px',
fontSize: '14px',
fontWeight: '500',
padding: '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: '16px 0 calc(16px - 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: '16px'
}
},
cameraTip: {
backdropFilter: 'blur(4px)',
borderRadius: '4px',
fontSize: '10px',
fontWeight: '700',
height: '24px',
padding: '0px 8px'
},
dialog: {
host: {
border: '1px solid',
borderRadius: '16px',
boxShadow: '0px 4px 16px rgba(0, 0, 0, 0.25)'
}
},
poweredBy: {
host: {
gap: '6px',
height: '13px'
},
icon: {
height: '13px',
width: '139px'
},
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: {
top: '14px'
},
headline: {
fontSize: '24px',
fontWeight: '600',
marginTop: '32px'
},
host: {
borderRadius: '16px',
gap: '32px',
padding: '16px 16px 40px 16px'
},
poweredBy: {
bottom: '12px'
},
text: {
fontSize: '16px',
fontWeight: '400'
},
texts: {
gap: '8px'
}
}
}
}
Example: Setting colors
The following example shows how to change the primary and secondary colors by updating the themeOptions property of the PingOne Recognize authorization object (auth):
Details
<!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"
lang="en"
size="375"
theme="light"
transaction-data='DATA_FROM_CUSTOMER_SERVER_TO_BE_SIGNED'
username="USERNAME"
service-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 PingOne Recognize Web SDK uses <slot> elements to customize the following components:
-
<kl-auth> -
<kl-auth-dialog> -
<kl-enroll> -
<kl-enroll-dialog>
Each component supports the following slots:
|
The circle button with the left arrow icon on the top left of the component. When clicked, it reconnects to the WebSocket and restarts the process from the beginning. |
|
The circle button with the X icon on the top right of the component. when clicked, it closes the WebSocket connection and activates the close event used by the dialog components to close the dialog. |
|
The spinner animation. |
|
The checkmark animation. |
|
The error animation. |
|
The title and description block. |
|
The list of camera instructions that the user should follow to perform a successful authentication or enrollment. |
|
The button that checks and potentially requests the camera permission to the user. |
|
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. |
|
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. |
|
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. |
|
The blurry camera stream that stays behind the main camera stream, its purpose is mainly for design. |
|
The main camera stream, useful for the user to adjust their camera quality in realtime. |
|
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. |
|
The QR code that allows the user to continue the flow on their phone. |
|
The retry button, reconnects to the WebSocket and restarts the whole flow from zero. |
|
The powered by PingOne Recognize element, always visible when enabled at the bottom of the component. |
Example: Changing the spinner
This example changes the appearance of the spinner in the authorization component. It defines a custom CSS style rule (#custom-spinner) and then assigns it to the spinner slot of the <kl-auth> element.
Details
<!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"
lang="en"
size="375"
theme="light"
transaction-data='DATA_FROM_CUSTOMER_SERVER_TO_BE_SIGNED'
username="USERNAME"
service-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>
Example: Retry when liveness fails
This example shows how to display components in specific situations. Here:
-
The Retry button is initially hidden by assigning a custom CSS rule to a custom button named
custom-button-retry. -
The button is assigned to the
button-retryslot of the<kl-auth>elements. -
Events are added to the button to control visibility when the button is clicked and when the liveness check fails.