Welcome to @adimis/react-formix
, the ultimate React library for creating dynamic, schema-based forms. Built with modern web development in mind, this library harnesses the power of React, Zod for robust schema validation, and react-hook-form for seamless form state management. Whether you need to build custom forms from the ground up or use prebuilt components for rapid deployment, @adimis/react-formix
is your go-to solution.
- Schema-Based Form Generation: Define your form fields and validation rules using a JSON schema.
- Zod-Powered Validation: Leverage Zod for strong data validation and meaningful feedback.
- Flexible Layout Options: Create responsive and dynamic form layouts effortlessly.
- Conditional Rendering: Dynamically show or hide form fields based on other field values.
- State Persistence: Save and restore form state using local storage or session storage.
- Custom Render Functions: Specify custom render functions inside the schema array's object.
- TypeScript Support: Utilize generic TypeScript typing for form field types.
- Dynamic Field Visibility: Update field visibility in real-time based on user input.
- Dynamic Validation: Remove field validations dynamically based on specific conditions.
- Developer Tools: Integrate developer tools for debugging and inspecting form state.
- Customizable Themes: Adapt form themes to align with your application's design.
- Latest React and Next.js Support: Ensure compatibility with modern web development practices.
-
Install the package via npm:
npm install @adimis/react-formix
-
Import the style in your
main.tsx
orindex.tsx
file:import "@adimis/react-formix/dist/style.css";
-
Install and setup Tailwind CSS: Tailwind CSS Docs
-
Add the following to your
index.css
file:@tailwind base; @tailwind components; @tailwind utilities; @layer base { :root { --background: hsl(0, 0%, 100%); --foreground: hsl(222.2, 84%, 4.9%); --card: hsl(0, 0%, 100%); --card-foreground: hsl(222.2, 84%, 4.9%); --popover: hsl(0, 0%, 100%); --popover-foreground: hsl(222.2, 84%, 4.9%); --primary: hsl(221.2, 83.2%, 53.3%); --primary-foreground: hsl(210, 40%, 98%); --secondary: hsl(210, 40%, 96.1%); --secondary-foreground: hsl(222.2, 47.4%, 11.2%); --muted: hsl(210, 40%, 96.1%); --muted-foreground: hsl(215.4, 16.3%, 46.9%); --accent: hsl(210, 40%, 96.1%); --accent-foreground: hsl(222.2, 47.4%, 11.2%); --destructive: hsl(0, 84.2%, 60.2%); --destructive-foreground: hsl(210, 40%, 98%); --border: hsl(214.3, 31.8%, 91.4%); --input: hsl(214.3, 31.8%, 91.4%); --ring: hsl(221.2, 83.2%, 53.3%); --radius: 1rem; } .dark { --background: hsl(222.2, 84%, 4.9%); --foreground: hsl(210, 40%, 98%); --card: hsl(222.2, 84%, 4.9%); --card-foreground: hsl(210, 40%, 98%); --popover: hsl(222.2, 84%, 4.9%); --popover-foreground: hsl(210, 40%, 98%); --primary: hsl(217.2, 91.2%, 59.8%); --primary-foreground: hsl(222.2, 47.4%, 11.2%); --secondary: hsl(217.2, 32.6%, 17.5%); --secondary-foreground: hsl(210, 40%, 98%); --muted: hsl(217.2, 32.6%, 17.5%); --muted-foreground: hsl(215, 20.2%, 65.1%); --accent: hsl(217.2, 32.6%, 17.5%); --accent-foreground: hsl(210, 40%, 98%); --destructive: hsl(0, 62.8%, 30.6%); --destructive-foreground: hsl(210, 40%, 98%); --border: hsl(217.2, 32.6%, 17.5%); --input: hsl(217.2, 32.6%, 17.5%); --ring: hsl(224.3, 76.3%, 48%); } } @layer base { * { @apply border-border; } body { @apply bg-background text-foreground; } }
-
Install required dependencies:
npm i tailwindcss-animate
-
Setup your
tsconfig.config.js
file:/** @type {import('tailwindcss').Config} */ export default { darkMode: ["class"], content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], theme: { container: { center: true, padding: "2rem", screens: { "2xl": "1400px", }, }, extend: { colors: { border: "var(--border)", input: "var(--input)", ring: "var(--ring)", background: "var(--background)", foreground: "var(--foreground)", primary: { DEFAULT: "var(--primary)", foreground: "var(--primary-foreground)", }, secondary: { DEFAULT: "var(--secondary)", foreground: "var(--secondary-foreground)", }, destructive: { DEFAULT: "var(--destructive)", foreground: "var(--destructive-foreground)", }, muted: { DEFAULT: "var(--muted)", foreground: "var(--muted-foreground)", }, accent: { DEFAULT: "var(--accent)", foreground: "var(--accent-foreground)", }, popover: { DEFAULT: "var(--popover)", foreground: "var(--popover-foreground)", }, card: { DEFAULT: "var(--card)", foreground: "var(--card-foreground)", }, }, borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)", }, keyframes: { "accordion-down": { from: { height: "0" }, to: { height: "var(--radix-accordion-content-height)" }, }, "accordion-up": { from: { height: "var(--radix-accordion-content-height)" }, to: { height: "0" }, }, }, animation: { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", }, }, }, plugins: [require("tailwindcss-animate")], };
Here's an example of how to create a basic signup form using @adimis/react-formix
:
"use client";
import { z } from "zod";
import { Input } from "./components/input";
import { Button } from "./components/button";
import {
FormBody,
FormContent,
FormixFormProvider,
FormDescription,
FormFlexFields,
FormFooter,
FormHeader,
FormTitle,
ISchemaFormProps,
ThemeProvider,
} from "@adimis/react-formix"; // 120.1k (gzipped: 34.2k)
import "@adimis/react-formix/dist/style.css";
interface SignUp {
username: string;
email: string;
address: string;
phone: string;
password: string;
gender: string;
terms: boolean;
file: File;
date: Date;
year: number;
expertise: string[];
}
const App = () => {
const schemaFormProps: ISchemaFormProps<SignUp> = {
formLabel: "Example Barebone Form",
formSlug: "example-barebone-form",
persistFormResponse: "localStorage",
devTools: true,
formDisabled: false,
enableConditionalRendering: true,
schema: [
{
key: "username",
label: "Username",
description: "Enter your desired username.",
autoComplete: "username",
type: "text",
placeholder: "Your username",
defaultValue: "",
validations: z
.string()
.min(1, "Username is required")
.max(20, "Username must not exceed 20 characters"),
render: ({
formDisabled,
formItem,
formMethods,
submitButtonLoading,
}) => (
<Input
type={formItem.type}
id={formItem.key}
disabled={formDisabled || submitButtonLoading}
style={{
width: "100%",
height: "10px",
border: "1px solid #ccc",
borderRadius: "5px",
padding: "10px",
margin: "5px 0",
}}
{...formMethods.register(formItem.key)}
/>
),
},
{
key: "email",
label: "Email",
description: "Enter your email address.",
autoComplete: "email",
type: "email",
placeholder: "Your email",
defaultValue: "",
validations: z
.string()
.email("Enter a valid email address")
.min(1, "Email is required"),
render: ({
formDisabled,
formItem,
formMethods,
submitButtonLoading,
}) => (
<Input
type={formItem.type}
id={formItem.key}
disabled={formDisabled || submitButtonLoading}
style={{
width: "100%",
height: "10px",
border: "1px solid #ccc",
borderRadius: "5px",
padding: "10px",
margin: "5px 0",
}}
{...formMethods.register(formItem.key)}
/>
),
},
{
key: "address",
label: "Address",
description: "Enter your full address.",
autoComplete: "address-line1",
type: "text",
placeholder: "Your address",
defaultValue: "",
validations: z
.string()
.min(10, "Address should be at least 10 characters"),
render: ({
formDisabled,
formItem,
formMethods,
submitButtonLoading,
}) => (
<Input
type={formItem.type}
id={formItem.key}
disabled={formDisabled || submitButtonLoading}
style={{
width: "100%",
height: "10px",
border: "1px solid #ccc",
borderRadius: "5px",
padding: "10px",
margin: "5px 0",
}}
{...formMethods.register(formItem.key)}
/>
),
},
{
key: "phone",
label: "Phone",
description: "Enter your phone number with country code.",
autoComplete: "tel",
type: "tel",
placeholder: "+1234567890",
defaultValue: "",
validations: z
.string()
.regex(/^\+?(\d.*){10,}$/, "Enter a valid phone number"),
render: ({
formDisabled,
formItem,
formMethods,
submitButtonLoading,
}) => (
<Input
type={formItem.type}
id={formItem.key}
disabled={formDisabled || submitButtonLoading}
style={{
width: "100%",
height: "10px",
border: "1px solid #ccc",
borderRadius: "5px",
padding: "10px",
margin: "5px 0",
}}
{...formMethods.register(formItem.key)}
/>
),
},
{
key: "password",
label: "Password",
description: "Enter a strong password.",
autoComplete: "new-password",
type: "password",
placeholder: "Your password",
defaultValue: "",
validations: z
.string()
.min(8, "Password should be at least 8 characters")
.max(20, "Password must not exceed 20 characters"),
displayConditions: [
{
dependentField: "email",
dependentFieldValue: "admin@adimis.in",
operator: "===",
},
],
removeValidationConditions: [
{
dependentField: "email",
dependentFieldValue: "admin@adimis.in",
operator: "!==",
},
],
render: ({
formDisabled,
formItem,
formMethods,
submitButtonLoading,
}) => (
<Input
type={formItem.type}
id={formItem.key}
disabled={formDisabled || submitButtonLoading}
style={{
width: "100%",
height: "10px",
border: "1px solid #ccc",
borderRadius: "5px",
padding: "10px",
margin: "5px 0",
}}
{...formMethods.register(formItem.key)}
/>
),
},
],
defaultValues: {
email: "adimis.ai.001@gmail.com",
phone: "919625183597",
},
onSubmit: (values) =>
console.log(
"On Submit Example Form Response: ",
JSON.stringify(values, null, 4)
),
onInvalidSubmit: (values) =>
console.log(
"On Submit Invalid Example Form Response: ",
JSON.stringify(values, null, 4)
),
};
return (
<ThemeProvider defaultTheme="dark">
<FormixFormProvider {...schemaFormProps}>
<FormBody>
<FormHeader>
<FormTitle />
<FormDescription />
</FormHeader>
<FormContent>
<FormFlexFields fluid columns={2} />
</FormContent>
<FormFooter>
<Button type="submit" className="mt-5">
Submit
</Button>
</FormFooter>
</FormBody>
</FormixFormProvider>
</ThemeProvider>
);
};
export default App;
Wraps the form and provides the Formix context to its children. This component initializes the form schema and context, making it accessible to all nested components.
A hook for accessing the Formix context. It provides access to the form's state and methods, enabling high-level interaction with the form.
The primary context component that utilizes the form schema and methods. It offers access to form state and functions for seamless form interaction.
The main body of the form. It wraps all form elements and handles form submission, maintaining a consistent structure and layout.
The header section of the form, used to display the form title and description, providing clear context for the form's purpose.
The footer section of the form, typically used for submit buttons or other form actions, ensuring user actions are clearly presented at the end of the form.
The content section of the form, wrapping the main form fields and organizing them within the form's structure.
Displays the form title. This component is used within FormHeader
to show the main heading of the form.
Displays the form description. This component is used within FormHeader
to provide additional context or instructions for the form.
Renders form fields in a flexible grid layout, helping in creating responsive forms with customizable grid settings such as column count and gap size.
Wraps individual form fields and connects them to the form context using react-hook-form
's Controller
component to manage field state and validation.
A wrapper for individual form items, providing a consistent structure and context for form fields.
Renders the label for a form field, ensuring that labels are consistently styled and correctly associated with their corresponding input elements.
Renders the control element (input, select, etc.) for a form field, managing the state and attributes necessary for the field's functionality and accessibility.
Renders the description for a form field, providing additional information or instructions about the field's purpose or expected input.
Displays error messages for form fields, showing validation errors to help users correct their inputs.
A hook for accessing the form field context and state, providing field-level information and methods for interacting with the form.
Provides theme context to its children, allowing for dynamic theme changes and customization across the application.
A hook for accessing the theme context, providing the current theme and a function to update the theme.
We welcome contributions to @adimis/react-formix
. If you have suggestions for new features, improvements, or bug fixes, please open an issue or submit a pull request on our GitHub repository.
This project is licensed under the GPL-3.0-only License. See the LICENSE file for details.
For detailed documentation, refer to the docs
folder. Open the index.html
file located at node_modules/@adimis/react-formix/docs/index.html
in a live server to access the documentation.