Créer des formulaires React robustes est rarement complexe… mais presque toujours répétitif.
Entre la validation, les composants UI, la gestion des erreurs et l’UX, le boilerplate s’accumule vite.
SnowForm (@snowpact/snowform) répond à ce problème :
générer automatiquement des formulaires React à partir de schémas Zod,
tout en vous laissant injecter votre propre design system.
TL;DR : Un seul appel de config définit vos composants UI, puis des formulaires auto-générés depuis Zod. Moins de boilerplate, zéro couplage UI, des formulaires cohérents sur tous vos projets.
Note : le package s’appelait auparavant @snowpact/react-rhf-zod-form. Il est désormais publié sous le nom @snowpact/snowform. Voir la section « Migration » en fin d’article.
Le boilerplate des formulaires React
Créer un formulaire React « proprement », c’est souvent ça :
- Définir un schéma Zod pour la validation
- Configurer
react-hook-formavec le resolver Zod - Créer un composant pour chaque champ (avec label, erreur, etc.)
- Gérer le loading state du submit
- Afficher les erreurs serveur
- Scroll vers la première erreur
- Toast en cas d’erreur
Sur chaque formulaire. Sur chaque projet.
Et si vous changez de design system (Shadcn → MUI), vous devez tout réécrire.
L’approche SnowForm
Notre solution : un seul appel de configuration qui définit vos composants UI, puis des formulaires auto-générés depuis Zod.
// Un formulaire complet en 20 lignes
<SnowForm
schema={userSchema}
onSubmit={async (data) => await api.createUser(data)}
onSuccess={() => toast.success('Utilisateur créé !')}
/>
Le package analyse le schéma Zod et génère automatiquement :
- Les champs appropriés (text, email, number, select, checkbox, date, tableaux…)
- Les labels traduits
- Les messages d’erreur
- Le bouton submit avec loading state
Zéro dépendance runtime : uniquement des peer dependencies (React, Zod, react-hook-form).
Prérequis & installation
npm install @snowpact/snowform # peer dependencies npm install react-hook-form zod @hookform/resolvers
- React ≥ 18
- react-hook-form ≥ 7
- @hookform/resolvers ≥ 3
- Zod ≥ 3.24 (Zod 4 pleinement supporté)
Quick Setup
1. Configurez SnowForm une seule fois
Tout passe désormais par une fonction unique : setupSnowForm(). Vous y mappez vos composants (Shadcn, MUI, custom…), vos composants de layout, votre bouton submit et vos traductions.
// configs/setupSnowForm.tsx
import { setupSnowForm } from '@snowpact/snowform';
import type { RegisteredComponentProps, FormUILabelProps } from '@snowpact/snowform';
import i18next from 'i18next';
import { toast } from 'sonner';
// Vos composants Shadcn/MUI/custom
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Select } from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import { Textarea } from '@/components/ui/textarea';
const TextInput = ({ value, onChange, name, disabled, placeholder, className }: RegisteredComponentProps<string>) => (
<Input
id={name}
name={name}
value={value ?? ''}
onChange={(e) => onChange(e.target.value)}
disabled={disabled}
placeholder={placeholder}
className={className}
/>
);
export function initSnowForm() {
setupSnowForm({
// 1. Fonction de traduction (requise) : reçoit le nom du champ, renvoie le label
translate: i18next.t.bind(i18next),
// 2. Les composants par type de champ
components: {
text: TextInput,
email: (props) => <TextInput {...props} type="email" />,
password: (props) => <TextInput {...props} type="password" />,
number: NumberInput,
textarea: ({ value, onChange, name, placeholder }) => (
<Textarea id={name} value={value ?? ''} onChange={(e) => onChange(e.target.value)} placeholder={placeholder} />
),
select: ({ value, onChange, options }) => (
<Select value={value} onValueChange={onChange} options={options} />
),
checkbox: ({ value, onChange, name }) => (
<Switch id={name} checked={value ?? false} onCheckedChange={onChange} />
),
date: DatePicker,
// Optionnels : radio, time, datetime-local, tel, url, color, file, hidden
},
// 3. Les composants de layout du formulaire
formUI: {
label: ({ children, required, invalid, htmlFor }: FormUILabelProps) => (
<label htmlFor={htmlFor} className={invalid ? 'text-destructive' : ''}>
{children}
{required && <span className="text-destructive ml-1">*</span>}
</label>
),
description: ({ children }) => <p className="text-muted-foreground text-sm">{children}</p>,
errorMessage: ({ message }) => <p className="text-destructive text-sm">{message}</p>,
},
// 4. Bouton submit custom
submitButton: ({ loading, disabled, children }) => (
<Button type="submit" disabled={disabled || loading} className="w-full">
{loading ? 'Chargement...' : children}
</Button>
),
// 5. Classes CSS appliquées au formulaire et aux champs
styles: {
form: 'space-y-6 w-full',
formItem: 'grid gap-2',
label: 'text-sm font-medium',
description: 'text-xs text-muted-foreground',
errorMessage: 'text-destructive text-sm',
chip: 'inline-flex items-center gap-1 px-2 py-1 text-sm bg-muted rounded-full mr-1 mt-1',
},
// 6. Comportement en cas d'erreur de validation
onError: (formRef, errors) => {
formRef?.scrollIntoView({ behavior: 'smooth', block: 'start' });
toast.error('Veuillez corriger les erreurs du formulaire');
},
});
}
setupSnowForm() est idempotent : un second appel est ignoré (avec un warning en console). Le registry est strict : si un type de champ n’est pas enregistré, SnowForm log un warning et ne rend pas le champ — pas de fallback silencieux.
2. Initialisez au démarrage
// main.tsx (ou _app.tsx / layout.tsx)
import { initSnowForm } from './configs/setupSnowForm';
initSnowForm();
3. Créez un formulaire
// components/LoginForm.tsx
import { SnowForm } from '@snowpact/snowform';
import { z } from 'zod';
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
export const LoginForm = () => {
return (
<SnowForm
schema={loginSchema}
overrides={{ password: { type: 'password' } }}
onSubmit={async (data) => {
await api.login(data);
}}
onSuccess={() => {
router.push('/dashboard');
}}
/>
);
};
Le formulaire affiche automatiquement :
- Un champ email (type détecté depuis
.email()) - Un champ password (déclaré via
overrides) - Labels traduits (la clé passée à
translateest le nom du champ) - Messages d’erreur Zod
- Bouton submit avec loading
Overrides : personnaliser les champs
Le système d’overrides permet de modifier le comportement par champ :
<SnowForm
schema={blogpostSchema}
onSubmit={handleSubmit}
overrides={{
// Changer le type de composant
content: {
type: 'rich-text',
description: 'Utilisez Markdown pour le formatage',
},
// Select avec options
category: {
type: 'select',
options: [
{ label: 'Tech', value: 'tech' },
{ label: 'Design', value: 'design' },
],
},
// Champ caché
authorId: {
type: 'hidden',
},
// Transformer les valeurs vides en null
videoUrl: {
type: 'text',
placeholder: 'https://youtube.com/...',
emptyAsNull: true,
},
// Composant custom inline
avatar: {
label: 'Photo de profil',
render: ({ value, onChange, error }) => (
<>
<ImageUploader value={value} onUpload={onChange} />
{error && <span className="text-destructive">{error}</span>}
</>
),
},
}}
/>
Les options disponibles par champ : label, description, placeholder, disabled, type, options, hideLabel, emptyAsNull, emptyAsUndefined, emptyAsZero, render et componentProps (props transmises telles quelles à votre composant).
Validations croisées : refine & superRefine
SnowForm supporte intégralement refine() et superRefine() : les validations inter-champs sont exécutées au submit et les erreurs remontent sur le bon champ.
const schema = z
.object({
password: z.string().min(8, '8 caractères minimum'),
confirmPassword: z.string(),
})
.refine((data) => data.password === data.confirmPassword, {
message: 'Les mots de passe ne correspondent pas',
path: ['confirmPassword'],
});
<SnowForm schema={schema} onSubmit={handleSubmit} />
Champs tableaux
Les z.array() sont rendus sous forme de « chips » : saisie + Entrée pour les tableaux de chaînes ou de nombres, sélection dans une liste pour les tableaux d’enums (les valeurs déjà choisies disparaissent du select). Chaque chip a un bouton × pour la retirer.
const schema = z.object({
tags: z.array(z.string()).default([]),
interests: z.array(z.enum(['tech', 'design', 'business'])).default([]),
});
<SnowForm
schema={schema}
overrides={{
tags: { label: 'Tags', placeholder: 'Tapez un tag puis Entrée' },
interests: {
label: 'Centres d’intérêt',
options: [
{ value: 'tech', label: 'Technologie' },
{ value: 'design', label: 'Design' },
{ value: 'business', label: 'Business' },
],
},
}}
/>
Pour des UIs de tableaux plus complexes, préférez une fonction render custom : le rendu intégré est une commodité pour les cas simples.
Layout sur mesure : le children pattern
Quand vous avez besoin de contrôler la mise en page, passez une fonction en enfant de SnowForm. Vous récupérez renderField, renderSubmitButton et l’instance react-hook-form complète.
<SnowForm schema={schema} onSubmit={handleSubmit}>
{({ renderField, renderSubmitButton, form }) => (
<div className="grid grid-cols-2 gap-4">
{/* Plusieurs champs d'un coup */}
{renderField('firstName', 'lastName')}
{/* Ou un par un, avec votre propre wrapper */}
<div className="col-span-2">{renderField('email')}</div>
{/* Champ conditionnel */}
{form.watch('showBio') && renderField('bio')}
<div className="col-span-2">
{renderSubmitButton({ children: 'Créer le compte' })}
</div>
</div>
)}
</SnowForm>
Mode édition avec fetchDefaultValues
SnowForm gère aussi les formulaires d’édition avec chargement asynchrone des données.
<SnowForm
schema={userSchema}
fetchDefaultValues={async () => {
const { data } = await api.getUser(userId);
return data.user;
}}
onSubmit={async (values) => {
await api.updateUser(userId, values);
}}
onSuccess={() => {
queryClient.invalidateQueries(['user', userId]);
toast.success('Utilisateur mis à jour');
}}
/>
Le formulaire :
- Désactive le bouton submit pendant le chargement (et en cas d’échec du fetch)
- Pré-remplit les champs avec les données reçues
- Gère le submit et l’invalidation du cache
Gestion des erreurs serveur
<SnowForm
schema={registerSchema}
onSubmit={async (data) => {
await api.register(data);
}}
onSubmitError={(setManualFormErrors, error) => {
// error.response.data = { errors: [{ field: 'email', message: 'Déjà utilisé' }] }
const fieldErrors = error.response?.data?.errors?.reduce((acc, e) => ({
...acc,
[e.field]: e.message,
}), {});
setManualFormErrors(fieldErrors);
}}
/>
Les erreurs s’affichent directement sous les champs concernés.
Composants business personnalisés
Vous pouvez enregistrer des types de champs qui n’existent pas nativement. D’abord, déclarez-les pour TypeScript :
// src/types/snow-form.d.ts
declare global {
interface SnowFormCustomTypes {
'rich-text': true;
'media-library': true;
}
}
export {};
Puis enregistrez-les dans votre setup :
setupSnowForm({
translate: t,
components: {
// ... composants standards
// Éditeur WYSIWYG
'rich-text': ({ value, onChange }) => (
<TipTapEditor content={value} onUpdate={onChange} />
),
// Sélecteur d'images depuis une bibliothèque
'media-library': ({ value, onChange }) => (
<MediaPicker selectedUrl={value} onSelect={onChange} />
),
// Sélecteur de couleur (type natif)
color: ({ value, onChange }) => (
<ColorPicker color={value} onChange={onChange} />
),
},
});
Puis utilisez-les via overrides :
<SnowForm
schema={pageSchema}
overrides={{
content: { type: 'rich-text' },
heroImage: { type: 'media-library' },
accentColor: { type: 'color' },
}}
/>
Intégration avec les SDK générés
Si vous utilisez un générateur OpenAPI (Orval, openapi-typescript, etc.), les schémas Zod sont déjà générés :
// Le schéma Zod vient directement du SDK généré depuis OpenAPI
import { zodUserCreateBody } from '@/sdk';
<SnowForm
schema={zodUserCreateBody} // Généré automatiquement
onSubmit={async (data) => {
await sdk.createUser(data);
}}
/>
Votre formulaire est toujours synchronisé avec l’API backend.
Tableau des fonctionnalités
| Fonctionnalité | Description |
|---|---|
| Auto-génération | Formulaire généré depuis le schéma Zod |
| Types de champs | text, email, password, number, textarea, select, checkbox, radio, date, time, datetime-local, tel, url, color, file, hidden |
| Types custom | Enregistrez vos propres types (rich-text, rating…) avec support TypeScript |
| Composants custom | Injectez vos propres composants via le registry |
| Form UI | Label, description et message d’erreur entièrement remplaçables |
| Overrides | Personnalisez chaque champ individuellement |
| Refinements | refine() et superRefine() pour la validation croisée |
| Champs tableaux | z.array() rendu en chips (chaînes, nombres, enums) |
| Children pattern | Contrôle total du layout via renderField / renderSubmitButton |
| fetchDefaultValues | Mode édition avec données pré-remplies |
| Loading states | Submit désactivé pendant le fetch, loading pendant le submit |
| Validation | Zod côté client + erreurs serveur |
| i18n | Labels traduits via une fonction injectable (i18next, next-intl…) |
| onError behavior | Scroll + toast personnalisables |
| emptyAsNull | Transforme « » en null (aussi emptyAsUndefined et emptyAsZero) |
| TypeScript | Types inférés depuis le schéma Zod (Zod 3 et Zod 4) |
| Zéro dépendance | Uniquement des peer dependencies |
Mapping Zod → type de champ
| Schéma Zod | Type détecté |
|---|---|
z.string() |
text |
z.string().email() |
|
z.number() |
number |
z.boolean() |
checkbox |
z.date() |
date |
z.enum([...]) |
select |
z.array(z.T) |
tableau de T (chips) |
La détection se fait uniquement sur le type Zod. Pour tous les autres types (password, textarea, url, tel, file…), déclarez-les explicitement avec type: 'xxx' dans overrides.
Migration depuis @snowpact/react-rhf-zod-form
L’ancien package est déprécié. La migration se fait en deux temps :
- Remplacez le nom du package dans votre
package.jsonet dans tous vos imports. - Remplacez les appels de registry séparés par un unique
setupSnowForm().
| Avant | Après |
|---|---|
registerComponents({...}) |
setupSnowForm({ components: {...} }) |
registerSubmitButton(...) |
setupSnowForm({ submitButton: ... }) |
registerFormUIStyles({ formLabel, formMessage… }) |
setupSnowForm({ styles: { form, formItem, label, description, errorMessage, chip } }) |
setTranslationHook(() => ({ t })) |
setupSnowForm({ translate: t }) — une fonction, plus un hook |
setOnErrorBehavior(fn) |
setupSnowForm({ onError: fn }) |
| — | setupSnowForm({ formUI: { label, description, errorMessage } }) (nouveau) |
Point d’attention : la détection par nom de champ a été supprimée. Un champ nommé password ou description n’est plus converti automatiquement — passez par overrides.
Comparaison
| Solution | Problème |
|---|---|
| react-hook-form seul | Pas de génération auto, beaucoup de boilerplate |
| Formik | Plus maintenu activement, pas de Zod natif |
| react-jsonschema-form | JSON Schema au lieu de Zod, style imposé |
| AutoForm (shadcn) | Copier-coller, pas de package, couplé à Shadcn |
| SnowForm | Package npm, injectez votre UI, zéro couplage |
Liens
- GitHub : https://github.com/snowpact/snowform
- Démo live : https://snowpact.github.io/snowform/
- npm : @snowpact/snowform
Article rédigé par l’équipe Snowpact. Retrouvez nos projets open-source sur GitHub.
Le boilerplate des formulaires React
Créer un formulaire React « proprement », c’est souvent ça :
- Définir un schéma Zod pour la validation
- Configurer
react-hook-formavec le resolver Zod - Créer un composant pour chaque champ (avec label, erreur, etc.)
- Gérer le loading state du submit
- Afficher les erreurs serveur
- Scroll vers la première erreur
- Toast en cas d’erreur
Sur chaque formulaire. Sur chaque projet.
Et si vous changez de design system (Shadcn → MUI), vous devez tout réécrire.
L’approche SnowForm
Notre solution : un seul fichier de config qui définit vos composants UI, puis des formulaires auto-générés depuis Zod.
// Un formulaire complet en 20 lignes
<SnowForm
schema={userSchema}
onSubmit={async (data) => await api.createUser(data)}
onSuccess={() => toast.success('Utilisateur créé !')}
/>
Le package analyse le schéma Zod et génère automatiquement :
- Les champs appropriés (text, email, number, select, etc.)
- Les labels traduits
- Les messages d’erreur
- Le bouton submit avec loading state
Installation
npm install @snowpact/react-rhf-zod-form
Peer Dependencies
| Dépendance | Version | Usage |
|---|---|---|
| react | >= 18.0 | Framework UI |
| react-dom | >= 18.0 | DOM rendering |
| react-hook-form | >= 7.0 | Gestion du state formulaire |
| @hookform/resolvers | >= 3.0 | Connecteur Zod |
| zod | >= 3.24 | Validation de schéma |
Quick Setup
1. Enregistrez vos composants UI
Vous mappez vos composants (Shadcn, MUI, custom…) une seule fois dans un fichier de configuration.
// configs/setupSnowForm.tsx
import {
registerComponents,
registerFormUIStyles,
registerSubmitButton,
setTranslationHook,
setOnErrorBehavior,
} from '@snowpact/react-rhf-zod-form';
import { useTranslation } from 'react-i18next';
import { toast } from 'sonner';
// Vos composants Shadcn/MUI/custom
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Select } from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
export function setupSnowForm() {
// 1. Enregistrer les composants par type
registerComponents({
text: ({ value, onChange, name, disabled, placeholder }) => (
<Input
value={value ?? ''}
onChange={(e) => onChange(e.target.value)}
name={name}
disabled={disabled}
placeholder={placeholder}
/>
),
email: ({ value, onChange, ...props }) => (
<Input type="email" value={value ?? ''} onChange={(e) => onChange(e.target.value)} {...props} />
),
password: ({ value, onChange, ...props }) => (
<Input type="password" value={value ?? ''} onChange={(e) => onChange(e.target.value)} {...props} />
),
select: ({ value, onChange, options }) => (
<Select value={value} onValueChange={onChange} options={options} />
),
checkbox: ({ value, onChange, name }) => (
<Switch id={name} checked={value ?? false} onCheckedChange={onChange} />
),
});
// 2. Styles CSS pour le layout du formulaire
registerFormUIStyles({
form: 'space-y-6 w-full',
formItem: 'grid gap-2',
formLabel: 'text-sm font-medium',
formLabelError: 'text-destructive',
formMessage: 'text-destructive text-sm',
});
// 3. Bouton submit custom
registerSubmitButton(({ loading, disabled, children }) => (
<Button type="submit" disabled={disabled || loading} className="w-full">
{loading ? 'Chargement...' : children}
</Button>
));
// 4. Traductions des labels (optionnel)
setTranslationHook(() => {
const { t } = useTranslation();
return {
t: (key) => {
if (key === 'submit') return 'Envoyer';
return t(`form.fields.${key}`);
},
};
});
// 5. Comportement en cas d'erreur
setOnErrorBehavior((formRef) => {
formRef?.scrollIntoView({ behavior: 'smooth', block: 'start' });
toast.error('Veuillez corriger les erreurs du formulaire');
});
}
2. Initialisez au démarrage
// main.tsx
import { setupSnowForm } from './configs/setupSnowForm';
setupSnowForm();
3. Créez un formulaire
// components/LoginForm.tsx
import { SnowForm } from '@snowpact/react-rhf-zod-form';
import { z } from 'zod';
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
export const LoginForm = () => {
return (
<SnowForm
schema={loginSchema}
onSubmit={async (data) => {
await api.login(data);
}}
onSuccess={() => {
router.push('/dashboard');
}}
/>
);
};
Le formulaire affiche automatiquement :
- Un champ email (type détecté depuis
.email()) - Un champ password (type détecté depuis le nom du champ)
- Labels traduits
- Messages d’erreur Zod
- Bouton submit avec loading
Overrides : Personnaliser les champs
Le système d’overrides permet de modifier le comportement par champ :
<SnowForm
schema={blogpostSchema}
onSubmit={handleSubmit}
overrides={{
// Changer le type de composant
content: {
type: 'rich-text',
description: 'Utilisez Markdown pour le formatage',
},
// Select avec options
category: {
type: 'select',
options: [
{ label: 'Tech', value: 'tech' },
{ label: 'Design', value: 'design' },
],
},
// Champ caché
authorId: {
type: 'hidden',
},
// Transformer les valeurs vides en null
videoUrl: {
type: 'text',
placeholder: 'https://youtube.com/...',
emptyAsNull: true,
},
// Composant custom inline
avatar: {
render: ({ value, onChange }) => (
<ImageUploader value={value} onUpload={onChange} />
),
},
}}
/>
Mode Édition avec fetchDefaultValues
SnowForm gère aussi les formulaires d’édition avec chargement asynchrone des données.
<SnowForm
schema={userSchema}
fetchDefaultValues={async () => {
const { data } = await api.getUser(userId);
return data.user;
}}
onSubmit={async (values) => {
await api.updateUser(userId, values);
}}
onSuccess={() => {
queryClient.invalidateQueries(['user', userId]);
toast.success('Utilisateur mis à jour');
}}
/>
Le formulaire :
- Affiche un skeleton pendant le chargement
- Pré-remplit les champs avec les données
- Gère le submit et l’invalidation du cache
Gestion des Erreurs Serveur
<SnowForm
schema={registerSchema}
onSubmit={async (data) => {
await api.register(data);
}}
onSubmitError={(setManualFormErrors, error) => {
// error.response.data = { errors: [{ field: 'email', message: 'Déjà utilisé' }] }
const fieldErrors = error.response?.data?.errors?.reduce((acc, e) => ({
...acc,
[e.field]: e.message,
}), {});
setManualFormErrors(fieldErrors);
}}
/>
Les erreurs s’affichent directement sous les champs concernés.
Composants Business Personnalisés
Enregistrez vos composants métier une seule fois :
// setup
registerComponents({
// ... composants standards
// Éditeur WYSIWYG
'rich-text': ({ value, onChange }) => (
<TipTapEditor content={value} onUpdate={onChange} />
),
// Sélecteur d'images depuis une bibliothèque
'media-library': ({ value, onChange }) => (
<MediaPicker selectedUrl={value} onSelect={onChange} />
),
// Sélecteur de couleur
'color': ({ value, onChange }) => (
<ColorPicker color={value} onChange={onChange} />
),
});
Puis utilisez-les via overrides :
<SnowForm
schema={pageSchema}
overrides={{
content: { type: 'rich-text' },
heroImage: { type: 'media-library' },
accentColor: { type: 'color' },
}}
/>
Intégration avec les SDK générés
Si vous utilisez un générateur OpenAPI (Orval, openapi-typescript, etc.), les schémas Zod sont déjà générés :
// Le schéma Zod vient directement du SDK généré depuis OpenAPI
import { zodUserCreateBody } from '@/sdk';
<SnowForm
schema={zodUserCreateBody} // Généré automatiquement
onSubmit={async (data) => {
await sdk.createUser(data);
}}
/>
Votre formulaire est toujours synchronisé avec l’API backend.
Tableau des Fonctionnalités
| Fonctionnalité | Description |
|---|---|
| Auto-génération | Formulaire généré depuis le schéma Zod |
| Types de champs | text, email, password, number, textarea, select, checkbox, date, hidden |
| Composants custom | Injectez vos propres composants via registry |
| Overrides | Personnalisez chaque champ individuellement |
| fetchDefaultValues | Mode édition avec données pré-remplies |
| Loading states | Skeleton pendant le fetch, spinner pendant le submit |
| Validation | Zod côté client + erreurs serveur |
| i18n | Labels traduits via hook injectable |
| onError behavior | Scroll + toast personnalisables |
| emptyAsNull | Transforme « » en null pour les champs optionnels |
| TypeScript | Types inférés depuis le schéma Zod |
Mapping Zod → Type de champ
| Schéma Zod | Type détecté |
|---|---|
z.string() |
text |
z.string().email() |
|
z.number() |
number |
z.boolean() |
checkbox |
z.date() |
date |
z.enum([...]) |
select |
| Nom contient « password » | password |
| Nom contient « description » | textarea |
Vous pouvez toujours overrider avec type: 'xxx'.
Comparaison
| Solution | Problème |
|---|---|
| react-hook-form seul | Pas de génération auto, beaucoup de boilerplate |
| Formik | Plus maintenu activement, pas de Zod natif |
| react-jsonschema-form | JSON Schema au lieu de Zod, style imposé |
| AutoForm (shadcn) | Copier-coller, pas de package, couplé à Shadcn |
| SnowForm | Package npm, injectez votre UI, zéro couplage |
Liens
Article rédigé par l’équipe Snowpact. Retrouvez nos projets open-source sur GitHub.
