Complete guide for embedding forms on your own site with custom styling.
There are three ways to embed SonicJS forms on your website:
| Method | Best For | Styling Control | Difficulty |
|---|---|---|---|
| 1. Direct API Integration | Full control, custom styling | ✅ Full | Medium |
| 2. iFrame Embed | Quick embed, minimal code | Easy | |
| 3. JavaScript Widget | Balance of ease and control | ✅ Good | Easy |
Best for: Developers who want full control over styling and behavior.
// Fetch form schema from SonicJS
const response = await fetch('https://your-sonicjs-site.com/forms/contact_form/schema');
const formData = await response.json();
// Response includes:
// - formData.schema (Form.io JSON schema)
// - formData.submitUrl (where to POST submissions)
// - formData.settings (button text, messages, etc.)<!DOCTYPE html>
<html>
<head>
<!-- Your site's CSS -->
<link rel="stylesheet" href="/your-styles.css">
<!-- Form.io CSS (optional, for base styling) -->
<link rel="stylesheet" href="https://cdn.form.io/formiojs/formio.full.min.css">
</head>
<body>
<!-- Your site layout -->
<div class="your-page-container">
<h1>Contact Us</h1>
<!-- Form container -->
<div id="sonicjs-form"></div>
</div>
<!-- Form.io JS -->
<script src="https://cdn.form.io/formiojs/formio.full.min.js"></script>
<script>
async function loadForm() {
// 1. Fetch schema
const response = await fetch('https://your-sonicjs-site.com/forms/contact_form/schema');
const formData = await response.json();
// 2. Render form
const form = await Formio.createForm(
document.getElementById('sonicjs-form'),
formData.schema,
{
// Custom submit handler
hooks: {
beforeSubmit: (submission, next) => {
next();
}
}
}
);
// 3. Handle submission
form.on('submit', async (submission) => {
try {
const result = await fetch(`https://your-sonicjs-site.com${formData.submitUrl}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data: submission.data })
});
if (result.ok) {
alert(formData.settings.successMessage || 'Form submitted successfully!');
form.emit('submitDone', submission);
}
} catch (error) {
console.error('Submission error:', error);
form.emit('submitError', error);
}
});
}
loadForm();
</script>
</body>
</html>import React, { useEffect, useState } from 'react';
import { Form } from '@formio/react';
function SonicJSForm({ formName }) {
const [formSchema, setFormSchema] = useState(null);
const [submitUrl, setSubmitUrl] = useState('');
useEffect(() => {
async function loadForm() {
const response = await fetch(`https://your-sonicjs-site.com/forms/${formName}/schema`);
const data = await response.json();
setFormSchema(data.schema);
setSubmitUrl(data.submitUrl);
}
loadForm();
}, [formName]);
const handleSubmit = async (submission) => {
const response = await fetch(`https://your-sonicjs-site.com${submitUrl}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data: submission.data })
});
if (response.ok) {
alert('Form submitted successfully!');
}
};
if (!formSchema) return <div>Loading form...</div>;
return (
<div className="my-custom-form-wrapper">
<Form form={formSchema} onSubmit={handleSubmit} />
</div>
);
}
export default SonicJSForm;/* Your site's custom form styles */
.formio-component {
margin-bottom: 1.5rem;
}
.formio-component label {
font-family: 'Your Font', sans-serif;
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 0.5rem;
display: block;
}
.formio-component input,
.formio-component textarea,
.formio-component select {
width: 100%;
padding: 0.75rem;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-family: 'Your Font', sans-serif;
font-size: 16px;
transition: border-color 0.2s;
}
.formio-component input:focus,
.formio-component textarea:focus,
.formio-component select:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
/* Submit button */
.formio-component button[type="submit"] {
background: #3b82f6;
color: white;
padding: 0.75rem 2rem;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
.formio-component button[type="submit"]:hover {
background: #2563eb;
}
/* Error messages */
.formio-errors {
color: #ef4444;
font-size: 14px;
margin-top: 0.25rem;
}
/* Success message */
.formio-component .alert-success {
background: #10b981;
color: white;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
}Best for: Quick embeds when you don't need deep styling customization.
<iframe
src="https://your-sonicjs-site.com/forms/contact_form"
width="100%"
height="600"
frameborder="0"
style="border: 1px solid #e0e0e0; border-radius: 8px;"
></iframe><div style="position: relative; padding-bottom: 100%; height: 0;">
<iframe
src="https://your-sonicjs-site.com/forms/contact_form"
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none;"
></iframe>
</div><iframe
id="sonicjs-form-iframe"
src="https://your-sonicjs-site.com/forms/contact_form"
width="100%"
frameborder="0"
></iframe>
<script>
// Auto-adjust height based on content
window.addEventListener('message', function(event) {
if (event.origin === 'https://your-sonicjs-site.com') {
const iframe = document.getElementById('sonicjs-form-iframe');
if (event.data.type === 'formHeight') {
iframe.style.height = event.data.height + 'px';
}
}
});
</script>- ✅ Control iframe container (border, shadow, margin)
- ❌ Cannot override internal form styles
- ❌ Cannot match your site's fonts/colors exactly
Solution: Use Method 1 (Direct API Integration) for full styling control.
Best for: Non-developers who want a simple embed code with decent styling.
<!-- Simple embed widget (planned feature) -->
<div id="sonicjs-form-widget" data-form="contact_form"></div>
<script src="https://your-sonicjs-site.com/widgets/forms.js"></script>
<script>
SonicJSForms.init({
container: '#sonicjs-form-widget',
form: 'contact_form',
theme: 'auto', // Detects your site's colors
customStyles: {
primaryColor: '#3b82f6',
fontFamily: 'Your Font'
}
});
</script>Don't load Form.io's CSS and write all styles from scratch:
<!-- DON'T include this: -->
<!-- <link rel="stylesheet" href="https://cdn.form.io/formiojs/formio.full.min.css"> -->
<!-- Only include Form.io JS: -->
<script src="https://cdn.form.io/formiojs/formio.full.min.js"></script>Then create your own CSS targeting .formio-component classes.
Load Form.io's CSS as a base, then override specific styles:
<!-- Load base styles -->
<link rel="stylesheet" href="https://cdn.form.io/formiojs/formio.full.min.css">
<!-- Your overrides -->
<style>
/* Override colors */
.formio-component .btn-primary {
background: var(--your-brand-color) !important;
}
/* Override fonts */
.formio-component * {
font-family: var(--your-font-stack) !important;
}
</style>Define CSS variables for easy theming:
:root {
--form-primary-color: #3b82f6;
--form-border-color: #e0e0e0;
--form-border-radius: 8px;
--form-input-padding: 0.75rem;
--form-font-family: 'Inter', sans-serif;
}
.formio-component input,
.formio-component select,
.formio-component textarea {
border: 2px solid var(--form-border-color);
border-radius: var(--form-border-radius);
padding: var(--form-input-padding);
font-family: var(--form-font-family);
}
.formio-component button[type="submit"] {
background: var(--form-primary-color);
border-radius: var(--form-border-radius);
}/* Desktop styles */
.formio-component {
max-width: 600px;
margin: 0 auto;
}
/* Mobile adjustments */
@media (max-width: 768px) {
.formio-component input,
.formio-component textarea,
.formio-component select {
font-size: 16px; /* Prevents iOS zoom */
padding: 1rem;
}
.formio-component button[type="submit"] {
width: 100%;
padding: 1rem;
}
}If your form requires authentication, include credentials:
// Fetch with auth
const response = await fetch('https://your-sonicjs-site.com/forms/private_form/schema', {
credentials: 'include', // Send cookies
headers: {
'Authorization': 'Bearer YOUR_TOKEN' // Or use Bearer token
}
});
// Submit with auth
await fetch(submitUrl, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN'
},
body: JSON.stringify({ data: submission.data })
});To embed forms on external domains, ensure your SonicJS instance allows CORS:
// In your SonicJS wrangler.toml or middleware
app.use('*', async (c, next) => {
c.res.headers.set('Access-Control-Allow-Origin', 'https://your-external-site.com');
c.res.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
c.res.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
c.res.headers.set('Access-Control-Allow-Credentials', 'true');
if (c.req.method === 'OPTIONS') {
return c.text('', 204);
}
await next();
});<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contact Us</title>
<style>
* {
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f9fafb;
padding: 2rem;
margin: 0;
}
.page-container {
max-width: 600px;
margin: 0 auto;
background: white;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
h1 {
color: #1f2937;
margin-bottom: 1.5rem;
}
/* Form styling */
.formio-component {
margin-bottom: 1.5rem;
}
.formio-component label {
display: block;
font-weight: 600;
color: #374151;
margin-bottom: 0.5rem;
}
.formio-component input:not([type="checkbox"]):not([type="radio"]),
.formio-component textarea,
.formio-component select {
width: 100%;
padding: 0.75rem;
border: 2px solid #d1d5db;
border-radius: 8px;
font-size: 16px;
font-family: inherit;
transition: all 0.2s;
}
.formio-component input:focus,
.formio-component textarea:focus,
.formio-component select:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.formio-component button[type="submit"] {
background: #3b82f6;
color: white;
padding: 0.875rem 2rem;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
width: 100%;
transition: background 0.2s;
}
.formio-component button[type="submit"]:hover {
background: #2563eb;
}
.formio-errors {
color: #ef4444;
font-size: 14px;
margin-top: 0.25rem;
}
.alert-success {
background: #10b981;
color: white;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
}
#loading {
text-align: center;
color: #6b7280;
padding: 2rem;
}
</style>
</head>
<body>
<div class="page-container">
<h1>📧 Contact Us</h1>
<div id="loading">Loading form...</div>
<div id="sonicjs-form" style="display: none;"></div>
</div>
<script src="https://cdn.form.io/formiojs/formio.full.min.js"></script>
<script>
const SONICJS_API = 'https://your-sonicjs-site.com';
const FORM_NAME = 'contact_form';
async function loadForm() {
try {
// 1. Fetch form schema
const response = await fetch(`${SONICJS_API}/forms/${FORM_NAME}/schema`);
if (!response.ok) throw new Error('Failed to load form');
const formData = await response.json();
// 2. Hide loading, show form
document.getElementById('loading').style.display = 'none';
document.getElementById('sonicjs-form').style.display = 'block';
// 3. Render form
const form = await Formio.createForm(
document.getElementById('sonicjs-form'),
formData.schema
);
// 4. Handle submission
form.on('submit', async (submission) => {
try {
const result = await fetch(`${SONICJS_API}${formData.submitUrl}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data: submission.data })
});
const data = await result.json();
if (result.ok) {
// Show success message
const successMsg = formData.settings.successMessage || 'Thank you! Your form has been submitted.';
const alertDiv = document.createElement('div');
alertDiv.className = 'alert-success';
alertDiv.textContent = successMsg;
form.element.prepend(alertDiv);
// Reset form
form.emit('submitDone', submission);
form.reset();
// Scroll to top
form.element.scrollIntoView({ behavior: 'smooth' });
} else {
throw new Error(data.error || 'Submission failed');
}
} catch (error) {
console.error('Submission error:', error);
alert('Failed to submit form. Please try again.');
form.emit('submitError', error);
}
});
} catch (error) {
console.error('Error loading form:', error);
document.getElementById('loading').innerHTML =
'<p style="color: #ef4444;">Failed to load form. Please refresh the page.</p>';
}
}
// Load form when page is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', loadForm);
} else {
loadForm();
}
</script>
</body>
</html>- ✅ Check browser console for errors
- ✅ Verify CORS is configured correctly
- ✅ Ensure form is set to "public" in SonicJS admin
- ✅ Use
!importantto override Form.io default styles - ✅ Check CSS specificity (use browser DevTools)
- ✅ Ensure custom CSS loads after Form.io CSS
- ✅ Check network tab for failed POST requests
- ✅ Verify
submitUrlin schema response - ✅ Ensure authentication is handled if required
- Forms Headless Frontend Guide - Framework-specific examples (React, Astro, Angular, Vue, Svelte)
- Forms Quick Reference - Cheat sheet for common tasks
- Form.io Documentation - Official Form.io docs
- SonicJS Forms API - Complete API reference
- 💬 Discord: Join our community for support
- 📧 Email: support@sonicjs.com
- 🐛 Issues: GitHub Issues
- 📖 Docs: Full documentation at docs.sonicjs.com
Created: January 2026
Last Updated: January 2026
Version: 1.0.0