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, MarkdownForm serializes the field values into a JSON string that includes an isFormSubmit: true flag, enabling the backend to distinguish form submissions from regular chat messages.
  • Plain text: By default, the component formats data as newline-separated key: value pairs, 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> with data-options attribute → Select
  • Multiline text: <textarea> → Input.TextArea
  • Hidden fields: <input type="hidden"> → included in state but not rendered
  • Labels: <label> → Form.Item labels

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 MarkdownRenderer component intercepts <form> tags in markdown responses and delegates rendering to MarkdownForm.
  • Dynamic UI Generation: MarkdownForm traverses 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-format attribute, enabling backend workflows to receive structured or human-readable data.
  • Seamless Integration: Form submissions propagate through MessageContent and ChatBoxWrapper before 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →