chroma-panel ships no React Hook Form integration, and it does not need one.
You can use the React color picker with React Hook Form in two ways: leave the
field uncontrolled and let ColorInput submit itself, or wrap it in
<Controller> so the form owns the value. This page shows both, and which of
the two change events to validate on.
Install
npm install chroma-panelReact Hook Form is not part of chroma-panel. The examples below assume you
already have it in your app and use the v7 Controller API.
Uncontrolled: let ColorInput submit itself
Give ColorInput a name and it renders a hidden input that submits with the
surrounding form, in whichever format you set. React Hook Form never sees the
field, so you read it from the form data yourself:
import { useRef } from "react";
import { useForm } from "react-hook-form";
import { ColorInput } from "chroma-panel";
type Values = { label: string };
export function BrandForm() {
const formRef = useRef<HTMLFormElement>(null);
const { register, handleSubmit } = useForm<Values>({
defaultValues: { label: "" },
});
const onSubmit = (values: Values): void => {
const form = formRef.current;
if (form === null) {
return;
}
const brand = String(new FormData(form).get("brand") ?? "");
save({ label: values.label, brand });
};
return (
<form ref={formRef} onSubmit={handleSubmit(onSubmit)}>
<input {...register("label")} />
<ColorInput
name="brand"
defaultValue="#3366cc"
aria-label="Brand color"
/>
<button type="submit">Save</button>
</form>
);
}This is the smaller of the two. The cost is that the color stays outside the
form state: it does not show up in formState, watch or a resolver, and
reset() does not touch it. form.reset() still returns the control to its
defaultValue, because that is native behavior. Pick this when the color needs
no validation beyond required, which ColorInput handles natively — see
forms.
Controller: let the form own the value
Wrap ColorInput in <Controller> and it becomes an ordinary controlled field.
Pass value and onChange yourself rather than spreading field:
import { Controller, useForm } from "react-hook-form";
import { ColorInput, type ColorChangeResult } from "chroma-panel";
type Values = { brand: string };
export function BrandForm() {
const { control, handleSubmit } = useForm<Values>({
defaultValues: { brand: "#3366cc" },
});
return (
<form onSubmit={handleSubmit((values) => save(values))}>
<Controller
name="brand"
control={control}
rules={{ required: "Pick a color" }}
render={({ field, fieldState }) => (
<div>
<ColorInput
value={field.value}
onChange={(result: ColorChangeResult) =>
field.onChange(result.hex)
}
onChangeComplete={() => field.onBlur()}
aria-label="Brand color"
/>
{fieldState.error && <p role="alert">{fieldState.error.message}</p>}
</div>
)}
/>
<button type="submit">Save</button>
</form>
);
}Two things to keep straight:
- Do not spread
{...field}ontoColorInput.field.onChangewould be handed a color object instead of the string you want stored, andfield.refwould not attach, becauseColorInputtakes noref. - Leave
nameoffColorInputhere. With anameit renders its own hidden input, which would submit the color a second time alongside the value the form already holds.
field.value is a string, which is what ColorInput expects. It also accepts an
Hsva object if you would rather store the color without the rounding that hex
applies — the trade-off is in
controlled and uncontrolled.
Which event to validate on
onChange fires continuously while you drag the panel. onChangeComplete fires
once, when you let go. That distinction decides how your validation mode
behaves:
useForm mode | What happens |
|---|---|
"onSubmit" (default) | Validates once on submit. Nothing to tune. |
"onBlur" | Validates when you call field.onBlur(). |
"onChange" | Revalidates on every frame of a drag. |
The example above calls field.onBlur() from onChangeComplete, so with
mode: "onBlur" the field is checked once per drag rather than sixty times a
second. If you are on mode: "onChange" and the form feels heavy, that is the
first thing to change.
onChange and onChangeComplete are both listed with the rest of the props on
ColorInput.