Custom Form Elements
The dashboard allows you to create custom form elements that provide complete control over how data is rendered and how users interact with forms. This includes:
- Custom Field Components — Globally-registered components that can be used to render custom fields and configurable operation arguments
- Detail Form Components — Form input components that target specific fields of detail pages
Anatomy of a Form Component
All form components must implement the DashboardFormComponent type.
This type is based on the props that are made available from react-hook-form, which is the
underlying form library used by the Dashboard.
Here's an example custom form component that has been annotated to explain the typical parts you will be working with:
Here's how this component will look when rendered in your form:
Custom Field Components
Let's configure a custom field which uses the ColorPickerComponent as its form component.
First we need to register the component with the defineDashboardExtension function:
Now that we've registered it as a custom field component, we can use it in our custom field definition.
Configurable Operation Components
The ColorPickerComponent can also be used as a configurable operation argument component. For example, we can add a color code
to a shipping calculator:
Detail Form Components
Detail form components allow you to replace specific input fields in existing dashboard forms with custom implementations. They are targeted to specific pages, blocks, and fields.
Let's say we want to use a plain text editor for the product description field rather than the default html-based editor.
You can then use this component in your detail form definition:
Targeting Input Components
Input components are targeted using three properties:
- pageId: The ID of the page (e.g., 'product-detail', 'customer-detail')
- blockId: The ID of the form block (e.g., 'product-form', 'customer-info')
- field: The name of the field to replace (e.g., 'price', 'email')
You can discover the required IDs by turning on dev mode:
and then hovering over any of the form elements will allow you to view the IDs:
Form Validation
Form validation is handled by the react-hook-form library, which is used by the Dashboard. Internally,
the Dashboard uses the zod library to validate the form data, based on the configuration of the custom field or
operation argument.
You can access validation data for the current field or the whole form by using the useFormContext hook.
Your component does not need to handle standard error messages - the Dashboard will handle them for you.
For example, if your custom field specifies a pattern property, the Dashboard will automatically display an error message
if the input does not match the pattern.
- Always use Shadcn UI components from the
@vendure/dashboardpackage for consistent styling - Handle React Hook Form events properly - call
onChangeandonBlurappropriately for custom field components - Display validation errors from
fieldState.errorwhen they exist (custom field components) - Use dashboard design tokens - leverage
text-destructive,text-muted-foreground, etc. - Provide clear visual feedback for user interactions
- Handle disabled states by using the
disabledprop - Target components precisely using pageId, blockId, and field for input components
:::important Design System Consistency
Always import UI components from the @vendure/dashboard package rather than creating custom inputs or buttons. This ensures your components follow the dashboard's design system and remain consistent with future updates.
:::
The unified custom form elements system gives you complete flexibility in how data is presented and edited in the dashboard, while maintaining seamless integration with React Hook Form and the dashboard's design system.
Nested Forms and Event Handling
When creating custom form components that contain their own forms (e.g., dialogs with forms inside detail pages), you need to prevent form submission events from bubbling up to parent forms. The dashboard provides the handleNestedFormSubmit utility for this purpose.
Why Use handleNestedFormSubmit?
Detail pages in the dashboard are themselves forms. If you add a custom component with its own form (like a dialog with create/edit functionality), submitting the inner form will also trigger the outer detail page form submission. This can cause:
- Unintended save operations on the detail page
- Validation errors on unrelated fields
- Loss of unsaved changes in the dialog
Using handleNestedFormSubmit
The handleNestedFormSubmit utility prevents event propagation and properly handles form submission:
What handleNestedFormSubmit Does
The utility function:
- Prevents the submit event from propagating to parent forms (
e.stopPropagation()) - Prevents the browser's default form submission behavior (
e.preventDefault()) - Properly triggers react-hook-form's handleSubmit with your custom handler
- Maintains type safety with TypeScript generics
When to Use It
Use handleNestedFormSubmit whenever you have:
- A dialog with a form inside a detail page
- A custom component with its own form that's nested within another form
- Any scenario where form submission events should not bubble up to parent forms
Relation Selectors
The dashboard includes powerful relation selector components for selecting related entities with built-in search and pagination:
Features include:
- Real-time search with debounced input
- Infinite scroll pagination loading 25 items by default
- Single and multi-select modes with type safety
- Customizable GraphQL queries and search filters
- Built-in UI components using the dashboard design system
Further Reading
For detailed information about specific types of custom form elements, see these dedicated guides:
- Form component examples - Detailed examples of how to use the APIs available for custom form components.
- Relation selectors - Build powerful entity selection components with search, pagination, and multi-select capabilities for custom fields and form inputs


