Overview
Camome does not include client-side form validation feature by JavaScript. Instead, it focuses on the ease of generating semantic and accessible HTML.
By using the FormField
component, you can automatically link <label />
and explanatory text (including errors) to <input />
element (by aria-labelledby
and aria-describedby
attributes).
Since Camome aims to provide HTML-based APIs as much as possible without using JavaScript, it can easily be integrated with existing form libraries like React Hook Form.
Example with React Hook Form
Copy
import React from "react";import { SubmitHandler, useForm } from "react-hook-form";
import { Button } from "@camome/core/Button";import { Checkbox } from "@camome/core/Checkbox";import { Input } from "@camome/core/Input";import { Radio } from "@camome/core/Radio";import { RadioGroup } from "@camome/core/RadioGroup";import { Select } from "@camome/core/Select";import { Textarea } from "@camome/core/Textarea";
type FormSchema = { firstName: string; lastName: string; jobTitle: "developer" | "designer" | "other"; message: string; favorite: "pen" | "pineapple" | "apple"; privacy: boolean;};
const errMsg = { required: "This field is required." } as const;
export default function WithReactHookForm() { const { handleSubmit, register, formState: { errors }, } = useForm<FormSchema>({ defaultValues: { jobTitle: "developer", }, }); const [data, setData] = React.useState<FormSchema>();
const onSubmit: SubmitHandler<FormSchema> = (data) => { setData(data); };
return ( <div style={styles.container}> <form onSubmit={handleSubmit(onSubmit)} style={styles.form}> <div style={styles.col2}> <Input label="First name" placeholder="John" error={errors.firstName?.message} {...register("firstName", { required: errMsg.required, })} fill /> <Input label="Last name" placeholder="Doe" error={errors.lastName?.message} {...register("lastName", { required: errMsg.required, })} fill /> </div> <RadioGroup label="Job title" aria-required orientation="horizontal"> <Radio label="Developer" value="developer" {...register("jobTitle")} /> <Radio label="Designer" value="designer" {...register("jobTitle")} /> <Radio label="Other" value="other" {...register("jobTitle")} /> </RadioGroup> <Select label="Favorite thing" error={errors.favorite?.message} {...register("favorite", { required: errMsg.required, })} fill > <option value="pen">Pen</option> <option value="pineapple">Pineapple</option> <option value="apple">Apple</option> </Select> <Textarea label="Message" placeholder="Your thoughts..." error={errors.message?.message} {...register("message", { required: errMsg.required, minLength: { message: "You must write at least 20 characters!", value: 20, }, })} rows={4} fill /> <Checkbox label="Agree to Privacy Policy" {...register("privacy", { required: errMsg.required, })} error={errors.privacy?.message} /> <Button type="submit" variant="soft"> Submit </Button> </form> <output style={styles.output}> {data ? JSON.stringify(data, null, 2) : "// Submit to see JSON data"} </output> </div> );}
const container = { display: "grid", gap: "2rem", width: "100%",};
const col2 = { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "1rem", placeItems: "start stretch",};
const size = { width: "min(calc(100% - 1rem), 28rem)", margin: "0 auto",};
const form = { ...size, display: "grid", gap: "1.5rem",};
const output = { ...size, padding: "1rem", borderRadius: "var(--cmm-radius-lg)", backgroundColor: "var(--cmm-color-black)", color: "var(--cmm-color-white)", fontSize: "var(--cmm-font-size-sm)", display: "grid", placeContent: "center start", whiteSpace: "pre-wrap",} as const;
const styles = { container, col2, form, output,};