Forms - Admin Components
In this guide, you'll learn how to create forms that match the Medusa Admin's design conventions.
The Medusa Admin has two types of forms:
- Create forms, created using the FocusModal UI component.
- Edit or update forms, created using the Drawer UI component.
This guide explains how to create these two form types following the Medusa Admin's conventions.
Form component that the @medusajs/dashboard package exports. Before that version, you had to compose the fields with FormProvider and Controller from react-hook-form yourself.Form Tooling#
The Medusa Admin uses the following tools to build the forms:
- The
Formcomponent from@medusajs/dashboard/components, which renders a field's label, control, hint, and error message with the Medusa Admin's styling. - react-hook-form to easily build forms and manage their states.
- Zod to validate the form's fields.
All of these are available in your project, so you don't have to install them to use them.
The Form component wraps react-hook-form's FormProvider, and has the following sub-components:
Component | Description |
|---|---|
| Registers a field in the form. It accepts the same props as react-hook-form's |
| Wraps a field's label, control, hint, and error message. |
| Shows the field's label. It accepts an |
| Wraps the field's input and connects it to the form's accessibility and error state. |
| Shows a hint below the field. |
| Shows the field's validation error, if any. |
Create Form#
In this section, you'll build a form component to create an item of a resource.
Full Component
Unlike other components in this documentation, this form component isn't reusable. You have to create one for every resource that has a create form in the admin.
Start by creating the file src/admin/components/create-form.tsx in which you'll create the form.
Create Validation Schema#
In src/admin/components/create-form.tsx, create a validation schema with Zod for the form's fields:
The form in this guide is simple, it only has a required name field, which is a string.
Initialize Form#
Next, you'll initialize the form using react-hook-form.
Add to src/admin/components/create-form.tsx the following:
1// other imports...2import { useForm } from "react-hook-form"3 4// validation schema...5 6export const CreateForm = () => {7 const form = useForm<zod.infer<typeof schema>>({8 defaultValues: {9 name: "",10 },11 })12 13 const handleSubmit = form.handleSubmit(({ name }) => {14 // TODO: submit to backend15 console.log(name)16 })17 18 // TODO render form19}
You create the CreateForm component. For now, it uses useForm from react-hook-form to initialize a form.
You also define a handleSubmit function to perform an action when the form is submitted.
You can replace the content of the function with sending a request to Medusa's routes. Refer to this guide for more details on how to do that.
Render Components#
You'll now add a return statement that renders the focus modal where the form is shown.
Replace // TODO render form with the following:
1// other imports...2import { 3 FocusModal,4 Heading,5 Input,6 Button,7} from "@medusajs/ui"8import { Form } from "@medusajs/dashboard/components"9 10export const CreateForm = () => {11 // ...12 13 return (14 <FocusModal>15 <FocusModal.Trigger asChild>16 <Button>Create</Button>17 </FocusModal.Trigger>18 <FocusModal.Content>19 <Form {...form}>20 <form21 onSubmit={handleSubmit}22 className="flex h-full flex-col overflow-hidden"23 >24 <FocusModal.Header>25 <div className="flex items-center justify-end gap-x-2">26 <FocusModal.Close asChild>27 <Button size="small" variant="secondary">28 Cancel29 </Button>30 </FocusModal.Close>31 <Button type="submit" size="small">32 Save33 </Button>34 </div>35 </FocusModal.Header>36 <FocusModal.Body>37 <div className="flex flex-1 flex-col items-center overflow-y-auto">38 <div className="mx-auto flex w-full max-w-[720px] flex-col gap-y-8 px-2 py-16">39 <div>40 <Heading className="capitalize">41 Create Item42 </Heading>43 </div>44 <div className="grid grid-cols-2 gap-4">45 <Form.Field46 control={form.control}47 name="name"48 render={({ field }) => (49 <Form.Item>50 <Form.Label>Name</Form.Label>51 <Form.Control>52 <Input {...field} />53 </Form.Control>54 <Form.ErrorMessage />55 </Form.Item>56 )}57 />58 </div>59 </div>60 </div>61 </FocusModal.Body>62 </form>63 </Form>64 </FocusModal.Content>65 </FocusModal>66 )67}
You render a focus modal, with a trigger button to open it.
In the FocusModal.Content component, you wrap the content with the Form component, spreading into it the form you initialized earlier. Form provides the form's state to every field below it.
In the Form component, you add a form element passing it the handleSubmit function you created earlier as the handler of the onSubmit event.
In the FocusModal.Header component, you add buttons to save or cancel the form submission.
Finally, you render the form's fields inside the FocusModal.Body. Each field is a Form.Field component, which accepts the form's control, the field's name, and a render function.
In the render function, you wrap the field in a Form.Item component, then add:
- A
Form.Labelcomponent showing the field's label. - A
Form.Controlcomponent wrapping the input, which connects the input to the form's accessibility and error state. - A
Form.ErrorMessagecomponent showing the field's validation error, if any.
Since Form.ErrorMessage shows the errors of your Zod schema, you don't have to render validation messages yourself.
Use Create Form Component#
You can use the CreateForm component in your widget or UI route.
For example, create the widget src/admin/widgets/product-widget.tsx with the following content:
1import { defineWidgetConfig } from "@medusajs/admin-sdk"2import { CreateForm } from "../components/create-form"3import { Container } from "../components/container"4import { Header } from "../components/header"5 6const ProductWidget = () => {7 return (8 <Container>9 <Header10 title="Items"11 actions={[12 {13 type: "custom",14 children: <CreateForm />,15 },16 ]}17 />18 </Container>19 )20}21 22export const config = defineWidgetConfig({23 zone: "product.details",24})25 26export default ProductWidget
This component uses the Container and Header custom components.
It will add at the top of a product's details page a new section, and in its header you'll find a Create button. If you click on it, it will open the focus modal with your form.
Edit Form#
In this section, you'll build a form component to edit an item of a resource.
Full Component
Unlike other components in this documentation, this form component isn't reusable. You have to create one for every resource that has an edit form in the admin.
Start by creating the file src/admin/components/edit-form.tsx in which you'll create the form.
Create Validation Schema#
In src/admin/components/edit-form.tsx, create a validation schema with Zod for the form's fields:
The form in this guide is simple, it only has a required name field, which is a string.
Initialize Form#
Next, you'll initialize the form using react-hook-form.
Add to src/admin/components/edit-form.tsx the following:
1// other imports...2import { useForm } from "react-hook-form"3 4// validation schema...5 6export const EditForm = () => {7 const form = useForm<zod.infer<typeof schema>>({8 defaultValues: {9 name: "",10 },11 })12 13 const handleSubmit = form.handleSubmit(({ name }) => {14 // TODO: submit to backend15 console.log(name)16 })17 18 // TODO render form19}
You create the EditForm component. For now, it uses useForm from react-hook-form to initialize a form.
You also define a handleSubmit function to perform an action when the form is submitted.
You can replace the content of the function with sending a request to Medusa's routes. Refer to this guide for more details on how to do that.
Render Components#
You'll now add a return statement that renders the drawer where the form is shown.
Replace // TODO render form with the following:
1// other imports...2import { 3 Drawer,4 Heading,5 Input,6 Button,7} from "@medusajs/ui"8import { Form } from "@medusajs/dashboard/components"9 10export const EditForm = () => {11 // ...12 13 return (14 <Drawer>15 <Drawer.Trigger asChild>16 <Button>Edit Item</Button>17 </Drawer.Trigger>18 <Drawer.Content>19 <Form {...form}>20 <form21 onSubmit={handleSubmit}22 className="flex flex-1 flex-col overflow-hidden"23 >24 <Drawer.Header>25 <Heading className="capitalize">26 Edit Item27 </Heading>28 </Drawer.Header>29 <Drawer.Body className="flex max-w-full flex-1 flex-col gap-y-8 overflow-y-auto">30 <Form.Field31 control={form.control}32 name="name"33 render={({ field }) => (34 <Form.Item>35 <Form.Label>Name</Form.Label>36 <Form.Control>37 <Input {...field} />38 </Form.Control>39 <Form.ErrorMessage />40 </Form.Item>41 )}42 />43 </Drawer.Body>44 <Drawer.Footer>45 <div className="flex items-center justify-end gap-x-2">46 <Drawer.Close asChild>47 <Button size="small" variant="secondary">48 Cancel49 </Button>50 </Drawer.Close>51 <Button size="small" type="submit">52 Save53 </Button>54 </div>55 </Drawer.Footer>56 </form>57 </Form>58 </Drawer.Content>59 </Drawer>60 )61}
You render a drawer, with a trigger button to open it.
In the Drawer.Content component, you wrap the content with the Form component, spreading into it the form you initialized earlier.
In the Form component, you add a form element passing it the handleSubmit function you created earlier as the handler of the onSubmit event.
You render the form's fields inside the Drawer.Body. Each field is a Form.Field component whose render function returns a Form.Item component holding the field's Form.Label, Form.Control, and Form.ErrorMessage components.
Finally, in the Drawer.Footer component, you add buttons to save or cancel the form submission.
Use Edit Form Component#
You can use the EditForm component in your widget or UI route.
For example, create the widget src/admin/widgets/product-widget.tsx with the following content:
1import { defineWidgetConfig } from "@medusajs/admin-sdk"2import { Container } from "../components/container"3import { Header } from "../components/header"4import { EditForm } from "../components/edit-form"5 6const ProductWidget = () => {7 return (8 <Container>9 <Header10 title="Items"11 actions={[12 {13 type: "custom",14 children: <EditForm />,15 },16 ]}17 />18 </Container>19 )20}21 22export const config = defineWidgetConfig({23 zone: "product.details",24})25 26export default ProductWidget
This component uses the Container and Header custom components.
It will add at the top of a product's details page a new section, and in its header you'll find an "Edit Item" button. If you click on it, it will open the drawer with your form.