Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tally-embed

Framework-agnostic TypeScript library for embedding Tally.so forms with full type safety

A lightweight, type-safe wrapper around Tally's embed script that works with any JavaScript framework (React, Vue, Svelte, Astro, vanilla JS, etc.).

Tally (tally.so) is the simplest way to create forms for free. This library provides a better developer experience when embedding Tally forms in your web applications.

Features

  • Full TypeScript support - Complete type definitions for all Tally options
  • Framework agnostic - Works with React, Vue, Svelte, Astro, vanilla JS, etc.
  • Zero dependencies - Only loads Tally's official embed script
  • Promise-based API - Async/await support for all methods
  • Error handling - Proper error messages for common mistakes
  • SSR-safe - Works in server-side rendering environments
  • Tree-shakeable - Import only what you need

About Tally

Tally.so is a free online form builder that lets you create beautiful forms, surveys, and quizzes without any code. If you're looking for how to embed Tally forms in your website or app, this library makes it easier with TypeScript support and a better developer experience.

Installation

# Using npm
npm install tally-embed

# Using yarn
yarn add tally-embed

# Using pnpm
pnpm add tally-embed

# Using bun
bun add tally-embed

Quick Start

Vanilla JavaScript / TypeScript

import TallyEmbed from "tally-embed";

// Open a popup form
await TallyEmbed.openPopup("YOUR_FORM_ID", {
  layout: "modal",
  width: 500,
  onSubmit: (payload) => {
    console.log("Form submitted:", payload);
  },
});

// Load all embeds on the page
await TallyEmbed.loadEmbeds();

// Close a popup
await TallyEmbed.closePopup("YOUR_FORM_ID");

React

import { useEffect } from "react";
import TallyEmbed from "tally-embed";

function App() {
  useEffect(() => {
    // Load embeds when component mounts
    TallyEmbed.loadEmbeds();
  }, []);

  const openForm = async () => {
    await TallyEmbed.openPopup("YOUR_FORM_ID", {
      layout: "modal",
      overlay: true,
      onSubmit: (payload) => {
        alert(`Thank you! Your response ID: ${payload.responseId}`);
      },
    });
  };

  return (
    <div>
      <button onClick={openForm}>Open Form</button>

      {/* Or use iframe embed */}
      <iframe
        data-tally-src={TallyEmbed.getEmbedUrl("YOUR_FORM_ID", {
          alignLeft: true,
          hideTitle: true,
          dynamicHeight: true,
        })}
        width="100%"
        height="500"
        title="Contact Form"
        style={{ border: 0, margin: 0 }}
      />
    </div>
  );
}

Vue 3

<script setup lang="ts">
import { onMounted } from "vue";
import TallyEmbed from "tally-embed";

onMounted(async () => {
  await TallyEmbed.loadEmbeds();
});

const openForm = async () => {
  await TallyEmbed.openPopup("YOUR_FORM_ID", {
    width: 600,
    alignLeft: false,
    onSubmit: (payload) => {
      console.log("Submitted:", payload);
    },
  });
};
</script>

<template>
  <div>
    <button @click="openForm">Open Form</button>
  </div>
</template>

Svelte

<script lang="ts">
  import { onMount } from 'svelte';
  import TallyEmbed from 'tally-embed';

  onMount(async () => {
    await TallyEmbed.loadEmbeds();
  });

  async function openForm() {
    await TallyEmbed.openPopup('YOUR_FORM_ID', {
      layout: 'default',
      emoji: {
        text: '👋',
        animation: 'wave'
      }
    });
  }
</script>

<button on:click={openForm}>Open Form</button>

Astro

---
import TallyEmbed from 'tally-embed';

const embedUrl = TallyEmbed.getEmbedUrl('YOUR_FORM_ID', {
  alignLeft: true,
  hideTitle: true,
  transparentBackground: true,
  dynamicHeight: true
});
---

<div>
  <iframe
    data-tally-src={embedUrl}
    width="100%"
    height="500"
    title="Tally Form"
    style="border: 0; margin: 0;"
  />
</div>

<script>
  import TallyEmbed from 'tally-embed';

  // Load embeds after page loads
  TallyEmbed.loadEmbeds();
</script>

API Reference

TallyEmbed.loadScript()

Load the Tally embed script. This is called automatically by other methods, but you can call it manually to preload the script.

await TallyEmbed.loadScript();

TallyEmbed.openPopup(formId, options?)

Open a popup form.

await TallyEmbed.openPopup("YOUR_FORM_ID", {
  layout: "modal", // 'default' | 'modal'
  width: 500, // Popup width in pixels
  alignLeft: true, // Align to left side
  hideTitle: false, // Hide form title
  overlay: true, // Show overlay background
  emoji: {
    // Emoji decoration
    text: "👋",
    animation: "wave", // 'wave' | 'heart-beat' | 'flash' | etc.
  },
  autoClose: 3000, // Auto-close after 3 seconds (after submit)
  hiddenFields: {
    // Pass hidden fields
    source: "website",
    campaign: "spring-2025",
  },
  onOpen: () => {
    console.log("Form opened");
  },
  onClose: () => {
    console.log("Form closed");
  },
  onPageView: (page) => {
    console.log("Viewing page:", page);
  },
  onSubmit: (payload) => {
    console.log("Submitted:", payload);
  },
});

TallyEmbed.closePopup(formId)

Close a specific popup form.

await TallyEmbed.closePopup("YOUR_FORM_ID");

TallyEmbed.loadEmbeds()

Load all Tally iframe embeds on the page (elements with data-tally-src attribute).

await TallyEmbed.loadEmbeds();

TallyEmbed.getEmbedUrl(formId, options?)

Generate an embed URL with query parameters.

const url = TallyEmbed.getEmbedUrl('YOUR_FORM_ID', {
  alignLeft: true,
  hideTitle: true,
  transparentBackground: true,
  dynamicHeight: true,
  hiddenFields: {
    source: 'landing-page'
  }
});

// Use in iframe
<iframe data-tally-src={url} ... />

TallyEmbed.createIframe(formId, options?)

Create a fully configured iframe element (client-side only).

const iframe = TallyEmbed.createIframe("YOUR_FORM_ID", {
  alignLeft: true,
  hideTitle: true,
  dynamicHeight: true,
});

document.getElementById("form-container").appendChild(iframe);

TallyEmbed.init(config?)

Initialize Tally with global configuration.

await TallyEmbed.init({
  formId: "YOUR_FORM_ID", // Auto-open this form
  popup: {
    layout: "modal",
    open: {
      trigger: "time", // 'time' | 'exit' | 'scroll'
      ms: 5000, // Wait 5 seconds before opening
    },
  },
});

TallyEmbed.isLoaded()

Check if Tally script is loaded.

if (TallyEmbed.isLoaded()) {
  console.log("Tally is ready!");
}

TypeScript Types

Full type definitions are included:

import type {
  TallyPopupOptions,
  TallyEmbedOptions,
  TallyEmbedConfig,
  TallySubmitPayload,
  TallyLayout,
  TallyEmojiAnimation,
  TallyTrigger,
} from "tally-embed";

Advanced Usage

Show Once per User

await TallyEmbed.openPopup("YOUR_FORM_ID", {
  showOnce: true, // Only show once (uses localStorage)
  key: "my-custom-storage-key", // Optional custom key
});

Don't Show After Submit

await TallyEmbed.openPopup("YOUR_FORM_ID", {
  doNotShowAfterSubmit: true, // Don't show again if user submitted
});

Auto-open with Triggers

// Time-based trigger
await TallyEmbed.init({
  formId: "YOUR_FORM_ID",
  popup: {
    open: {
      trigger: "time",
      ms: 5000, // Open after 5 seconds
    },
  },
});

// Exit intent trigger
await TallyEmbed.init({
  formId: "YOUR_FORM_ID",
  popup: {
    open: {
      trigger: "exit", // Open when user tries to leave
    },
  },
});

// Scroll-based trigger
await TallyEmbed.init({
  formId: "YOUR_FORM_ID",
  popup: {
    open: {
      trigger: "scroll",
      scrollPercent: 50, // Open when user scrolls 50% down
    },
  },
});

Analytics Integration

await TallyEmbed.openPopup("YOUR_FORM_ID", {
  formEventsForwarding: true, // Forward events to Google Analytics/Facebook Pixel
  onPageView: (page) => {
    // Track page views in your analytics
    gtag("event", "form_page_view", { page });
  },
  onSubmit: (payload) => {
    // Track submissions
    gtag("event", "form_submit", {
      form_name: payload.formName,
      response_id: payload.responseId,
    });
  },
});

Error Handling

All methods return Promises and will throw errors if something goes wrong:

try {
  await TallyEmbed.openPopup("YOUR_FORM_ID");
} catch (error) {
  console.error("Failed to open form:", error);
}

SSR Compatibility

The library is SSR-safe and will not throw errors in server-side environments:

// Safe to call during SSR - will only execute in browser
TallyEmbed.loadEmbeds(); // No-op on server

Browser Support

Works in all modern browsers that support:

  • ES2020
  • Promises
  • fetch API
  • IntersectionObserver (for lazy loading)

License

MIT © Farhan Syah

Contributing

Contributions welcome! Please open an issue or PR on GitHub.

Acknowledgments

This library is built on top of Tally.so's official embed script. Tally is a trademark of Tally Forms Inc. This is an unofficial community library and is not affiliated with or endorsed by Tally.so.

Created by Farhan Syah.

About

Framework-agnostic TypeScript library for embedding Tally.so forms with full type safety

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages