Skip to content

Vue

Live projectOpen in StackBlitz

Payment Widget Component

vue
<!-- components/PaymentWidget.vue -->
<template>
  <div ref="containerRef" style="min-height: 560px"></div>
</template>

<script setup lang="ts">

const props = defineProps<{
  theme?: 'light' | 'dark' | 'auto';
}>();

const containerRef = ref<HTMLDivElement>();
let widget: TokenFlightWidget | null = null;

registerWidgetElement();

onMounted(() => {
  if (!containerRef.value) return;

  widget = new TokenFlightWidget({
    container: containerRef.value,
    config: {
      toToken: { chainId: 8453, address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' },
      amount: '100',
      tradeType: 'EXACT_OUTPUT',
      theme: props.theme ?? 'dark',
    },
    callbacks: {
      onSwapSuccess: (data) => {
        console.log('Payment completed:', data.orderId, data.txHash);
      },
      onSwapError: (error) => {
        console.error(`[${error.code}] ${error.message}`);
      },
      onConnectWallet: () => {
        // Open your wallet connection modal
        console.log('Connect wallet requested');
      },
    },
  });

  widget.initialize();
});

onBeforeUnmount(() => {
  widget?.destroy();
  widget = null;
});
</script>

Usage:

vue
<template>
  <main>
    <h1>Payment</h1>
    <PaymentWidget theme="dark" />
  </main>
</template>

<script setup>

</script>

Widget Component

vue
<!-- components/ReceiveWidget.vue -->
<template>
  <div ref="containerRef" style="min-height: 560px"></div>
</template>

<script setup lang="ts">

const props = defineProps<{
  targetChainId: number;
  targetAddress: string;
  amount: string;
  theme?: 'light' | 'dark' | 'auto';
}>();

const containerRef = ref<HTMLDivElement>();
let receive: TokenFlightWidget | null = null;

registerWidgetElement();

onMounted(() => {
  if (!containerRef.value) return;

  receive = new TokenFlightWidget({
    container: containerRef.value,
    config: {
      toToken: {
        chainId: props.targetChainId,
        address: props.targetAddress,
      },
      amount: props.amount,
      tradeType: 'EXACT_OUTPUT',
      theme: props.theme ?? 'dark',
    },
    callbacks: {
      onSwapSuccess: (data) => {
        console.log('Payment received:', data);
      },
      onSwapError: (error) => {
        console.error('Payment error:', error);
      },
    },
  });

  receive.initialize();
});

onBeforeUnmount(() => {
  receive?.destroy();
  receive = null;
});
</script>

Usage:

vue
<template>
  <ReceiveWidget
    :target-chain-id="8453"
    target-address="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
    amount="100"
    theme="dark"
  />
</template>

<script setup>

</script>

With a Wallet Adapter

vue
<!-- components/PaymentWidgetWithAdapter.vue -->
<template>
  <div ref="containerRef" style="min-height: 560px"></div>
</template>

<script setup lang="ts">

const containerRef = ref<HTMLDivElement>();
let widget: TokenFlightWidget | null = null;

onMounted(() => {
  if (!containerRef.value || !window.ethereum) return;

  const adapter = new EthersWalletAdapter(window.ethereum);

  widget = new TokenFlightWidget({
    container: containerRef.value,
    config: {
      toToken: { chainId: 8453, address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' },
      amount: '100',
      tradeType: 'EXACT_OUTPUT',
      theme: 'dark',
    },
    walletAdapter: adapter,
    callbacks: {
      onSwapSuccess: (data) => console.log('Success:', data),
    },
  });

  widget.initialize();
});

onBeforeUnmount(() => {
  widget?.destroy();
  widget = null;
});
</script>

Nuxt 3

For Nuxt, either use <ClientOnly> or name the file .client.vue:

vue
<!-- pages/payment.vue -->
<template>
  <ClientOnly>
    <PaymentWidget theme="dark" />
  </ClientOnly>
</template>

<script setup>

</script>

See SSR Guide for more Nuxt patterns.

Declarative Usage

Vue has excellent Web Component support. You can use the custom HTML tags directly in templates after registering the elements:

Widget (Declarative)

vue
<script setup>

registerWidgetElement();
</script>

<template>
  <tokenflight-widget
    theme="dark"
    locale="en-US"
    to-token="eip155:8453:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
    trade-type="EXACT_OUTPUT"
    amount="100"
  />
</template>

Widget

vue
<script setup>

registerWidgetElement();
</script>

<template>
  <tokenflight-widget
    theme="dark"
    to-token="eip155:8453:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
    trade-type="EXACT_OUTPUT"
    amount="100"
  />
</template>

With Custom Colors

For runtime methods like setCustomColors(), use a template ref:

vue
<script setup>

registerWidgetElement();
const el = ref(null);

onMounted(() => {
  el.value?.setCustomColors({ primary: '#FF007A' });
});
</script>

<template>
  <tokenflight-widget ref="el" theme="dark" to-token="eip155:8453:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" trade-type="EXACT_OUTPUT" amount="100" />
</template>

TIP

Vue's compilerOptions.isCustomElement can suppress unknown-element warnings. Add to vite.config.ts:

ts
vue({
  template: {
    compilerOptions: {
      isCustomElement: (tag) => tag.startsWith('tokenflight-'),
    },
  },
})

When to Use Which

ImperativeDeclarative
Wallet adapterPass via constructorPass via registerWidgetElement()
CallbacksPass via constructorPass via registerWidgetElement()
Custom colorsPass in configUse ref + setCustomColors()
SimplicityMore boilerplateCleaner templates
FlexibilityFull controlLimited to HTML attributes

Use declarative for simple embeds with string-only config. Use imperative when you need wallet adapters, callbacks, or dynamic configuration.

Key Points

  • Use onMounted to initialize — the container must be in the DOM
  • Call destroy() in onBeforeUnmount — cleans up the Shadow DOM on unmount
  • Store the widget instance outside ref() — it doesn't need Vue reactivity
  • For Nuxt, wrap with <ClientOnly> to avoid SSR issues
  • registerWidgetElement() can be called multiple times safely