Getting Started With Forms: Mantine Form Components
Don't build your forms from scratch (nobody does that anymore).
There are a metric-TON of popular React component libraries out there, including:
- · Material UI,
- · Ant Design,
- · Chakra UI,
- · Next UI,
- · Headless UI,
- · Semantic UI React,
- · Shadcn/ui,
- · Blueprint,
- · Daisy UI,
- · Radix UI,
- · Reactstrap,
- · Aceternity UI
- · Park UI,
- · Core UI,
- · Evergreen,
- · Grommet
ARE YOU KIDDING ME?! And that's not even all of them! I went with the Mantine library which has mucho components, a starter guide for Next.js (Rich likes this), some really good looking charts and some handy tools. In a future post I'll be highlighting their Drawer component, which is a lot of fun for data-heavy pages.
Form Demo Using Mantine Form Components
Fill out the form below and then click the Submit Form button. Your information is completely safe with me. Summary: This post is to demonstrate the actual UI/form/Mantine controls on the front end (I'll go over form processing and server side functionality at a later time). To set this file up, I imported the Mantine controls (including the Modal screen), created local states for the actual form values and their possible errors. When the Submit Form button is clicked the handleSubmit() function kicks in and sets the error states, as needed, and displays any errors in red text immediately beneath the affected control. CSS has been mixed in (I'll clean it up later - hmmm probably not) but after all the errors clear I pump the form values into a modal box so you can see that I actually captured (but didn't save!) the submitted form values. In a future post instead of using this modal I'll use a server action to process the submitted values - stay tuned. I could ramble on and on here, but check the comments inline below, you'll get a better feel for what I actually did.
My 2 Cents Worth: I like working with the Mantine controls, they're consistent and straight-forward to implement. The docs are good/very good and there are more than a few examples to get you going. Chances are good that if you run into any problems with these controls, it's likely a Next.js/React issue and not a Mantine control issue. I'm going to get a lot of mileage out of their Modal box as it is very configurable and customizable. I could drone on about, " ... how Mantine offers this control and that control where none of the other libraries offer anything like it..." but features for all of the libraries will eventually 'bubble up to same' after enough time goes by....
Disclaimer: If you didn't like how I coded something in particular then change it for your own use case and quit bombing my inbox. I'm sensitive and you're hurting my feelings ;-)
My 2 Cents Worth: I like working with the Mantine controls, they're consistent and straight-forward to implement. The docs are good/very good and there are more than a few examples to get you going. Chances are good that if you run into any problems with these controls, it's likely a Next.js/React issue and not a Mantine control issue. I'm going to get a lot of mileage out of their Modal box as it is very configurable and customizable. I could drone on about, " ... how Mantine offers this control and that control where none of the other libraries offer anything like it..." but features for all of the libraries will eventually 'bubble up to same' after enough time goes by....
Disclaimer: If you didn't like how I coded something in particular then change it for your own use case and quit bombing my inbox. I'm sensitive and you're hurting my feelings ;-)
1//mantine controls are client side, so don't try running them in a server component!
2'use client'
3
4import React, {useState} from "react";
5
6//import all of your mantine controls
7import {
8 TextInput,
9 Button,
10 Group,
11 PinInput,
12 SegmentedControl,
13 Rating,
14 Fieldset,
15 NumberInput,
16 ScrollArea,
17 Switch,
18 Modal
19} from '@mantine/core';
20
21// you need the useDisclosure hook for the Modal box
22import {useDisclosure} from '@mantine/hooks';
23
24import Image from "next/image";
25
26//form controls
27 const [firstValue, setFirstValue] = useState('');
28 const [firstError, setFirstError] = useState('');
29 const [lastValue, setLastValue] = useState('');
30 const [lastError, setLastError] = useState('');
31 const [pinValue, setPinValue] = useState('');
32 const [pinError, setPinError] = useState('');
33 const [switchChecked, setSwitchChecked]: [boolean, (value: (((prevState: boolean) => boolean) | boolean)) => void] =
34 useState(true);
35 const [switchError, setSwitchError] = useState('');
36 const [ratingValue, setRatingValue] = useState(4);
37 const [ageValue, setAgeValue] = useState<any>('');
38 const [ageError, setAgeError] = useState('');
39 const [favTechValue, setFavTechValue] = useState('Next.js');
40 //modal controls
41 //I open and close the modal box programmatically, using the useDisclosure hook and state
42 const [opened, {open, close}] = useDisclosure(false);
43 const [modalOpen, setModalOpen] = useState(false);
44
45 //error trapping on submit
46 const handleSubmit = () => {
47 //check for errors and state as needed. When everything checks out open the Modal
48 (!firstValue) ? setFirstError('Enter your first name') : setFirstError('');
49 (!lastValue) ? setLastError('Enter your last name') : setLastError('');
50 (pinValue.length < 4) ? setPinError('PIN must contain 4 numbers') : setPinError('');
51 (!ageValue) ? setAgeError('Select your age from the list') : setAgeError('');
52 (!switchChecked) ? setSwitchError('You must agree to this End User License Agreement before proceeding') :
53 setSwitchError('');
54
55 //if no errors, then automatically open the modal to display results/confirmation
56 if (!firstValue || !lastValue || (pinValue.length < 4) || !switchChecked || !ageValue)
57 return;
58 else {
59 setModalOpen(true);
60 }
61 };
62
63 return (
64 <div>
65 <div className="min-h-screen m-4">
66 <h2 className="w-full text-center text-xl">
67 Getting Started With Forms: Mantine Form Components
68 </h2>
69
70 <div className="shadow-md bg-white" style={{
71 marginLeft: "2px",
72 marginRight: "2px",
73 marginTop: "1px",
74 marginBottom: "4px",
75 border: "thin solid silver",
76 padding: "15px",
77 borderRadius: "10px"
78 }}>
79 <div className="text-lg">Form Demo Using Mantine Form Components</div>
80 <span>Fill out the form below and then click the Submit Form button. You're information is completely
81 safe with me. </span>
82 <div className="flex">
83 <div className="w-1/2">
84 {/*I display any error messages immediately beneath the troubled form control*/}
85 <Fieldset legend="Confidential Information"
86 style={{marginTop: "20px", fontWeight: "bold", width: "90%"}}>
87 <TextInput
88 label="First Name"
89 name="first"
90 description="Enter your first name (required)"
91 value={firstValue}
92 onChange={(event) => setFirstValue(event.currentTarget.value)}
93 required
94 className="w-[300px] pt-4"
95 />
96 {firstError && <span className="text-sm text-red-500">ERROR! {firstError}</span>}
97
98 <TextInput
99 label="Last Name"
100 name="last"
101 description="Enter your last name (required)"
102 value={lastValue}
103 onChange={(event) => setLastValue(event.currentTarget.value)}
104 required
105 className="w-[300px] pt-4"
106 />
107 {lastError && <span className="text-sm text-red-500">ERROR! {lastError}</span>}
108
109 </Fieldset>
110
111 <Fieldset legend="Super-Duper Confidential Information"
112 style={{marginTop: "20px", fontWeight: "bold", width: "90%"}}>
113 <div className="pt-5">
114 <div style={{fontSize: "14px", fontWeight: "500"}}>ATM PIN <span
115 style={{color: "red"}}>*</span>
116 </div>
117 <div style={{
118 fontSize: "12px",
119 color: "#868E96",
120 paddingBottom: "6px",
121 fontWeight: "700px"
122 }}>Enter your PIN. What, you don't trust me? (definitely required)
123 </div>
124 <PinInput value={pinValue} onChange={setPinValue} type="number"/>
125 </div>
126 {pinError && <span className="text-sm text-red-500">ERROR! {pinError}</span>}
127 </Fieldset>
128
129 <Fieldset legend="Required... But Nobody Really Cares"
130 style={{marginTop: "20px", fontWeight: "bold", width: "90%"}}>
131 <div className="pt-5">
132 <div style={{fontSize: "14px", fontWeight: "500"}}>Your favorite web technology?
133 </div>
134 <div style={{
135 fontSize: "12px",
136 color: "#868E96",
137 paddingBottom: "4px",
138 fontWeight: "700px"
139 }}> Pick one
140 </div>
141 <SegmentedControl data={['React', 'Next.js', 'Angular', 'Vue', 'CSS']}
142 value={favTechValue} onChange={setFavTechValue}/>
143 </div>
144
145 <div className="pt-5">
146
147 <NumberInput
148 label="Enter your age"
149 description="Don't lie...."
150 className="w-[100px]"
151 value={ageValue}
152 onChange={setAgeValue}
153 min={18}
154 max={99}
155 placeholder="18-99"
156
157 />
158 {ageError && <span className="text-sm text-red-500">ERROR! {ageError}</span>}
159 </div>
160
161 </Fieldset>
162
163 <Fieldset legend="Finally, how would you rate this form?"
164 style={{marginTop: "20px", fontWeight: "bold", width: "90%"}}>
165 <div className="pt-5" style={{fontSize: "14px", fontWeight: "500"}}>Be honest....
166 </div>
167 <div style={{
168 fontSize: "12px",
169 color: "#868E96",
170 paddingBottom: "6px",
171 fontWeight: "700px"
172 }}> Pick one
173 </div>
174 <Rating defaultValue={ratingValue} color="orange" value={ratingValue}
175 onChange={setRatingValue}/>
176 </Fieldset>
177 </div>
178
179 <div className="w-1/2 pt-5">
180 <Fieldset legend="End User License Agreement"
181 style={{fontWeight: "bold", width: "90%"}}>
182 <span className="text-sm" style={{fontWeight: "normal"}}>Scroll to read, then agree to
183 this agreement by agreeing at the bottom of this agreement.<br/><br/></span>
184 <ScrollArea h={730} style={{
185 paddingRight: "30px",
186 paddingLeft: "20px",
187 paddingTop: "10px",
188 border: "thin solid lightgray",
189 borderRadius: "5px"
190 }}>
191 <div className="text-lg font-normal">HumancentiPad Plot</div>
192 <div className="text-sm font-normal">
193 After.... [EULA text here - saved for brevity]
194 </div>
195 <hr style={{color: "lightgrey"}}/>
196 <br/>
197 <Switch
198 style={{fontSize: "14px"}}
199 checked={switchChecked}
200 onChange={(event) => setSwitchChecked(event.currentTarget.checked)}
201 label="I agree that I've read this End User License Agreement and I'm OK with
202 selling my soul to Corporate America. I mean, did you NOT see this South Park episode???"
203 />
204 <br/>
205 </ScrollArea>
206 {switchError && <span className="text-sm text-red-500">ERROR! {switchError}</span>}
207 </Fieldset>
208 </div>
209 </div>
210
211 <div className="flex items-center justify-center">
212 <Group mt="md" className="pt-6">
213 <Button onClick={handleSubmit}>Submit Form</Button>
214 </Group>
215 </div>
216 </div>
217 </div>
218
219 <Modal opened={modalOpen} onClose={() => setModalOpen(false)} withinPortal={true} title={
220 <Group>
221 <Image
222 src="/images/logo.png"
223 alt="Modal Icon"
224 width={60}
225 height={60}
226 />
227 <span>Form Results - Thanks For Playing!</span>
228 </Group>
229 } withCloseButton={true}
230 className="text-base shadow-md" transitionProps={{transition: 'fade', duration: 300}}
231 styles={{
232 title: {color: '#27272A',},
233 body: {color: '#27272A'},
234 }}>
235
236 <table style={{margin:'auto'}}>
237 <tbody>
238 <tr>
239 <td align="right">First name: </td>
240 <td align="left"> {firstValue}</td>
241 </tr>
242 <tr>
243 <td align="right">Last name: </td>
244 <td align="left"> {lastValue}</td>
245 </tr>
246 <tr>
247 <td align="right">PIN: </td>
248 <td align="left"> {pinValue}</td>
249 </tr>
250 <tr>
251 <td align="right">Favorite Tech: </td>
252 <td align="left"> {favTechValue}</td>
253 </tr>
254 <tr>
255 <td align="right">Age: </td>
256 <td align="left"> {ageValue}</td>
257 </tr>
258 <tr>
259 <td align="right">Rating: </td>
260 <td align="left"> {ratingValue} stars</td>
261 </tr>
262 <tr>
263 <td align="right">EULA: </td>
264 <td align="left"> {switchChecked ? 'Agreed' : 'Not Agreed'}</td>
265 </tr>
266 </tbody>
267 </table>
268 <div className="pt-3">
269 Rest assured, none of your data has been saved or stored. No animals were harmed, no trees were
270 cut down and no water was polluted during the production of this form.
271 </div>
272 </Modal>
273 </div>
274 )