# How Dify Chat React-App Handles Interactive Forms: Markdown-Driven UI Architecture

> Discover how Dify Chat's react-app uses markdown to build interactive forms with Ant Design components. Learn about its unique UI architecture for seamless form submissions.

- Repository: [lexmin0412/dify-chat](https://github.com/lexmin0412/dify-chat)
- Tags: architecture
- Published: 2026-03-06

---

**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`](https://github.com/lexmin0412/dify-chat/blob/main/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.

```tsx
// 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`](https://github.com/lexmin0412/dify-chat/blob/main/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.).

```tsx
// 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`](https://github.com/lexmin0412/dify-chat/blob/main/packages/react-app/src/components/chatbox/message/content.tsx), the `MessageContent` component passes its own `onSubmit` handler to `MarkdownRenderer`:

```tsx
// 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`](https://github.com/lexmin0412/dify-chat/blob/main/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:

```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`:

```tsx
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:

```tsx
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`](https://github.com/lexmin0412/dify-chat/blob/main/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`](https://github.com/lexmin0412/dify-chat/blob/main/index.tsx), which contains the `MarkdownRenderer` component that configures React-Markdown to intercept `<form>` tags. The actual UI implementation lives in [`blocks/form.tsx`](https://github.com/lexmin0412/dify-chat/blob/main/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`](https://github.com/lexmin0412/dify-chat/blob/main/packages/react-app/src/components/chatbox/message/content.tsx), where the `onSubmit` callback bridges the form component to the message pipeline.