Vue.js integration: component patterns
This page covers the four component integration patterns for Vue.js. If you haven't set up the SDK yet, start with Getting started.
Component mounting patterns
Basic mounting with lifecycle hooks
All Weavr SDK components follow this pattern in Vue:
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from "vue";
// Template ref for the container element
const containerRef = ref<HTMLElement | null>(null);
// SDK instance references (not reactive - they're external objects)
let sdkComponent: any = null;
onMounted(() => {
// DOM is ready, safe to mount
sdkComponent = /* create SDK component */;
sdkComponent.mount(containerRef.value);
});
onUnmounted(() => {
// Clean up to prevent memory leaks
try {
if (sdkComponent && typeof sdkComponent.unmount === "function") {
sdkComponent.unmount();
}
} catch (e) {
console.error("Error unmounting component:", e);
}
});
</script>
<template>
<div ref="containerRef"></div>
</template>
Reusable composable pattern
Create a composable for cleaner component code:
// src/composables/useWeavrComponent.ts
import { ref, onMounted, onUnmounted, type Ref } from "vue";
export function useWeavrComponent<
T extends { mount: (el: HTMLElement | null) => void; unmount: () => void },
>(
factory: () => T | null
): {
containerRef: Ref<HTMLElement | null>;
component: Ref<T | null>;
} {
const containerRef = ref<HTMLElement | null>(null);
const component = ref<T | null>(null) as Ref<T | null>;
onMounted(() => {
const instance = factory();
if (instance && containerRef.value) {
instance.mount(containerRef.value);
component.value = instance;
}
});
onUnmounted(() => {
try {
if (component.value && typeof component.value.unmount === "function") {
component.value.unmount();
}
} catch (e) {
console.error("Error unmounting component:", e);
}
});
return { containerRef, component };
}
Usage:
<script setup lang="ts">
import { useWeavrComponent } from "@/composables/useWeavrComponent";
const form = window.OpcUxSecureClient.form();
const { containerRef } = useWeavrComponent(() =>
form.input("password", "password", { placeholder: "Password" })
);
</script>
<template>
<div ref="containerRef"></div>
</template>
Component patterns
The Weavr SDK components follow a few distinct patterns. This section demonstrates each pattern with a representative example. For component-specific parameters and options, refer to the individual component documentation.
| Pattern | Components | Key characteristics |
|---|---|---|
| Input + tokenizeTokenize Replace a card's primary account number (PAN) with a unique digital token that stands in for the real card during a transaction. When a cardholder adds a card to Apple Pay or Google Pay via push provisioning, the wallet provider stores a device-specific token rather than the underlying PAN, so the real card number isn't exposed on the device or shared with merchants. | Password, Confirm password, Passcode, Confirm passcode | form.input() → form.tokenize() |
| Authenticated display | Card number, CVV, Show card PIN | associate() → span() → mount() |
| Authenticated input | Capture card PIN | associate() → form.input() → tokenize() |
| Full-screen verification | KYC, KYB, Stakeholder due diligence | associate() → init() with callbacks |
Input + tokenize pattern
Used for collecting sensitive credentials (passwords, passcodes). The SDK captures input securely and returns a token for server-side use.
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from "vue";
const inputRef = ref<HTMLElement | null>(null);
const isLoading = ref(false);
const error = ref<string | null>(null);
let form: any = null;
let input: any = null;
onMounted(() => {
if (!window.OpcUxSecureClient) {
error.value = "SDK not loaded";
return;
}
form = window.OpcUxSecureClient.form();
// For Password use: form.input("password", "password", options)
// For Passcode use: form.input("passcode", "passCode", options)
// For Confirm Password use: form.input("confirmPassword", "confirmPassword", options)
input = form.input("password", "password", {
placeholder: "Enter your password",
maxlength: 50,
style: {
base: { fontSize: "16px", color: "#333" },
invalid: { color: "#dc3545" },
},
});
input.mount(inputRef.value);
// Optional: Handle enter key
input.on("submit", () => handleSubmit());
});
onUnmounted(() => {
// Defensive cleanup to prevent errors if SDK component wasn't initialized
try {
if (input && typeof input.unmount === "function") {
input.unmount();
}
} catch (e) {
console.error("Error unmounting component:", e);
}
});
const handleSubmit = async () => {
isLoading.value = true;
error.value = null;
form.tokenize(async (tokens: Record<string, string>) => {
try {
const response = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ password: tokens.password }),
});
if (!response.ok) throw new Error("Authentication failed");
console.log("Success");
} catch (e) {
error.value = e instanceof Error ? e.message : "Failed";
} finally {
isLoading.value = false;
}
});
};
</script>
<template>
<form @submit.prevent="handleSubmit">
<div ref="inputRef" class="weavr-input"></div>
<p v-if="error" class="error">{{ error }}</p>
<button type="submit" :disabled="isLoading">Submit</button>
</form>
</template>
<style scoped>
.weavr-input {
border: 1px solid #ccc;
border-radius: 4px;
padding: 8px;
min-height: 40px;
}
</style>
Authenticated display pattern
Used for displaying sensitive card data (card number, CVVCVV Card Verification Value - the 3-digit security code printed on a payment card, used to authenticate card-not-present transactions. Weavr returns CVV in tokenized form on `GET /managed_cards/{id}` (with a stepped-up token); the value is only detokenized inside the SDK's secure CVV display component., PINPIN Personal Identification Number - the numeric code a cardholder enters to authorize chip-and-PIN purchases and ATM withdrawals. PIN is only present on physical managed cards. Weavr returns it tokenized on `GET /managed_cards/{id}` (with a stepped-up token), and the SDK detokenizes it inside a secure PIN display component.). Requires user authentication via associate() before the data can be shown.
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from "vue";
const displayRef = ref<HTMLElement | null>(null);
const isLoading = ref(true);
const error = ref<string | null>(null);
let span: any = null;
const props = defineProps<{
authToken: string;
token: string; // Tokenized value from API
}>();
onMounted(() => {
if (!window.OpcUxSecureClient) {
error.value = "SDK not loaded";
return;
}
// Must authenticate before displaying sensitive data
window.OpcUxSecureClient.associate(
`Bearer ${props.authToken}`,
() => {
// For Card Number use: span("cardNumber", token)
// For CVV use: span("cvv", token)
// For Card PIN use: span("cardPin", token)
span = window.OpcUxSecureClient.span("cardNumber", props.token, {
style: {
fontSize: "18px",
fontFamily: "monospace",
letterSpacing: "2px",
},
});
span.mount(displayRef.value);
isLoading.value = false;
},
(e) => {
console.error("Association failed:", e);
error.value = "Failed to authenticate";
isLoading.value = false;
}
);
});
onUnmounted(() => {
try {
if (span && typeof span.unmount === "function") {
span.unmount();
}
} catch (e) {
console.error("Error unmounting component:", e);
}
});
</script>
<template>
<div class="secure-display">
<span v-if="isLoading">Loading...</span>
<span v-else-if="error" class="error">{{ error }}</span>
<span ref="displayRef"></span>
</div>
</template>
Authenticated input pattern
Used for capturing card PINsPIN Personal Identification Number - the numeric code a cardholder enters to authorize chip-and-PIN purchases and ATM withdrawals. PIN is only present on physical managed cards. Weavr returns it tokenized on `GET /managed_cards/{id}` (with a stepped-up token), and the SDK detokenizes it inside a secure PIN display component.. Combines authentication (associate()) with the input/tokenizeTokenize Replace a card's primary account number (PAN) with a unique digital token that stands in for the real card during a transaction. When a cardholder adds a card to Apple Pay or Google Pay via push provisioning, the wallet provider stores a device-specific token rather than the underlying PAN, so the real card number isn't exposed on the device or shared with merchants. pattern.
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from "vue";
const inputRef = ref<HTMLElement | null>(null);
const isReady = ref(false);
let form: any = null;
let input: any = null;
const props = defineProps<{
authToken: string;
}>();
const emit = defineEmits<{
(e: "tokenized", token: string): void;
}>();
onMounted(() => {
form = window.OpcUxSecureClient.form();
// Must authenticate before capturing card PIN
window.OpcUxSecureClient.associate(
`Bearer ${props.authToken}`,
() => {
input = form.input("cardPin", "cardPin", {
placeholder: "Enter 4-digit PIN",
});
input.mount(inputRef.value);
input.on("submit", () => handleSubmit());
isReady.value = true;
},
(e) => console.error("Associate failed:", e)
);
});
onUnmounted(() => {
// Defensive cleanup to prevent errors if SDK component wasn't initialized
try {
if (input && typeof input.unmount === "function") {
input.unmount();
}
} catch (e) {
console.error("Error unmounting component:", e);
}
});
const handleSubmit = () => {
form.tokenize((tokens: Record<string, string>) => {
emit("tokenized", tokens.cardPin);
});
};
</script>
<template>
<form @submit.prevent="handleSubmit">
<div ref="inputRef"></div>
<button type="submit" :disabled="!isReady">Set PIN</button>
</form>
</template>
Full-screen verification pattern
Used for KYCKYC Know Your Customer - the identity verification process for consumer identities. This process allows you to seamlessly and securely verify your user's identity. Weavr will ask users to submit the necessary information and documentation so that they can get approved by financial providers./KYBKYB Know Your Business - the identity verification process for corporate identities. This process allows you to seamlessly and securely verify your business customer's identity. Weavr will ask users to submit the necessary information and documentation so that they can get approved by financial providers. identity verification flows. These components take over a container element and manage their own UI.
KYBKYB Know Your Business - the identity verification process for corporate identities. This process allows you to seamlessly and securely verify your business customer's identity. Weavr will ask users to submit the necessary information and documentation so that they can get approved by financial providers. and stakeholder due diligence follow the same pattern with minor differences:
- KYBKYB Know Your Business - the identity verification process for corporate identities. This process allows you to seamlessly and securely verify your business customer's identity. Weavr will ask users to submit the necessary information and documentation so that they can get approved by financial providers.: uses
kyb().init()instead ofconsumer_kyc().init() - Stakeholder due diligence: uses
kyc().init()and doesn't requireassociate()(accessed via email link)
<script setup lang="ts">
import { ref, onMounted } from "vue";
const isLoading = ref(true);
const props = defineProps<{
reference: string; // From API: POST /consumers/kyc or POST /corporates/kyb
authToken: string;
}>();
const emit = defineEmits<{
(e: "complete"): void;
(e: "error", message: string): void;
}>();
onMounted(() => {
if (!window.OpcUxSecureClient) {
emit("error", "SDK not loaded");
return;
}
window.OpcUxSecureClient.associate(
`Bearer ${props.authToken}`,
() => {
// For KYB use: window.OpcUxSecureClient.kyb().init(selector, options, callback, config)
window.OpcUxSecureClient.consumer_kyc().init({
selector: "#kyc-container",
reference: props.reference,
lang: "en",
onMessage: (message: string, additionalInfo: any) => {
console.log("KYC message:", message, additionalInfo);
if (message === "kycSubmitted") {
emit("complete");
}
},
onError: (error: string) => {
console.error("KYC error:", error);
emit("error", error);
},
});
isLoading.value = false;
},
(error) => {
console.error("Association failed:", error);
emit("error", "Failed to authenticate");
isLoading.value = false;
}
);
});
// Note: Full-screen components handle their own cleanup
</script>
<template>
<div>
<div v-if="isLoading" class="loading">Loading verification...</div>
<div id="kyc-container" :class="{ hidden: isLoading }"></div>
</div>
</template>
<style scoped>
#kyc-container {
min-height: 500px;
}
.hidden {
display: none;
}
</style>
Advanced patterns
Modal and dialog integration
When using Weavr components in modals or dialogs, handle visibility carefully:
Using v-if (recommended for complex components)
<script setup lang="ts">
import { ref, watch, nextTick } from "vue";
const showModal = ref(false);
const kycRef = ref<HTMLElement | null>(null);
// Initialize when modal opens
watch(showModal, async (isOpen) => {
if (isOpen) {
await nextTick(); // Wait for DOM update
initializeKYC();
}
});
const initializeKYC = () => {
window.OpcUxSecureClient.consumer_kyc().init({
selector: "#modal-kyc-container",
reference: "your-reference",
// ... other options
});
};
</script>
<template>
<button @click="showModal = true">Start Verification</button>
<Teleport to="body">
<div v-if="showModal" class="modal-overlay">
<div class="modal-content">
<button @click="showModal = false">Close</button>
<div id="modal-kyc-container" ref="kycRef"></div>
</div>
</div>
</Teleport>
</template>
Using v-show (for simple input components)
For simple inputs that need quick show/hide, v-show preserves the mounted state:
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from "vue";
const showPassword = ref(false);
const passwordRef = ref<HTMLElement | null>(null);
let input: any = null;
onMounted(() => {
const form = window.OpcUxSecureClient.form();
input = form.input("password", "password", { placeholder: "Password" });
input.mount(passwordRef.value);
});
onUnmounted(() => {
// Defensive cleanup to prevent errors if SDK component wasn't initialized
try {
if (input && typeof input.unmount === "function") {
input.unmount();
}
} catch (e) {
console.error("Error unmounting component:", e);
}
});
</script>
<template>
<button @click="showPassword = !showPassword">Toggle Password</button>
<div v-show="showPassword" ref="passwordRef"></div>
</template>
Dynamic component loading
Use defineAsyncComponent for code-splitting Weavr-integrated components:
// src/components/LazyKYC.ts
import { defineAsyncComponent } from "vue";
export const LazyKYC = defineAsyncComponent({
loader: () => import("./KYCComponent.vue"),
loadingComponent: {
template: "<div>Loading verification module...</div>",
},
delay: 200,
});
<script setup lang="ts">
import { ref } from "vue";
import { LazyKYC } from "./LazyKYC";
const showKYC = ref(false);
const kycReference = ref("");
const startKYC = async () => {
// Fetch KYC reference from your API
const response = await fetch("/api/kyc/start");
const data = await response.json();
kycReference.value = data.reference;
showKYC.value = true;
};
</script>
<template>
<button @click="startKYC">Start Verification</button>
<LazyKYC v-if="showKYC" :reference="kycReference" />
</template>
Form integration with validation libraries
Integrating Weavr inputs with VeeValidate or other validation libraries:
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from "vue";
import { useForm, useField } from "vee-validate";
import * as yup from "yup";
const schema = yup.object({
email: yup.string().required().email(),
// Password is handled by Weavr, so we track it separately
});
const { handleSubmit, errors } = useForm({ validationSchema: schema });
const { value: email } = useField("email");
const passwordRef = ref<HTMLElement | null>(null);
const passwordToken = ref<string | null>(null);
const passwordError = ref<string | null>(null);
let form: any = null;
let passwordInput: any = null;
onMounted(() => {
form = window.OpcUxSecureClient.form();
passwordInput = form.input("password", "password", {
placeholder: "Password (min 8 characters)",
});
passwordInput.mount(passwordRef.value);
});
onUnmounted(() => {
try {
if (passwordInput && typeof passwordInput.unmount === "function") {
passwordInput.unmount();
}
} catch (e) {
console.error("Error unmounting component:", e);
}
});
const onSubmit = handleSubmit(async (values) => {
// First tokenize the password
form.tokenize(async (tokens: Record<string, string>) => {
if (!tokens.password) {
passwordError.value = "Password is required";
return;
}
try {
const response = await fetch("/api/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: values.email,
password: tokens.password,
}),
});
if (!response.ok) throw new Error("Registration failed");
console.log("Registered successfully");
} catch (e) {
console.error(e);
}
});
});
</script>
<template>
<form @submit="onSubmit">
<div>
<label>Email</label>
<input v-model="email" type="email" />
<span v-if="errors.email">{{ errors.email }}</span>
</div>
<div>
<label>Password</label>
<div ref="passwordRef"></div>
<span v-if="passwordError">{{ passwordError }}</span>
</div>
<button type="submit">Register</button>
</form>
</template>
Multi-component card display
Display multiple card details together:
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from "vue";
const cardNumberRef = ref<HTMLElement | null>(null);
const cvvRef = ref<HTMLElement | null>(null);
const pinRef = ref<HTMLElement | null>(null);
let cardNumberSpan: any = null;
let cvvSpan: any = null;
let pinSpan: any = null;
const props = defineProps<{
authToken: string;
cardNumberToken: string;
cvvToken: string;
pinToken?: string; // Optional for physical cards
}>();
onMounted(() => {
window.OpcUxSecureClient.associate(
`Bearer ${props.authToken}`,
() => {
// Mount all card components
cardNumberSpan = window.OpcUxSecureClient.span(
"cardNumber",
props.cardNumberToken
);
cardNumberSpan.mount(cardNumberRef.value);
cvvSpan = window.OpcUxSecureClient.span("cvv", props.cvvToken);
cvvSpan.mount(cvvRef.value);
if (props.pinToken) {
pinSpan = window.OpcUxSecureClient.span("cardPin", props.pinToken);
pinSpan.mount(pinRef.value);
}
},
(e) => console.error("Associate failed:", e)
);
});
onUnmounted(() => {
try {
if (cardNumberSpan && typeof cardNumberSpan.unmount === "function") {
cardNumberSpan.unmount();
}
if (cvvSpan && typeof cvvSpan.unmount === "function") {
cvvSpan.unmount();
}
if (pinSpan && typeof pinSpan.unmount === "function") {
pinSpan.unmount();
}
} catch (e) {
console.error("Error unmounting components:", e);
}
});
</script>
<template>
<div class="card-display">
<div class="card-field">
<label>Card Number</label>
<span ref="cardNumberRef"></span>
</div>
<div class="card-field">
<label>CVV</label>
<span ref="cvvRef"></span>
</div>
<div v-if="pinToken" class="card-field">
<label>PIN</label>
<span ref="pinRef"></span>
</div>
</div>
</template>
Next steps
- Reusable components-Production-ready wrapper components built on these patterns
- Troubleshooting-Error handling, common pitfalls, and solutions