tally-embed
| Install | |
|---|---|
npm install tally-embed |
|
| Latest Version: | 1.0.0 |
| License: | MIT |
| Last Updated: | Nov 8, 2025 |
| Links: | GitHub · npm |
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.
Related Packages
Embed Metigan forms anywhere: a framework-free core, a DOM renderer, React and V...
Version History
| Version | Released | License |
|---|---|---|
| 1.0.0 | MIT |