Guide

Form-Edit Mode

Place, move, resize, and configure form fields directly on the document

Form-edit mode turns the viewer into a form designer. Users can drop new form fields onto pages, drag and resize existing ones, edit field properties (name, default value, options, lock rules, …), and tag fields with roles for color-coded multi-signer flows.

When form-edit mode is off, fields render normally — fillable but not movable. When it's on, every field gets selection chrome and a property sidebar appears.

Toggling the Mode

The default ViewerBar includes a form-edit toggle button (i-lucide-form-input). For a programmatic toggle, use the formEditMode prop or the exposed methods.

Via v-model

<template>
  <KViewer
    v-model:form-edit-mode="isEditing"
    :source="pdfUrl"
  />
  <button @click="isEditing = !isEditing">
    {{ isEditing ? 'Done' : 'Edit fields' }}
  </button>
</template>

<script setup lang="ts">
const isEditing = ref(false)
</script>

Via Methods

const viewer = ref()

viewer.value?.setFormEditMode(true)         // enter
viewer.value?.toggleFormEditMode()          // flip, returns the new state
const editing = viewer.value?.formEditMode  // reactive Ref<boolean>
formEditMode exposed on the viewer is a reactive Ref<boolean> — read it with .value.

Roles

Real-world form workflows usually have multiple parties touching the same document — a signer, a witness, an approver, a "fill out section A" role, etc. Form-edit mode supports this with an opaque role-tag system: each field carries an optional roleId, and the viewer paints chrome for that field in the role's color.

The viewer stays unopinionated about what a role is. The host app:

  1. Owns the role list (names, ids, palette, persistence).
  2. Hands the viewer a flat roleId → CSS color map via the roleColors prop.
  3. Drives which role new placements adopt via v-model:active-role-id.

That's the whole API surface. Call them "signers", "approvers", "departments" — the viewer doesn't care.

Defining Roles

<template>
  <!-- Your role picker UI -->
  <div class="flex gap-2">
    <button
      v-for="r in roles"
      :key="r.id"
      :style="{ borderColor: r.color }"
      :class="{ 'ring-2': activeRoleId === r.id }"
      @click="activeRoleId = activeRoleId === r.id ? null : r.id"
    >
      {{ r.name }}
    </button>
  </div>

  <KViewer
    v-model:active-role-id="activeRoleId"
    :source="pdfUrl"
    :role-colors="roleColors"
    form-edit-mode
  />
</template>

<script setup lang="ts">
const roles = ref([
  { id: 'signer',   name: 'Signer',   color: '#1677ff' },
  { id: 'approver', name: 'Approver', color: '#52c41a' },
  { id: 'witness',  name: 'Witness',  color: '#fa8c16' },
])

const activeRoleId = ref<string | null>(null)

// Flat map fed to the viewer. Recomputes when colors change so chrome
// repaints immediately.
const roleColors = computed<Record<string, string>>(() => {
  const out: Record<string, string> = {}
  for (const r of roles.value) out[r.id] = r.color
  return out
})
</script>

When activeRoleId is set, fields placed via the placement tool — or via addFormField() without an explicit roleId — inherit the active role automatically. Fields tagged with an unknown or removed role id fall back to the default blue.

Role tags are a viewer-only color hint. They don't round-trip into the exported PDF — they're meant for the editing UI, not the document.

Placing Fields in the UI

In form-edit mode the toolbar swaps drawing tools for field-placement tools (text, checkbox, radio, dropdown, signature). Pick a tool, drag a rect on the page, and the field is placed.

Radio grouping has a quality-of-life shortcut: if you have a radio currently selected when you place a new radio, the new widget joins that radio's group automatically. To start a fresh group, deselect first or change the group via the property sidebar.

For programmatic placement, see Add Fields Programmatically.

Common Patterns

Open the Document Already in Edit Mode

<KViewer :source="pdfUrl" form-edit-mode />

Toggle Edit Mode From a Custom Header

<template>
  <KViewer
    ref="viewer"
    v-model:form-edit-mode="editing"
    :source="pdfUrl"
  >
    <template #header>
      <div class="flex items-center justify-between p-2">
        <span class="font-medium">My Custom Toolbar</span>
        <UButton
          :icon="editing ? 'i-lucide-check' : 'i-lucide-pencil'"
          :label="editing ? 'Done' : 'Edit form'"
          @click="viewer?.toggleFormEditMode()"
        />
      </div>
    </template>
  </KViewer>
</template>

<script setup lang="ts">
const viewer = ref()
const editing = ref(false)
</script>

Pre-Configure a Form Programmatically

Combine setFormEditMode with addFormField to seed a document with a known field layout:

const viewer = ref()

async function setupContract() {
  viewer.value?.setFormEditMode(true)

  viewer.value?.addFormField({
    pageNumber: 1,
    fieldType: 'text',
    rect: [50, 700, 250, 720],
    fieldName: 'fullName',
    roleId: 'signer',
    required: true,
  })

  viewer.value?.addFormField({
    pageNumber: 1,
    fieldType: 'signature',
    rect: [50, 640, 250, 680],
    fieldName: 'signature',
    roleId: 'signer',
    promptText: 'Click to sign',
    lockAction: 'all',
  })
}
Copyright © 2026