Form

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

// Submit to see JSON data
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,
};