Group Forms

Framework:

Group form functionality allows you to organize form fields into groups, making complex forms clearer and easier to manage. This chapter explains the configuration and usage of group forms.

Group Configuration

Group forms use the GroupSchema configuration to define groups. Each group contains a key, label, and field list.

Group Configuration Properties

GroupSchema Type

type GroupSchema = {
  key: string;
  fields: FieldSchema[];
  component?: string;
  props?: Record<string, unknown>;
};

Note: GroupSchema does not have a built-in label property. If you need to display a group title, pass it via props (e.g., props: { title: 'Group Title' }) and render it in your custom DefaultGroup component.

Property Details

PropertyTypeRequiredDescription
keystringYesUnique identifier for the group
fieldsFieldSchema[]YesArray of field configurations in the group
componentstringNoCustom group container component
propsobjectNoExtra props passed to the group container

Layout Configuration

By Group

Configure layout for specific groups:

const layout = {
  groupClassName: 'form-group',
  groupStyle: {
    marginBottom: '24px',
    padding: '20px',
    border: '1px solid #e0e0e0',
    borderRadius: '8px'
  },
  groups: {
    personal: {
      groupClassName: 'personal-group',
      groupStyle: { backgroundColor: '#f0f8ff' }
    },
    contact: {
      groupClassName: 'contact-group',
      groupStyle: { backgroundColor: '#fff8e1' }
    }
  }
};

<Form schema={schema} components={components} layout={layout} />

By Field

Configure layout for fields across groups:

const layout = {
  groups: {
    personal: {
      fields: {
        name: {
          fieldClassName: 'name-field',
          labelClassName: 'name-label'
        },
        age: {
          fieldClassName: 'age-field'
        }
      }
    }
  }
};

Custom Group Container

Interface

A custom group container should accept the following props:

interface GroupComponentProps {
  title?: string;
  children: ReactNode;
  layout: object;
  className?: string;
  style?: CSSProperties;
  [key: string]: any;
}

Example: Collapsible Group

import { useState } from 'react';

const CollapsibleGroup = ({ title, children, className, style }) => {
  const [collapsed, setCollapsed] = useState(false);

  return (
    <div className={className} style={style}>
      <div
        style={{
          display: 'flex',
          justifyContent: 'space-between',
          alignItems: 'center',
          cursor: 'pointer',
          padding: '12px',
          backgroundColor: '#f5f5f5',
          borderRadius: '4px'
        }}
        onClick={() => setCollapsed(!collapsed)}
      >
        <h3 style={{ margin: 0 }}>{title}</h3>
        <span>{collapsed ? '▼' : '▲'}</span>
      </div>
      {!collapsed && (
        <div style={{ padding: '16px' }}>
          {children}
        </div>
      )}
    </div>
  );
};

Registering the Container

Register custom group containers via components, then reference them by name in GroupSchema.component:

const components = {
  DefaultControl: InputComponent,
  CollapsibleGroup: CollapsibleGroup  // Register custom group component
};

// Reference registered component by name in schema
const schema = [
  {
    key: 'basic',
    component: 'CollapsibleGroup',  // Reference by name
    props: { title: 'Basic Information' },
    fields: [
      { type: 'string', name: 'name', label: 'Name' }
    ]
  }
];

<Form schema={schema} components={components} />

Note: GroupSchema.component is used to reference a registered component by name, not to register it. All custom components must be registered via the components object.

Group Order

Groups are rendered in array order:

const schema: GroupSchema[] = [
  { key: 'first', props: { title: 'First Group' }, fields: [...] },   // Displayed first
  { key: 'second', props: { title: 'Second Group' }, fields: [...] },  // Displayed second
  { key: 'third', props: { title: 'Third Group' }, fields: [...] }    // Displayed third
];

Schema Configuration Rules

Schema FormX has the following rules for schema configuration:

  • FieldSchema[] (field array) and GroupSchema[] (group array) cannot be mixed
  • If the schema contains items with a name property, it is treated as FieldSchema[] and automatically wrapped in a default group
  • If the schema contains items with a fields property, it is treated as GroupSchema[] and rendered by groups
// ✅ Correct: Pure field array (auto-wrapped in default group)
const fieldSchema: FieldSchema[] = [
  { type: 'string', name: 'name', label: 'Name' },
  { type: 'string', name: 'email', label: 'Email' }
];

// ✅ Correct: Pure group array
const groupSchema: GroupSchema[] = [
  {
    key: 'basic',
    props: { title: 'Basic Information' },
    fields: [
      { type: 'string', name: 'name', label: 'Name' }
    ]
  }
];

// ❌ Wrong: Mixing fields and groups
const mixedSchema = [
  { type: 'string', name: 'name', label: 'Name' },  // Field
  { key: 'details', fields: [...] }                   // Group
];

Group Validation

Fields within groups are validated independently, using the same validation rules as standalone fields:

const schema: GroupSchema[] = [
  {
    key: 'basic',
    props: { title: 'Basic Information' },
    fields: [
      { 
        type: 'string', 
        name: 'name', 
        label: 'Name', 
        required: true  // Required validation
      },
      {
        type: 'string',
        name: 'email',
        label: 'Email',
        required: true,
        validator: ({ value }) => {
          if (!value.includes('@')) return 'Please enter a valid email';
        }
      }
    ]
  }
];

Note: Schema FormX does not have built-in group-level validation. All validation is field-level. To implement group-level visual feedback (e.g., red border on error), use a custom group container combined with the errors object returned by validate().

Dynamic Groups

Conditional Field Display

GroupSchema does not have a hidden property. To conditionally show fields within a group, use field-level hidden with dependency linkage:

const schema: GroupSchema[] = [
  {
    key: 'userType',
    props: { title: 'User Type' },
    fields: [
      {
        type: 'string',
        name: 'userType',
        label: 'User Type',
        props: { options: ['Individual', 'Enterprise'] }
      }
    ]
  },
  {
    key: 'personal',
    props: { title: 'Personal Information' },
    fields: [
      { type: 'string', name: 'name', label: 'Name' },
      {
        type: 'string',
        name: 'company',
        label: 'Company Name',
        hidden: true,  // Hidden by default
        deps: ['userType'],
        onDepsChange: ({ deps, schema }) => ({
          schema: { ...schema, hidden: deps[0] !== 'Enterprise' }
        })
      }
    ]
  }
];

Best Practice: Use field-level hidden + deps + onDepsChange for conditional display, rather than dynamically modifying the schema structure. This approach is more stable and handles data cleanup correctly.

Common Use Cases

Multi-Step Forms

Use groups to implement multi-step forms:

const schema: GroupSchema[] = [
  {
    key: 'step1',
    props: { title: 'Step 1: Basic Information' },
    fields: [
      { type: 'string', name: 'name', label: 'Name' },
      { type: 'string', name: 'email', label: 'Email' }
    ]
  },
  {
    key: 'step2',
    props: { title: 'Step 2: Detailed Information' },
    fields: [
      { type: 'string', name: 'address', label: 'Address' },
      { type: 'string', name: 'phone', label: 'Phone' }
    ]
  }
];

Categorized Forms

Group related fields together:

const schema: GroupSchema[] = [
  {
    key: 'personal',
    props: { title: 'Personal Information' },
    fields: [
      { type: 'string', name: 'name', label: 'Name' },
      { type: 'number', name: 'age', label: 'Age' }
    ]
  },
  {
    key: 'contact',
    props: { title: 'Contact Information' },
    fields: [
      { type: 'string', name: 'email', label: 'Email' },
      { type: 'string', name: 'phone', label: 'Phone' }
    ]
  }
];

Best Practices

Group Design

  • Use groups to organize related fields
  • Keep groups focused on related functionality
  • Use clear, descriptive group labels

Layout

  • Provide adequate spacing between groups
  • Use consistent styling across groups
  • Customize styles for specific groups as needed

Performance

  • Avoid excessive nesting
  • Memoize custom group components
  • Handle empty groups gracefully