How Dify Chat React-App Handles Interactive Forms: Markdown-Driven UI Architecture
The Dify Chat react-app handles interactive forms by parsing <form> elements embedded in markdown responses, rendering them as Ant Design components via a custom MarkdownForm component, and propagating submission data back through the chat message pipeline.
The lexmin0412/dify-chat repository implements a React-based frontend that transforms static markdown into dynamic user interfaces. When backend workflows return HTML form elements inside markdown content, the Dify Chat react-app handles interactive forms by converting these tags into fully functional Ant Design components, managing state locally, and submitting responses as standard chat messages.
How Markdown Forms Are Parsed and Rendered
The architecture relies on a two-stage process: first, the markdown parser identifies form elements; second, a specialized component constructs the interactive UI.
The MarkdownRenderer Component
Located at packages/react-app/src/components/markdown-renderer/index.tsx, the MarkdownRenderer component uses React-Markdown to parse the raw markdown string received from the backend. It registers a custom component mapping for the form tag, intercepting standard HTML rendering and replacing it with the interactive MarkdownForm component.
// packages/react-app/src/components/markdown-renderer/index.tsx
components={{
// ...other custom components
form: props => (
<MarkdownForm
{...props}
onSend={values => onSubmit?.(values)} // forwards the result upstream
/>
),
// ...
}}
The onSubmit prop passed to MarkdownRenderer originates from parent chat components and represents the callback that will ultimately transmit data to the Dify backend.
The MarkdownForm Component
The actual UI construction happens in packages/react-app/src/components/markdown-renderer/blocks/form.tsx. The MarkdownForm component traverses the child nodes of the <form> element and maps each supported HTML tag to its corresponding Ant Design equivalent (Input, Select, DatePicker, Button, etc.).
// packages/react-app/src/components/markdown-renderer/blocks/form.tsx
const MarkdownForm = ({ node, onSend }: any) => {
const [formValues, setFormValues] = useState({});
// Initialize field values from default attributes
useEffect(() => {
const init = {};
node.children.forEach(child => {
if (['input', 'textarea'].includes(child.tagName)) {
init[child.properties.name] = child.properties.value || '';
}
});
setFormValues(init);
}, [node.children]);
const onSubmit = (e) => {
e.preventDefault();
const result = getFormValues(node.children);
// Check for data-format="json" attribute
if (node.properties.dataFormat === 'json') {
onSend?.(JSON.stringify({ ...result, isFormSubmit: true }));
} else {
// Default plain text format
onSend?.(Object.entries(result)
.map(([k, v]) => `${k}: ${v}`).join('\n'));
}
};
return (
<form onSubmit={onSubmit}>
{/* Render Ant Design components based on child tags */}
<Button type="primary" htmlType="submit">Submit</Button>
</form>
);
};
State Management and Data Flow
The Dify Chat react-app handles interactive forms through a unidirectional data flow that maintains local state while propagating submissions upward through the component hierarchy.
Tracking Field Values with React State
MarkdownForm initializes a formValues state object using React's useState hook. As users interact with Ant Design input components, onChange handlers update this state dictionary, keyed by the name attribute of each form field. This approach ensures the component remains the single source of truth for form data until submission.
Formatting Submissions as JSON or Plain Text
The component supports two output formats controlled by the data-format attribute on the <form> tag:
- JSON format: When
data-format="json"is present,MarkdownFormserializes the field values into a JSON string that includes anisFormSubmit: trueflag, enabling the backend to distinguish form submissions from regular chat messages. - Plain text: By default, the component formats data as newline-separated
key: valuepairs, creating a human-readable summary suitable for simple chat-based workflows.
Propagating Form Data Through the Chat Pipeline
Once MarkdownForm formats the submission, it invokes the onSend callback provided by MarkdownRenderer. This callback maps to the onSubmit prop passed from parent components.
In packages/react-app/src/components/chatbox/message/content.tsx, the MessageContent component passes its own onSubmit handler to MarkdownRenderer:
// packages/react-app/src/components/chatbox/message/content.tsx
<MarkdownRenderer
markdownText={text}
onSubmit={onSubmit} // receives formatted form data
/>
This handler ultimately reaches ChatBoxWrapper (packages/react-app/src/components/chatbox-wrapper.tsx), which executes the API call to the Dify backend, transmitting the form payload as a standard chat message. This seamless integration allows bot workflows to request structured user input without breaking the conversational flow.
Supported Form Field Types and Attributes
MarkdownForm recognizes standard HTML form tags and maps them to specialized Ant Design components:
- Text inputs:
<input type="text">,<input type="password">,<input type="email">,<input type="number">→Input,Input.Password - Date and time:
<input type="date">,<input type="datetime-local">→DatePicker - Selection:
<select>withdata-optionsattribute →Select - Multiline text:
<textarea>→Input.TextArea - Hidden fields:
<input type="hidden">→ included in state but not rendered - Labels:
<label>→Form.Itemlabels
Special attributes parsed by the component include data-format (for submission type), data-options (for select dropdowns), and standard HTML attributes like name, placeholder, required, and value.
Implementation Examples
Example 1: Backend Markdown Response
When a Dify workflow returns a markdown message containing a form, it appears as standard HTML embedded in the markdown:
<form data-format="json">
<label for="name">Name</label>
<input type="text" name="name" placeholder="Your name" />
<label for="dob">Date of birth</label>
<input type="date" name="dob" />
<button type="submit">Send</button>
</form>
Example 2: React Component Integration
To render forms in your chat interface, pass the markdown text and a submission handler to MarkdownRenderer:
import { MarkdownRenderer } from './components/markdown-renderer';
<MarkdownRenderer
markdownText={markdownFromServer}
onSubmit={value => {
// `value` is a JSON string when data-format="json":
// {"name":"Alice","dob":"1990-01-01","isFormSubmit":true}
sendMessageToBackend(value);
}}
/>
Example 3: Internal Form State Handling
The MarkdownForm component dynamically constructs the form UI by iterating over child nodes:
const MarkdownForm = ({ node, onSend }) => {
const [formValues, setFormValues] = useState({});
// Initialize fields from default values
useEffect(() => {
const init = {};
node.children.forEach(child => {
if (['input', 'textarea'].includes(child.tagName)) {
init[child.properties.name] = child.properties.value || '';
}
});
setFormValues(init);
}, [node.children]);
const onSubmit = (e) => {
e.preventDefault();
const result = formValues;
if (node.properties.dataFormat === 'json') {
onSend?.(JSON.stringify({ ...result, isFormSubmit: true }));
} else {
onSend?.(Object.entries(result)
.map(([k, v]) => `${k}: ${v}`).join('\n'));
}
};
return (
<form onSubmit={onSubmit}>
{/* Render Ant Design components based on child tags */}
<Button type="primary" htmlType="submit">Submit</Button>
</form>
);
};
Summary
The Dify Chat react-app handles interactive forms through a sophisticated markdown-to-UI pipeline that bridges backend workflows and frontend interactions:
- Markdown Parsing: The
MarkdownRenderercomponent intercepts<form>tags in markdown responses and delegates rendering toMarkdownForm. - Dynamic UI Generation:
MarkdownFormtraverses child nodes and maps HTML input types to corresponding Ant Design components, supporting text, date, select, and textarea fields. - State Management: Local React state tracks field values in real-time, with initialization from default attributes and updates on user interaction.
- Flexible Formatting: The component supports both JSON and plain-text output formats via the
data-formatattribute, enabling backend workflows to receive structured or human-readable data. - Seamless Integration: Form submissions propagate through
MessageContentandChatBoxWrapperbefore reaching the Dify API, treating user input as standard chat messages.
Frequently Asked Questions
How does Dify Chat handle form submissions from interactive markdown?
When a user submits a form, the MarkdownForm component prevents default browser behavior and formats the collected field values according to the data-format attribute. It invokes the onSend callback passed from MarkdownRenderer, which maps to the onSubmit handler in MessageContent. This handler ultimately reaches ChatBoxWrapper, which transmits the formatted data to the Dify backend API as a standard chat message, allowing bots to process structured user input within conversational flows.
What form field types are supported in Dify Chat markdown?
The MarkdownForm component in packages/react-app/src/components/markdown-renderer/blocks/form.tsx supports standard HTML form tags including text inputs (type="text", type="password", type="email", type="number"), date and datetime inputs (type="date", type="datetime-local"), textareas, selects (with data-options attributes for dropdown values), hidden inputs, and labels. Each tag maps to a corresponding Ant Design React component to ensure consistent styling and behavior.
Can Dify Chat forms submit data as JSON instead of plain text?
Yes, the form component supports JSON output through the data-format="json" attribute on the <form> tag. When this attribute is present, MarkdownForm serializes the field values into a JSON string that includes an isFormSubmit: true flag, enabling backend workflows to distinguish form submissions from regular text messages. Without this attribute, the component defaults to plain text formatting, outputting newline-separated key: value pairs.
Where is the form rendering logic located in the Dify Chat codebase?
The primary form handling logic resides in two key files within the packages/react-app/src/components/markdown-renderer/ directory. The entry point is index.tsx, which contains the MarkdownRenderer component that configures React-Markdown to intercept <form> tags. The actual UI implementation lives in blocks/form.tsx, which exports the MarkdownForm component responsible for state management, field rendering, and submission handling. The integration point with the chat system is packages/react-app/src/components/chatbox/message/content.tsx, where the onSubmit callback bridges the form component to the message pipeline.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →