npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@embarkai/ui-kit

v0.3.4

Published

React UI components and hooks for EmbarkAI authentication and Account Abstraction

Downloads

2,078

Readme

@embarkai/ui-kit

React UI components and hooks for EmbarkAI - a secure, user-friendly authentication and Account Abstraction wallet solution with MPC (Multi-Party Computation) key management.

Features

  • 🔐 Secure Authentication - Multiple auth methods: Email, Passkey, Telegram, Wallet connect
  • 🔑 MPC Key Management - Distributed key generation with iframe isolation
  • 💼 Account Abstraction - ERC-4337 compliant smart contract wallets
  • 🎨 Pre-built UI Components - Ready-to-use React components with customizable themes
  • Easy Integration - Just wrap your app with Provider

Installation

npm install @embarkai/ui-kit
# or
pnpm add @embarkai/ui-kit
# or
yarn add @embarkai/ui-kit

Quick Start

The UI Kit requires @tanstack/react-query for query management & i18next + react-i18next for multilanguages support.

1. QueryClient Setup (Required)

First, create a query client:

// queryClient.ts
import { QueryClient } from '@tanstack/react-query'

export const queryClient = new QueryClient({
  defaultOptions: {}
})

2. I18n config (Required) & typing (Optional)

i18next lib must be initiated for both UIKIT & your app (namespace concept used to manage translations), so your app's language can be switched by UIKIT's lang selector.

There is no need to provide any translations for UIKIT ( has built-in default lang-set ), unless it's not required to expand supported languages by custom langs, but it is required to init i18next inside DApp.

NOTE If you don't need multi-language support inside your Dapp go to step 3 from here providing combineI18NResources() call with no params

First, create the following folder structure for your dapp's translations (or update existant, if needed):

example: common.json

{
  "appname": "My App",
  "signin": "Sign IN"
}
src/
└── i18n/
    ├── locales/
    │   ├── en/
    │   ├──  ├── common.json
    │   ├──  ├── header.json
    │   ├──  ├── page1.json
    │   ├──  ├── page2.json
    │   ├──  └── ...restTranslationsJsons
    │   ├── /...restLocales/...
    └── index.ts

Where:

  • locales/**/*.json files contain i18next translation maps. OPTIONAL Use dedicated files for easier translations maintainance.
  • index.ts exports translation resources

IMPORTANT: YOUR_APP_TRANSLATION_RESOURSES must be structured with namespaces (as shown), where "app" is example namespace

import commonEn from './i18n/locales/en/common.json'
import headerEn from './i18n/locales/en/header.json'
import page1en from './locales/en/page1.json'
import page2en from './locales/en/page2.json'

export const YOUR_APP_TRANSLATION_RESOURSES = {
  // language: { namespace: jsonLocale }
  en: {
    app: {
      common: commonEn,
      header: headerEn,
      page1: page1en,
      page2: page2en
    }
  }
  // ...
} as const

Note: If you don't need multi-language support inside your Dapp leave YOUR_APP_TRANSLATION_RESOURSES empty for now

Decalre types (usualy at src/i18next.d.ts) so t-method provides intellisence typings for your translations. Default locale is recomended to be used for typing as shown

import { YOUR_APP_TRANSLATION_RESOURSES } from './i18n'

declare module 'i18next' {
  interface CustomTypeOptions {
    defaultNS: 'app' //
    resources: {
      app: typeof YOUR_APP_TRANSLATION_RESOURSES.en.app
    }
  }
}

3. Wrap your app with providers & init i18n

Note: Declare your specific dapp's high order useTranslation hook re-expoting namespaced react-i18next useTranslation, since now t-method is typed by your default locale.

import { useTranslation } from 'react-i18next'

export function useT() {
  return useTranslation('app') // this will make t-method accordingly typed by locale
}
import { combineI18NResources, LOCAL_STORAGE_I18N_KEY, Provider } from '@embarkai/ui-kit'
//
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'

import { YOUR_APP_TRANSLATION_RESOURSES } from './i18n'
import { queryClient } from './queryClient'
import { useT } from './useTranslation'

i18n.use(initReactI18next).init({
  resources: combineI18NResources(YOUR_APP_TRANSLATION_RESOURSES), // combineI18NResources() call with no params to skip milti-lang inside dapp
  lng: localStorage.getItem(LOCAL_STORAGE_I18N_KEY) || 'en', // persisted locale
  fallbackLng: 'en', // default
  defaultNS: 'app', // your app i18n-namespace, example: app
  namespace: 'app', // your app i18n-namespace, example: app
  debug: false
})

function YourDapp() {
  const { t } = useT()

  return (
    <>
      <header>
        <h1>{t('common.appname')}</h1>
      </header>

      <main>
        <span>{t('page1.title')}</span>
      </main>
    </>
  )
}

function Root() {
  return (
    <QueryClientProvider client={queryClient}>
      <Provider
        projectId="your-project-id" // Get from EmbarkAI Dashboard
      >
        <YourDapp />
      </Provider>
    </QueryClientProvider>
  )
}

4. Add the ConnectWalletButton

import { ConnectWalletButton } from '@embarkai/ui-kit'

function YourDappComponent() {
  const { t } = useT()

  return (
    <div>
      <h1>{t('common.appname')}</h1>
      <ConnectWalletButton label={t('common.signin')} />
    </div>
  )
}

5. (Optional)

Custom unconnected button can be provided via ConnectButton prop. Prop consumes standart HTMLButton component and will provide required onClick to it

import { ConnectWalletButton } from '@embarkai/ui-kit'

function CustomButtonComponent(props: HTMLAttributes<HTMLButtonElement>) => {
  return (<button {...props}/>)
}

function YourApp() {
  const { t } = useT()

  return (
    <div>
      <h1>My App</h1>
      <ConnectWalletButton label={t('common.signin')} ConnectButton={CustomButtonComponent} />
    </div>
  )
}

That's it! The ConnectWalletButton provides a complete authentication UI with wallet management.

Note: Don't forget to wrap your app with QueryClientProvider from @tanstack/react-query before using Provider, otherwise you'll get an error: "No QueryClient set, use QueryClientProvider to set one"

Configuration Options

Basic Configuration

<Provider
  projectId="your-project-id" // Required
  initialConfig={{
    network: {
      name: 'BSC Testnet',
      symbol: 'BNB',
      chainId: 97, // Default chain for your dApp
      rpcUrl: "https://bnb-testnet.g.alchemy.com/v2/8WLaZS09KaoheJmGa4sXA",
      explorerUrl: "https://testnet.bscscan.com",
      testnet: true,
      forceChain: false
    },
  }}
>

Network Chain Priority:

The SDK uses the following priority for determining the active chain:

  1. dApp config network.forceChain = true && network.chainId provided - Your configured default chain will be forced inside your Dapp with no chain selector available
  2. User's explicit selection - If user manually switched chains in the UI, their choice is preserved (stored in localStorage)
  3. dApp config network.chainId - Preferred chainId. If no forceChain flag provided SwitchChainChange ux pops whenever user is not on preferred chainID
  4. SDK default - BSC

Advanced Configuration

<Provider
  projectId="your-project-id"
  initialConfig={{
    // UI customization
    preferedColorMode?: 'light', // 'light'  | 'dark'

    ui: {
      title: 'Welcome to MyApp',
      subtitle: 'Sign in to continue',

      dialogClassName: 'string',

      useExternalIcons: false, // each social configured auth provider can be supllied by custom icons, flag forces to use provided icons via config, otherwise default icons is used

      authOrder: ['email', 'passkey', 'social'],
    },

    // Authentication providers
    email: {
      enabled: true,
      placeholder: 'Enter your email',
      buttonText: 'Continue',
      verificationTitle: 'Check your email',
    },
    passkey: {
      enabled: true,
      showCreateButton: true,
      primaryButtonText: 'Sign in with Passkey',
    },
    social: {
      enabled: true,
      providers: [
        { id: 'Discord', name: 'Discord', enabled: true, comingSoon: false, icon: 'ReactIconComponent' },
        { id: 'telegram', name: 'Telegram', enabled: true, comingSoon: false, icon: 'ReactIconComponent' },
      ],
    },
    wallet: {
      enabled: true,
      supportedChains: [994873017, 2030232745],
      requireSignature: true,
      walletConnectProjectId: 'your-walletconnect-id',
    },

    // Features
    features: {
      mpcSecurity: true,
      strictMode: false,
      requestDeduplication: true,
      kycNeeded: false,
      displayNameNeeded: false,
      showActiveBalanceAsFiat: false,
    },

    // KYC configuration (if needed)
    kyc: {
      provider: 'sumsub',
      options: { levelName: 'basic-kyc', flowName: 'default' }
    },

    // Warnings
    warnings: {
      backupWarning: true,
      emailNotConnectedWarning: true,
    },

    // Network configuration
    // chainId sets default chain for new users (see "Network Chain Priority" above)
    network: {
      "name": "BSC Testnet",
      "symbol": "BNB",
      "chainId": 97, // Your dApp's default chain
      "rpcUrl": "https\:\/\/bnb-testnet.g.alchemy.com/v2/8WLaZS09KaoheJmGa4sXA",
      "explorerUrl": "https\:\/\/testnet.bscscan.com",
      "testnet": true,
      "forceChain": false // if true, UIKIT is immediatly forced to switch into provided chainId, chain selector via SeetingsMenu becomes anavailable
    }
  }}
  callbacks={{
    onConnecting: ({ method, provider }) => {
      console.info('Connecting with:', method, provider);
    },
    onConnect: ({ address, session }) => {
      console.info('Connected:', address, session);
    },
    onAccount: ({ userId, address, session, hasKeyshare }) => {
      console.info('Account ready:', userId);
    },
    onAccountUpdate: ({ providers }) => {
      console.info('Profile updated:', providers);
    },
    onDisconnect: ({ address, userId }) => {
      console.info('Disconnected:', address);
    },
    onError: ({ error, message }) => {
      console.error('Error:', message);
    },
    onWalletReady: (status) => {
      console.info('Wallet ready:', status.ready);
    },
    onChainChange: ({ chainId, previousChainId }) => {
      console.info('Chain Changed:', { previousChainId, chainId })
    },
  }}
>

Using Hooks

Note: The useSession hook is based on pure Zustand store so if you're already using useSession hook please consider 2 options: 1) refactor state extarction so it uses zustand state extraction feature. 2) consider using dedicated EmbarkAI shared store values hooks: useAccountSession, useAddress etc. Otherwise you might experience excessive re-rendering issues as EmbarkAI shares its internal store and might update some state values which should not affect app render.

import { useAccountSession, useLoadingStatus } from '@embarkai/ui-kit'

function MyComponent() {
  // const session = useSession(s => s.session) - with prev hook & Zustand state extraction feature, please prefer this instead:
  const session = useAccountSession()
  const { isSessionLoading } = useLoadingStatus()

  if (isSessionLoading) return <div>Loading...</div>

  if (!session) {
    return <div>Not authenticated. Please connect your wallet.</div>
  }

  return (
    <div>
      <p>Welcome!</p>
      <p>User ID: {session.userId}</p>
      <p>Wallet Address: {session.ownerAddress}</p>
      <p>Smart Account: {session.accountAddress}</p>
    </div>
  )
}

EmbarkAI hared store values hooks

  • useActiveChainId - Returns current ChainID
  • useIsMobileView - Returns boolean indicating if UI is in mobile view mode
  • useAccountSession - Returns current user session object with userId, addresses, and auth info
  • useLoadingStatus - Returns { isSessionLoading, sessionStatus } for tracking authentication state
  • useBalance - Returns wallet balance data: { walletBalance, fiatBalance, cryptoRate, fiatSymbol, cryptoSymbol }
  • useIFrameReady - Returns boolean indicating if the MPC iframe is ready for operations
  • useAddress - Returns the current user's wallet address
  • useError - Returns any error that occurred during authentication or operations
  • useRecoveryUserId - Returns userId for account recovery flow
  • useHasServerVault - Returns boolean indicating if user has server-side keyshare backup

useSendTransaction - Send Transactions

import { useSendTransaction } from '@embarkai/ui-kit'

function TransactionExample() {
  const { sendTransaction, isPending } = useSendTransaction()

  const handleSend = async () => {
    try {
      const userOpHash = await sendTransaction({
        to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
        value: '1000000000000000000', // 1 ETH in wei
        data: '0x' // Optional contract call data
      })
      console.log('UserOp hash:', userOpHash)
    } catch (error) {
      console.error('Transaction failed:', error)
    }
  }

  return (
    <div>
      <button onClick={handleSend} disabled={isPending}>
        {isPending ? 'Sending...' : 'Send Transaction'}
      </button>
    </div>
  )
}

sendUserOperation - Direct Transaction Submission

For direct control without using the React hook, you can use sendUserOperation function:

import { sendUserOperation, useAccountSession } from '@embarkai/ui-kit'

function DirectTransactionExample() {
  const session = useAccountSession()

  const handleSend = async () => {
    if (!session) {
      console.error('No active session')
      return
    }

    try {
      // Send transaction directly with full control
      const userOpHash = await sendUserOperation(session, {
        to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
        value: '1000000000000000000', // 1 ETH in wei
        data: '0x', // optional contract call
        feeType: 'standard', // (Optional) 'economy' | 'standard' | 'fast'
      })

      console.log('Transaction submitted:', userOpHash)
    } catch (error) {
      console.error('Transaction failed:', error)
    }
  }

  return <button onClick={handleSend}>Send Transaction</button>
}

When to use:

  • ✅ Use useSendTransaction() hook for React components (automatic session management)
  • ✅ Use sendUserOperation() function for custom logic, utility functions, or non-React code

deployAccount - Deploy Smart Account (Optional)

Deploy the smart account contract immediately after registration. This is optional - accounts are automatically deployed on first transaction.

Smart behavior: Automatically checks if account is already deployed and skips transaction to save gas.

import { deployAccount, useAccountSession } from '@embarkai/ui-kit'

function DeployAccountExample() {
  const session = useAccountSession()

  const handleDeploy = async () => {
    if (!session) return

    try {
      // Deploy account with minimal gas cost (skips if already deployed)
      const userOpHash = await deployAccount(session, 'economy')

      if (userOpHash) {
        console.log('Account deployed:', userOpHash)
      } else {
        console.log('Account already deployed, skipped')
      }
    } catch (error) {
      console.error('Deployment failed:', error)
    }
  }

  return <button onClick={handleDeploy}>Deploy Account</button>
}

Return values:

  • Returns userOpHash (string) - if deployment was needed and executed
  • Returns null - if account already deployed (saves gas)

Advanced usage:

// Force deployment even if already deployed (not recommended)
const hash = await deployAccount(session, 'economy', { force: true })

Why use this?

  • Pre-deploy account before first real transaction
  • No user consent required (safe minimal operation)
  • Cleaner UX - separate deployment from first payment
  • Smart gas savings - auto-skips if already deployed
  • ⚠️ Optional - accounts auto-deploy on first transaction anyway

How it works:

  1. Checks if smart account contract exists on-chain
  2. If exists: returns null immediately (no gas cost)
  3. If not exists: sends minimal UserOperation (to=0x0, value=0, data=0x)
  4. Factory deploys contract without user confirmation

signTypedData - Sign EIP712 Structured Messages

Sign structured data according to EIP-712 standard. This is commonly used for off-chain signatures in dApps (e.g., NFT marketplace orders, gasless transactions, permit signatures).

import { signTypedData, useAccountSession } from '@embarkai/ui-kit'

function SignatureExample() {
  const session = useAccountSession()

  const handleSign = async () => {
    if (!session) return

    try {
      // Define EIP712 typed data
      const signature = await signTypedData(session, {
        domain: {
          name: 'MyDApp', // Your dApp name (must match contract)
          version: '1', // Contract version
          chainId: 994,
          verifyingContract: '0x...' // Your contract address (REQUIRED in production!)
        },
        types: {
          Order: [
            { name: 'tokenIds', type: 'uint256[]' },
            { name: 'price', type: 'uint256' },
            { name: 'deadline', type: 'uint256' }
          ]
        },
        primaryType: 'Order',
        message: {
          tokenIds: [1n, 2n, 3n],
          price: 1000000000000000000n, // 1 token in wei
          deadline: BigInt(Math.floor(Date.now() / 1000) + 3600) // 1 hour from now
        }
      })

      console.log('Signature:', signature)

      // Verify signature (optional)
      const { recoverTypedDataAddress } = await import('viem')
      const recoveredAddress = await recoverTypedDataAddress({
        domain: {
          /* same domain */
        },
        types: {
          /* same types */
        },
        primaryType: 'Order',
        message: {
          /* same message */
        },
        signature
      })

      console.log('Signer:', recoveredAddress) // Should match session.ownerAddress
    } catch (error) {
      console.error('Signing failed:', error)
    }
  }

  return <button onClick={handleSign}>Sign Message</button>
}

Important Notes about ERC-4337 Smart Accounts:

In Account Abstraction (ERC-4337), there are two addresses:

  1. Owner Address (EOA) - The address that signs messages/transactions
  2. Smart Account Address - The contract wallet address

⚠️ Critical: The signature is created by the owner address (EOA), NOT the smart account address!

Compatibility with existing protocols:

  • Works: Protocols that verify signatures off-chain (e.g., your backend verifies the owner EOA signature)
  • ⚠️ May not work: Protocols designed for EOA wallets that store and verify against msg.sender or wallet address
    • Example: Uniswap Permit2, some NFT marketplaces
    • These protocols expect the signer address to match the wallet address
    • With smart accounts: signer = owner EOA, wallet = smart account contract
    • Solution: Use ERC-1271 signature validation in your smart contracts (allows contracts to validate signatures)

Domain Configuration:

  • In production, use your actual verifyingContract address (not zero address!)
  • The domain parameters must match exactly between frontend and smart contract
  • The chainId should match the network you're deploying to

Technical Details:

  • Shows a MetaMask-like confirmation modal with structured message preview
  • All BigInt values are supported in the message
  • Signature can be verified using viem.recoverTypedDataAddress() - will return owner EOA address

When to use signTypedData:

  • ✅ Custom backend signature verification (you control the verification logic)
  • ✅ Gasless transactions with meta-transaction relayers
  • ✅ DAO voting and governance (off-chain signatures)
  • ✅ Custom smart contracts with ERC-1271 support
  • ⚠️ Be cautious with protocols designed exclusively for EOA wallets

prepareUserOperation - Prepare for Backend Submission

import { prepareUserOperation, useAccountSession } from '@embarkai/ui-kit'

function BackendSubmissionExample() {
  const session = useAccountSession()

  const handlePrepare = async () => {
    if (!session) return

    // Prepare and sign UserOp without sending to bundler
    const { userOp, userOpHash } = await prepareUserOperation(session, {
      to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
      value: '1000000000000000000', // 1 ETH in wei
      data: '0x',
      feeType: 'standard', // (Optional) 'economy' | 'standard' | 'fast'
    })

    // Send to backend for validation and submission
    await fetch('/api/submit-transaction', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        userOp,
        userOpHash,
        ownerAddress: session.ownerAddress // for signature verification
      })
    })
  }

  return <button onClick={handlePrepare}>Prepare & Send to Backend</button>
}

Backend example (using @embarkai/core):

import { getUserOperationReceipt, sendUserOperationRaw } from '@embarkai/core'
import { getChainConfig } from '@embarkai/core/read'
import { recoverAddress } from 'viem'

const CHAIN_ID = 2030232745

// Get chain config for bundler URL
const chainConfig = getChainConfig(CHAIN_ID)
if (!chainConfig) throw new Error('Unsupported chain')

// Receive from frontend
const { userOp, userOpHash, ownerAddress } = await request.json()

// Verify signature
const recoveredAddress = await recoverAddress({
  hash: userOpHash,
  signature: userOp.signature
})

if (recoveredAddress.toLowerCase() !== ownerAddress.toLowerCase()) {
  throw new Error('Invalid signature')
}

// Submit to bundler - REQUIRES chainId parameter
const submittedUserOpHash = await sendUserOperationRaw(userOp, { chainId: CHAIN_ID })

// Poll for receipt to get transaction hash and status
const waitForReceipt = async (hash: string, maxAttempts = 60, delayMs = 1000) => {
  for (let i = 0; i < maxAttempts; i++) {
    // REQUIRES bundlerUrl parameter
    const receipt = await getUserOperationReceipt(hash as `0x${string}`, chainConfig.bundlerUrl)
    if (receipt) {
      return {
        success: receipt.success,
        transactionHash: receipt.receipt?.transactionHash,
        blockNumber: receipt.receipt?.blockNumber
      }
    }
    await new Promise((resolve) => setTimeout(resolve, delayMs))
  }
  throw new Error('Transaction timeout')
}

const result = await waitForReceipt(submittedUserOpHash)
return {
  success: result.success,
  transactionHash: result.transactionHash,
  blockNumber: result.blockNumber
}

useOpenPage - Programmatic EmbarkAI Dialog Control

Control the EmbarkAI dialog programmatically.

import { PageKey, useOpenPage } from '@embarkai/ui-kit'

function CustomAuthButton() {
  const { isOpen, open: openEmbarkAI, close } = useOpenPage()

  return (
    <div>
      <button onClick={() => openEmbarkAI(PageKey.AUTH)}>Sign In</button>
      <button onClick={() => openEmbarkAI(PageKey.RECEIVE)}>Receive CRYPTO</button>

      <button onClick={close}>Close Dialog</button>
    </div>
  )
}

ThemeToggle - Quick Theme Switcher

Pre-built theme toggle button component to use in combo with useColorMode.

import { ThemeToggle } from '@embarkai/ui-kit'

function AppHeader() {
  return (
    <header>
      <h1>My App</h1>
      <ThemeToggle />
    </header>
  )
}

Authentication Methods

Email OTP

Configured by default, no additional setup needed

Passkey (WebAuthn)

Secure biometric authentication with device passkeys.

// Configured by default
// Users can register passkey after initial login

Telegram Mini App

Authentication via Telegram for mini apps.

// Configure via social providers:
initialConfig={{
  social: {
    enabled: true,
    providers: [
      { id: 'telegram', name: 'Telegram', enabled: true },
    ],
  },
}}

External Wallet

...to be updated

Styling

UIKIT Dialog Window

Global UIKIT window view can be redefined via config prop - dialogClassName.

Note Providing "dialogClassName" will disable default dialog window styles

.uikit-dialog-classname {
  background-color: rgba(14, 14, 14, 0.7);
  backdrop-filter: blur(10px);

  box-shadow:
    0 0 8px rgba(14, 14, 14, 0.1),
    inset 0 0 0 1px var(--embark-ui-bd);
}
import 'index.css'

<Provider
  ...
  initialConfig={{
    ...
    ui: {
      ...
      dialogClassName: 'uikit-dialog-classname',
      ...
    },
    ...
  }}/>

CSSV styles & Dark/Light modes

UIKIT provides global hook for Light/Dark modes - useColorMode - use it within yor application instead of any local mode declaration.

import { useColorMode } from '@embarkai/ui-kit'

function YourAnyComponent() {
  const { colorMode, setColorMode } = useColorMode()

  return (
    <div>
      <p>Current theme: {colorMode}</p>
      <button onClick={() => setColorMode('light')}>Light</button>
      <button onClick={() => setColorMode('dark')}>Dark</button>
    </div>
  )
}

UIKIT styles are possible to match with your project via css-variables set (use separate for dark & light). Values will be automatically applied according to selected colorMode. The simpliest way to customize cssv is via app index.css file:

.embarkai-ui-scope[data-current-theme-mode='light'],
.embarkai-ui-scope[data-current-theme-mode='dark'] {
  /* fixed cssv */
  --embark-ui-maw: 384px;
  --embark-ui-pd: 12px;
  --embark-ui-gap: 10px;
  --embark-ui-bdrs: 20px;
  --embark-ui-element-bdrs: 10px;

  /** overlay */
  --embark-ui-overlay: rgba(255, 255, 255, 0.8);
  --embark-ui-backdrop-blur: 10px;

  /** surface backgrounds */
  --embark-ui-bg: #ffffff;

  /** text */
  --embark-ui-fg: #000000;
  --embark-ui-fg-h: rgba(0, 0, 0, 0.6);
  --embark-ui-fg-a: rgba(0, 0, 0, 0.4);
  --embark-ui-fg-inverted: #ffffff;
  --embark-ui-fg-muted: rgba(0, 0, 0, 0.6);

  /** backgrounds i.e. buttons bg etc */
  --embark-ui-primary: #000000;
  --embark-ui-primary-h: rgba(0, 0, 0, 0.8);
  --embark-ui-primary-a: rgba(0, 0, 0, 0.6);

  --embark-ui-secondary: #e4e4e4;
  --embark-ui-secondary-h: rgba(228, 228, 228, 0.8);
  --embark-ui-secondary-a: rgba(228, 228, 228, 0.6);

  /** borders */
  --embark-ui-bd: #ebebeb;
  --embark-ui-bd-intense: rgb(169, 169, 169);

  /** shadows */
  --embark-ui-shadow-c: rgba(0, 0, 0, 0.1);

  /** highlight colors */
  --embark-ui-info: #000000;
  --embark-ui-bg-info: #e4e4e4;

  --embark-ui-success: #000000;
  --embark-ui-bg-success: #21ff51;

  --embark-ui-warning: #000000;
  --embark-ui-bg-warning: #e9fa00;

  --embark-ui-error: #ffffff;
  --embark-ui-bg-error: #d6204e;
}

TypeScript Support

Full TypeScript support with exported types:

import type { AuthProvider, ProviderConfig, User, WalletInfo } from '@embarkai/ui-kit'

Examples

Check out the /examples directory for complete working examples:

  • React + Vite - Modern React setup with Vite

Security

  • 🔒 MPC Key Management - Keys are split between client and server using threshold cryptography
  • 🏝️ Iframe Isolation - Sensitive operations run in isolated iframe context
  • 🔐 No Private Key Exposure - Private keys never exist in complete form on client
  • Non-custodial - Users maintain full control of their accounts

License

MIT License - see LICENSE file for details.

Links