diff --git a/.yarn/versions/52f80f42.yml b/.yarn/versions/52f80f42.yml
new file mode 100644
index 000000000..6ba30a2d7
--- /dev/null
+++ b/.yarn/versions/52f80f42.yml
@@ -0,0 +1,33 @@
+releases:
+ "@radix-ui/react-context": patch
+ "@radix-ui/react-form": patch
+
+declined:
+ - primitives
+ - "@radix-ui/react-accordion"
+ - "@radix-ui/react-alert-dialog"
+ - "@radix-ui/react-avatar"
+ - "@radix-ui/react-checkbox"
+ - "@radix-ui/react-collapsible"
+ - "@radix-ui/react-collection"
+ - "@radix-ui/react-context-menu"
+ - "@radix-ui/react-dialog"
+ - "@radix-ui/react-dropdown-menu"
+ - "@radix-ui/react-hover-card"
+ - "@radix-ui/react-menu"
+ - "@radix-ui/react-menubar"
+ - "@radix-ui/react-navigation-menu"
+ - "@radix-ui/react-popover"
+ - "@radix-ui/react-popper"
+ - "@radix-ui/react-progress"
+ - "@radix-ui/react-radio-group"
+ - "@radix-ui/react-roving-focus"
+ - "@radix-ui/react-scroll-area"
+ - "@radix-ui/react-select"
+ - "@radix-ui/react-slider"
+ - "@radix-ui/react-switch"
+ - "@radix-ui/react-tabs"
+ - "@radix-ui/react-toast"
+ - "@radix-ui/react-toggle-group"
+ - "@radix-ui/react-toolbar"
+ - "@radix-ui/react-tooltip"
diff --git a/packages/react/context/src/createContext.tsx b/packages/react/context/src/createContext.tsx
index e3ca734f7..aa2cf530a 100644
--- a/packages/react/context/src/createContext.tsx
+++ b/packages/react/context/src/createContext.tsx
@@ -63,12 +63,18 @@ function createContextScope(scopeName: string, createContextScopeDeps: CreateSco
return {children};
}
- function useContext(consumerName: string, scope: Scope) {
+ function useContext(
+ consumerName: string,
+ scope: Scope,
+ options: { isOptional?: boolean } = {}
+ ) {
const Context = scope?.[scopeName][index] || BaseContext;
const context = React.useContext(Context);
+ const { isOptional } = options;
+ if (isOptional) return {} as ContextValueType;
if (context) return context;
if (defaultContext !== undefined) return defaultContext;
- // if a defaultContext wasn't specified, it's a required context.
+ // if a defaultContext wasn't specified and isOptional is false, it's a required context.
throw new Error(`\`${consumerName}\` must be used within \`${rootComponentName}\``);
}
diff --git a/packages/react/form/src/Form.stories.tsx b/packages/react/form/src/Form.stories.tsx
index 3d6e20245..5dd30fe59 100644
--- a/packages/react/form/src/Form.stories.tsx
+++ b/packages/react/form/src/Form.stories.tsx
@@ -107,10 +107,11 @@ export const Cypress = () => {
Name (required)
-
valid!
+ {/* This Form.Message is used out of the Form.Field context */}
+
Age (0-99)
diff --git a/packages/react/form/src/Form.tsx b/packages/react/form/src/Form.tsx
index 834bc4147..ebe7d813f 100644
--- a/packages/react/form/src/Form.tsx
+++ b/packages/react/form/src/Form.tsx
@@ -462,9 +462,17 @@ interface FormMessageProps extends Omit {
const FormMessage = React.forwardRef(
(props: ScopedProps, forwardedRef) => {
const { match, name: nameProp, ...messageProps } = props;
- const fieldContext = useFormFieldContext(MESSAGE_NAME, props.__scopeForm);
+ const fieldContext = useFormFieldContext(MESSAGE_NAME, props.__scopeForm, {
+ isOptional: !!nameProp,
+ });
const name = nameProp ?? fieldContext.name;
+ if (!nameProp && !fieldContext?.name) {
+ throw new Error(
+ `\`${MESSAGE_NAME}\` must be used within \`${FIELD_NAME}\` or specify the \`name\` prop`
+ );
+ }
+
if (match === undefined) {
return (