Hi @kolluax,
Thanks for sharing the details of your POC and the questions. Below is a consolidated response that you can use as a reference while continuing your React‑based headless implementation.
1. @aemforms/af-react-components vs @aemforms/af-react-vanilla-components
Within the @aemforms npm scope there are two main React component libraries:
In most production implementations customers:
- Use
@aemforms/af-react-components as the base, and - Gradually replace specific fields with their own React components via the
mappings configuration.
You do not need to use af-react-vanilla-components unless you explicitly want the lighter, HTML‑like component set or to reuse some of the utilities exported from that package.
2. af-custom-functions – how it is meant to be used
The recommended pattern for custom business functions (used in rules and expressions) is:
- Implement them once in a dedicated JS module/package (similar to the internal
af-custom-functions repo used by Adobe). - Consume that same module:
- From AEM (via clientlibs or dynamic ESM import), and
- From your headless React app (regular npm import).
- Register the functions with the Headless runtime (
af-core) so that the rule engine can invoke them.
This gives you a single source of truth for custom logic. You should not maintain two divergent copies of the same function (one in AEM clientlibs and one in the React SPA). Instead:
- Treat your custom function library as a shared dependency.
- Let AEM include it as a client library for headful/core‑component forms.
- Let your React app import it as an npm dependency and register it with the headless runtime.
The form runtime does not “auto-detect” functions just because they exist in a clientlib; they need to be registered with the function runtime, which your React app typically does during initialization.[^custom_functions]
3. Scribble Signature (Core Components) in a headless / React setup
The Scribble Signature component is available as part of Adaptive Forms Core Components and is styled through a theme (for example, the Canvas or WKND themes). The important points for a headless React setup are:
- The form JSON for a Scribble field can be rendered headless just like any other field, using
@aemforms/af-core and @aemforms/af-react-renderer.[^arch] - The look and feel (including Scribble controls) is governed by the theme CSS, which is a front‑end asset independent of AEM.
- For headless, the recommended approach is:
- Use the same theme that your Core Component form uses (e.g. a theme based on
aem-forms-theme-canvas). - Include the generated theme CSS in your React app bundle (for example via the
@aemforms/af-canvas-theme npm package or a similar theme build.[^theme]]
In other words, you do not need Adobe to “bake the theme into the React renderer”. Instead, you:
- Author and configure the theme in AEM for your Core Component form.
- Build that theme as a standalone CSS asset.
- Import that CSS into your React SPA so that the Scribble field (and other components) are styled consistently for headful and headless channels.
If you share the exact field type / resource type used in your Scribble field’s JSON, we can help you define the precise React mapping for it as needed.
4. Using your own React components (instead of af-react-vanilla-components) while reusing JS logic
Yes, this is a fully supported and recommended pattern.
At a high level, your React setup will look like:
-
Logic and state:
@aemforms/af-core – core form model and rule engine. @aemforms/af-react-renderer – React bindings and hooks (useRuleEngine) that connect the JSON model to your components.[^arch]
-
UI components:
- Either use
@aemforms/af-react-components (Spectrum) as a starting point, or - Define your own React components for each field type and wire them to the runtime.
The key mechanism is the mappings object passed into <AdaptiveForm>:
import {AdaptiveForm} from '@aemforms/af-react-renderer';
import {mappings as defaultMappings} from '@aemforms/af-react-components';
import MyTextField from './MyTextField';
const mappings = {
...defaultMappings,
'text-input': MyTextField, // replace default for all text-input fields
};
<AdaptiveForm formJson={formJSON} mappings={mappings} />;
Inside MyTextField, you reuse the same runtime hooks:
import {useRuleEngine} from '@aemforms/af-react-renderer';
import {FieldJson, State} from '@aemforms/af-core';
const MyTextField = (props: State<FieldJson>) => {
const [state, handlers] = useRuleEngine(props);
if (!state.visible) return null;
return (
<input
value={state.value ?? ''}
onChange={e => handlers.change(e.target.value)}
onBlur={handlers.blur}
/>
);
};
This approach gives you:
- Full control over the UI (your design system, Material UI, etc.), and
- Reuse of all the form logic (state, validation, rules, events) provided by the Web SDK.[^custom_components]
So you can absolutely skip af-react-vanilla-components if you prefer to build your own components or rely on another UI library.
5. Header and Footer in headless forms
Headless Adaptive Forms are intentionally focused on the form body. Global chrome like header and footer is expected to be owned by the host application (your React SPA, mobile app, etc.), not by the form JSON.
Internally, the current feature matrix shows that:
- Header and Footer components are not implemented on the React/Spectrum runtime side, and
- Themes are treated purely as front‑end assets to be consumed by the application, not embedded in the Web SDK.[^gaps]
This matches what you’ve seen:
- There are no header/footer components in the React packages.
- The recommended practice is:
- Implement your site/app header and footer in your SPA layout (outside
<AdaptiveForm>). - Use the form JSON only for the form itself, so the same form can be reused across multiple channels (web, mobile, chat) without carrying channel‑specific chrome.
The earlier AFv1 “JS embed” behavior (where header/footer were effectively stripped when embedding) still conceptually applies in the headless model: the SPA owns its overall layout; the headless runtime focuses on the form fields and their logic.
Summary for your POC
For your POC based on the React starter kit:
- Runtime: keep
@aemforms/af-core and @aemforms/af-react-renderer as-is. - Components: start from
@aemforms/af-react-components and override individual field types with your own React components via mappings where needed. - Custom functions: centralize them in one shared JS/TS package and consume it from both AEM and the SPA. Register them with the
af-core runtime rather than duplicating logic. - Scribble Signature: author it using Core Components in AEM, then include the same theme CSS in your React app. Only ask Adobe for help in making that theme consumable as an npm/webpack artifact if needed.
- Header & Footer: implement them in your React SPA, outside the
<AdaptiveForm>; do not expect them from the headless JSON.
If you’d like, we can review one of your form JSON definitions and propose a concrete mappings.ts plus a skeleton for your shared custom-functions package.
Thanks
Pranay
[^starter]: Create and preview a Headless Form using a React app, https://experienceleague.adobe.com/en/docs/experience-manager-headless-adaptive-forms/using/get-started/create-and-publish-a-headless-form
[^arch]: Headless adaptive forms architecture, https://experienceleague.adobe.com/en/docs/experience-manager-headless-adaptive-forms/using/architecture
[^custom_components]: Use custom components to render a headless form, https://experienceleague.adobe.com/en/docs/experience-manager-headless-adaptive-forms/using/get-started/developing-for-headless-forms-using-your-own-components