Skip to content

Form Component Props & Events

Complete reference for the <form-component> custom element API.


PropTypeDefaultRequiredDescription
idstringUnique identifier for the form instance. Used for hook subscriptions.
theme'light' | 'dark''light'Color theme for the form.
specField | stringForm specification schema. Can be a JSON string or object.
valuesRecord<string, any> | string{}Initial form values. Can be a JSON string or object.
hideMapeablebooleantrueHide mapper context options in fields.
submitButtonTextstringundefinedIf set, displays a submit button with this text.

A unique identifier that distinguishes this form from others on the page. This ID is used when subscribing to form events through the window.$useFormCore hooks.

<form-component id="checkout-form" />
<form-component id="profile-form" />

Controls the visual appearance of the form:

<!-- Light theme (default) -->
<form-component id="form1" theme="light" />
<!-- Dark theme -->
<form-component id="form2" theme="dark" />

The form specification that defines the structure and fields. Can be passed as:

As a JavaScript object (Vue):

<template>
<form-component :spec="formSpec" />
</template>
<script setup>
const formSpec = {
type: 'collection',
spec: [
{ type: 'text', name: 'name', label: 'Name' }
]
};
</script>

As a JSON string (HTML):

<form-component
spec='{"type":"collection","spec":[{"type":"text","name":"name","label":"Name"}]}'
/>

Initial values to populate the form. Keys should match field names in the spec:

<template>
<form-component
:spec="spec"
:values="initialValues"
/>
</template>
<script setup>
const initialValues = {
name: 'John Doe',
preferences: {
newsletter: true
}
};
</script>

When set to true (default), hides the mapper context UI that allows mapping fields to dynamic data sources. Set to false when using forms within a flow/mapper context.

<!-- Show mapper options (for flow editor context) -->
<form-component :hide-mapeable="false" />

When provided, displays a submit button at the bottom of the form:

<form-component submit-button-text="Save Changes" />

If not provided, no submit button is rendered (useful when embedding in parent forms).


EventPayloadDescription
submit(value: unknown, meta?: GenericObject)Emitted when the form is submitted and validation passes.
change(value: unknown)Emitted whenever any field value changes.
action(action: Item, value: unknown)Emitted when a field action button is clicked.

Triggered when the form is submitted (either via submit button or programmatically):

<template>
<form-component
@submit="handleSubmit"
submit-button-text="Submit"
/>
</template>
<script setup>
function handleSubmit(data, meta) {
// data: The complete form values
console.log('Form data:', data);
// meta: Validation result from VeeValidate
if (meta?.valid) {
await saveData(data);
}
}
</script>

Payload structure:

// data - merged form values
{
name: "John Doe",
preferences: {
newsletter: true
}
}
// meta - validation result (optional)
{
valid: true,
errors: {},
results: {...}
}

Emitted on every field change, debounced at the form level:

<template>
<form-component @change="handleChange" />
</template>
<script setup>
function handleChange(data) {
// data: Current complete form values
console.log('Current values:', data);
// Use for auto-save, conditional logic, etc.
if (data.country === 'US') {
loadUSStates();
}
}
</script>

Triggered when a field’s action button is clicked:

const spec = {
type: 'collection',
spec: [
{
type: 'select',
name: 'connection',
label: 'Connection',
options: [],
action: {
label: 'Create New',
value: 'create.connection',
icon: 'plus',
},
},
],
};
<template>
<form-component :spec="spec" @action="handleAction" />
</template>
<script setup>
function handleAction(action, currentValue) {
// action.value: The action identifier
// action.label: Display label
// currentValue: Current field value
if (action.value === 'create.connection') {
const newConnection = await openConnectionDialog();
// Update form spec with new options...
}
}
</script>

import type { Field, Item } from '@ion-components/ui/types';
import type { GenericObject } from 'env';
// Props interface
interface FormComponentProps {
id: string;
theme?: 'light' | 'dark';
spec: Field | string;
values?: Record<string, any> | string;
hideMapeable?: boolean;
submitButtonText?: string;
}
// Events interface
interface FormComponentEmits {
(event: 'action', action: Item, value: unknown): void;
(event: 'submit', value: unknown, meta?: GenericObject): void;
(event: 'change', value: unknown): void;
}

<script setup lang="ts">
import { ref } from 'vue';
const formId = 'complete-form';
const spec = {
type: 'collection',
name: 'user',
spec: [
{ type: 'text', name: 'name', label: 'Full Name', required: true },
{ type: 'email', name: 'email', label: 'Email', required: true },
{
type: 'select',
name: 'role',
label: 'Role',
options: [
{ label: 'Admin', value: 'admin' },
{ label: 'User', value: 'user' },
{ label: 'Guest', value: 'guest' },
],
},
{ type: 'boolean', name: 'active', label: 'Active', default: true },
{ type: 'number', name: 'maxSessions', label: 'Max Sessions', advanced: true },
],
};
const values = ref({
name: 'Jane Doe',
role: 'user',
active: true,
});
function onSubmit(data: unknown, meta?: Record<string, any>) {
if (meta?.valid) {
console.log('Valid submission:', data);
}
}
function onChange(data: unknown) {
console.log('Form updated:', data);
}
function onAction(action: { label: string; value: string }, value: unknown) {
console.log('Action triggered:', action.value, 'with value:', value);
}
</script>
<template>
<form-component
:id="formId"
theme="dark"
:spec="spec"
:values="values"
:hide-mapeable="true"
submit-button-text="Save User"
@submit="onSubmit"
@change="onChange"
@action="onAction"
/>
</template>