Render a Fillo form as native elements in your React component tree, with no iframe. You end with a form that validates in the browser and saves responses to your Fillo project.
Render a published form
Install the package:
npm install @usefillo/reactPass the form ID or slug from the dashboard's Share panel:
import { FilloForm } from "@usefillo/react";
import "@usefillo/react/styles.css"; // optional default theme
export function FeedbackCard() {
return (
<section aria-labelledby="feedback-title">
<h2 id="feedback-title">Product feedback</h2>
<FilloForm
formId="your-published-form-id"
showTitle={false}
onSubmitted={(responseId) => console.info("Fillo response", responseId)}
/>
</section>
);
}A published form loads and accepts responses without a key. Hide the form title only when the page shows its own heading.
Define the form in code
Keep the schema in your repo with defineForm(). A client with your project's publishable pk_ key lets the form save responses.
import { createClient, defineForm, FilloForm } from "@usefillo/react";
const client = createClient({ key: import.meta.env.VITE_FILLO_KEY });
const feedback = defineForm({
id: "product-feedback",
title: "Product feedback",
pages: [{ id: "main", blocks: [
{ id: "message", kind: "long_text", label: "What should we improve?", required: true },
{ id: "email", kind: "email", label: "Email for a reply" },
] }],
settings: { submitLabel: "Send feedback" },
});
export function Feedback() {
return <FilloForm form={feedback} client={client} />;
}Create the client and the form at module scope, not inside the component.
In a claimed workspace, a changed schema becomes a draft. Publish it in the dashboard or with fillo publish before you rely on it. See the code-defined form lifecycle.
Why the form lives at module scope
The form's identity must not change between renders. Recreating the client or the schema on every render adds work and can hide sync errors. For a Vite app, the VITE_ prefix exposes the key to browser code. The pk_ key is publishable by design. It can't publish a live change.
Props you'll use
| Prop | Use |
|---|---|
formId | A published form ID or slug. No key needed. |
form and client | A code-defined form that saves responses. |
renderOnly | A local preview. It can't save responses or upload files. |
onSubmitted | Runs after Fillo records the response. |
showTitle | Set false when the page supplies its own heading. |
theme, appearance | Design tokens and named slot classes. |
initialData | Prefill answers by field ID. |
respondent | Signed-in account context, hashed on your server. |
Match your styles
Use theme for tokens or appearance for slot classes. Tailwind can target state attributes such as data-[invalid] and data-[selected]. See styling for the slot list.
Check it before you ship
- Tab through every field and the submit button.
- Submit empty and confirm focus moves to the first error.
- Submit with the network off, then retry without retyping.
- Check the pending state, the success state, and a narrow screen.
- Open Responses in Fillo and find the saved answer.
Related
- Custom layout example: Compare the default renderer with host-owned React markup.
- Add a form to Next.js: Use the same component in the App Router.
- Build a shadcn form: Keep your existing inputs with Fillo validation.
- Custom UI: Replace single fields or own the full layout.