Skip to main content
The Form Builder uses a powerful drag-and-drop system powered by @dnd-kit to let you visually organize form fields. This guide explains all the drag-and-drop interactions available.

Overview

The drag-and-drop interface allows you to:
  • Drag fields from the palette to add them to your layout
  • Reorder fields within the canvas by dragging
  • Combine fields into rows (up to 3 per row)
  • Create new rows by dropping fields above or below existing ones
  • Move fields between steps in multi-step forms

Interface Components

The drag-and-drop system has three main areas:

1. The Palette (Left Sidebar)

Location: “Campos detectados” section in the sidebar What it shows: Form fields that are NOT currently in the canvas
┌─────────────────────────┐
│ Campos detectados       │
├─────────────────────────┤
│ □ First Name            │  ← Hover to see grab cursor
│ □ Phone Number ⭐ Req.  │  ← Green badge = Required
│ □ Company               │
└─────────────────────────┘
Visual indicators:
  • Underline on hover: Field is draggable
  • Green “Requerido” badge: Field is required
  • Grab cursor (cursor: grab): Ready to drag
  • Grabbing cursor (cursor: grabbing): Currently dragging
See Sidebar.tsx:32-48 and index.css for palette styling.

2. The Canvas (Center)

Location: Main editing area showing your form layout What it shows: All fields currently in your layout, organized by steps and rows
┌─────────────────────────────┐
│ Step 1: Personal Info       │  ← Step header
├─────────────────────────────┤
│  [First Name]  [Last Name]  │  ← Row 1 (2 fields)
│  [Email]                    │  ← Row 2 (1 field)
│                             │  ← Drop zone
└─────────────────────────────┘
Visual indicators:
  • Drag handle (⋮⋮): Click and hold to drag a field
  • Drop indicators: Blue lines show where field will be placed
  • Semi-transparent: Field being dragged
  • Highlighted zones: Valid drop targets

3. The Drag Overlay

Location: Floats under your cursor while dragging What it shows: A preview of the field you’re dragging Implemented in App.tsx:252-261.

Drop Zones Explained

When dragging a field over an existing field in the canvas, you can drop in three positions:

Top Zone (20% of field height)

Action: Creates a NEW ROW above the target field
     ↓ Drop here
┌─────────────────┐
│  [New Field]    │  ← New row created
├─────────────────┤
│  [Target Field] │  ← Existing field
└─────────────────┘
Visual: Blue line appears above the target field

Center Zone (60% of field height)

Action: Combines with target field in the SAME ROW (max 3 fields per row)
┌─────────────────────────────┐
│  [Target]  [New Field]      │  ← Combined in same row
└─────────────────────────────┘
Visual: Target field gets highlighted Side positioning:
  • Drop on left half of target → inserts to the left
  • Drop on right half of target → inserts to the right
Validation: If the row already has 3 fields, the drop is rejected (useLayoutDnd.ts:343, moveFieldWithinStep:519-524)

Bottom Zone (20% of field height)

Action: Creates a NEW ROW below the target field
┌─────────────────┐
│  [Target Field] │  ← Existing field
├─────────────────┤
│  [New Field]    │  ← New row created
└─────────────────┘
     ↑ Drop here
Visual: Blue line appears below the target field

Step Drop Zone

Action: Drops field at the end of a step (creates new row)
┌─────────────────────────────┐
│ Step 1                      │
├─────────────────────────────┤
│  [Existing Field 1]         │
│  [Existing Field 2]         │
│  ← Drop anywhere in empty   │  ← Step drop zone
│     space to add at end     │
└─────────────────────────────┘
Visual: Entire step highlights when dragging over it Implemented in useLayoutDnd.ts:132-136, 240-252.

Dragging from Palette to Canvas

Adding fields from the palette is the primary way to build your layout.
1

Locate Available Fields

Scroll to the “Campos detectados” section in the left sidebar.You’ll see all fields that aren’t currently in your canvas.
2

Start Dragging

Click and hold on any field in the palette.The cursor changes to a grabbing hand, and a preview appears under your cursor.
3

Position Over Target

Drag over the canvas to your desired location.Drop zones will appear as you hover over existing fields or empty step areas.
4

Drop the Field

Release the mouse button when you see the desired drop indicator.The field is added to your layout and disappears from the palette.

Drop Options from Palette

Drop on the top or bottom zone of any existing field.
// Implementation
addFieldToNewRow(stepId, fieldId, insertAtIndex)
The field is placed in a new row at the specified position (useLayoutStore.ts:287-323).
Drop in the center zone of an existing field.
// Implementation
addFieldToRow(stepId, fieldId, rowId, insertAtIndex)
The field is added to the same row, up to a maximum of 3 fields (useLayoutStore.ts:324-364).
Drop anywhere in the empty space of a step.
// Implementation
addFieldToNewRow(stepId, fieldId) // No insertAtIndex = end of step
The field is added in a new row at the end of that step (useLayoutDnd.ts:249-251).
All palette drags are identified by the prefix palette: in their drag ID (Sidebar.tsx:34, useLayoutDnd.ts:31-33).

Reordering Fields Within Canvas

Once fields are in the canvas, you can reorganize them.

Moving Within Same Step

1

Grab the Field

Click and hold the drag handle (⋮⋮) on the field you want to move.
2

Drag to New Position

Move your cursor over another field in the same step.Drop zones appear: top, center, or bottom.
3

Drop

Release when you see the desired drop indicator.The field moves and the layout updates.
Behind the scenes:
  • Same row combination: moveFieldWithinStep() combines fields if dropped in center zone
  • New row creation: moveFieldToNewRow() creates a new row if dropped on top/bottom zones
See useLayoutDnd.ts:272-308, 475-550.

Moving Between Steps

In multi-step forms, you can move fields across steps:
1

Drag the Field

Grab a field from one step using its drag handle.
2

Drop on Different Step

Drag over a different step and drop in the step zone (empty space).The field moves to the new step in a new row at the end.
Implementation: moveFieldBetweenSteps() in useLayoutStore.ts:164-204.
Currently, you can only move fields between steps using the step drop zone, not by dropping directly on fields in other steps (useLayoutDnd.ts:156-161).

Creating Multi-Column Rows

Rows can contain up to 3 fields for compact layouts.

Adding a Second Field to a Row

1

Find a Single-Field Row

Locate a row with only one field.
2

Drag Another Field Over It

Drag a field from the palette or another row.Position your cursor over the center zone of the target field.
3

Choose Left or Right

  • Left half: New field inserts to the left
  • Right half: New field inserts to the right
4

Drop

Release the mouse.Both fields now share the same row.

Adding a Third Field to a Row

Repeat the same process to add a third field. Result: Three fields in one row, responsive and evenly spaced.

Maximum Fields Per Row

You cannot add more than 3 fields to a row:
// Validation in useLayoutStore.ts:343
if (targetRow.fields.length >= 3) {
  return step; // Drop rejected
}
If you try to drop a 4th field in the center zone, nothing happens.
For best mobile responsiveness, stick to 2 fields per row for most layouts. Use 3-field rows sparingly for compact info like city/state/zip.

Removing Fields

Remove fields to simplify your form or experiment with different layouts.
1

Find the Field

Locate the field you want to remove in the canvas.
2

Click the Delete Button

Click the × button on the field.Note: Required fields don’t have a delete button.
3

Field Returns to Palette

The field disappears from the canvas.It automatically appears in the “Campos detectados” palette.
4

Re-add If Needed

You can drag the field back from the palette anytime.
Implementation: removeFieldFromStep() in useLayoutStore.ts:238-265.
When a field is removed, its row is also deleted if it becomes empty (useLayoutStore.ts:246-251).

Step Management

Steps themselves can be reordered (though this is less common).

Reordering Steps

The step header acts as a drag handle:
  1. Click and hold the step header (not the title input)
  2. Drag the entire step up or down
  3. Drop when you see the desired position
Implementation: reorderSteps() in useLayoutStore.ts:149-163.

Deleting Steps

See the Building Forms Guide for details.

Visual Feedback and Indicators

Cursor States

StateCursorMeaning
Hovering over palette fieldgrabField is draggable
Dragging fieldgrabbingCurrently dragging
Over valid drop zonedefaultCan drop here
Over invalid targetnot-allowedCannot drop

Drop Position Classes

The canvas applies CSS classes to show drop positions:
.drop-before::before {
  /* Blue line above field */
  border-top: 2px solid #3b82f6;
}

.drop-after::after {
  /* Blue line below field */
  border-bottom: 2px solid #3b82f6;
}

.drop-inside {
  /* Highlight entire field */
  background-color: rgba(59, 130, 246, 0.1);
}
See LayoutBuilder.tsx:157 and index.css for styling.

Drag Overlay

While dragging, a semi-transparent preview follows your cursor showing:
  • Field label
  • Required badge (if applicable)
This provides visual confirmation of what you’re dragging.

Keyboard and Mouse Interactions

Mouse Actions

ActionResult
Click drag handle (⋮⋮) + holdStart dragging field
Drag over field (top 20%)Show “drop above” indicator
Drag over field (center 60%)Show “combine” indicator
Drag over field (bottom 20%)Show “drop below” indicator
Release mouse buttonComplete the drop
Click × on fieldRemove field (non-required only)

Activation Constraint

A drag must move 8 pixels before activating:
// useLayoutDnd.ts:55-60
useSensor(PointerSensor, {
  activationConstraint: {
    distance: 8,
  },
})
This prevents accidental drags when clicking fields.

Touch Support

The drag-and-drop system supports touch devices:
  • Long press to start dragging
  • Drag with finger
  • Release to drop
Touch support may vary by device. Test thoroughly on tablets if targeting mobile users.

Advanced Techniques

Quick Field Removal and Re-addition

Scenario: You want to temporarily remove a field to try different layouts.
  1. Click × to remove the field
  2. It appears in the palette
  3. Experiment with other fields
  4. Drag the removed field back from the palette when ready
Use case: Testing whether a field works better in step 1 or step 2.

Building Row-First Layouts

Technique: Create all your rows first, then fill them.
  1. Drag one field from palette to create row 1
  2. Drag another field, drop below to create row 2
  3. Repeat for all rows
  4. Go back and combine fields into rows as needed
Advantage: Gives you a clear skeleton before optimizing spacing.

Reorganizing Entire Steps

Scenario: You want to swap step order. Option 1 - Reorder Steps:
  1. Drag step header to new position
  2. All fields move with the step
Option 2 - Move Fields:
  1. Drag each field from step 1 to step 2 drop zone
  2. Repeat until all fields are moved
  3. Delete empty step

Troubleshooting

Possible causes:
  • Target row already has 3 fields (max limit)
  • Field is already in the canvas (no duplicates allowed)
  • Trying to drop on field in different step (must use step drop zone)
Solution: Check the target row’s field count and try a different drop zone.
Possible causes:
  • Not clicking the drag handle (⋮⋮) on canvas fields
  • Not holding click long enough (8px activation distance)
  • JavaScript error in console
Solution: Check browser console (F12) for errors. Ensure you’re clicking and holding on draggable elements.
Cause: State synchronization bug in collectFieldNames().Solution: Refresh the page. This shouldn’t happen but if it does, report the issue.
Possible causes:
  • CSS not loaded properly
  • DragOver handler not firing
Solution: Check that index.css is loaded and useLayoutDnd.ts:105-208 is working.

Performance Considerations

Large Forms

For forms with 20+ fields:
  • Palette rendering is efficient (no virtualization needed)
  • Canvas updates are optimized via Zustand
  • Drag operations are smooth due to @dnd-kit’s performance

Reducing Layout Shifts

  • Drop indicators use ::before and ::after pseudo-elements to avoid layout reflow
  • Drag overlay uses fixed positioning
  • Field dimensions are preserved during drag

Tips for Efficient Form Building

Plan before dragging: Sketch your ideal layout on paper first. This saves time rearranging fields.
Use step drop zones: When moving fields between steps, use the empty space drop zone rather than dropping on specific fields.
Preview frequently: Switch to Preview tab often to see how your layout looks to end users.
Build mobile-first: Use 1-2 fields per row for better mobile experience, even if it means more vertical scrolling on desktop.
Group logically, not visually: Prioritize grouping related information over creating visually balanced rows.

Next Steps

Building Forms

Learn the complete form building workflow

Exporting Modules

Export your layout as a HubSpot module

Build docs developers (and LLMs) love