A production-ready Vite + React template for building forms with form0-react. This template demonstrates best practices for integrating form0 into a React application with custom styling, field renderers, and centralized configuration.
- ⚡️ Vite - Lightning-fast development with Hot Module Replacement (HMR)
- ⚛️ React 19 - Latest React with concurrent features
- 🎨 Tailwind CSS - Utility-first CSS framework with custom configuration
- 🎭 shadcn/ui - High-quality, accessible UI components
- 📝 form0-react - Powerful form engine with schema-driven forms
- 🔧 Centralized Configuration - Single config file for all form0 settings
- 🎨 Custom Field Renderers - Production-ready shadcn-based field components
- 🌗 Dark Mode Ready - Built-in light/dark theme support
- 📦 Clean Architecture - Organized file structure with clear separation of concerns
npm installnpm run devOpen http://localhost:5173 to view the app in your browser.
npm run buildBuilds the app for production to the dist folder.
npm run previewThis template uses a centralized configuration file (form0.config.js in the project root) to manage all form0-related settings. This provides a single source of truth and makes it easy to customize form behavior across your application.
export default {
// Layout settings
layout: {
labelPosition: 'side', // 'side' | 'top'
labelWidthPercent: 30, // 0-100
formWidth: '70vw', // Any valid CSS width
},
// Per-variant presentation presets (theme + layout overrides)
presentations: {
standard: {
theme: 'standard',
layout: { formWidth: '70vw', labelPosition: 'side', labelWidthPercent: 30 },
},
simplified: {
theme: 'simplified',
layout: { formWidth: '65vw', labelPosition: 'top', labelWidthPercent: 100 },
},
modal: {
theme: 'standard',
layout: { formWidth: '60vw', labelPosition: 'side', labelWidthPercent: 35 },
},
spotlight: {
theme: 'standard',
layout: { formWidth: '55vw', labelPosition: 'side', labelWidthPercent: 35 },
},
},
// Theme settings
theme: {
mode: 'light', // 'light' | 'dark' | 'system'
customTheme: null, // Path to custom theme or null
},
// Schema location
schemas: {
directory: './src', // Where form schemas are stored
},
// Field renderer overrides
fieldRenderers: {
DateField: 'date-field-shadcn',
// Add more custom renderers here
},
// Output format
output: {
useKeys: false, // Use keys instead of data_names
},
};-
labelPosition: Controls label placement relative to inputs'side': Labels appear to the left (horizontal layout)'top': Labels appear above (vertical layout)
-
labelWidthPercent: Width percentage for labels when positioned on the side (0-100) -
formWidth: Default width for form containers (any valid CSS width value)
- Provide per-variant overrides for layout + theme without touching component code.
- Each key corresponds to a presentation (
standard,simplified,modal,spotlight). theme: which form0-react theme string to use for the variant.layout: optional overrides forformWidth,labelPosition, andlabelWidthPercent.
-
mode: Color scheme for forms'light': Always use light theme'dark': Always use dark theme'system': Follow system preference (requires implementation)
-
customTheme: Path to a custom vanilla-extract theme file (relative tosrc/components/)- Set to
nullto use form0-react's default theme - Example:
'../themes/custom-example.css.js' - The theme is dynamically imported when the app loads
- Must export a theme created with
createTheme()from@vanilla-extract/css
- Set to
Map field types to custom implementations. Keys are field type names from form0-core (e.g., DateField, TextField), and values are renderer identifiers defined in src/field-renderers/resolver.js.
useKeys: Whentrue, uses schema keys instead ofdata_namevalues in form output
Form0Forminjects its default structured-submit handler whenonSubmitis omitted.- Pass
useDefaultSubmitHandler={false}to opt out and render the form without that fallback.
form0-web-tmpl-react-vite/
├── form0.config.js # Central configuration
├── src/
│ ├── components/
│ │ ├── Form0Form.jsx # Main form wrapper component
│ │ ├── FormModal.jsx # Modal form variant
│ │ ├── FormSpotlight.jsx # Drawer/spotlight form variant
│ │ └── ui/ # shadcn UI components
│ ├── field-renderers/
│ │ ├── resolver.js # Maps config strings to components
│ │ ├── date-field.jsx # Basic date field renderer
│ │ ├── date-field-shadcn.jsx # Enhanced shadcn date field
│ │ └── index.js # Exports all renderers
│ ├── pages/
│ │ ├── Home.jsx # Landing page
│ │ └── FormPage.jsx # Form display page
│ ├── styles/
│ │ └── form0-overrides.css # Custom form0 styling
│ ├── themes/
│ │ └── custom-example.css.js # Example custom theme
│ ├── forms/
│ │ ├── registry.js # Multi-form metadata + loaders
│ │ ├── use-form-schema.js # Hook for loading schema JSON
│ │ └── demo/
│ │ └── schema.json # Sample schema used across variants
│ ├── index.css # Global app styles + Tailwind
│ ├── App.css # App-specific styles
│ └── main.jsx # App entry point
- Create your renderer component in
src/field-renderers/:
// src/field-renderers/my-custom-field.jsx
export function MyCustomField({ field, value, onChange, readOnly, inputProps }) {
return (
<input
{...inputProps}
value={value || ''}
onChange={(e) => onChange(e.target.value)}
disabled={readOnly}
placeholder={field.label}
/>
);
}- Register it in the resolver (
src/field-renderers/resolver.js):
import { MyCustomField } from './my-custom-field.jsx';
const RENDERER_MAP = {
'my-custom-field': MyCustomField,
// ... other renderers
};- Configure it in
form0.config.js:
fieldRenderers: {
TextField: 'my-custom-field',
}Custom themes allow you to completely override form0's default styling to match your brand.
- Create a theme file in
src/themes/:
// src/themes/my-brand-theme.css.js
import { createTheme } from '@vanilla-extract/css';
import { vars } from 'form0-react/theme.css.js';
export const myBrandTheme = createTheme(vars, {
color: {
background: '#ffffff',
foreground: '#000000',
primary: '#0066cc',
// ... more color tokens
},
borderRadius: '8px',
fontSize: {
base: '1rem',
label: '0.875rem',
section: '1.25rem',
},
spacing: {
sm: '0.5rem',
md: '1rem',
lg: '1.5rem',
},
});- Configure it in
form0.config.js:
theme: {
mode: 'light',
// Path is relative to src/components/ directory
customTheme: '../themes/my-brand-theme.css.js',
}The theme will be automatically imported and applied when the form loads.
See src/themes/custom-example.css.js for a complete example. To test it, uncomment the customTheme line in form0.config.js.
form0 components can be styled in multiple ways:
- CSS Custom Properties - Override design tokens in
src/styles/form0-overrides.css:
:root {
--form0-accent: 210 100% 56%;
--form0-field-border: 214 16% 86%;
/* ... more tokens */
}- Component Classes - Override specific component styles:
.form0-field {
border-radius: 12px;
padding: 1rem;
}- vanilla-extract Themes - For comprehensive theming, create a custom theme (see above)
While form0.config.js sets global defaults, you can override layout settings per component:
<Form0Form
labelPosition="top"
labelWidthPercent={40}
formWidth="500px"
/>This is useful for modals, drawers, or special form contexts.
This template uses a three-layer styling system that provides flexibility for different customization needs. Understanding how these layers interact is crucial for effective theming.
┌─────────────────────────────────────────────────────────┐
│ Layer 3: Plain CSS Overrides (form0-overrides.css) │
│ • Custom field renderer styles │
│ • CSS variables: --form0-accent, --form0-field-* │
│ • Used by: date-field.jsx custom renderer │
└─────────────────────────────────────────────────────────┘
↓ overrides
┌─────────────────────────────────────────────────────────┐
│ Layer 2: Custom Themes (custom-example.css.js) │
│ • vanilla-extract theme overrides │
│ • Affects: ALL default form0-react field renderers │
│ • Configured via: form0.config.js │
└─────────────────────────────────────────────────────────┘
↓ overrides
┌─────────────────────────────────────────────────────────┐
│ Layer 1: Default Theme (form0-react/theme.css.js) │
│ • Built-in form0-react themes │
│ • Defines: vars.color.*, vars.spacing.*, etc. │
│ • Used by: field-renderer.css, form-renderer.css │
└─────────────────────────────────────────────────────────┘
Located in the form0-react package, this provides built-in themes:
standard(light/dark)modal(light/dark)simplified(light/dark)spotlight(light/dark)
These themes style all default field renderers (TextField, NumericField, SelectField, etc.) using vanilla-extract CSS-in-JS.
When to use: You don't need to do anything - this is the automatic fallback when no custom theme is specified.
Create custom vanilla-extract themes to override the default form0-react styling for your entire project.
Location: src/themes/custom-example.css.js
How it works:
- Import the theme contract from form0-react
- Create your theme with
createTheme() - Configure it in
form0.config.js
// src/themes/my-brand-theme.css.js
import { createTheme } from '@vanilla-extract/css';
import { vars } from 'form0-react/theme.css.js';
export const myBrandTheme = createTheme(vars, {
color: {
background: '#ffffff',
foreground: '#000000',
border: '#e5e7eb',
primary: '#0066cc',
error: '#dc2626',
// ... all color tokens
},
borderRadius: '8px',
fontSize: { base: '1rem', label: '0.875rem', section: '1.25rem' },
spacing: { sm: '0.5rem', md: '1rem', lg: '1.5rem' },
});Activation:
// form0.config.js
theme: {
customTheme: '../themes/my-brand-theme.css.js',
}What it affects: ALL default form0-react field renderers (TextField, NumericField, etc.)
What it does NOT affect: Custom field renderers that use their own styling (like date-field-shadcn.jsx)
When you create custom field renderers, they can use their own styling approach.
Uses classes and CSS variables defined in form0-overrides.css:
/* src/styles/form0-overrides.css */
:root {
--form0-accent: 210 100% 56%;
--form0-field-border: 214 16% 86%;
}
.form0-field {
border: 1px solid hsl(var(--form0-field-border));
background-color: hsl(var(--form0-field-bg));
}// src/field-renderers/date-field.jsx
<button className="form0-field form0-field-trigger">
{/* Uses styles from form0-overrides.css */}
</button>Pros: Centralized styling, framework-agnostic, easier to maintain consistent design system
Uses Tailwind utility classes and shadcn component styles:
// src/field-renderers/date-field-shadcn.jsx
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
<Input className="bg-background pr-10" />
<Button variant="ghost" className="absolute top-1/2 right-2">
<CalendarIcon />
</Button>Pros: More consistent with shadcn ecosystem, flexible per-component, better tree-shaking
| Field Type | Renderer | Styling System |
|---|---|---|
| TextField (default) | form0-react | Layer 2 → Layer 1 |
| NumericField (default) | form0-react | Layer 2 → Layer 1 |
| SelectField (default) | form0-react | Layer 2 → Layer 1 |
| DateField (custom) | date-field-shadcn.jsx |
Tailwind + shadcn (Layer 3) |
| DateField (alternate) | date-field.jsx |
form0-overrides.css (Layer 3) |
To switch DateField styling:
// form0.config.js
fieldRenderers: {
DateField: 'date-field-shadcn', // Uses Tailwind + shadcn
// OR
DateField: 'date-field', // Uses form0-overrides.css
}index.css: Tailwind v4 config, shadcn design tokens, global app stylesApp.css: Application-specific layout and page structurestyles/form0-overrides.css: Custom styling for form0 custom renderers (plain CSS approach)themes/custom-example.css.js: Example vanilla-extract custom theme for form0-react defaults
To customize default form0 fields globally:
→ Edit or create a theme in src/themes/ and configure it in form0.config.js
To customize the DateField (shadcn version):
→ Edit shadcn component styles in src/components/ui/ or modify Tailwind config in index.css
To customize the DateField (plain CSS version):
→ Edit classes in src/styles/form0-overrides.css
To create a new custom field renderer:
→ Choose your approach (Tailwind or plain CSS) and follow the pattern in src/field-renderers/
This template uses Tailwind CSS v4 with CSS-based configuration (no tailwind.config.js needed).
Configuration location: src/index.css
/* Tailwind v4 imports */
@import "tailwindcss";
/* Theme configuration */
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--color-primary: var(--primary);
/* ... more design tokens */
}This modern approach keeps all styling configuration in CSS files, making it easier to manage and understand.
- Keep styling concerns separated: App styles in
index.css/App.css, form0 overrides inform0-overrides.css - Use custom themes for global changes: If you want all default fields to match your brand, create a custom vanilla-extract theme
- Use custom renderers for specific field types: If you need special behavior or styling for one field type (like DateField), create a custom renderer
- Choose one approach per renderer: Either use plain CSS (like
date-field.jsx) or Tailwind (likedate-field-shadcn.jsx), but keep them consistent - Test theme changes: When creating custom themes, use dramatic values (like red borders) initially to verify the theme is loading correctly
Custom theme not applying?
- Check browser console for
[form0] Custom theme loaded successfully - Verify the path in
form0.config.jsis correct (relative tosrc/components/) - Ensure you've rebuilt
form0-reactif you modified package files:cd form0-react && npm run build
Custom renderer not appearing?
- Verify it's registered in
src/field-renderers/resolver.js - Check
form0.config.jsfieldRenderers mapping - Ensure the field type matches exactly (e.g.,
DateField, notdateField)
npm run dev- Start development servernpm run build- Build for productionnpm run preview- Preview production buildnpm run lint- Run ESLint
- form0-core Documentation
- form0-react Documentation
- Vite Documentation
- React Documentation
- Tailwind CSS Documentation
- shadcn/ui Documentation
This template is MIT licensed. See LICENSE for details.