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

@codebards/ik-embeddable-form

v1.0.39

Published

Production-ready config-driven embeddable form platform with Shadow DOM isolation

Downloads

7,947

Readme

IK Embeddable Form

A production-ready, config-driven embeddable form platform built with Preact, TypeScript, Vite, Tailwind CSS, and Shadow DOM isolation.

Integrating the form on your site? See INTEGRATION.md for step-by-step guides (HTML, WordPress, React), variant usage, custom JSON variants, and fallback behavior.


Table of Contents

  1. Architecture Overview
  2. Installation
  3. Script Usage
  4. Config Schema
  5. Environment Setup
  6. Build & Deployment
  7. Versioning Strategy
  8. Adding New Form Variants
  9. Analytics Integration
  10. API Integration

Architecture Overview

src/
├── core/
│   ├── FormEngine/       # Orchestrates step transitions, state, API calls
│   ├── StepResolver/     # Evaluates conditions, resolves next steps
│   ├── ConfigResolver/   # Resolves FormConfig from OpenConfig + variants
│   ├── ApiClient/        # HTTP client with retries, body/response mapping
│   └── Analytics/        # GTM · Sentry · Clarity · Cookie facades
│
├── components/
│   ├── Modal/            # Accessible modal with backdrop + progress bar
│   ├── FormSteps/        # StepRenderer · AutoStep · SuccessStep · FieldRenderer
│   └── Shared/           # Button · Input · Select · Textarea · Radio · Checkbox
│
├── configs/
│   ├── gql-webinar.base.ts        # Full webinar form superset
│   ├── gql-webinar.config.ts      # Base + bundled variants registry
│   └── variants/                  # Structural overrides (default, india, event)
│
├── hooks/
│   ├── useFormEngine.ts   # Subscribes to FormEngine state
│   └── useAnalytics.ts    # Analytics action hooks
│
├── styles/
│   └── base.css           # Tailwind directives + Shadow DOM resets
│
├── types/
│   └── index.ts           # FormConfig · FormStep · ValidationRule · ApiContract · AnalyticsEvent
│
├── App.tsx                # Root Preact component
└── sdk/
    ├── index.ts           # window.IKForm global API
    └── mount.ts           # Shadow DOM mount/unmount/destroy

Shadow DOM Isolation

The form is mounted inside a Shadow DOM attached to a <div id="ik-form-host"> injected into document.body. This means:

  • Global CSS on the host page cannot bleed into the form
  • Form styles cannot leak out to the host page
  • No class name or selector conflicts

Installation

Via CDN (Recommended)

<script src="https://cdn.example.com/forms/v1.0.11/embed.js" async></script>

Self-hosted

# Build for production
npm run build:production

# Upload dist/embed.js to your CDN / server

npm (for framework integration)

npm install @codebards/ik-embeddable-form
import { IKForm } from '@codebards/ik-embeddable-form';

IKForm.open({
  eventName:   'How to Nail your next Technical Interview',
  webinarType: 'REGULAR',
  site:        'organic',
  variant:     'default',
});

Full integration examples (HTML, WordPress, React, variants, custom JSON): INTEGRATION.md


Script Usage

Basic

<script src="https://cdn.example.com/forms/v1.0.11/embed.js" async></script>
<script>
  window.IKForm.open({
    eventName:   'How to Nail your next Technical Interview',
    webinarType: 'REGULAR',
    site:        'organic',   // or 'learn'
    variant:     'default',   // 'default' | 'india' | 'event' | content variant name
  });
</script>

Common options

window.IKForm.open({
  // Required
  eventName:   'How to Nail your next Technical Interview',
  webinarType: 'REGULAR',
  site:        'learn',
  variant:     'india',

  // Optional
  prefilledValues: {
    email:    '[email protected]',
    fullName: 'Jane Doe',
  },

  configOverrides: {
    layout: {
      leftPanel: { data: { headline: 'Campaign headline' } },
    },
  },

  dataClickId: 'hero_cta',

  // WordPress — theme already loads GTM
  loadGtm:     false,
  loadClarity: false,

  onSuccess: (result) => console.log('Submitted:', result.data),
  onClose:   () => console.log('Closed'),
  onError:   (err) => console.error(err.code, err.message),
});

See INTEGRATION.md for WordPress, React/SPA, variant JSON, and fallback details.

Programmatic Control

window.IKForm.close();
window.IKForm.destroy();              // SPA route change
console.log(window.IKForm.getVersion()); // "1.0.11"

Config Schema

FormConfig (root)

| Property | Type | Required | Description | |------------------|-------------------|----------|--------------------------------------------------| | id | string | ✅ | Unique config identifier | | name | string | ✅ | Human-readable form name | | version | string | ✅ | Semver (e.g. "1.2.0") | | eventName | string | ✅ | Matches OpenConfig.eventName | | webinarType | string | – | Matches OpenConfig.webinarType | | defaultVariant | string | ✅ | Fallback variant ID | | variants | FormVariant[] | – | Named overrides applied on top of base config | | steps | FormStep[] | ✅ | Ordered step definitions | | api | ApiContract | ✅ | Default submission endpoint | | analytics | AnalyticsConfig | – | Open/close/submit event config | | theme | ThemeConfig | – | Visual customisation | | i18n | Record<string, string> | – | Localisation strings | | metadata | Record<string, string \| number \| boolean> | – | Forwarded to analytics |

FormStep

| Property | Type | Description | |------------------|----------------------------------|--------------------------------------------------| | id | string | Unique step identifier | | type | "form" \| "auto" \| "success" \| "error" \| "info" | Step type | | title | string? | Step heading | | subtitle | string? | Step sub-heading | | hidden | boolean? | Hide from progress indicator | | condition | ConditionalRule? | Step only reachable when condition passes | | autoExecute | AutoExecuteConfig? | API call fired automatically on step entry | | fields | FormField[]? | Input fields rendered in this step | | api | ApiContract? | Per-step API call (fires on Next) | | nextStep | string \| ConditionalNextStep[]? | Fixed or conditional next step routing | | allowBack | boolean? | Show Back button (default: true) | | submitLabel | string? | Override CTA label | | analyticsEvents| AnalyticsEvent[]? | Events fired when step becomes active |

FormField

| Property | Type | Description | |----------------|-------------------|--------------------------------------------------| | name | string | Field name (key in form data) | | type | FieldType | text \| email \| tel \| number \| textarea \| select \| radio \| checkbox \| date \| hidden \| slot-picker | | label | string? | Label text | | placeholder | string? | Placeholder text | | defaultValue | string \| number \| boolean? | Initial value | | options | FieldOption[]? | Options for select/radio | | validation | ValidationRule[]? | Array of validation rules | | condition | ConditionalRule? | Show/hide field based on other field values | | colSpan | number? | Grid column span (1 = half, 2 = full) |

ValidationRule

| Property | Type | Description | |--------------|------------------|---------------------------------------------------| | type | ValidationType | required \| email \| phone \| min \| max \| minLength \| maxLength \| pattern \| custom \| async | | value | number \| string? | Threshold or pattern string | | message | string | Error message shown to user | | condition | ConditionalRule? | Only apply this rule when condition is true |

ApiContract

| Property | Type | Description | |-------------------|-----------------------|--------------------------------------------------| | endpoint | string | Relative path or full URL. Supports {fieldName} interpolation | | method | HttpMethod | GET \| POST \| PUT \| PATCH \| DELETE | | bodyMapping | Record<string,string> \| "$all" | Map form fields → request body keys | | responseMapping | Record<string,string>? | Map response JSON paths → form field names | | queryParams | Record<string,string>? | Query params with {fieldName} interpolation| | authenticated | boolean? | Include Authorization: Bearer header | | retries | number? | Retry count (default: 1) | | timeoutMs | number? | Request timeout (default: 10,000ms) | | mockResponse | unknown? | Used in local env instead of real network call |

ConditionalRule

{
  logical?: "AND" | "OR",  // default: "AND"
  clauses: [
    {
      field:    "fieldName",        // dot notation supported: "address.city"
      operator: "equals" | "not_equals" | "contains" | "not_contains" |
                "starts_with" | "ends_with" | "greater_than" | "less_than" |
                "is_empty" | "is_not_empty" | "matches_regex",
      value?:   "some value"        // not needed for is_empty / is_not_empty
    }
  ]
}

Environment Setup

| File | Used For | |-------------------|------------------------| | .env | Local development | | .env.staging | Staging builds | | .env.production | Production builds |

Required Variables

VITE_API_BASE_URL=https://api.example.com/api
VITE_GTM_ID=GTM-XXXXXXX
VITE_SENTRY_DSN=https://[email protected]/xxx
VITE_CLARITY_ID=xxxxxxxxxx

Build & Deployment

Local Dev Server

npm install
npm run dev
# Opens http://localhost:3000 with a test page

Build Commands

npm run build            # Builds with .env (local defaults)
npm run build:staging    # Builds with .env.staging
npm run build:production # Builds with .env.production (minified)

Build Output

dist/
├── embed.js        # Self-contained IIFE bundle (reference in <script>)
└── embed.js.map    # Source map (only in staging/local builds)

Deployment Checklist

  1. Run npm run build:production

  2. Upload dist/embed.js to your CDN with a versioned path:

    https://cdn.example.com/forms/v1.0.0/embed.js
  3. Set cache headers:

    • embed.jsCache-Control: public, max-age=31536000, immutable (versioned path)
    • Use a latest alias for convenience: cdn.example.com/forms/latest/embed.js with shorter TTL
  4. Reference on the host page:

    <script src="https://cdn.example.com/forms/v1.0.0/embed.js" async></script>

Versioning Strategy

This project follows Semantic Versioning (MAJOR.MINOR.PATCH):

| Change Type | Version Bump | Example | |----------------------|-------------|---------| | Breaking API change | MAJOR | 2.0.0 | | New form variant | MINOR | 1.1.0 | | Bug fix / patch | PATCH | 1.0.1 |

Release Process

# 1. Bump version in package.json
npm version patch   # or minor / major

# 2. Build production artefact
npm run build:production

# 3. Tag the release
git tag v$(node -p "require('./package.json').version")
git push --tags

# 4. Upload dist/embed.js to CDN under versioned path

The __VERSION__ constant is automatically injected from package.json at build time.


Adding New Form Variants

The form uses a base config + variant overrides merge pattern. See INTEGRATION.md for host-page usage, JSON schema, resolution order, and fallback behavior.

| Tier | Examples | Where | Deploy needed? | |------|----------|-------|----------------| | Structural | default, india, event | src/configs/variants/*.variant.ts | Yes (code review) | | Content | masterclass, campaign names | CDN JSON at {VITE_FORM_VARIANTS_BASE_URL}/{variant}.json | No (upload JSON only) |

Resolution order: bundled variant → remote JSON → fallback to default (console warning).

Set VITE_FORM_VARIANTS_BASE_URL at build time. Local dev defaults to /form-variants (public/form-variants/masterclass.json).

Bundled variant files

| File | Purpose | |------|---------| | default.variant.ts | Standard flow | | india.variant.ts | Hide primaryGoal on profile step | | event.variant.ts | Hide slot picker; event copy; auto-book slot |

Base config (gql-webinar.base.ts) holds the full superset. Each variant file declares only what differs.

Adding a structural variant (engineering)

  1. Create src/configs/variants/my-variant.variant.ts with only the diff:
import type { FormVariant } from '@/types';

export const myVariant: FormVariant = {
  id: 'my-variant',
  overrides: {
    steps: {
      'profile-details': {
        fields: { primaryGoal: { show: false } },
      },
    },
  },
};
  1. Register in src/configs/variants/index.ts
  2. Deploy embed

Adding a content variant (no code deploy)

  1. Create {variant}.json — see INTEGRATION.md → Create your own variant JSON
  2. Upload to {VITE_FORM_VARIANTS_BASE_URL}/{variant}.json
  3. Landing page: IKForm.open({ ..., variant: 'masterclass' })

Option B — New FormConfig File

For a fundamentally different product flow (not a variant of GQL webinar):

  1. Create src/configs/my-new-form.base.ts
  2. Export a FormConfig with a unique webinarType
  3. Register in src/configs/index.ts
IKForm.open({ webinarType: 'MASTERCLASS', variant: 'default', ... });

Analytics Integration

GTM

The embed can load its own GTM container (per architecture doc) so form conversions work on Lovable / Framer / plain HTML without host-page GTM.

| Setting | Default | Purpose | |---------|---------|---------| | VITE_GTM_ID | GTM-P335R9N (staging/prod) | Container injected when form opens | | loadGtm in IKForm.open() | true | Set false on WordPress if the theme already loads the same container | | gtmContainerId in IKForm.open() | build-time ID | Optional override (e.g. dedicated embed container) |

// Self-contained LP (embed loads GTM)
IKForm.open({
  eventName:   'How to Nail your next Technical Interview',
  webinarType: 'REGULAR',
  site:        'learn',
  variant:     'default',
});

// WordPress page that already has GTM-P335R9N in the theme
IKForm.open({ ..., loadGtm: false, loadClarity: false });

dataLayer events (WordPress parity + embed lifecycle):

| When | Event | |------|--------| | Contact step success | new_webinar_registration_form_submitted | | Slot proceed | wordpress_form_submitted (formName: "Webinar Slot Selection") | | Profile step | pa_new_webinar_registration_form_submitted (conversion — Meta / Google Ads / LinkedIn) | | Form open / step view | form_open, form_step_view, … |

GTM fires marketing pixels. Clickstream (ve2lt1a8il/qa → BigQuery) is separate — see above.

Events are pushed to window.dataLayer. Configure GTM triggers on:

  • form_open — fired when modal opens
  • form_close — fired when modal closes
  • form_step_view — fired on each step
  • form_step_complete — fired after Next/Submit per step
  • form_submit_success — fired on final submission
  • form_submit_error — fired on submission failure

Sentry

Install @sentry/browser and uncomment the placeholder code in src/core/Analytics/sentry.ts. The DSN is injected from VITE_SENTRY_DSN.

Clarity

Ensure the Clarity script is on the host page, or uncomment the injection block in src/core/Analytics/clarity.ts.

Custom Events

Listen on the host page:

window.addEventListener('ikform:analytics', (e) => {
  console.log('IKForm event:', e.detail);
});

API Integration

All API calls are defined in ApiContract objects inside your form config — no hardcoded fetch calls.

Dynamic URL Interpolation

endpoint: 'webinar/{site}/add-info'
// If form data has { site: "organic" }
// → POST https://api.example.com/api/webinar/organic/add-info

Body Mapping

bodyMapping: {
  email:    'user.email',      // form field → nested request body key
  slot:     'booking.slotId',
}
// → { user: { email: "..." }, booking: { slotId: "..." } }

// OR forward all fields:
bodyMapping: '$all'

Response Mapping

responseMapping: {
  'data.registrationId': 'registrationId',  // response path → form field
}
// Writes response.data.registrationId back into form state as "registrationId"

Auth Token

import { apiClient } from '@codebards/ik-embeddable-form';

apiClient.setAuthToken('Bearer your-token-here');

TypeScript Types Reference

import type {
  FormConfig,
  FormStep,
  FormField,
  ValidationRule,
  ApiContract,
  AnalyticsEvent,
  OpenConfig,
  ConditionalRule,
  IKFormSDK,
} from '@codebards/ik-embeddable-form';

License

MIT — see LICENSE