appsmithorg/appsmith · error · Error

FormControl compound components cannot be rendered outside…

Error message

FormControl compound components cannot be rendered outside the FormControl component

What it means

Intended to fire from useFormControlContext() in the Appsmith Design System when a FormControl compound component (e.g. FormLabel, FormHelperText, FormInput) is rendered without a FormControl ancestor. IMPORTANT DEFECT grounded in the source: FormControlContext is created with createContext({...}) passing a non-null default object ({isRequired:false,isDisabled:false,size:"sm"}), so useContext always returns a truthy value and the `if (!context)` guard is DEAD CODE — this error can never actually be thrown at runtime as written. The intent is real; the implementation does not achieve it.

Solutions

  1. Always render compound components inside <FormControl> so context is provided explicitly.
  2. Fix the underlying defect: pass null as the createContext default (createContext<Pick<...> | null>(null)) so the guard becomes live, matching the Sidebar pattern.
  3. If fixing the library, audit all consumers once the throw becomes reachable — currently any out-of-tree usage has been silently passing.
  4. Add a unit test rendering a compound component without a provider to lock in the intended behavior.

Example fix

// before (dead guard)
export const FormControlContext = createContext({ isRequired:false, isDisabled:false, size:"sm" });
export const useFormControlContext = () => {
  const context = useContext(FormControlContext);
  if (!context) { throw new Error(...); }  // unreachable
  return context;
};

// after (live guard)
export const FormControlContext = createContext<Pick<FormControlProps,"isRequired"|"isDisabled"|"size"> | null>(null);
export const useFormControlContext = () => {
  const context = useContext(FormControlContext);
  if (!context) { throw new Error("FormControl compound components cannot be rendered outside the FormControl component"); }
  return context;
};
Defensive patterns

Strategy: type-guard

Type guard

// NOTE: current default is non-null so this never throws; fix the lib first.
// After fix (createContext<T|null>(null)):
function hasFormControlContext<T>(ctx: T | null): ctx is T { return ctx !== null; }

Try / catch

// Wrap risky rendering; today this is dead code, so add the provider regardless.
try { return renderCompound(); } catch (e) {
  if (/FormControl compound components/i.test(e.message)) return <FormControl>{renderCompound()}</FormControl>;
  throw e;
}

Prevention

When it happens

Trigger: Render a FormControl.* subcomponent outside <FormControl>. Intended trigger only — in practice the non-null default suppresses the throw, so the component silently renders with default context values instead.

Common situations: Copying a Form subcomponent into a story or test without the FormControl wrapper; refactoring a form and hoisting a label out of FormControl; using the design system primitives standalone. Developer expects a clear error but gets silent default sizing.

Related errors


AI-assisted analysis of appsmithorg/appsmith@8cd9021c24 (2026-08-12). Data as JSON: /api/errors/a216d242e259a410. Report an issue: GitHub.

Appendix: source

Thrown at app/client/packages/design-system/ads/src/FormControl/FormControl.context.ts:16

import { createContext, useContext } from "react";
import type { FormControlProps } from "./FormControl.types";

export const FormControlContext = createContext<
  Pick<FormControlProps, "isRequired" | "isDisabled" | "size">
>({
  isRequired: false,
  isDisabled: false,
  size: "sm",
});

export const useFormControlContext = () => {
  const context = useContext(FormControlContext);

  if (!context) {
    throw new Error(
      "FormControl compound components cannot be rendered outside the FormControl component",
    );
  }

  return context;
};

export const FormControlProvider = FormControlContext.Provider;

View on GitHub (pinned to 8cd9021c24)