/* Copyright 2026 Marimo. All rights reserved. */
import { AlertCircleIcon } from "lucide-react";
import { type Label as LabelPrimitive, Slot as SlotPrimitive } from "radix-ui";
import * as React from "react";
import {
Controller,
type ControllerProps,
type FieldPath,
type FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form";
import { Label } from "@/components/ui/label";
import { Tooltip } from "@/components/ui/tooltip";
import { cn } from "@/utils/cn";
const Form = FormProvider;
export const FormErrorsBanner = () => {
const ctx = useFormContext();
const errors = ctx.formState.errors;
if (Object.keys(errors).length === 0) {
return null;
}
return (
{Object.values(errors)
.map((error) => error?.message)
.filter(Boolean)
.join(", ")}
);
};
interface FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath = FieldPath,
> {
name: TName;
}
const FormFieldContext = React.createContext(
{} as FormFieldContextValue,
);
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath = FieldPath,
>({
...props
}: ControllerProps) => {
return (
// Need key to prevent context value from being stale
);
};
const useFormField = () => {
const fieldContext = React.use(FormFieldContext);
const itemContext = React.use(FormItemContext);
const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error("useFormField should be used within ");
}
const { id } = itemContext;
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
};
};
interface FormItemContextValue {
id: string;
}
const FormItemContext = React.createContext(
{} as FormItemContextValue,
);
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes
>(({ className, ...props }, ref) => {
const id = React.useId();
return (
);
});
FormItem.displayName = "FormItem";
const FormLabel = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField();
if (!props.children) {
return;
}
return (
);
});
FormLabel.displayName = "FormLabel";
const FormControl = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } =
useFormField();
return (
);
});
FormControl.displayName = "FormControl";
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField();
if (!props.children) {
return null;
}
return (
);
});
FormDescription.displayName = "FormDescription";
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField();
const body = error?.message ? String(error?.message) : children;
if (!body) {
return null;
}
return (
{body}
);
});
FormMessage.displayName = "FormMessage";
const FormMessageTooltip = ({ className }: { className: string }) => {
const { error } = useFormField();
const body = error?.message ? String(error?.message) : null;
if (!body) {
return null;
}
return (
);
};
FormMessageTooltip.displayName = "FormMessageTooltip";
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormMessageTooltip,
FormField,
};