Compare commits
21 Commits
@formbrick
...
@formbrick
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ba9704af2 | ||
|
|
16254951ba | ||
|
|
86f6811271 | ||
|
|
f8b90505c3 | ||
|
|
f649cdb674 | ||
|
|
d4f0478a77 | ||
|
|
b890570a7d | ||
|
|
f69626a5c7 | ||
|
|
b775d458c4 | ||
|
|
0a02b1a184 | ||
|
|
3b4d5f1446 | ||
|
|
820fcc2c29 | ||
|
|
d435f6ff72 | ||
|
|
ce6269fa6f | ||
|
|
212fa99ab4 | ||
|
|
126700870f | ||
|
|
c4328b8709 | ||
|
|
d687158c0c | ||
|
|
497654bdcd | ||
|
|
b631ee9c11 | ||
|
|
f575e41063 |
22
README.md
@@ -1,6 +1,6 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/formbricks/formbricks">
|
||||
<img src="https://user-images.githubusercontent.com/675065/201035557-a94a6bde-dff0-4bd3-9693-ec9257a9b1b3.svg" alt="Logo" width="500">
|
||||
<img src="https://user-images.githubusercontent.com/675065/203262290-3c2bc5b8-839c-468a-b675-e26a369c7fe2.png" alt="Logo" width="500">
|
||||
</a>
|
||||
<h3 align="center">Formbricks</h3>
|
||||
|
||||
@@ -33,7 +33,7 @@ We're building all essential form functionality so you don't have to. Modular, c
|
||||
|
||||
We want to solve forms once and for all. If, in 10 years, a web developer rewrites core form functionality instead of building on top of our stack, we didn’t do our job. We want you to build your next big thing faster. Our big thing is the last form tool humanity needs. Hold us accountable!
|
||||
|
||||
[Read more in our blog](https://formbricks-com.vercel.app/blog/snoopforms-becomes-formbricks)
|
||||
[Read more in our blog](https://formbricks.com/blog/snoopforms-becomes-formbricks)
|
||||
|
||||
## Our Toolbox
|
||||
|
||||
@@ -46,6 +46,24 @@ Building React forms has never been quicker. But there is more...
|
||||
Loads of question types, validation, multi-page forms, logic jumps, i18n, custom styles - all the good stuff you want, but don't want to build yourself.
|
||||
Building forms fast is great, but where do you pipe your data? And what is it worth without a schema?"
|
||||
|
||||
```jsx
|
||||
import { Form, Text, Textarea, Submit } from "@formbricks/react";
|
||||
import "@formbricks/react/styles.css";
|
||||
|
||||
export default function WaitlistForm() {
|
||||
return (
|
||||
<Form onSubmit={({ data, schema }) => console.log("data:", data, "schema:", schema)}>
|
||||
<Text name="firstname" label="What's your first name?" validation="required" />
|
||||
<Text name="lastname" label="What's your last name?" />
|
||||
<Textarea name="about" label="About you" help="Please keep it short" />
|
||||
<Submit label="Submit" />
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
[Get started with the React Library](https://formbricks.com/docs/react-form-library/introduction)
|
||||
|
||||
### Core API - The OS form engine
|
||||
|
||||
Your form looks perfect? Time to build integrations...
|
||||
|
||||
2
apps/formbricks-com/.gitignore
vendored
@@ -36,3 +36,5 @@ yarn-error.log*
|
||||
next-env.d.ts
|
||||
|
||||
.vscode
|
||||
|
||||
public/sitemap*.xml
|
||||
|
||||
@@ -58,7 +58,7 @@ function Header({ navigation }: any) {
|
||||
variant="secondary"
|
||||
onClick={() => router.push("/")}
|
||||
size="sm"
|
||||
className="ml-10 flex justify-center opacity-60">
|
||||
className="ml-10 hidden justify-center opacity-60 sm:flex">
|
||||
← Back to Mainpage
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -122,7 +122,7 @@ export default function Header() {
|
||||
"group inline-flex items-center rounded-md text-base font-medium hover:text-slate-700 focus:outline-none focus:ring-2 focus:ring-teal-500 focus:ring-offset-2 dark:hover:text-slate-300"
|
||||
)}>
|
||||
<span>Bricks</span>
|
||||
<ChevronDownIcon className="ml-2 h-5 w-5 " aria-hidden="true" />
|
||||
<ChevronDownIcon className="ml-2 h-5 w-5" aria-hidden="true" />
|
||||
</Popover.Button>
|
||||
|
||||
<Transition
|
||||
@@ -294,7 +294,7 @@ export default function Header() {
|
||||
<Popover.Panel
|
||||
focus
|
||||
className="absolute inset-x-0 top-0 z-20 origin-top-right transform p-2 transition md:hidden">
|
||||
<div className="dark:divide-slate divide-y-2 divide-gray-50 rounded-lg bg-gray-100 shadow-lg ring-1 ring-black ring-opacity-5 dark:bg-slate-900">
|
||||
<div className="dark:divide-slate divide-y-2 divide-gray-100 rounded-lg bg-gray-200 shadow-lg ring-1 ring-black ring-opacity-5 dark:divide-gray-700 dark:bg-slate-800">
|
||||
<div className="px-5 pt-5 pb-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
@@ -308,7 +308,7 @@ export default function Header() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav className="relative bg-gray-100 px-5 py-6 dark:bg-slate-900">
|
||||
<nav className="relative bg-gray-200 px-5 py-6 dark:bg-slate-800">
|
||||
<div>
|
||||
<h4 className="mb-3 text-sm text-gray-900 dark:text-gray-300">Form Creation</h4>
|
||||
{creation.map((brick) => (
|
||||
@@ -319,7 +319,11 @@ export default function Header() {
|
||||
brick.status ? "cursor-pointer" : "cursor-default",
|
||||
"-m-3 flex items-start rounded-lg p-3 py-3"
|
||||
)}>
|
||||
<div className="text-brand-dark dark:text-brand-light flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-md sm:h-12 sm:w-12">
|
||||
<div
|
||||
className={clsx(
|
||||
brick.status ? "text-brand-dark dark:text-brand-light" : "text-slate-500",
|
||||
"flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-md sm:h-12 sm:w-12"
|
||||
)}>
|
||||
<brick.icon className="h-6 w-6" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
@@ -335,7 +339,7 @@ export default function Header() {
|
||||
<p
|
||||
className={clsx(
|
||||
brick.status
|
||||
? "text-gray-900 dark:text-gray-300"
|
||||
? "text-gray-900 dark:text-gray-400"
|
||||
: "text-gray-400 dark:text-gray-600",
|
||||
"text-sm"
|
||||
)}>
|
||||
@@ -355,7 +359,11 @@ export default function Header() {
|
||||
brick.status ? "cursor-pointer" : "cursor-default",
|
||||
"-m-3 flex items-start rounded-lg p-3 py-3"
|
||||
)}>
|
||||
<div className="text-brand-dark dark:text-brand-light flex h-10 w-10 flex-shrink-0 items-center justify-center sm:h-12 sm:w-12">
|
||||
<div
|
||||
className={clsx(
|
||||
brick.status ? "text-brand-dark dark:text-brand-light" : "text-slate-500",
|
||||
"flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-md sm:h-12 sm:w-12"
|
||||
)}>
|
||||
<brick.icon className="h-6 w-6" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
@@ -371,7 +379,7 @@ export default function Header() {
|
||||
<p
|
||||
className={clsx(
|
||||
brick.status
|
||||
? "text-gray-900 dark:text-gray-300"
|
||||
? "text-gray-900 dark:text-gray-400"
|
||||
: "text-gray-400 dark:text-gray-600",
|
||||
"text-sm"
|
||||
)}>
|
||||
@@ -391,7 +399,11 @@ export default function Header() {
|
||||
brick.status ? "cursor-pointer" : "cursor-default",
|
||||
"-m-3 flex items-start rounded-lg p-3 py-3"
|
||||
)}>
|
||||
<div className="text-brand-dark dark:text-brand-light flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-md sm:h-12 sm:w-12">
|
||||
<div
|
||||
className={clsx(
|
||||
brick.status ? "text-brand-dark dark:text-brand-light" : "text-slate-500",
|
||||
"flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-md sm:h-12 sm:w-12"
|
||||
)}>
|
||||
<brick.icon className="h-6 w-6" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
@@ -407,7 +419,7 @@ export default function Header() {
|
||||
<p
|
||||
className={clsx(
|
||||
brick.status
|
||||
? "text-gray-900 dark:text-gray-300"
|
||||
? "text-gray-900 dark:text-gray-400"
|
||||
: "text-gray-400 dark:text-gray-600",
|
||||
"text-sm"
|
||||
)}>
|
||||
@@ -432,7 +444,7 @@ export default function Header() {
|
||||
variant="secondary"
|
||||
EndIcon={GitHubIcon}
|
||||
onClick={() => router.push("https://github.com/formbricks/formbricks")}
|
||||
className="flex w-full justify-center">
|
||||
className="flex w-full justify-center fill-slate-800 dark:fill-slate-200">
|
||||
View on Github
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
35
apps/formbricks-com/components/shared/MdxCTA.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import Button from "@/components/shared/Button";
|
||||
import { useRouter } from "next/router";
|
||||
import HeadingCentered from "./HeadingCentered";
|
||||
|
||||
export default function CTA() {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<>
|
||||
<div className="mx-auto py-16 lg:pt-24 lg:pb-40">
|
||||
<p className="text-md text-brand-dark dark:text-brand-light font-semibold uppercase">
|
||||
It's free & open-source
|
||||
</p>
|
||||
<p className="my-0 text-4xl font-semibold tracking-tight text-slate-800 dark:text-slate-100">
|
||||
Try Formbricks right now!
|
||||
</p>
|
||||
<div className="mt-12 grid grid-cols-1 content-center md:grid-cols-2">
|
||||
<div className="-mb-2 rounded-t-xl bg-gradient-to-br from-slate-300 to-slate-200 text-center text-gray-900 dark:from-slate-800 dark:to-slate-900 dark:text-slate-200 md:mb-0 md:ml-2.5 md:-mr-5 md:rounded-l-xl">
|
||||
<h3 className="text-3xl font-bold">Self-hosted</h3>
|
||||
<p className="mt-2 mb-4 dark:text-slate-400">Run locally with docker-compose.</p>
|
||||
<Button variant="secondary" onClick={() => router.push("/docs")} className="mt-3 mb-8 md:mb-0">
|
||||
Read docs
|
||||
</Button>
|
||||
</div>
|
||||
<div className="rounded-xl bg-gradient-to-br from-slate-400 to-slate-300 pb-10 text-center text-gray-800 dark:from-slate-800 dark:to-slate-700 dark:text-slate-200">
|
||||
<h3 className="text-3xl font-bold">Cloud</h3>
|
||||
<p className="mt-2 mb-4 dark:text-slate-400">Use our free managed service.</p>
|
||||
<Button variant="secondary" onClick={() => router.push("/get-started")} className="mt-3">
|
||||
Get started
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -24,7 +24,7 @@ export default function HeadingCentered() {
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center pt-10">
|
||||
<div className="flex h-20 w-full items-center justify-between rounded-lg bg-slate-900 px-8 text-gray-100 ">
|
||||
<div className="flex h-20 w-full items-center justify-between rounded-lg bg-slate-300 px-8 text-slate-700 dark:bg-slate-800 dark:text-slate-200 ">
|
||||
<p>npm install @formbricks/react</p>
|
||||
<button onClick={() => navigator.clipboard.writeText("npm install @formbricks/react")}>
|
||||
<DocumentDuplicateIcon className="h-8 w-8" />
|
||||
|
||||
@@ -49,7 +49,7 @@ export function Search() {
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="group flex h-6 w-6 items-center justify-center sm:justify-start md:h-auto md:w-80 md:flex-none md:rounded-lg md:py-2.5 md:pl-4 md:pr-3.5 md:text-sm md:ring-1 md:ring-slate-200 md:hover:ring-slate-300 dark:md:bg-slate-800/75 dark:md:ring-inset dark:md:ring-white/5 dark:md:hover:bg-slate-700/40 dark:md:hover:ring-slate-500 lg:w-96"
|
||||
className="group flex h-6 w-6 items-center justify-center sm:justify-start md:h-auto md:w-60 md:flex-none md:rounded-lg md:py-2.5 md:pl-4 md:pr-3.5 md:text-sm md:ring-1 md:ring-slate-200 md:hover:ring-slate-300 dark:md:bg-slate-800/75 dark:md:ring-inset dark:md:ring-white/5 dark:md:hover:bg-slate-700/40 dark:md:hover:ring-slate-500 xl:w-80"
|
||||
onClick={onOpen}>
|
||||
<SearchIcon className="h-5 w-5 flex-none fill-slate-400 group-hover:fill-slate-500 dark:fill-slate-500 md:group-hover:fill-slate-400" />
|
||||
<span className="sr-only md:not-sr-only md:ml-2 md:text-slate-500 md:dark:text-slate-400">
|
||||
|
||||
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 361 KiB |
|
After Width: | Height: | Size: 198 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 290 KiB |
|
After Width: | Height: | Size: 2.5 MiB |
@@ -19,6 +19,42 @@ const navigation = [
|
||||
title: "Getting started",
|
||||
href: "/docs/react-form-library/getting-started",
|
||||
},
|
||||
{
|
||||
title: "Link with Formbricks HQ",
|
||||
href: "/docs/react-form-library/link-formbricks-hq",
|
||||
},
|
||||
{
|
||||
title: "Work with Components",
|
||||
href: "/docs/react-form-library/work-with-components",
|
||||
},
|
||||
{
|
||||
title: "Validation & Errors",
|
||||
href: "/docs/react-form-library/validation-errors",
|
||||
},
|
||||
{
|
||||
title: "Form Wrapper",
|
||||
href: "/docs/react-form-library/el-form-wrapper",
|
||||
},
|
||||
{
|
||||
title: "Input: Text",
|
||||
href: "/docs/react-form-library/el-text",
|
||||
},
|
||||
{
|
||||
title: "Input: Textarea",
|
||||
href: "/docs/react-form-library/el-textarea",
|
||||
},
|
||||
{
|
||||
title: "Input: Submit",
|
||||
href: "/docs/react-form-library/el-submit",
|
||||
},
|
||||
{
|
||||
title: "Style with CSS",
|
||||
href: "/docs/react-form-library/style-css",
|
||||
},
|
||||
{
|
||||
title: "Style with Tailwind",
|
||||
href: "/docs/react-form-library/style-tailwind",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import nextMDX from "@next/mdx";
|
||||
import { withPlausibleProxy } from "next-plausible";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import rehypePrism from "@mapbox/rehype-prism";
|
||||
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
@@ -34,7 +35,7 @@ const withMDX = nextMDX({
|
||||
// as the package is ESM only
|
||||
// https://github.com/remarkjs/remark-gfm#install
|
||||
remarkPlugins: [remarkGfm],
|
||||
rehypePlugins: [],
|
||||
rehypePlugins: [rehypePrism],
|
||||
// If you use `MDXProvider`, uncomment the following line.
|
||||
// providerImportSource: "@mdx-js/react",
|
||||
},
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"@docsearch/react": "^3.3.0",
|
||||
"@headlessui/react": "^1.7.3",
|
||||
"@heroicons/react": "^2.0.13",
|
||||
"@mapbox/rehype-prism": "^0.8.0",
|
||||
"@mdx-js/loader": "^2.1.5",
|
||||
"@mdx-js/react": "^2.1.5",
|
||||
"@next/mdx": "^13.0.0",
|
||||
@@ -29,6 +30,7 @@
|
||||
"remark-gfm": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.7",
|
||||
"@types/node": "18.11.6",
|
||||
"@types/react": "18.0.23",
|
||||
|
||||
@@ -44,14 +44,14 @@ export default function Document() {
|
||||
<Head>
|
||||
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/faveicon/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/faveicon/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/faveicon/favicon-16x16.png" />
|
||||
<link rel="manifest" href="/faveicon/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/faveicon/safari-pinned-tab.svg" color="#002941" />
|
||||
<link rel="shortcut icon" href="/faveicon/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png" />
|
||||
<link rel="manifest" href="/favicon/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/favicon/safari-pinned-tab.svg" color="#002941" />
|
||||
<link rel="shortcut icon" href="/favicon/favicon.ico" />
|
||||
<meta name="msapplication-TileColor" content="#002941" />
|
||||
<meta name="msapplication-config" content="/faveicon/browserconfig.xml" />
|
||||
<meta name="msapplication-config" content="/favicon/browserconfig.xml" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
</Head>
|
||||
<body className="bg-slate-100 dark:bg-slate-900">
|
||||
|
||||
222
apps/formbricks-com/pages/best-react-form-library-2023.mdx
Normal file
@@ -0,0 +1,222 @@
|
||||
import LayoutMdx from "@/components/shared/LayoutMdx";
|
||||
import Image from "next/image";
|
||||
import { Callout } from "@/components/shared/Callout";
|
||||
import HeroAnimation from "@/components/shared/HeroAnimation.tsx";
|
||||
import MdxTryItCTA from "@/components/shared/MdxTryItCTA.tsx";
|
||||
import HeaderImage from "/images/SEO/Best React Form Library and Builder 2023 to create and build forms surveys easy and fast.png";
|
||||
import GithubStars from "/images/SEO/Stars - best open source react survey builder 2023 comparison of github repository stars for form builder library.png";
|
||||
import Winner from "/images/SEO/Winner comparison best React survey Library and Builder 2023 2022.png";
|
||||
|
||||
export const meta = {
|
||||
title: "Best React Form Library 2023 (easy form creation)",
|
||||
description:
|
||||
"What is the best form & survey library for React? React forms and surveys can be pretty annoying. We compared three form builder libraries in 2023.",
|
||||
};
|
||||
|
||||
_Building forms in React natively is pretty annoying. Different input mechanisms, validations, data transformations, initial default values... it never ends. Today we will look at tools which makes all of this a whole lot easier. Let’s have a look!_
|
||||
|
||||
<Image
|
||||
src={HeaderImage}
|
||||
alt="Best React form library builder comparison between Formik Formbricks and React Form Builder 2"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
We’re bringing you three different solutions with different pros and cons. But first...
|
||||
|
||||
<Callout title="What's your objective?" type="note">
|
||||
Do you want to build a highly customized form optimized to the last millisecond? Do you want a slick,
|
||||
multi-page survey? Or do you need a visual editor to have your users create forms? Anyways, we've got you
|
||||
covered!
|
||||
</Callout>
|
||||
|
||||
## What we compare: Ease of form creation, custom styling and additional work required
|
||||
|
||||
We have tested 12 different libraries to share the 3 most compelling ones with you. In order to build exactly the form you envision in the shortest amount of time possible, we shine light one these three aspects of the contending React form libraries:
|
||||
|
||||
### 1. Ease of building forms
|
||||
|
||||
Here we looked at the overall developer experience and the “completeness” of the solution. In other words: Will you be able to build the form you want just with this library? And how fast?
|
||||
|
||||
### 2. Ease of custom styling
|
||||
|
||||
In many cases forms have to have certain look & feel to match the application you are building. Here we look at how easy it is to get this look & feel done.
|
||||
|
||||
### 3. Additional work required to access data
|
||||
|
||||
When the form is done the next question comes up: Where do I pipe the submissions to? And how can I or someone else analyze them? Here we look at how quickly you can achieve your ultimate goal which is not creating forms but gathering and accessing qualitative data.
|
||||
|
||||
Now that we have this cleared out of the way, let’s look at how popular the solutions are:
|
||||
|
||||
<Image
|
||||
src={GithubStars}
|
||||
alt="Comparison of GitHub Stars of the best react form builder libraries 2023"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
As you can pretty well see, **Formik** plays in a different league. Launched 4 years ago, it has accumulated over 31k stars - kudos!
|
||||
|
||||
**React Form Builder 2** is almost as old as Formik but never took off. However, very recently it is more actively developed and starts picking up traction (6k weekly NPM downloads).
|
||||
|
||||
**Formbricks** is a pretty new repository which collected 1.1k stars over the past couple of weeks.
|
||||
|
||||
Let’s have a closer look!
|
||||
|
||||
## Closer look: Who is competing?
|
||||
|
||||
### 1. Formbricks React Form Library
|
||||
|
||||
The Formbricks [React Library](/react-form-library) is the newests kid on the block. While still early, it looks very promising. It uses the impressive performance of React Hook Forms with the objective to make it faster and easier to use. With a modern developer experience and a growing number of HTML and non-HTML question-types, it’s a sweet option for most use cases.
|
||||
|
||||
<Callout title="No backend needed" type="note">
|
||||
Even though Formbricks React works as a standalone solution, the full power is unleashed when using it with the [self-hostable dashboard](/form-hq). Setup webhooks, integrations & email notifications or directly view at your data.
|
||||
|
||||
</Callout>
|
||||
|
||||
### 2. Formik
|
||||
|
||||
No React form generator comparison post can do without Formik. Formik is alongside React Hook Forms by far the most popular library to build forms in React (5M weekly downloads combined). It’s fast, it’s comprehensive and it’s battletested. It comes with input validation, formatting, masking, arrays, and error handling.
|
||||
|
||||
### 3. React Form Builder 2
|
||||
|
||||
The React Form Builder 2 adds a visual builder to the mix. It comes with React Drag and Drop out of the box that interfaces with a JSON endpoint to load and save generated forms via a schema. It contains 16 question types ranging from star rating question to a signature field. The updated version 2 can be extended with custom components easily. It is a hobby project which picked up quite a bit of traction recently.
|
||||
|
||||
<Callout title="In development" type="warning">
|
||||
The Formbricks React Library is currently in alpha testing. Most features are still in development. Follow
|
||||
us on [Twitter](https://twitter.com/formbricks) to stay uptodate on the release!
|
||||
</Callout>
|
||||
|
||||
##
|
||||
|
||||
# Overview
|
||||
|
||||
Before we get into the details, here is the overview for you:
|
||||
|
||||
| React Library | Ease of building forms | Ease of custom styling | Additional work required |
|
||||
| -------------------- | ---------------------- | ---------------------- | ------------------------ |
|
||||
| Formbricks React | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||||
| Formik | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
|
||||
| React Form Builder 2 | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐ |
|
||||
|
||||
## Ease of building forms
|
||||
|
||||
When you made a decision on which React Library to use, the last thing you want is to go out and look for another package just because your initial choice lacks e.g. a slider component.
|
||||
|
||||
**Formbricks React** currently offers 8 question types and keeps adding more in a fast pace. Check out [the repository](https://formbricks.com/github) for more details.
|
||||
|
||||
**React Form Builder** comes with 16 questions types out of the box. It includes star-rating questions, NPS questions, etc. Additionally, the second version makes it really easy to add your custom components to it, if you want.
|
||||
|
||||
**Formik** only supports standard HTML question types out of the box. Anything non-HTML (like a date picker, sliders, rating questions) you have to build yourself or find compatible packages for.
|
||||
|
||||
## Ease of custom styling
|
||||
|
||||
When it comes to custom styling, the options differ quiet a bit. Let’s start with React Form Builder 2 as it is the most limited:
|
||||
|
||||
**React Form Builder 2** uses Bootstrap so within the realms of Bootstrap you are free to make changes. If you don’t want to use Bootstrap, you’ll have a hard time styling your form and form builder.
|
||||
|
||||
**Formik** works smoothly with Tailwind, if you know how to set it up. In a nutshell, you can pass predefined tailwind styles from the parent component to avoid repetition and keep our form file tidy. [Here is a tutorial which touches upon it](https://dev.to/przpiw/build-elegant-forms-reactformik-tailwind-54d8). Here is a sneak peak:
|
||||
|
||||
```jsx
|
||||
export const LoginForm = ({styles}) => (
|
||||
<Formik>
|
||||
<Form>
|
||||
<label className={styles.label} htmlFor='Email'>
|
||||
Email
|
||||
</label>
|
||||
<Field className={styles.field} id='email' name='email' />
|
||||
<ErrorMessage component='a' className={styles.errorMsg} name='email' />
|
||||
<label className={styles.label} htmlFor='Email'>
|
||||
Password
|
||||
</label>
|
||||
<Field className={styles.field} id='password' name='password' />
|
||||
<ErrorMessage
|
||||
component='a'
|
||||
className={styles.errorMsg}
|
||||
name='password'
|
||||
/>
|
||||
<div className='mt-8'>
|
||||
<button type='submit' className={styles.button}>
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
</Form>
|
||||
</Formik>
|
||||
```
|
||||
|
||||
**Formbricks React** was built with a great developer experience in mind. There is a native Tailwind support so you can style your components with “className” like you’re used to in React:
|
||||
|
||||
```jsx
|
||||
<FormElement type="text" name="name" label="Your name" className="rounded-sm" />
|
||||
```
|
||||
|
||||
Not using Tailwind? Maybe you should 😛 Jokes aside, you can also pass a custom style sheet and Formbricks React will apply it to your form. As soon as the React Lib is in public beta, we’ll add a step-by-step tutorial [in our docs](/docs).
|
||||
|
||||
## Additional work required
|
||||
|
||||
Building your form or survey is only the first step. What happens with the qualitative data after your respondents hit “Submit”?
|
||||
|
||||
### React Form Builder
|
||||
|
||||
Unfortunately, the data handling is not part of the scope of the React Form Builder 2 package.
|
||||
|
||||
### Formik (Formium) & Formbricks
|
||||
|
||||
Not many know that Formik has a commercial counterpart called Formium. Formium offers a versatile submission API where you can pipe your submissions to and take it from there.
|
||||
|
||||
Formbricks has a very similar offer called Form HQ which lets you forward form responses via email or integrate with third-party tools. Here is a comparison of their free plans:
|
||||
|
||||
| Feature | Formium (free) | Formbricks Form HQ (free) |
|
||||
| ------------------------- | -------------- | ------------------------- |
|
||||
| Free submissions / month | 100 | 500 |
|
||||
| File Uploads | 100MB | 200MB |
|
||||
| Auto Responder | ✅ | ⚙️ |
|
||||
| Email forwarding | ✅ | ✅ |
|
||||
| Custom Email Templates | ✅ | ⚙️ |
|
||||
| Multiple Email Recipients | ✅ | ✅ |
|
||||
| Custom Mail Server | ✅ | ⚙️ |
|
||||
| Webhooks | ✅ | ✅ |
|
||||
| Airtable Integration | ✅ | ⚙️ |
|
||||
| Google Sheets Integration | ✅ | ⚙️ |
|
||||
| Zapier Integration | ✅ | ⚙️ |
|
||||
| Automated Workflows | ❌ | ⚙️ |
|
||||
| Easy self-hosting | ❌ | ✅ |
|
||||
| Can be used in EU? | ❌ | ✅ |
|
||||
| Last feature shipped | 2020 | Nov 2022 |
|
||||
|
||||
<Callout title="In development" type="note">
|
||||
The Formbricks Form HQ is currently in alpha testing. Most features are still in development. Follow us on
|
||||
[Twitter](https://twitter.com/formbricks) to stay uptodate on the release!
|
||||
</Callout>
|
||||
|
||||
Overall, both tools have a very similar set of features. However, Formium has not been further developed over the past two years whereas Formbricks shows quite some ambition to offer a full suite of form and survey solutions on the one open-source platform.
|
||||
|
||||
<Callout title="Formium no option for EU-based companies" type="warning">
|
||||
|
||||
Since Formium is hosted in the US and operated by a US entity, European companies and institutions cannot use Formium without running the legal risk of violating the data privacy of the respondents.
|
||||
|
||||
</Callout>
|
||||
##
|
||||
# Results
|
||||
|
||||
### 3rd place
|
||||
|
||||
[React Form Builder 2](https://github.com/kiho/react-form-builder) has a unique differentiator: A visual form builder. If this is essential for what you're building, this is your choice.7
|
||||
|
||||
### 2nd place
|
||||
|
||||
[Formik](https://formik.org/) has a lot of strengths - and a few weaknesses. It is very customizable and comes with a powerful validation engine. Many developers are very happy with the reduced complexity compared to building forms natively in React. However, it isn't very easy to get started with for junior developers. Lastly, it causes a lot of rerenders, something that React Hook Form solved better.
|
||||
|
||||
### 1st place
|
||||
|
||||
It's still verrry early for [Formbricks](/react-form-library). However, the approach looks promising as it makes working with forms and surveys in React a lot easier. The React Library packs a lot of powerful features which makes it by far the easiest option for coding forms in React. The upcoming Form HQ takes setting up a backend off developers plate completely. The modularity and extendability makes it a future-proof choice.
|
||||
|
||||
<Image
|
||||
src={Winner}
|
||||
alt="Best React form library builder comparison between Formik Formbricks and React Form Builder 2"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
Follow us on [Twitter](https://twitter.com/formbricks) or [join our Discord community](https://formbricks.com/discord) to stay uptodate on the progress.
|
||||
|
||||
<MdxTryItCTA />
|
||||
|
||||
export default ({ children }) => <LayoutMdx meta={meta}>{children}</LayoutMdx>;
|
||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 49 KiB |
132
apps/formbricks-com/pages/blog/weekly-update-181122/index.mdx
Normal file
@@ -0,0 +1,132 @@
|
||||
import Image from "next/image";
|
||||
import LayoutMdx from "@/components/shared/LayoutMdx";
|
||||
import Button from "@/components/shared/Button";
|
||||
import Mockup from "./demo mockup for formbricks hq open source form and survey tool.png";
|
||||
import NewLogo from "./formbricks new logo mockup community feedback.png";
|
||||
import Community from "./community feedback logo and name.png";
|
||||
import Storybook from "./storybook for formbricks survey form builder.png";
|
||||
import HeaderImage from "./weeklyupdate.png";
|
||||
|
||||
export const meta = {
|
||||
title: "Weekly Summary - 18th Nov 2022",
|
||||
description:
|
||||
"Every week, we’ll share an update about all things Formbricks. We’ll look at what we did on the product end as well as what’s new in the community 👋 Let’s get going!",
|
||||
date: "2022-11-18",
|
||||
};
|
||||
|
||||
_Every week, we’ll share an update about all things Formbricks. We’ll look at what we did on the product end as well as what’s new in the community 👋 Let’s get going!_
|
||||
|
||||
<Image src={HeaderImage} alt="Weekly Update" className="rounded-lg" />
|
||||
|
||||
# Product
|
||||
|
||||
### Formbricks React
|
||||
|
||||
Matti has been working on the first release of the Formbricks React Library. Here are the objectives:
|
||||
|
||||
- Make it as easy as possible to build forms in react even with more complex components like ratings or color pickers.
|
||||
- Allow component-based approach to building forms like so:
|
||||
|
||||
```jsx
|
||||
<TextArea name="firstname" label="What's your first name?" validation="required” />
|
||||
```
|
||||
|
||||
- Keep rerenders at a minimum
|
||||
- Keep package size at a minimum while offering more than HTML inputs out of the box
|
||||
|
||||
**Challenge**: Tree-shaking has been tricky. We want to offer input types like sliders and date pickers out of the box. To keep the library performant these components should only be included in the build when they are actually used. The tree-shaking has been tricky, but Matti made good progress yesterday.
|
||||
|
||||
### Formbricks “HQ”
|
||||
|
||||
Since down the line there will be apps running on our platform we decided to call our own application “Formbricks HQ”. Here is a demo of the first iteration, it’ll look like this:
|
||||
|
||||
<Image
|
||||
src={Mockup}
|
||||
alt="Mockup of Formbricks HQ to manage form and survey data self hosted open source"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
**In the first version of the HQ you will be able to:**
|
||||
|
||||
- Receive form submissions from any form
|
||||
- View & manage submissions
|
||||
- Webhook submission data where you need it
|
||||
- Email submission data where you need it
|
||||
|
||||
### SEO
|
||||
|
||||
We have created 5 long-taily SEO pages to drive organic traffic to Formbricks in the medium-term. It can take a couple of months to rank high and we want to be up there when the public beta is ready 🚀
|
||||
|
||||
If you’re curious you find them [here](/best-react-form-library-2023), [here](/vs-formspree), [here](/vs-google-forms), [here](/vs-ohmyform) and [here](/vs-react-hook-form). We added them to the sitemap but do not yet link to them on our website because the product isn’t there yet.
|
||||
|
||||
##
|
||||
|
||||
# Community
|
||||
|
||||
Shoutout to [@Ari](https://twitter.com/aridutilh) from buildersgroop and Dylan from Canu for sharing feedback on the Logo:
|
||||
|
||||
<Image src={Community} alt="Screenshot from Discord with Community Feedback" className="rounded-lg" />
|
||||
|
||||
We heard you and Matti and his wife made it their weekend project to come up with a slicker one:
|
||||
|
||||
<Image src={NewLogo} alt="Formbricks new and old logo" className="rounded-lg" />
|
||||
|
||||
### Exploring Storybook
|
||||
|
||||
Johannes is exploring Storybook. In the medium-term we want to provide you guys with a well-documented set of UI components to build your form solutions faster.
|
||||
|
||||
Additionally, it will make community contributions a lot easier and more valuable to the rest of the community since the look & feel will be consistent.
|
||||
|
||||
Thirdly, in the long-term we want to encourage other builders to build apps to cover specific use cases around working with qualitative data on top of Formbricks.
|
||||
|
||||
Setting things up correctly now will save everyone time down the line.
|
||||
|
||||
<Image src={Storybook} alt="Formbricks new and old logo" className="rounded-lg" />
|
||||
|
||||
### Twitter
|
||||
|
||||
We started being more active on Twitter again. [Say Hi 👋](https://twitter.com/formbricks)
|
||||
|
||||
Let’s hope Elon doesn’t break it too soon.
|
||||
|
||||
## Have a great weekend!
|
||||
|
||||
---
|
||||
|
||||
<div className="mx-auto max-w-7xl px-5 py-5 bg-white shadow-lg rounded-lg text-md text-gray-700">
|
||||
|
||||
You would like to have Formbricks "Build in Public Updates" like this in your mailbox? Subscribe to our
|
||||
newsletter 💪
|
||||
|
||||
<form method="post" action="https://listmonk.formbricks.com/subscription/form" className="listmonk-form">
|
||||
<div>
|
||||
<input type="hidden" name="nonce" />
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
|
||||
Email
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input type="email" name="email" required placeholder="you@example.com" className="block w-full rounded-md border-gray-300 shadow-sm focus:border-slate-500 focus:ring-slate-500 sm:text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-gray-700">
|
||||
Name (optional)
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input type="text" name="name" required className="block w-full rounded-md border-gray-300 shadow-sm focus:border-slate-500 focus:ring-slate-500 sm:text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='hidden'>
|
||||
<input id="e0084" type="checkbox" name="l" checked value="e0084486-8751-43e4-8cfb-58b7c3f5f318" readOnly />
|
||||
<label htmlFor="e0084">Build in public</label>
|
||||
</div>
|
||||
|
||||
<Button type="submit" className="mt-4 w-full justify-center">Subscribe</Button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
export default ({ children }) => <LayoutMdx meta={meta}>{children}</LayoutMdx>;
|
||||
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 53 KiB |
@@ -2,7 +2,7 @@ import HeroTitle from "@/components/shared/HeroTitle";
|
||||
import Layout from "@/components/shared/Layout";
|
||||
import ImageCoreApi from "@/images/core-api.svg";
|
||||
import Image from "next/image";
|
||||
import TryItCTA from "../components/shared/TryItCTA";
|
||||
import CTA from "../components/shared/CTA";
|
||||
import WhyFormbricks from "../components/shared/WhyFormbricks";
|
||||
import FeatureHighlight from "@/components/shared/FeatureHighlight";
|
||||
import { CodeBracketSquareIcon, TableCellsIcon, ServerStackIcon } from "@heroicons/react/24/outline";
|
||||
@@ -64,7 +64,7 @@ const CoreAPIPage = () => (
|
||||
</ul>
|
||||
<WhyFormbricks />
|
||||
<div className="h-12"></div>
|
||||
<TryItCTA />
|
||||
<CTA />
|
||||
</Layout>
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import { Callout } from "@/components/shared/Callout";
|
||||
import Image from "next/image";
|
||||
|
||||
export const meta = {
|
||||
title: "Form Wrapper - Formbricks React Form Library",
|
||||
};
|
||||
|
||||
The form wrapper is the envelope of you form: you put all important information inside and write on the back what happens with the envelope and its content.
|
||||
|
||||
In code terms, this means that it is up to you to decide what happens when the respondent clicks on the Submit button of your form. Formbricks React allows to pass a function to onSubmit which will be executed when the form is submitted.
|
||||
|
||||
### Logging to browser console
|
||||
|
||||
In this example, we’re handing over the form _data_ and the form _schema_ to the onSubmit function. In the function itself we use the _data_ and the _schema_ and log it in the browser console:
|
||||
|
||||
```jsx
|
||||
onSubmit={({ data, schema }) => console.log("data:", data, "schema:", schema)}
|
||||
```
|
||||
|
||||
### Sending to API endpoint
|
||||
|
||||
Here we’re sending the data to an API endpoint to receive and display out submission:
|
||||
|
||||
```jsx
|
||||
<Form
|
||||
onSubmit={({ data }) => {
|
||||
fetch("API ENDPOINT URL", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
});
|
||||
}}>
|
||||
{/* YOUR FORM */}
|
||||
</Form>
|
||||
```
|
||||
|
||||
### What else would you like to do with your data?
|
||||
|
||||
[Join our Discord to suggest features 🤸](https://formbricks.com/discord)
|
||||
|
||||
### Props & Attributes
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| -------- | -------- | ---------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| onSubmit | Function | `() => {}` | Gets passed on object with `{data, schema, event}` and is triggered when the form gets submitted without errors |
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import { Callout } from "@/components/shared/Callout";
|
||||
import Image from "next/image";
|
||||
|
||||
export const meta = {
|
||||
title: "Submit Button - Formbricks React Form Library",
|
||||
};
|
||||
|
||||
The submit button component is very straight forward:
|
||||
|
||||
```jsx
|
||||
<Submit label="Submit" />
|
||||
```
|
||||
|
||||
To keep it working make sure that there is **only one Submit button** per form.
|
||||
|
||||
### Props & Attributes
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| ---------- | ---- | ------- | --------------------------- |
|
||||
| PrefixIcon | SVG | - | Icon displayed before label |
|
||||
| SuffixIcon | SVG | - | Icon displayed after label |
|
||||
|
||||
### Universal props all inputs have
|
||||
|
||||
| Prop | What is it? | Required? | Shown to respondent? | Comment |
|
||||
| ---------------- | --------------------------------------- | --------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| name | name of the field | no | no | It is not shown to respondents but important as a unique identifier to understand which field holds what content. You cannot have two fields with the same name. |
|
||||
| label | label of the field | yes | yes | The label above the input |
|
||||
| help | help text | no | yes | You can add a little help text to each field to provide more detail on how to fill out the field. |
|
||||
| validation | determines how field input is validated | no | depends on field input | If you want to validate the content of a field before the form is sent, you can do so with the validation prop. [read more](/docs/react-form-library/validation-errors) |
|
||||
| id | own identifier for input | no | no | To target your input with css or javascript directly |
|
||||
| outerClassName | append styling class | no | - | [read more](/docs/react-form-library/style-tailwind) |
|
||||
| wrapperClassName | append styling class | no | - | [read more](/docs/react-form-library/style-tailwind) |
|
||||
| innerClassName | append styling class | no | - | [read more](/docs/react-form-library/style-tailwind) |
|
||||
| inputClassName | append styling class | no | - | [read more](/docs/react-form-library/style-tailwind) |
|
||||
| helpClassName | append styling class | no | - | [read more](/docs/react-form-library/style-tailwind) |
|
||||
| |
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
@@ -0,0 +1,81 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import { Callout } from "@/components/shared/Callout";
|
||||
import Image from "next/image";
|
||||
import StyleClasses from "../style-css/HTML classes of Formbricks React Form Library to custom style surveys and forms.png";
|
||||
|
||||
export const meta = {
|
||||
title: "Text Input - Formbricks React Form Library",
|
||||
};
|
||||
|
||||
The `Text` input uses HTML's [native text input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/text). It allows a user to enter a single line of text.
|
||||
|
||||
Like all other inputs it needs a `name` prop as an unique identifier:
|
||||
|
||||
```jsx
|
||||
<Text name="firstname" />
|
||||
```
|
||||
|
||||
In most cases, it looks something like this:
|
||||
|
||||
```jsx
|
||||
<Text name="firstname" label="What's your first name?" placeholder="First Name" validation="required" />
|
||||
```
|
||||
|
||||
### Props & Attributes
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| ----------- | ------ | ------- | ------------------------------------------------ |
|
||||
| minLength | Number | 0 | Limits min length accepted in the field |
|
||||
| maxLength | Number | 524288 | Limits max length accepted in the field |
|
||||
| placeholder | String | - | Placeholder before respondent clicks into field. |
|
||||
|
||||
### Universal props all inputs have
|
||||
|
||||
| Prop | What is it? | Required? | Shown to respondent? | Comment |
|
||||
| ----------------- | --------------------------------------- | --------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| name | name of the field | yes | no | It is not shown to respondents but important as a unique identifier to understand which field holds what content. You cannot have two fields with the same name. |
|
||||
| label | label of the field | yes | yes | The label above the input |
|
||||
| help | help text | no | yes | You can add a little help text to each field to provide more detail on how to fill out the field. |
|
||||
| validation | determines how field input is validated | no | depends on field input | If you want to validate the content of a field before the form is sent, you can do so with the validation prop. [read more](/docs/react-form-library/validation-errors) |
|
||||
| id | own identifier for input | no | no | To target your input with css or javascript directly |
|
||||
| outerClassName | append styling class | no | - | [read more](/docs/react-form-library/style-tailwind) |
|
||||
| wrapperClassName | append styling class | no | - | [read more](/docs/react-form-library/style-tailwind) |
|
||||
| innerClassName | append styling class | no | - | [read more](/docs/react-form-library/style-tailwind) |
|
||||
| inputClassName | append styling class | no | - | [read more](/docs/react-form-library/style-tailwind) |
|
||||
| helpClassName | append styling class | no | - | [read more](/docs/react-form-library/style-tailwind) |
|
||||
| messagesClassName | append styling class | no | - | [read more](/docs/react-form-library/style-tailwind) |
|
||||
| messageClassName | append styling class | no | - | [read more](/docs/react-form-library/style-tailwind) |
|
||||
|
||||
### Styling with CSS
|
||||
|
||||
You can style your form with adding CSS to the pre-assigned classes. This is how the classes are assigned:
|
||||
|
||||
<Image
|
||||
src={StyleClasses}
|
||||
alt="Data log of a form created with the fastest react form builder"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
| CSS Class | Description |
|
||||
| ------------------- | ----------------------------------------------------------------------------------------------- |
|
||||
| formbricks-form | The outermost wrapping element. |
|
||||
| formbricks-outer | A wrapper around the label input, help text and error messages. |
|
||||
| formbricks-help | The help text itself. |
|
||||
| formbricks-wrapper | A wrapper around the label and the input field (no help text). |
|
||||
| formbricks-label | The label itself. |
|
||||
| formbricks-inner | A wrapper around the input element. |
|
||||
| formbricks-input | The input element itself. |
|
||||
| formbricks-message | The element (or many elements) containing a message — most often validation and error messages. |
|
||||
| formbricks-messages | A wrapper around all the messages. |
|
||||
|
||||
### Styling with Tailwind
|
||||
|
||||
You can also use Tailwind to extend the classes, like so:
|
||||
|
||||
```jsx
|
||||
<Text name="firstname" label="What's your first name?" outerClassName="bg-gray-800 my-5" />
|
||||
```
|
||||
|
||||
Here are the in-depth guides for [CSS](/docs/react-form-library/style-css) or [Tailwind](/docs/react-form-library/style-tailwind).
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import { Callout } from "@/components/shared/Callout";
|
||||
import Image from "next/image";
|
||||
import StyleClasses from "../style-css/HTML classes of Formbricks React Form Library to custom style surveys and forms.png";
|
||||
|
||||
export const meta = {
|
||||
title: "Text Input - Formbricks React Form Library",
|
||||
};
|
||||
|
||||
The `textarea` input uses HTML's [native textarea input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea) . It allows a user to enter multiple lines of text and is resizable in most browsers.
|
||||
|
||||
Like all other inputs it needs a name prop as an unique identifier:
|
||||
|
||||
```jsx
|
||||
<Textarea name="description" />
|
||||
```
|
||||
|
||||
In most cases, it looks something like this:
|
||||
|
||||
```jsx
|
||||
<Textarea
|
||||
name="firstname"
|
||||
label="Tell us about you:"
|
||||
placeholder="When I was young..."
|
||||
validation="required"
|
||||
/>
|
||||
```
|
||||
|
||||
### Props & Attributes
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| ----------- | ------ | ------- | ----------------------------------------------- |
|
||||
| minLength | Number | 0 | Limits min length accepted in the field |
|
||||
| maxLength | Number | 524288 | Limits max length accepted in the field |
|
||||
| placeholder | String | - | Placeholder before respondent clicks into field |
|
||||
| cols | Number | - | Number of cols (width) of the textarea |
|
||||
| rows | Number | - | Number of rows (height) of the textarea |
|
||||
|
||||
### Universal props all inputs have
|
||||
|
||||
| Prop | What is it? | Required? | Shown to respondent? | Comment |
|
||||
| ----------------- | --------------------------------------- | --------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| name | name of the field | yes | no | It is not shown to respondents but important as a unique identifier to understand which field holds what content. You cannot have two fields with the same name. |
|
||||
| label | label of the field | yes | yes | The label above the input |
|
||||
| help | help text | no | yes | You can add a little help text to each field to provide more detail on how to fill out the field. |
|
||||
| validation | determines how field input is validated | no | depends on field input | If you want to validate the content of a field before the form is sent, you can do so with the validation prop. [read more](/docs/react-form-library/validation-errors) |
|
||||
| id | own identifier for input | no | no | To target your input with css or javascript directly |
|
||||
| outerClassName | append styling class | no | - | [read more](/docs/react-form-library/style-tailwind) |
|
||||
| wrapperClassName | append styling class | no | - | [read more](/docs/react-form-library/style-tailwind) |
|
||||
| innerClassName | append styling class | no | - | [read more](/docs/react-form-library/style-tailwind) |
|
||||
| inputClassName | append styling class | no | - | [read more](/docs/react-form-library/style-tailwind) |
|
||||
| helpClassName | append styling class | no | - | [read more](/docs/react-form-library/style-tailwind) |
|
||||
| messagesClassName | append styling class | no | - | [read more](/docs/react-form-library/style-tailwind) |
|
||||
| messageClassName | append styling class | no | - | [read more](/docs/react-form-library/style-tailwind) |
|
||||
|
||||
### Styling with CSS
|
||||
|
||||
You can style your form with adding CSS to the pre-assigned classes. This is how the classes are assigned:
|
||||
|
||||
<Image
|
||||
src={StyleClasses}
|
||||
alt="Data log of a form created with the fastest react form builder"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
| CSS Class | Description |
|
||||
| ------------------- | ----------------------------------------------------------------------------------------------- |
|
||||
| formbricks-form | The outermost wrapping element. |
|
||||
| formbricks-outer | A wrapper around the label input, help text and error messages. |
|
||||
| formbricks-help | The help text itself. |
|
||||
| formbricks-wrapper | A wrapper around the label and the input field (no help text). |
|
||||
| formbricks-label | The label itself. |
|
||||
| formbricks-inner | A wrapper around the input element. |
|
||||
| formbricks-input | The input element itself. |
|
||||
| formbricks-message | The element (or many elements) containing a message — most often validation and error messages. |
|
||||
| formbricks-messages | A wrapper around all the messages. |
|
||||
|
||||
### Styling with Tailwind
|
||||
|
||||
You can also use Tailwind to extend the classes, like so:
|
||||
|
||||
```jsx
|
||||
<Text name="firstname" label="What's your first name?" outerClassName="bg-gray-800 my-5" />
|
||||
```
|
||||
|
||||
Here are the in-depth guides for [CSS](/docs/react-form-library/style-css) or [Tailwind](/docs/react-form-library/style-tailwind).
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 26 KiB |
@@ -1,22 +1,101 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import { Fence } from "@/components/shared/Fence";
|
||||
import { Callout } from "@/components/shared/Callout";
|
||||
import Image from "next/image";
|
||||
import SimpleForm from "./simple-form-basic-theme-formbricks-react-form-survey-library-builder-v1.png";
|
||||
import LogData from "./data output console for open source form made with react library for free.png";
|
||||
|
||||
export const meta = {
|
||||
title: "Introduction - React Form Library",
|
||||
title: "Getting started - Formbricks React Form Library",
|
||||
};
|
||||
|
||||
## Installation
|
||||
This is a step-by-step guide on how to get started with Formbricks React.
|
||||
|
||||
Getting the Formbricks React Library up and running with Node Package Manager:
|
||||
|
||||
```bash
|
||||
npm install @formbricks/react
|
||||
```
|
||||
|
||||
or using yarn package manager:
|
||||
### Preparation: Create React App
|
||||
|
||||
To use Formbricks React, you need a React application. If you don't have your application already, please follow these guides to either create a React or a Next.js app:
|
||||
|
||||
- [Create new React app](https://reactjs.org/docs/create-a-new-react-app.html)
|
||||
- [Create new Next.js app](https://nextjs.org/docs/api-reference/create-next-app)
|
||||
|
||||
### 1. Install Formbricks React
|
||||
|
||||
Install Formbricks React using the package manager of your choice:
|
||||
|
||||
```bash
|
||||
npm install -s @formbricks/react
|
||||
# or
|
||||
yarn add @formbricks/react
|
||||
# or
|
||||
pnpm add @formbricks/react
|
||||
```
|
||||
|
||||
### 2. Create new form component
|
||||
|
||||
Since React is based on components, we will create a new component for your form. To do so, create a file called `Form.jsx` - if you work with TypeScript you need to change the suffix to `.tsx`.
|
||||
|
||||
### 3. Import form components
|
||||
|
||||
To be able to use Formbricks React in your file, you need to import the components that you intend to use. [Here is an overview](/docs/react-form-library/work-with-components) of all available components.
|
||||
|
||||
For a simple sign up form we import the following:
|
||||
|
||||
```jsx
|
||||
import { Form, Text, Textarea, Submit } from "@formbricks/react";
|
||||
```
|
||||
|
||||
<Callout title="Import only what you need" type="warning">
|
||||
Currently, all components you import will be part of your production build. Please only import components
|
||||
that you’re using for the best possible performance.
|
||||
</Callout>
|
||||
|
||||
### 4. Code your form
|
||||
|
||||
Now we’re getting to the fun part: Coding your form. This is the full form code you can copy and play around with. Below we go through it step-by-step:
|
||||
|
||||
```jsx
|
||||
import { Form, Text, Textarea, Submit } from "@formbricks/react";
|
||||
import "@formbricks/react/styles.css";
|
||||
|
||||
export default function WaitlistForm() {
|
||||
return (
|
||||
<Form onSubmit={({ data, schema }) => console.log("data:", data, "schema:", schema)}>
|
||||
<Text name="firstname" label="What's your first name?" validation="required" />
|
||||
<Text name="lastname" label="What's your last name?" />
|
||||
<Textarea name="about" label="About you" help="Please keep it short" />
|
||||
<Submit label="Submit" />
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
This form imports the default formbricks theme for styling, we cover [styling](/docs/react-form-library/style-css) later.
|
||||
|
||||
Your browser window should look like this now:
|
||||
|
||||
<Image src={SimpleForm} alt="Unstyled form created with best react form library" className="rounded-lg" />
|
||||
|
||||
### 5. Test if it works
|
||||
|
||||
To test if the form works as planned open the browser console and fill out the form.
|
||||
|
||||
The console should show you the data and the schema of your form:
|
||||
|
||||
<Image
|
||||
src={LogData}
|
||||
alt="Data log of a form created with the fastest react form builder"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
### What’s next?
|
||||
|
||||
Congratulations, your React form works!
|
||||
|
||||
To get to the desired outcome, there are three more things on your list:
|
||||
|
||||
[→ Receive and manage form submissions](/docs/react-form-library/link-formbricks-hq)
|
||||
|
||||
[→ Explore all input types and how props define them](/docs/react-form-library/work-with-components)
|
||||
|
||||
→ Learn how to style the form with [CSS](/docs/react-form-library/style-css) or [Tailwind](/docs/react-form-library/style-tailwind)
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 17 KiB |
@@ -7,31 +7,42 @@ export const meta = {
|
||||
|
||||
## Building React forms has never been quicker.
|
||||
|
||||
Forms are something that every developer uses frequently. But building Forms is harder than you might think. React doesn't provide good form functionality out-of-the-box and you have to take care of state management, validation and form components yourself. And always keep performance, accessibility and best practices in mind. And that makes Forms not only complex to build, but also to maintain and learn how to set up.
|
||||
We make Forms easier than ever in the React world.
|
||||
Every developer works with forms, few like their experience. Building Forms, especially in React, can be pretty annoying. State management, validation, form components, accessibility, internationalization and performance are all things you have to deal with, but don't really want to.
|
||||
|
||||
Here's what a simple Form looks like in Formbrick's React Library:
|
||||
We make building - and maintaining - forms easier than ever in the React world.
|
||||
|
||||
### Formbricks React Example:
|
||||
|
||||
```jsx
|
||||
import React from "react";
|
||||
import { Form, Formbricks } from "@formbricks/react";
|
||||
import { Form, Text, Textarea, Submit } from "@formbricks/react";
|
||||
import "@formbricks/react/styles.css";
|
||||
|
||||
export default function Web() {
|
||||
export default function WaitlistForm() {
|
||||
return (
|
||||
<Form
|
||||
onSubmit={({ data }) => {
|
||||
console.log(data);
|
||||
}}>
|
||||
<Formbricks type="text" name="firstname" label="What's your first name?" />
|
||||
<Formbricks type="text" name="lastname" label="What's your last name?" />
|
||||
<Formbricks type="textarea" name="about" label="About you" help="Please keep it short" />
|
||||
<Formbricks type="submit" label="Submit" />
|
||||
<Form onSubmit={({ data, schema }) => console.log("data:", data, "schema:", schema)}>
|
||||
<Text name="firstname" label="What's your first name?" validation="required" />
|
||||
<Text name="lastname" label="What's your last name?" />
|
||||
<Textarea name="about" label="About you" help="Please keep it short" />
|
||||
<Submit label="Submit" />
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
You don't need to remember a lot of components, or nested HTML- and React Form Structure - only the `<Formbricks />` component.
|
||||
No matter what kind of form input you need, simple text input, checkboxes or even complex elements like sliders which you could previously only include with external plugins - Formbricks takes care of everything.
|
||||
### Why is this easier already?
|
||||
|
||||
- One easy to use syntax for input types including labels, validation and help texts
|
||||
- HTML & non-HTML input types available out of the box
|
||||
- Easily maintainable with component-based approach
|
||||
- All characteristics adjustable via props
|
||||
- Automatic schema generation
|
||||
|
||||
### What is to come?
|
||||
|
||||
- Conditional logic
|
||||
- Multi-page forms
|
||||
- Accessibility
|
||||
- Internationalization
|
||||
- Form Templates
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
|
||||
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,70 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import { Callout } from "@/components/shared/Callout";
|
||||
import Image from "next/image";
|
||||
import Image1 from "./image 1 formspree free alternative open source.png";
|
||||
import Image2 from "./image 2 formspree alternative for free and open source.png";
|
||||
import Image3 from "./image3 formspree open source alternative for free opensorce.png";
|
||||
|
||||
export const meta = {
|
||||
title: "Receive and manage submissions - Link with Formbricks HQ",
|
||||
};
|
||||
|
||||
Creating forms is only half of the work. Dealing with the qualitative data you gather is as time-consuming - at least until we release the Formbricks HQ 😉
|
||||
|
||||
<Callout title="No backend needed" type="note">
|
||||
In a few weeks, you’ll be able to use Formbricks HQ to receive and manage submissions from any pre-existing
|
||||
form. Until then, you can use Formspree.
|
||||
</Callout>
|
||||
|
||||
## Formspree
|
||||
|
||||
The closest solution to Formbricks HQ is Formspree. Formspree has a rather limited free plan, but it should be enough to get you started. Once Formbricks HQ is live, you’ll be able to switch within minutes.
|
||||
|
||||
### 1. Setup Formspree account & create form
|
||||
|
||||
To setup Formspree, go over to [formspree.io](https://formspree.io), create an account and create a form:
|
||||
|
||||
<Image src={Image1} alt="Formspree open source alternative for free step 1" className="rounded-lg" />
|
||||
|
||||
### 2. Copy endpoint URL
|
||||
|
||||
Copy endpoint URL out of Formspree dashboard:
|
||||
|
||||
<Image
|
||||
src={Image2}
|
||||
alt="formspree alternative to open source for free and opensource step 2"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
### 3. Update form action
|
||||
|
||||
All you have to do now is to set the endpoint URL as the form action (replace ID with your form ID):
|
||||
|
||||
```jsx
|
||||
<Form
|
||||
onSubmit={({ data }) => {
|
||||
fetch("https://formspree.io/f/YOUR_FORMSPREE_ID", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
});
|
||||
}}>
|
||||
{/* YOUR FORM */}
|
||||
</Form>
|
||||
```
|
||||
|
||||
### 4. Test it
|
||||
|
||||
When you’re now filling out your form, you should receive the submissions in your Formspree dashboard.
|
||||
|
||||
<Image src={Image3} alt="free alternative to formspree open source step 3" className="rounded-lg" />
|
||||
|
||||
### 5. Work with form data
|
||||
|
||||
Out of Formspree you can send the submissions to yourself via email (only the email you’re signed up with), forward it to Zapier, etc.
|
||||
|
||||
**Remember**: You’re using the **free plan which includes only 50 submissions / month**, apart from quite a lot of other limitations. For more features please see the [Formspree pricing](https://formspree.io/plans).
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
|
After Width: | Height: | Size: 45 KiB |
@@ -0,0 +1,139 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import { Callout } from "@/components/shared/Callout";
|
||||
import Image from "next/image";
|
||||
import StyleClasses from "../style-css/HTML classes of Formbricks React Form Library to custom style surveys and forms.png";
|
||||
import Styled from "./simple styling for best react from builder library to make open source surveys and forms.png";
|
||||
|
||||
export const meta = {
|
||||
title: "Styling with CSS - Formbricks React Form Library",
|
||||
};
|
||||
|
||||
Giving your form the right look and feel is very likely why you chose to use code. Formbricks React supports styling with a custom style sheet as well as Tailwind CSS.
|
||||
|
||||
### Creating custom style sheet
|
||||
|
||||
1. Create a new `style.css` file in your project folder.
|
||||
2. Import it into your form component (e.g. `Form.jsx`)
|
||||
|
||||
```jsx
|
||||
import style from "./style.css";
|
||||
```
|
||||
|
||||
3. Add styles to it. You can copy this template as a quick start
|
||||
|
||||
```css
|
||||
.formbricks-form {
|
||||
background-color: #e2e8f0;
|
||||
padding: 1em;
|
||||
margin: 2em;
|
||||
border-radius: 0.3em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.formbricks-form .formbricks-outer:not(:first-child) {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.formbricks-help {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.formbricks-wrapper {
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.formbricks-label {
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.formbricks-inner {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.formbricks-input {
|
||||
border-radius: 6px;
|
||||
color: black;
|
||||
border: 0px;
|
||||
padding: 8px;
|
||||
font-size: 1em;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
width: 100%;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
```
|
||||
|
||||
When you copied the style above to your styles.css you form should look like this now:
|
||||
|
||||
<Image
|
||||
src={Styled}
|
||||
alt="Data log of a form created with the fastest react form builder"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
## How does it work?
|
||||
|
||||
Each form element component follows the same naming convention. This means that you can style e.g. all input fields at the same time. These are the classes each form has:
|
||||
|
||||
<Image
|
||||
src={StyleClasses}
|
||||
alt="Data log of a form created with the fastest react form builder"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
| CSS Class | Description |
|
||||
| ------------------- | ----------------------------------------------------------------------------------------------- |
|
||||
| formbricks-form | The outermost wrapping element. |
|
||||
| formbricks-outer | A wrapper around the label input, help text and error messages. |
|
||||
| formbricks-help | The help text itself. |
|
||||
| formbricks-wrapper | A wrapper around the label and the input field (no help text). |
|
||||
| formbricks-label | The label itself. |
|
||||
| formbricks-inner | A wrapper around the input element. |
|
||||
| formbricks-input | The input element itself. |
|
||||
| formbricks-message | The element (or many elements) containing a message — most often validation and error messages. |
|
||||
| formbricks-messages | A wrapper around all the messages. |
|
||||
|
||||
### In React app, a simple form with one field looks like this:
|
||||
|
||||
```jsx
|
||||
<Form onSubmit={() => {}>
|
||||
<Text name="firstname" label="What's your first name?" validation="required" />
|
||||
<Submit name="submit" label="Submit" />
|
||||
</Form>
|
||||
```
|
||||
|
||||
In your browser, it is interpreted as follows:
|
||||
|
||||
```html
|
||||
<form class="formbricks-form">
|
||||
|
||||
<!-- Text input component -->
|
||||
<div class="formbricks-outer" data-type="text">
|
||||
<div class="formbricks-wrapper">
|
||||
<label class="formbricks-label" for="firstname-JCc">What's your first name?</label>
|
||||
<div class="formbricks-inner">
|
||||
<input class="formbricks-input"
|
||||
type="text"
|
||||
id="firstname=JCc"
|
||||
placeholder=""
|
||||
name="firstname" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button Component -->
|
||||
</div>
|
||||
<div class="formbricks-outer" data-type="submit">
|
||||
<div class="formbricks-wrapper">
|
||||
<button class="formbricks-input" type="submit" id="submit-tDy">
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
```
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,31 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import { Callout } from "@/components/shared/Callout";
|
||||
import Image from "next/image";
|
||||
|
||||
export const meta = {
|
||||
title: "Tailwind CSS - Formbricks React Form Library",
|
||||
};
|
||||
|
||||
We love Tailwind! This is why Formbricks React natively supports Tailwind. All you have to do is target the classes using props. For example, to extend “formbricks-outer” class:
|
||||
|
||||
```jsx
|
||||
<Text name="firstname" label="What's your first name?" outerClassName="bg-gray-800 my-5" />
|
||||
```
|
||||
|
||||
### Overview of props to target CSS classes
|
||||
|
||||
Here are all the props you can use to extend the styling of the different form elements:
|
||||
|
||||
| CSS class | Prop | Content |
|
||||
| ------------------- | ----------------- | --------------------------------------------------- |
|
||||
| formbricks-form | formClassName | The wrapper around the complete form |
|
||||
| formbricks-outer | outerClassName | The wrapper around label, input field and help text |
|
||||
| formbricks-help | helpClassName | The help text |
|
||||
| formbricks-wrapper | wrapperClassName | The wrapper around the label and the input field |
|
||||
| formbricks-label | labelClassName | The label |
|
||||
| formbricks-inner | innerClassName | The input field |
|
||||
| formbricks-input | inputClassName | The input |
|
||||
| formbricks-message | messageClassName | The validation / error message itself |
|
||||
| formbricks-messages | messagesClassName | Wrapper around all error messages |
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
@@ -0,0 +1,74 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import { Callout } from "@/components/shared/Callout";
|
||||
import Image from "next/image";
|
||||
import ErrorMsgImg from "./validation errors form react library open source best way to build forms react.png";
|
||||
import Styling from "./styling of validation errors form react library open source best and fastest way to build forms react.png";
|
||||
|
||||
export const meta = {
|
||||
title: "Validation & Error Message - Formbricks React Form Library",
|
||||
};
|
||||
|
||||
Validation prevents users from submitting missing of false data.
|
||||
|
||||
To add a validation to your inputs, add the `validation` prop and write the rules in string syntax. Rules are divided by a pipe (`|`), e.g.:
|
||||
|
||||
```bash
|
||||
<Text name="age" label="What's your age?" validation="required|number|min:0|max:100" />
|
||||
```
|
||||
|
||||
The code above checks four characteristics:
|
||||
|
||||
- The field is required, so is there a value?
|
||||
- Is the value a `number`?
|
||||
- Is the value positive?
|
||||
- Is the value less or equal to 100?
|
||||
|
||||
Only if all four requirements are met, the form can be submitted. If not an error message is shown.
|
||||
|
||||
### Available rules
|
||||
|
||||
| Rule | Explanation | Example |
|
||||
| -------- | -------------------------------------------------------------------------- | ---------- |
|
||||
| required | Only accepts non-empty fields | “required” |
|
||||
| number | Only accepts fields with a number or float value | “number” |
|
||||
| min | Only accepts number values that are greater or equal to the value provided | “min:10” |
|
||||
| max | Only accepts number values that are greater or equal to the value provided | “max:50” |
|
||||
|
||||
### Message position
|
||||
|
||||
If one validation rule fails it will throw an error message. The error messages are displayed below the input field as soon as the input changes to an invalid value.
|
||||
|
||||
<Image
|
||||
src={ErrorMsgImg}
|
||||
alt="Illustration of error message and validation handling in best form react lib"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
### Message styling with CSS
|
||||
|
||||
You can style it as you like with CSS and Tailwind. For a custom style sheet, use the classes
|
||||
|
||||
`formbricks-message` and `formbricks-messages`
|
||||
|
||||
to style the message wrapper and the message itself:
|
||||
|
||||
<Image
|
||||
src={Styling}
|
||||
alt="Illustration of error message and validation handling in best form react lib"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
### Message styling with Tailwind
|
||||
|
||||
```jsx
|
||||
<Text
|
||||
name="email"
|
||||
label="Email"
|
||||
messagesClassName="bg-gray-100 rounded"
|
||||
messageClassName="text-base sm:text-sm text-red-500"
|
||||
/>
|
||||
```
|
||||
|
||||
Here are the in-depth guides for [CSS](/docs/react-form-library/style-css) or [Tailwind](/docs/react-form-library/style-tailwind).
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,51 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import { Callout } from "@/components/shared/Callout";
|
||||
import Image from "next/image";
|
||||
|
||||
export const meta = {
|
||||
title: "Working with Components - Formbricks React Form Library",
|
||||
};
|
||||
|
||||
We adopt the component-based approach many front-end libraries popularized over the past years. Every element in your form is a dedicated component.
|
||||
|
||||
For performance reasons, we do not automatically import all available form components. This is why you have to import components before you can use them, like so:
|
||||
|
||||
```jsx
|
||||
import { Text } from "@formbricks/react";
|
||||
|
||||
// or several at once
|
||||
|
||||
import { Form, Text, Textarea, Submit } from "@formbricks/react";
|
||||
```
|
||||
|
||||
<Callout title="Only import inputs you use" type="warning">
|
||||
All imported components will be included in the production build - even when you do not use them in your form. To keep the package size as small as possible only import the components that you need in your form.
|
||||
|
||||
</Callout>
|
||||
|
||||
### Components available
|
||||
|
||||
| Component | Details | Use |
|
||||
| -------------- | ------------------- | ------------------------------------------- |
|
||||
| `<Form />` | Form Wrapper | Handles the onSubmit action, wraps the form |
|
||||
| `<Text />` | HTML Text Input | Single line of text input |
|
||||
| `<Textarea />` | HTML Textarea Input | Multiple lines of text |
|
||||
| `<Submit />` | Submit Button | Submits the form |
|
||||
|
||||
---
|
||||
|
||||
### Coming soon 🚧
|
||||
|
||||
| Component | Details | Use |
|
||||
| ------------------ | ----------------------------- | ----------------------- |
|
||||
| `<Radio />` | Radio Buttons | Single choice |
|
||||
| `<Checkbox />` | Checkboxes | Multiple choice |
|
||||
| `<Rating />` | Star-Rating | Simple rating in steps |
|
||||
| `<Nps />` | Nps Score | Evaluate experience |
|
||||
| `<Color />` | Color-Picker | Let user choose a color |
|
||||
| `<Slider />` | Slider | Stepless rating |
|
||||
| `<Autocomplete />` | Autocomplete possible options | e.g. Tags |
|
||||
|
||||
and many more...
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
@@ -1,6 +1,7 @@
|
||||
import Layout from "@/components/shared/Layout";
|
||||
import HeroTitle from "@/components/shared/HeroTitle";
|
||||
import Button from "../components/shared/Button";
|
||||
import { useRouter } from "next/router";
|
||||
import {
|
||||
CloudIcon,
|
||||
ArrowPathIcon,
|
||||
@@ -12,67 +13,72 @@ import {
|
||||
BuildingLibraryIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
|
||||
const GetStartedPage = () => (
|
||||
<Layout
|
||||
title="Get started"
|
||||
description="We offer our open source form & survey software for self-hosters and in our managed cloud. Get started within minutes!">
|
||||
<HeroTitle headingPt1="How do you want to" headingTeal="run" headingPt2="Formbricks?" />
|
||||
<div className="mb-32 grid px-6 md:grid-cols-2 md:gap-8 md:px-16">
|
||||
<div className="mb-6 rounded-lg bg-gradient-to-b from-slate-200 to-slate-300 px-10 py-6 dark:from-slate-800 dark:to-slate-700 md:mb-0">
|
||||
<CloudIcon className="h-20 w-20 flex-shrink-0 text-slate-600 dark:text-slate-400" />
|
||||
<h2 className="mt-7 text-4xl font-bold text-slate-800 dark:text-slate-200">Cloud</h2>
|
||||
<p className="text-sm text-slate-500">Managed hosting by Formbricks core team</p>
|
||||
<p className="mt-7 font-semibold text-slate-700 dark:text-slate-300">
|
||||
<span className="font-bold ">Free</span> for 500 submissions/mo
|
||||
</p>
|
||||
<p className="text-sm text-slate-500 dark:text-slate-500">then $0.01/submission</p>
|
||||
<div className="font-medium text-slate-500 dark:text-slate-400">
|
||||
<div className="mt-7 flex items-center py-1">
|
||||
<InboxArrowDownIcon className="mr-3 h-7 w-7 flex-shrink-0 text-slate-600 dark:text-slate-300" />
|
||||
<p>Start receiving submissions right away</p>
|
||||
</div>
|
||||
<div className="flex items-center py-1">
|
||||
<ArrowPathIcon className="mr-3 h-7 w-7 flex-shrink-0 text-slate-600 dark:text-slate-300" />
|
||||
<p>Automatic upgrades</p>
|
||||
</div>
|
||||
<div className="flex items-center py-1">
|
||||
<PuzzlePieceIcon className="mr-3 h-7 w-7 flex-shrink-0 text-slate-600 dark:text-slate-300" />
|
||||
<p>All enterprise features included</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button disabled className="mt-7 w-full justify-center text-center font-bold" variant="highlight">
|
||||
Start FREE on Formbricks Cloud
|
||||
</Button>
|
||||
</div>
|
||||
export default function GetStartedPage() {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<Layout
|
||||
title="Get started"
|
||||
description="We offer our open source form & survey software for self-hosters and in our managed cloud. Get started within minutes!">
|
||||
<HeroTitle headingPt1="How do you want to" headingTeal="run" headingPt2="Formbricks?" />
|
||||
|
||||
<div className="rounded-lg bg-gradient-to-b from-slate-200 to-slate-300 px-10 py-6 dark:from-slate-800 dark:to-slate-700 ">
|
||||
<ServerStackIcon className="h-20 w-20 flex-shrink-0 text-slate-600 dark:text-slate-400" />
|
||||
<h2 className="mt-7 text-4xl font-bold text-slate-800 dark:text-slate-200">Self-host</h2>
|
||||
<p className="text-sm text-gray-500">Submission data never leaves your infrastructure</p>
|
||||
<p className="mt-7 font-semibold text-slate-700 dark:text-slate-300">
|
||||
<span className="font-bold ">Free</span> for 500 submissions/mo
|
||||
</p>
|
||||
<p className="text-sm text-slate-500 dark:text-slate-500">then $0.01/submission</p>
|
||||
<div className="font-medium text-slate-500 dark:text-slate-400">
|
||||
<div className="mt-7 flex items-center py-1">
|
||||
<ShieldCheckIcon className="h- mr-3 w-7 flex-shrink-0 text-slate-600 dark:text-slate-300" />
|
||||
<p>Easy deploy for most private cloud platforms</p>
|
||||
</div>
|
||||
<div className="flex items-center py-1">
|
||||
<CommandLineIcon className="mr-3 h-7 w-7 flex-shrink-0 text-slate-600 dark:text-slate-300" />
|
||||
<p>Full access to production instance</p>
|
||||
</div>
|
||||
<div className="flex items-center py-1">
|
||||
<BuildingLibraryIcon className="mr-3 h-7 w-7 flex-shrink-0 text-slate-600 dark:text-slate-300" />
|
||||
<p>Full compliance with all data privacy regulation</p>
|
||||
<div className="mb-32 grid px-6 md:grid-cols-2 md:gap-8 md:px-16">
|
||||
<div className="mb-6 rounded-lg bg-gradient-to-b from-slate-200 to-slate-300 px-10 py-6 dark:from-slate-800 dark:to-slate-700 md:mb-0">
|
||||
<CloudIcon className="h-20 w-20 flex-shrink-0 text-slate-600 dark:text-slate-400" />
|
||||
<h2 className="mt-7 text-4xl font-bold text-slate-800 dark:text-slate-200">Cloud</h2>
|
||||
<p className="text-sm text-slate-500">Managed hosting by Formbricks core team</p>
|
||||
<p className="mt-7 font-semibold text-slate-700 dark:text-slate-300">
|
||||
<span className="font-bold ">Free</span> for 500 submissions/mo
|
||||
</p>
|
||||
<p className="text-sm text-slate-500 dark:text-slate-500">then $0.01/submission</p>
|
||||
<div className="font-medium text-slate-500 dark:text-slate-400">
|
||||
<div className="mt-7 flex items-center py-1">
|
||||
<InboxArrowDownIcon className="mr-3 h-7 w-7 flex-shrink-0 text-slate-600 dark:text-slate-300" />
|
||||
<p>Start receiving submissions right away</p>
|
||||
</div>
|
||||
<div className="flex items-center py-1">
|
||||
<ArrowPathIcon className="mr-3 h-7 w-7 flex-shrink-0 text-slate-600 dark:text-slate-300" />
|
||||
<p>Automatic upgrades</p>
|
||||
</div>
|
||||
<div className="flex items-center py-1">
|
||||
<PuzzlePieceIcon className="mr-3 h-7 w-7 flex-shrink-0 text-slate-600 dark:text-slate-300" />
|
||||
<p>All enterprise features included</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button disabled className="mt-7 w-full justify-center text-center font-bold" variant="highlight">
|
||||
Start FREE on Formbricks Cloud
|
||||
</Button>
|
||||
</div>
|
||||
<Button disabled className="mt-7 w-full justify-center text-center font-bold" variant="highlight">
|
||||
Get started
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
export default GetStartedPage;
|
||||
<div className="rounded-lg bg-gradient-to-b from-slate-200 to-slate-300 px-10 py-6 dark:from-slate-800 dark:to-slate-700 ">
|
||||
<ServerStackIcon className="h-20 w-20 flex-shrink-0 text-slate-600 dark:text-slate-400" />
|
||||
<h2 className="mt-7 text-4xl font-bold text-slate-800 dark:text-slate-200">Self-host</h2>
|
||||
<p className="text-sm text-gray-500">Submission data never leaves your infrastructure</p>
|
||||
<p className="mt-7 font-semibold text-slate-700 dark:text-slate-300">
|
||||
<span className="font-bold ">Free</span> for 500 submissions/mo
|
||||
</p>
|
||||
<p className="text-sm text-slate-500 dark:text-slate-500">then $0.01/submission</p>
|
||||
<div className="font-medium text-slate-500 dark:text-slate-400">
|
||||
<div className="mt-7 flex items-center py-1">
|
||||
<ShieldCheckIcon className="h- mr-3 w-7 flex-shrink-0 text-slate-600 dark:text-slate-300" />
|
||||
<p>Easy deploy for most private cloud platforms</p>
|
||||
</div>
|
||||
<div className="flex items-center py-1">
|
||||
<CommandLineIcon className="mr-3 h-7 w-7 flex-shrink-0 text-slate-600 dark:text-slate-300" />
|
||||
<p>Full access to production instance</p>
|
||||
</div>
|
||||
<div className="flex items-center py-1">
|
||||
<BuildingLibraryIcon className="mr-3 h-7 w-7 flex-shrink-0 text-slate-600 dark:text-slate-300" />
|
||||
<p>Full compliance with all data privacy regulation</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="mt-7 w-full justify-center text-center font-bold"
|
||||
variant="highlight"
|
||||
onClick={() => router.push("/discord")}>
|
||||
Sign up for beta test
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import ImageReactLib from "@/images/react-lib.png";
|
||||
import ImageSchemaGeneration from "@/images/schema-generation-svg.svg";
|
||||
import HeadingCentered from "@/components/shared/HeadingCentered";
|
||||
import { CheckIcon, PlusIcon } from "@heroicons/react/24/outline";
|
||||
import CTA from "../components/shared/CTA";
|
||||
import TryItCTA from "../components/shared/TryItCTA";
|
||||
import FeatureHighlight from "@/components/shared/FeatureHighlight";
|
||||
|
||||
const hereFeatures = [
|
||||
@@ -140,7 +140,7 @@ const ReactFormBuilderPage = () => (
|
||||
</a>
|
||||
</dl>
|
||||
</div>
|
||||
<CTA />
|
||||
<TryItCTA />
|
||||
</Layout>
|
||||
);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import LayoutMdx from "@/components/shared/LayoutMdx";
|
||||
import Image from "next/image";
|
||||
import { Callout } from "@/components/shared/Callout";
|
||||
import HeroAnimation from "@/components/shared/HeroAnimation.tsx";
|
||||
import MdxTryItCTA from "@/components/shared/MdxTryItCTA.tsx";
|
||||
import MdxCTA from "@/components/shared/MdxCTA.tsx";
|
||||
import HeaderImage from "/images/SEO/Formspree open source alternative vs Formbricks FormHQ comparison post for form backend as a service.png";
|
||||
|
||||
export const meta = {
|
||||
@@ -121,6 +121,6 @@ Data ownership is not only important since GDPR, CCPA, HIPAA and other data priv
|
||||
|
||||
As of now, Formspree offers a more comprehensive set of features. However, for the really useful functionality you have to pay $18 / month. Formbricks on the other hand is the new kid on the blog with many cool features in development. The option to easily self-host makes the difference for many engineers.
|
||||
|
||||
<MdxTryItCTA />
|
||||
<MdxCTA />
|
||||
|
||||
export default ({ children }) => <LayoutMdx meta={meta}>{children}</LayoutMdx>;
|
||||
|
||||
232
apps/formbricks-com/pages/vs-google-forms.mdx
Normal file
@@ -0,0 +1,232 @@
|
||||
import LayoutMdx from "@/components/shared/LayoutMdx";
|
||||
import Image from "next/image";
|
||||
import { Callout } from "@/components/shared/Callout";
|
||||
import HeroAnimation from "@/components/shared/HeroAnimation.tsx";
|
||||
import MdxCTA from "@/components/shared/MdxCTA.tsx";
|
||||
import HeaderImage from "/images/SEO/Google Forms Open Source Alternative Comparison with Formbricks Open-source Online Form Builder.png";
|
||||
import WhyGoogle from "/images/SEO/GoogleForms GDPR compliant for EU company open source self-hosting alternative.png";
|
||||
import ControllerVsProcessor from "/images/SEO/Data Controller vs Data Processor Overview for open source forms and surveys.png";
|
||||
import LemonadeExample from "/images/SEO/Example Lemonade Radio Field with Image in React Library Open Source.PNG";
|
||||
import GoogleExample from "/images/SEO/Google Form Example Customize and make it comply with GDPR CCPA HIPAA open source alternative.png";
|
||||
import CrownGIF from "/images/SEO/who gets the crown of open source forms.gif";
|
||||
import HeaderAnimation from "@/components/shared/HeroAnimation.tsx";
|
||||
|
||||
export const meta = {
|
||||
title: "Google Forms Open Source Alternative",
|
||||
description:
|
||||
"Google Forms is among the most used form and survey solutions out there. However, the lack of data privacy and old-fashioned look and feel have people look for alternatives.",
|
||||
};
|
||||
|
||||
_Google Forms is among the most used form and survey solutions out there. It’s native integration into Google Sheets makes it an obvious first choice to run surveys. However, the lack of data privacy and old-fashioned look and feel have people look for alternatives._
|
||||
|
||||
<Image
|
||||
src={HeaderImage}
|
||||
alt="Google Forms open source alternative comparison Formbricks survey solution"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
When you’re looking for an alternative to Google Forms you usually have one of the following problems with it:
|
||||
|
||||
- **Google Forms is not really GDPR compliant** so you need an alternative when you are an institution based in the EU or process data from EU citizens
|
||||
- **Google Forms looks and feels old** and you’d like an alternative which looks more like Typeform
|
||||
|
||||
### How will we compare Google Forms vs Formbricks?
|
||||
|
||||
To give you a full picture of how Google Forms and Formbricks compare, we will look at these three aspects:
|
||||
|
||||
- Data Privacy
|
||||
- Features & Functionality
|
||||
- Look & Feel
|
||||
|
||||
Let's dive in!
|
||||
|
||||
<Image src={WhyGoogle} alt="Man facepalming because he sent out a Google Form" className="rounded-lg" />
|
||||
|
||||
## Data Privacy
|
||||
|
||||
<Callout title="No legal advice" type="note">
|
||||
None of the information here is legal advice. Please consult with an expert before making your decision.
|
||||
</Callout>
|
||||
|
||||
### Can I use Google Forms compliant with GDPR?
|
||||
|
||||
The short answer is: With a lot of additional work you might but there is still legal risk attached to it.
|
||||
|
||||
Here are all the measure you have to take to use Google forms when you are based in the EU or there is the possibility that EU citizens fill in one of your Google Forms:
|
||||
|
||||
1. Before the Google Form is loaded on your page you need to **collect and document consent** from the visitors. Google always sets non-essential cookies and according to the European Court of Justice (Az. C-673/17) this requires consent. And,
|
||||
2. **Sign a Data Processing Agreement (DPA) with Google** because you are forwarding personal data to Google. This DPA has to include what user data Google stores, how long it stores this data, why it stores the data and what rights and obligations companies and Google have.
|
||||
3. **Update your Privacy Policy** to inform your respondents why you collect personal data via Google Forms, how long you want to store the data, which legal basis allows you to do so and that you have concluded an DPA agreement with Google for the data transfer
|
||||
4. **Check standard contractual clauses.** This is the tricky part because the European Court of Justice decided in July 2020 that the Privacy Shield is not active anymore. In simple terms, the Privacy Shield assured that data transfers between the EU and USA are legal. Without the Privacy Shiel it is your responsibility to prove that. This includes information about when you asked Google about the new standard contractual clauses, what the review of the standard contractual clauses revealed, why the review of the standard contractual clauses turned out the way it did, what type of data is involved (e.g. particularly sensitive data such as health data), which legal provisions apply in the third country - in this case the USA - and whether your respondents can use additional protection measures, such as SSL encryption.
|
||||
|
||||
**Good luck 😊**
|
||||
|
||||
<Image
|
||||
src={ControllerVsProcessor}
|
||||
alt="Information if you are a data controller or processor"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
### Why open-source **self-hosted** solutions are GDPR compliant
|
||||
|
||||
Now, this obviously is a lot to take in. The good news is that **you don’t need any of it** with a self-hosted solution 🎉 Why not?
|
||||
|
||||
It is a lot easier to comply with GDPR when you don’t forward your data to a different company or jurisdiction. If you hold your data on your own servers, all you have to do is:
|
||||
|
||||
- Inform your respondents that you store their data and for how long
|
||||
- Allow them to request what data you have stored about them (an email address is enough)
|
||||
- And delete the data when they ask you to (e.g. also via email)
|
||||
|
||||
You can cover all of this in a few sentences in your Privacy Policy.
|
||||
|
||||
<Callout title="You are the data controller" type="note">
|
||||
In all cases, when you create and share around a survey, you are the data controller. The form and survey
|
||||
provider is only the data processor. This means that you are responsible to comply with the GDPR framework.
|
||||
</Callout>
|
||||
|
||||
### Why open-source **EU-hosted** solutions are GDPR compliant
|
||||
|
||||
If you don’t have the expertise or capacity to self-host a form and survey solution you can use one hosted within the European Union. This also takes out a lot of the complexity because the Privacy Shield issue is taken out of the picture.
|
||||
|
||||
We’ve put together a guide on how you can create a form which is compliant with GDPR. Here is a short overview of your tasks:
|
||||
|
||||
- Add the form & survey provider as a data processor to your Privacy Policy
|
||||
- Collect consent from the respondents with a separate checkbox
|
||||
- Link to your Privacy Policy to inform respondents how you’ll handle their data and what rights they have
|
||||
- For some tools you have to sign a DPA, here at Formbricks we include it in our Terms of Service to make sure you are covered :)
|
||||
|
||||
That's about it! It's pretty straight forward an does not require a lot of additional work.
|
||||
|
||||
##
|
||||
|
||||
<HeaderAnimation />
|
||||
|
||||
## Features & Functionality
|
||||
|
||||
Let’s have a closer look at what you can do with Google Forms and Formbricks. Google Forms has been around for years but it has stayed pretty flat in what you can do with it. Formbricks is the new kid on the block with some features shipped and a lot more on the roadmap.
|
||||
|
||||
Let’s go through it in detail:
|
||||
|
||||
### Question Types
|
||||
|
||||
| Question Types | Google Forms | Formbricks |
|
||||
| ----------------------- | ------------ | ---------- |
|
||||
| Short Text | ✅ | ✅ |
|
||||
| Long Text | ✅ | ✅ |
|
||||
| Multiple Choice / Radio | ✅ | ✅ |
|
||||
| Checkbox | ✅ | ✅ |
|
||||
| Dropdown / Select | ✅ | ✅ |
|
||||
| File Upload | ✅ | ✅ |
|
||||
| Linear Scale | ✅ | ⚙️ |
|
||||
| Multiple Choice Grid | ✅ | ⚙️ |
|
||||
| Checkbox Grid | ✅ | ⚙️ |
|
||||
| Date | ✅ | ✅ |
|
||||
| Time | ✅ | ✅ |
|
||||
| Star Rating | ❌ | ✅ |
|
||||
| NPS | ❌ | ✅ |
|
||||
| Toggle | ❌ | ✅ |
|
||||
| Payments | ❌ | ⚙️ |
|
||||
| ⚙️ = in development | | |
|
||||
|
||||
### Working with your data
|
||||
|
||||
Once your responses are in, you want to work with the qualitative data you gathered. Let’s see how Google Forms compares here with Formbricks:
|
||||
|
||||
| Export to | Google Forms | Formbricks |
|
||||
| ------------------- | ------------ | ---------- |
|
||||
| Google Sheets | ✅ | ⚙️ |
|
||||
| Airtable | ❌ | ⚙️ |
|
||||
| Webhook | ❌ | ✅ |
|
||||
| Email | ❌ | ✅ |
|
||||
| Zapier | ❌ | ⚙️ |
|
||||
| n8n | ❌ | ⚙️ |
|
||||
| ⚙️ = in development | | |
|
||||
|
||||
Google wants you to stay in their ecosystem of apps. If you’re familiar with Javascript you can write Google App Scripts to email your data somewhere after every submission.
|
||||
|
||||
### Additional functionality
|
||||
|
||||
Google brings along a few handy features to change and enhance the user experience. Formbricks is still early, in terms of additional functionality not a lot has been built yet.
|
||||
|
||||
Let’s have a look:
|
||||
|
||||
| Additional Features | Google Forms | Formbricks |
|
||||
| ---------------------------- | ------------ | ---------- |
|
||||
| Turn into quiz | ✅ | ❌ |
|
||||
| Send form data to respondent | ✅ | ✅ |
|
||||
| Allow changing form data | ✅ | ⚙️ |
|
||||
| Progress bar | ✅ | ❌ |
|
||||
| Randomize questions | ✅ | ❌ |
|
||||
| Show results | ✅ | ⚙️ |
|
||||
| Form Logic | ❌ | ⚙️ |
|
||||
| Forward to custom URL | ❌ | ✅ |
|
||||
| Welcome & Thank You Page | ❌ | ✅ |
|
||||
| Custom mail server | ❌ | ⚙️ |
|
||||
| Custom mail templates | ❌ | ⚙️ |
|
||||
| Schedule close date | ❌ | ⚙️ |
|
||||
| Close on submission limit | ❌ | ⚙️ |
|
||||
| Survey Templates | ❌ | ⚙️ |
|
||||
| ⚙️ = in development | | |
|
||||
|
||||
<Image
|
||||
src={LemonadeExample}
|
||||
alt="Example of a conversion-optimized lead generation form from Lemonade"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
## Look & Feel
|
||||
|
||||
Comparing look and feel in many cases comes down to taste. However, things like customizability are very good to compare. Let’s have a look at a standard Google Form, like we have seen many:
|
||||
|
||||
<Image
|
||||
src={GoogleExample}
|
||||
alt="Screenshot of Customer Satisfaction Survey on Google Forms"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
On the plus side the Google Forms are very accessible: They’re rich in contrast and can be filled out just with a keyboard easily. Even less tech savy users would know what to do when they see the form. It is a very pragmatic design - if you respondents **have to** fill this out, it’ll get the job done!
|
||||
|
||||
On the flip side this isn’t conversion-optimized. Multi-Step forms are possible to build but don’t have a great UX when you do so. You cannot add images in the options of radio buttons which is proven to drive up conversion significantly. It will always look like Google. It very much _feels_ like a form while it would be beneficial if it would feel more like an app.
|
||||
|
||||
### Customizability
|
||||
|
||||
To make your Google Form align more with your Corporate Design, you have a few options to do so. Here is a list of things you can change:
|
||||
|
||||
- Fonts
|
||||
- Main Color
|
||||
- Header Image
|
||||
|
||||
If you have your logo at hand and know how to place it in a 1200 x 400px image you are able to brand your survey with the header image.
|
||||
|
||||
<Callout title="Visual Builder in development" type="note">
|
||||
As of now, Formbricks only offer the opportunity to create forms with Code (React.js). The No Code builder
|
||||
is planned to launch in Feburary 2023.
|
||||
</Callout>
|
||||
|
||||
Formbricks offers two ways to create forms: Code and No Code. If you are willing and able to code your forms, you have maximum freedom to style them. You can import your own CSS style sheet or use Tailwind CSS to style every pixel of it.
|
||||
|
||||
[In our docs you'll find detailed information](/docs) on how to style your forms and surveys.
|
||||
|
||||
<Image
|
||||
src={CrownGIF}
|
||||
alt="GIF from the Office showing who gets the crown of form and survey tools OS"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
## Overall Result: Who gets the form crown?
|
||||
|
||||
All in all, Google Forms is a good option if you can be sure that no EU citizen will fill out your form and you want to collect data from a group of people which **has to** fill out your form.
|
||||
|
||||
Formbricks on the other hand, at the current state of development, is only an option if you are able to code your own form or survey with React. If this is an option for you, we prepared an [in-depth comparison](/best-react-form-library-2023) of Formbricks React with other popular React libraries.
|
||||
|
||||
If need to build your forms visually, we ask you for a little bit of patience. We plan to roll out the No Code editor early 2023.
|
||||
|
||||
<Callout title="Looking for a Typeform Alternative?" type="note">
|
||||
If you are looking for a Typeform alternative, we can highly recommend using Tally. Tally has a generous
|
||||
free plan, can be used with GDPR compliance (it’s a Belgium startup) and will most likely server all your
|
||||
needs. [Tally is great! 👏](https://tally.so)
|
||||
</Callout>
|
||||
|
||||
<MdxCTA />
|
||||
|
||||
export default ({ children }) => <LayoutMdx meta={meta}>{children}</LayoutMdx>;
|
||||
89
apps/formbricks-com/pages/vs-ohmyform.mdx
Normal file
@@ -0,0 +1,89 @@
|
||||
import LayoutMdx from "@/components/shared/LayoutMdx";
|
||||
import Image from "next/image";
|
||||
import { Callout } from "@/components/shared/Callout";
|
||||
import HeroAnimation from "@/components/shared/HeroAnimation.tsx";
|
||||
import MdxCTA from "@/components/shared/MdxCTA.tsx";
|
||||
import HeaderImage from "/images/SEO/OhMyForm Typeform Alternative comparison Formbricks open source forms and survey library builder.png";
|
||||
|
||||
export const meta = {
|
||||
title: "Comparison: OhMyForm Open Source Typeform Alternative vs Formbricks React Library",
|
||||
description:
|
||||
"OhMyForm always pops up when you’re looking for open source forms - it seems like it is the best we have got in terms of open source form and survey libraries. But how good is it? Let’s find out 👇",
|
||||
};
|
||||
|
||||
_OhMyForm always pops up when you’re looking for open source forms - it seems like it is the best we have got in terms of open source form and survey libraries. But how good is it? Let’s find out 👇_
|
||||
|
||||
<Image
|
||||
src={HeaderImage}
|
||||
alt="Formspree open source alternative vs Formbricks FormHQ"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
## How good is OhMyForms?
|
||||
|
||||
To answer the question of how good OhMyForms actually is, we tested it extensively. When you get it to run, it does a few things well. Here is an overview of what we liked and what we missed:
|
||||
|
||||
| | OhMyForm | Formbricks |
|
||||
| -------------------------- | --------------- | ---------- |
|
||||
| Multi-Language Support | ✅ | ❌ |
|
||||
| Modern UI | 🟨 | ✅ |
|
||||
| 11 possible question types | 🟨 | 🟨 |
|
||||
| Editable start / end pages | 🟨 | ✅ |
|
||||
| Export Submissions | XLS, JSON & CSV | CSV |
|
||||
| Analytics | 🟨 | ❌ |
|
||||
| Embeddable Forms | ✅ | ❌ |
|
||||
| Forms API | 🟨 | ✅ |
|
||||
| Customizable Notifications | 🟨 | ⚙️ |
|
||||
| Web Hooks | ✅ | ✅ |
|
||||
| Modern Tech Stack | 🟨 | ✅ |
|
||||
| 3rd party integrations | ❌ | ⚙️ |
|
||||
| Email Responder | ❌ | ⚙️ |
|
||||
| Custom Mail Server | ❌ | ⚙️ |
|
||||
| Conditional Logic | ❌ | ⚙️ |
|
||||
| Self-hosting | ✅ | ✅ |
|
||||
| Documentation | ❌ | ✅ |
|
||||
| Last Feature shipped | Aug 2020 | Nov 2022 |
|
||||
|
||||
<Callout title="Hobby project" type="note">
|
||||
OhMyForms is a hobby project and we are thankful for the team to develop and open source it 🤍 We in no way
|
||||
want to dunk on OhMyForm.
|
||||
</Callout>
|
||||
|
||||
Let’s go into details on a few of these points:
|
||||
|
||||
### ❌ Docker not working & support not replying
|
||||
|
||||
Getting OhMyForm up and running ended up to be a lot more time-consuming than expected. DockerHub should make it really easy but it doesn’t work. Apart from that, the lack of documentation makes it quite tedious for beginners. The Quick Start Guide is very basic and does not give any hints on how to troubleshoot if the installation doesn’t work.
|
||||
|
||||
Since I ran into issues on both ways to get a local instance up and running, I reached out in the OhMyForm Discord and asked for help, without a response.
|
||||
|
||||
### ✅ Multi-language support works quite well
|
||||
|
||||
It’s quite easy to internationalize your OhMyForm forms.
|
||||
|
||||
### ❌ No Documentation
|
||||
|
||||
Writing docs isn’t too much fun. However, working with a tool that doesn’t have any kind of documentation isn’t fun either :) No matter what I would like to achieve, I have to find out myself how it works. This is quite inefficient for devs of any proficiency.
|
||||
|
||||
### ✅ Quite complete set of question types
|
||||
|
||||
OhMyForm packs quite a nice set of question types. It’s not complete but for most use cases it should suffice.
|
||||
|
||||
### ❌ Inactive repository
|
||||
|
||||
In 2022 a few bugs have been fixed, but no feature has been built in the past two years. It’s unclear if the team will pick up the project at some point. Currently, it feels quite abandoned with incorrect and incomplete information on the website and the inactive Discord community.
|
||||
|
||||
## Overall Result
|
||||
|
||||
OhMyForm is an ok starting point for hosting your own forms. If the Typeform-esque survey is what you are looking for, it might be a good foundation to start with.
|
||||
|
||||
On the flipside, the lack of documentation and community activity makes it quite difficult to work with. The last feature has been shipped over 2 years ago, which doesn’t make OhMyForm a future-proof option.
|
||||
|
||||
<Callout title="Once more" type="note">
|
||||
OhMyForms is a hobby project and we are thankful for the team to develop and open source it! We in no way
|
||||
want to dunk on OhMyForm and appreciate their work 🤍
|
||||
</Callout>
|
||||
|
||||
<MdxCTA />
|
||||
|
||||
export default ({ children }) => <LayoutMdx meta={meta}>{children}</LayoutMdx>;
|
||||
@@ -3,4 +3,4 @@ module.exports = {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 891 B After Width: | Height: | Size: 891 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
|
||||
<url><loc>https://formbricks.com</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/open-source-forms-will-save-the-world</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/snoopforms-becomes-formbricks</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/why-open-source-no-code-is-the-future-of-enterprise-gov-software</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/community</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/core-api</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/data-insights/form-hq</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/data-pipelines/core-api</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/data-pipelines/email-notifications</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/data-pipelines/webhooks</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/introduction/how-to-achieve-this</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/introduction/quick-start</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/introduction/what-is-formbricks</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/introduction/why-formbricks</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/react-form-library/getting-started</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/react-form-library/introduction</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/email</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/form-hq</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/get-started</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/imprint</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/privacy</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/react-form-library</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/visual-builder</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/vs-formspree</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/vs-react-hook-form</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/webhooks</loc><lastmod>2022-11-16T09:56:29.421Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
</urlset>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<sitemap><loc>https://formbricks.com/sitemap-0.xml</loc></sitemap>
|
||||
</sitemapindex>
|
||||
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 31 KiB |
@@ -43,5 +43,5 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("@tailwindcss/typography")],
|
||||
plugins: [require("@tailwindcss/typography"), require("@tailwindcss/forms")],
|
||||
};
|
||||
|
||||
4
apps/storybook/.eslintrc.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["formbricks"],
|
||||
};
|
||||
1
apps/storybook/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
storybook-static/
|
||||
29
apps/storybook/.storybook/main.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
stories: [
|
||||
"../stories/**/*.stories.mdx",
|
||||
"../stories/**/*.stories.tsx",
|
||||
"../../../packages/ui/src/components/**/*.stories.mdx",
|
||||
"../../../packages/ui/src/components/**/*.stories.@(js|jsx|ts|tsx)",
|
||||
],
|
||||
addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
|
||||
framework: "@storybook/react",
|
||||
core: {
|
||||
builder: "@storybook/builder-vite",
|
||||
},
|
||||
async viteFinal(config, { configType }) {
|
||||
// customize the Vite config here
|
||||
return {
|
||||
...config,
|
||||
resolve: {
|
||||
alias: [
|
||||
{
|
||||
find: "@formbricks/ui",
|
||||
replacement: path.resolve(__dirname, "../../../packages/ui/"),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
1
apps/storybook/.storybook/preview.jsx
Normal file
@@ -0,0 +1 @@
|
||||
import "../styles.css";
|
||||
34
apps/storybook/package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@formbricks/storybook",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "start-storybook -p 6006",
|
||||
"preview-storybook": "serve storybook-static",
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formbricks/ui": "workspace:*",
|
||||
"next": "^13.0.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/tailwind-config": "workspace:*",
|
||||
"@formbricks/tsconfig": "workspace:*",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"@storybook/addon-actions": "^6.5.13",
|
||||
"@storybook/addon-docs": "^6.5.13",
|
||||
"@storybook/addon-essentials": "^6.5.13",
|
||||
"@storybook/addon-links": "^6.5.13",
|
||||
"@storybook/builder-vite": "^0.2.5",
|
||||
"@storybook/react": "^6.5.13",
|
||||
"@vitejs/plugin-react": "^2.2.0",
|
||||
"eslint-config-formbricks": "workspace:*",
|
||||
"postcss": "^8.4.18",
|
||||
"serve": "^14.1.1",
|
||||
"tailwindcss": "^3.2.2",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^3.2.4"
|
||||
}
|
||||
}
|
||||
13
apps/storybook/postcss.config.js
Normal file
@@ -0,0 +1,13 @@
|
||||
// If you want to use other PostCSS plugins, see the following:
|
||||
// https://tailwindcss.com/docs/using-with-preprocessors
|
||||
|
||||
const config = require("@formbricks/tailwind-config/tailwind.config.js");
|
||||
|
||||
module.exports = {
|
||||
plugins: {
|
||||
// Specifying the config is not necessary in most cases, but it is included
|
||||
// here to share the same config across the entire monorepo
|
||||
tailwindcss: { config },
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
8
apps/storybook/stories/intro.stories.mdx
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Meta } from "@storybook/addon-docs";
|
||||
|
||||
<Meta title="Introduction" />
|
||||
|
||||
<div className="text-center flex flex-col items-center">
|
||||
<h1 style={{ marginBottom: "24px", marginTop: "36px" }}>Welcome to Formbricks UI</h1>
|
||||
<p>Here you can find all the UI components used within the Formbricks Stack.</p>
|
||||
</div>
|
||||
3
apps/storybook/styles.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
3
apps/storybook/tailwind.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const config = require("@formbricks/tailwind-config/tailwind.config.js");
|
||||
|
||||
module.exports = config;
|
||||
8
apps/storybook/vite.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import('vite').UserConfig} */
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
preserveSymlinks: true,
|
||||
},
|
||||
});
|
||||
@@ -1,8 +1,8 @@
|
||||
# @formbricks/database
|
||||
|
||||
## 1.0.1
|
||||
## 1.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [933be01]
|
||||
- @formbricks/react@0.0.3
|
||||
- Updated dependencies [1625495]
|
||||
- @formbricks/react@0.1.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@formbricks/database",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# @formbricks/react
|
||||
|
||||
## 0.0.3
|
||||
## 0.1.0
|
||||
|
||||
### Patch Changes
|
||||
### Minor Changes
|
||||
|
||||
- 933be01: add simple validation, add minLength & maxLength to Text & Textarea
|
||||
- 1625495: First release with Text, Textarea, Submit, simple validation and schema support
|
||||
|
||||
@@ -7,4 +7,4 @@ Under the hood we are using [React Hook Form](https://github.com/react-hook-form
|
||||
|
||||
## Get started
|
||||
|
||||
To get started, please check out our [docs](https://formbricks-com.vercel.app/docs/form-creation/react-form-builder)
|
||||
To get started, please check out our [docs](https://formbricks.com/docs/react-form-library/introduction)
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
{
|
||||
"name": "@formbricks/react",
|
||||
"version": "0.0.3",
|
||||
"version": "0.1.0",
|
||||
"author": "Formbricks <hola@formbricks.com>",
|
||||
"description": "Building React forms has never been quicker.",
|
||||
"homepage": "https://formbricks.com",
|
||||
"main": "./dist/index.js",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": "./dist",
|
||||
"./styles.css": "./dist/styles.css"
|
||||
"./package.json": "./package.json",
|
||||
"./styles.css": "./dist/styles.css",
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "tsup src/index.tsx --format esm,cjs --dts --external react && tailwindcss -i ./src/styles.css -o ./dist/styles.css",
|
||||
"dev": "concurrently \"tsup src/index.tsx --format esm,cjs --dts --external react --watch\" \"tailwindcss -i ./src/styles.css -o ./dist/styles.css --watch\"",
|
||||
"build": "tsup --dts && tailwindcss -i ./src/styles.css -o ./dist/styles.css --minify",
|
||||
"dev": "concurrently \"tsup --dts --external react --watch && generate-tailwind\" \"tailwindcss -i ./src/styles.css -o ./dist/styles.css --watch\"",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/tailwind-config": "workspace:*",
|
||||
"@formbricks/tsconfig": "workspace:*",
|
||||
"@types/react": "^18.0.25",
|
||||
"@types/react-dom": "^18.0.8",
|
||||
@@ -30,10 +36,30 @@
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/error-message": "^2.0.1",
|
||||
"clsx": "^1.2.1",
|
||||
"react-hook-form": "^7.39.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17 || ^18"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
"form",
|
||||
"forms",
|
||||
"form-validation",
|
||||
"validation",
|
||||
"typescript",
|
||||
"formbricks",
|
||||
"survey",
|
||||
"surveys",
|
||||
"select"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/formbricks/formbricks"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ interface FormProps {
|
||||
|
||||
export function Form({ onSubmit, children }: FormProps) {
|
||||
const [schema, setSchema] = useState<any>([]);
|
||||
const methods = useForm();
|
||||
const methods = useForm({ criteriaMode: "all", mode: "onChange" });
|
||||
const onFormSubmit = (data: any, event: React.BaseSyntheticEvent<object, any, any> | undefined) =>
|
||||
onSubmit({ data, schema, event });
|
||||
return (
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
import React, { useContext, useEffect, useMemo } from "react";
|
||||
import { generateId } from "../lib/utils";
|
||||
import { getValidationRules } from "../lib/validation";
|
||||
import { SchemaContext } from "./Form";
|
||||
import { Text, TextInputUniqueProps } from "./inputs/Text";
|
||||
import { Textarea, TextareaInputUniqueProps } from "./inputs/Textarea";
|
||||
import { Help } from "./shared/Help";
|
||||
|
||||
interface TypeProps {
|
||||
type: "text" | "textarea" | "submit";
|
||||
}
|
||||
|
||||
interface SubmitTypeProps {
|
||||
id?: string;
|
||||
name?: string;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
help?: string;
|
||||
validation?: string;
|
||||
}
|
||||
|
||||
export interface UniversalInputProps {
|
||||
id?: string;
|
||||
help?: string;
|
||||
name?: string;
|
||||
label?: string;
|
||||
elemId: string;
|
||||
validation?: string;
|
||||
}
|
||||
|
||||
type FormbricksProps = TextInputUniqueProps & TextareaInputUniqueProps & SubmitTypeProps & TypeProps;
|
||||
|
||||
export function Formbricks({
|
||||
id,
|
||||
name,
|
||||
label,
|
||||
placeholder,
|
||||
help,
|
||||
type,
|
||||
validation,
|
||||
minLength,
|
||||
maxLength,
|
||||
}: FormbricksProps) {
|
||||
const elemId = useMemo(() => (typeof id !== "undefined" ? id : `${name}=${generateId(3)}`), [id]);
|
||||
const { setSchema } = useContext(SchemaContext);
|
||||
|
||||
useEffect(() => {
|
||||
setSchema((schema: any) => {
|
||||
const newSchema = JSON.parse(JSON.stringify(schema));
|
||||
let elementIdx = newSchema.findIndex((e: any) => e.name === name);
|
||||
if (elementIdx === -1) {
|
||||
newSchema.push({
|
||||
id,
|
||||
name,
|
||||
label,
|
||||
placeholder,
|
||||
help,
|
||||
type,
|
||||
validation,
|
||||
minLength,
|
||||
maxLength,
|
||||
});
|
||||
elementIdx = newSchema.length - 1;
|
||||
}
|
||||
/* if (["checkbox", "radio"].includes(type)) {
|
||||
newSchema.elements[elementIdx].options = getOptionsSchema(options);
|
||||
} */
|
||||
return newSchema;
|
||||
});
|
||||
}, [name, setSchema]);
|
||||
|
||||
return (
|
||||
<div className="formbricks-outer" data-type={type} data-family={type}>
|
||||
<div className="formbricks-wrapper">
|
||||
{type === "text" ? (
|
||||
<Text
|
||||
name={name}
|
||||
label={label}
|
||||
elemId={elemId}
|
||||
placeholder={placeholder}
|
||||
validation={validation}
|
||||
minLength={minLength}
|
||||
maxLength={maxLength}
|
||||
/>
|
||||
) : type === "textarea" ? (
|
||||
<Textarea
|
||||
name={name}
|
||||
label={label}
|
||||
elemId={elemId}
|
||||
placeholder={placeholder}
|
||||
validation={validation}
|
||||
minLength={minLength}
|
||||
maxLength={maxLength}
|
||||
/>
|
||||
) : type === "submit" ? (
|
||||
<button className="formbricks-input" type="submit" id={elemId}>
|
||||
{label}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
{help && <Help help={help} elemId={elemId} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { Form } from "./Form";
|
||||
import { Formbricks } from "./Formbricks";
|
||||
import { Text, Textarea } from "./Inputs";
|
||||
|
||||
interface OnSubmitProps {
|
||||
data: any;
|
||||
@@ -17,9 +17,13 @@ export function FormbricksSchema({ schema, onSubmit }: FormbricksSchemaProps) {
|
||||
// TODO validate schema
|
||||
return (
|
||||
<Form onSubmit={onSubmit}>
|
||||
{schema.map((element: any) => (
|
||||
<Formbricks {...element} />
|
||||
))}
|
||||
{schema.map((element: any) =>
|
||||
element.type === "text" ? (
|
||||
<Text {...element} />
|
||||
) : element.type === "textarea" ? (
|
||||
<Textarea {...element} />
|
||||
) : null
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
4
packages/react/src/components/Inputs.tsx
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from "./inputs/Button";
|
||||
export * from "./inputs/Submit";
|
||||
export * from "./inputs/Text";
|
||||
export * from "./inputs/Textarea";
|
||||
31
packages/react/src/components/inputs/Button.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import clsx from "clsx";
|
||||
import React, { useMemo } from "react";
|
||||
import { getElementId } from "../../lib/element";
|
||||
import { useEffectUpdateSchema } from "../../lib/schema";
|
||||
import { SVGComponent, UniversalInputProps } from "../../types";
|
||||
import ButtonComponent from "../shared/ButtonComponent";
|
||||
import { Help } from "../shared/Help";
|
||||
|
||||
interface ButtonInputUniqueProps {
|
||||
PrefixIcon?: SVGComponent;
|
||||
SuffixIcon?: SVGComponent;
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement> | undefined;
|
||||
}
|
||||
|
||||
type FormbricksProps = ButtonInputUniqueProps & UniversalInputProps;
|
||||
|
||||
const inputType = "button";
|
||||
|
||||
export function Button(props: FormbricksProps) {
|
||||
const elemId = useMemo(() => getElementId(props.id, props.name), [props.id, props.name]);
|
||||
useEffectUpdateSchema(props, inputType);
|
||||
|
||||
return (
|
||||
<div className={clsx("formbricks-outer", props.outerClassName)} data-type={inputType}>
|
||||
<div className={clsx("formbricks-wrapper", props.wrapperClassName)}>
|
||||
<ButtonComponent elemId={elemId} {...props} />
|
||||
</div>
|
||||
{props.help && <Help help={props.help} elemId={elemId} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
30
packages/react/src/components/inputs/Submit.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import clsx from "clsx";
|
||||
import React, { useMemo } from "react";
|
||||
import { getElementId } from "../../lib/element";
|
||||
import { useEffectUpdateSchema } from "../../lib/schema";
|
||||
import { SVGComponent, UniversalInputProps } from "../../types";
|
||||
import ButtonComponent from "../shared/ButtonComponent";
|
||||
import { Help } from "../shared/Help";
|
||||
|
||||
interface SubmitInputUniqueProps {
|
||||
PrefixIcon?: SVGComponent;
|
||||
SuffixIcon?: SVGComponent;
|
||||
}
|
||||
|
||||
type FormbricksProps = SubmitInputUniqueProps & UniversalInputProps;
|
||||
|
||||
const inputType = "submit";
|
||||
|
||||
export function Submit(props: FormbricksProps) {
|
||||
const elemId = useMemo(() => getElementId(props.id, props.name), [props.id, props.name]);
|
||||
useEffectUpdateSchema(props, inputType);
|
||||
|
||||
return (
|
||||
<div className={clsx("formbricks-outer", props.outerClassName)} data-type={inputType}>
|
||||
<div className={clsx("formbricks-wrapper", props.wrapperClassName)}>
|
||||
<ButtonComponent type="submit" elemId={elemId} {...props} />
|
||||
</div>
|
||||
{props.help && <Help help={props.help} elemId={elemId} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,46 +1,62 @@
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
import React, { useMemo } from "react";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { getValidationRules } from "../../lib/validation";
|
||||
import { UniversalInputProps } from "../Formbricks";
|
||||
import { getElementId } from "../../lib/element";
|
||||
import { useEffectUpdateSchema } from "../../lib/schema";
|
||||
import { getValidationRules, validate } from "../../lib/validation";
|
||||
import { NameRequired, UniversalInputProps } from "../../types";
|
||||
import { Help } from "../shared/Help";
|
||||
import { Label } from "../shared/Label";
|
||||
import { Messages } from "../shared/Messages";
|
||||
|
||||
export interface TextInputUniqueProps {
|
||||
interface TextInputUniqueProps {
|
||||
maxLength?: number;
|
||||
minLength?: number;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
type TextProps = UniversalInputProps & TextInputUniqueProps;
|
||||
type FormbricksProps = TextInputUniqueProps & UniversalInputProps & NameRequired;
|
||||
|
||||
export function Text({
|
||||
name,
|
||||
label,
|
||||
elemId,
|
||||
placeholder,
|
||||
validation,
|
||||
minLength = 0,
|
||||
maxLength = 524288,
|
||||
}: TextProps) {
|
||||
const { register } = useFormContext();
|
||||
const validationRules = getValidationRules(validation);
|
||||
const inputType = "text";
|
||||
|
||||
if (!name) {
|
||||
console.error("🧱 Fomrbricks Error: Textarea has no name attribute");
|
||||
return <div></div>;
|
||||
}
|
||||
export function Text(props: FormbricksProps) {
|
||||
const elemId = useMemo(() => getElementId(props.id, props.name), [props.id, props.name]);
|
||||
useEffectUpdateSchema(props, inputType);
|
||||
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
} = useFormContext();
|
||||
const validationRules = getValidationRules(props.validation);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Label label={label} elemId={elemId} />
|
||||
<div className="formbricks-inner">
|
||||
<input
|
||||
className="formbricks-input"
|
||||
type="text"
|
||||
id={elemId}
|
||||
placeholder={placeholder || ""}
|
||||
{...register(name, { required: validationRules?.includes("required"), minLength, maxLength })}
|
||||
/>
|
||||
<div className={clsx("formbricks-outer", props.outerClassName)} data-type={inputType}>
|
||||
<div className={clsx("formbricks-wrapper", props.wrapperClassName)}>
|
||||
<Label label={props.label} elemId={elemId} />
|
||||
<div className={clsx("formbricks-inner", props.innerClassName)}>
|
||||
<input
|
||||
className={clsx("form-input", "formbricks-input", props.inputClassName)}
|
||||
type="text"
|
||||
id={elemId}
|
||||
placeholder={props.placeholder || ""}
|
||||
aria-invalid={errors[props.name] ? "true" : "false"}
|
||||
{...register(props.name, {
|
||||
required: { value: "required" in validationRules, message: "This field is required" },
|
||||
minLength: {
|
||||
value: props.minLength || 0,
|
||||
message: `Your answer must be at least ${props.minLength} characters long`,
|
||||
},
|
||||
maxLength: {
|
||||
value: props.maxLength || 524288,
|
||||
message: `Your answer musn't be longer than ${props.maxLength} characters`,
|
||||
},
|
||||
validate: validate(validationRules),
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
{props.help && <Help help={props.help} elemId={elemId} helpClassName={props.helpClassName} />}
|
||||
<Messages errors={errors} {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,49 +1,64 @@
|
||||
import React from "react";
|
||||
import React, { useMemo } from "react";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { getValidationRules } from "../../lib/validation";
|
||||
import { UniversalInputProps } from "../Formbricks";
|
||||
import { getElementId } from "../../lib/element";
|
||||
import { useEffectUpdateSchema } from "../../lib/schema";
|
||||
import { getValidationRules, validate } from "../../lib/validation";
|
||||
import { NameRequired, UniversalInputProps } from "../../types";
|
||||
import { Help } from "../shared/Help";
|
||||
import { Label } from "../shared/Label";
|
||||
import { Messages } from "../shared/Messages";
|
||||
|
||||
export interface TextareaInputUniqueProps {
|
||||
interface TextareaInputUniqueProps {
|
||||
cols?: number;
|
||||
maxLength?: number;
|
||||
minLength?: number;
|
||||
placeholder?: string;
|
||||
rows?: number;
|
||||
}
|
||||
|
||||
type TextareaProps = UniversalInputProps & TextareaInputUniqueProps;
|
||||
type TextareaProps = TextareaInputUniqueProps & UniversalInputProps & NameRequired;
|
||||
|
||||
export function Textarea({
|
||||
name,
|
||||
label,
|
||||
elemId,
|
||||
placeholder,
|
||||
validation,
|
||||
minLength = 0,
|
||||
maxLength = 524288,
|
||||
}: TextareaProps) {
|
||||
const { register } = useFormContext();
|
||||
const validationRules = getValidationRules(validation);
|
||||
const inputType = "textarea";
|
||||
|
||||
if (!name) {
|
||||
console.error("🧱 Fomrbricks Error: Textarea has no name attribute");
|
||||
return <div></div>;
|
||||
}
|
||||
export function Textarea(props: TextareaProps) {
|
||||
const elemId = useMemo(() => getElementId(props.id, props.name), [props.id, props.name]);
|
||||
useEffectUpdateSchema(props, inputType);
|
||||
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
} = useFormContext();
|
||||
const validationRules = getValidationRules(props.validation);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Label label={label} elemId={elemId} />
|
||||
<div className="formbricks-inner">
|
||||
<textarea
|
||||
className="formbricks-input"
|
||||
id={elemId}
|
||||
placeholder={placeholder || ""}
|
||||
{...register(name, {
|
||||
required: validationRules?.includes("required"),
|
||||
minLength,
|
||||
maxLength,
|
||||
})}
|
||||
/>
|
||||
<div className="formbricks-outer" data-type={inputType}>
|
||||
<div className="formbricks-wrapper">
|
||||
<Label label={props.label} elemId={elemId} />
|
||||
<div className="formbricks-inner">
|
||||
<textarea
|
||||
className="formbricks-input"
|
||||
id={elemId}
|
||||
placeholder={props.placeholder || ""}
|
||||
cols={props.cols}
|
||||
rows={props.rows}
|
||||
aria-invalid={errors[props.name] ? "true" : "false"}
|
||||
{...register(props.name, {
|
||||
required: { value: "required" in validationRules, message: "This field is required" },
|
||||
minLength: {
|
||||
value: props.minLength || 0,
|
||||
message: `Your answer must be at least ${props.minLength} characters long`,
|
||||
},
|
||||
maxLength: {
|
||||
value: props.maxLength || 524288,
|
||||
message: `Your answer musn't be longer than ${props.maxLength} characters`,
|
||||
},
|
||||
validate: validate(validationRules),
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
{props.help && <Help help={props.help} elemId={elemId} />}
|
||||
<Messages errors={errors} {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||