Kotchasan Framework Documentation

Kotchasan Framework Documentation

Form and HTML Generation

EN 05 Feb 2026 06:23

Form and HTML Generation

Kotchasan Framework provides a comprehensive set of classes for creating forms and managing HTML, from basic input fields and table management to complex layout creation.

Table of Contents

  1. Form Class - Creating Form Fields
  2. Html Class - Managing HTML Elements
  3. HtmlTable Class - Creating Tables
  4. Grid Class - Managing Grid Layout
  5. Real-world Usage
  6. Best Practices

Form Class - Creating Form Fields

Basic Input Fields

use Kotchasan\Form;

// Text input
$textInput = Form::text([
    'id' => 'username',
    'name' => 'username',
    'class' => 'g-input',
    'placeholder' => 'Enter username',
    'maxlength' => 50,
    'required' => true
]);

// Password input
$passwordInput = Form::password([
    'id' => 'password',
    'name' => 'password',
    'class' => 'g-input',
    'placeholder' => 'Enter password',
    'required' => true
]);

// Email input with validation
$emailInput = Form::email([
    'id' => 'email',
    'name' => 'email',
    'class' => 'g-input',
    'placeholder' => 'name@example.com',
    'required' => true,
    'title' => 'Please enter a valid email address'
]);

// Number input
$ageInput = Form::number([
    'id' => 'age',
    'name' => 'age',
    'class' => 'g-input',
    'min' => 1,
    'max' => 120,
    'placeholder' => 'Age'
]);

Select and Options

// Simple select
$countrySelect = Form::select([
    'id' => 'country',
    'name' => 'country',
    'class' => 'g-select',
    'options' => [
        'TH' => 'Thailand',
        'US' => 'United States',
        'JP' => 'Japan',
        'UK' => 'United Kingdom'
    ],
    'value' => 'TH' // Default value
]);

// Select with grouped options
$productSelect = Form::select([
    'id' => 'product',
    'name' => 'product',
    'class' => 'g-select',
    'options' => [
        'Electronics' => [
            'laptop' => 'Laptop',
            'smartphone' => 'Smartphone',
            'tablet' => 'Tablet'
        ],
        'Clothing' => [
            'shirt' => 'Shirt',
            'pants' => 'Pants',
            'shoes' => 'Shoes'
        ]
    ],
    'required' => true
]);

// Multi-select
$skillsSelect = Form::select([
    'id' => 'skills',
    'name' => 'skills[]',
    'class' => 'g-select',
    'multiple' => true,
    'size' => 5,
    'options' => [
        'php' => 'PHP',
        'javascript' => 'JavaScript',
        'python' => 'Python',
        'java' => 'Java',
        'css' => 'CSS/HTML'
    ]
]);

Checkbox and Radio

// Single checkbox
$agreeCheckbox = Form::checkbox([
    'id' => 'agree',
    'name' => 'agree',
    'value' => '1',
    'checked' => false
]);

// Radio group
$genderOptions = [
    'M' => 'Male',
    'F' => 'Female',
    'O' => 'Other'
];

foreach ($genderOptions as $value => $label) {
    $genderRadio = Form::radio([
        'id' => 'gender_' . $value,
        'name' => 'gender',
        'value' => $value,
        'checked' => ($value === 'M') // Set default
    ]);
}

// Checkbox group
$hobbies = [
    'reading' => 'Reading',
    'music' => 'Music',
    'sports' => 'Sports',
    'travel' => 'Travel',
    'cooking' => 'Cooking'
];

foreach ($hobbies as $value => $label) {
    $hobbyCheckbox = Form::checkbox([
        'id' => 'hobby_' . $value,
        'name' => 'hobbies[]',
        'value' => $value
    ]);
}

Special Fields

// File upload
$avatarUpload = Form::file([
    'id' => 'avatar',
    'name' => 'avatar',
    'accept' => 'image/*',
    'class' => 'g-file'
]);

// Multiple file upload
$documentsUpload = Form::file([
    'id' => 'documents',
    'name' => 'documents[]',
    'multiple' => true,
    'accept' => '.pdf,.doc,.docx,.txt'
]);

// Date input
$birthdateInput = Form::date([
    'id' => 'birthdate',
    'name' => 'birthdate',
    'class' => 'g-input',
    'max' => date('Y-m-d'), // Not later than today
    'required' => true
]);

// DateTime-local input
$appointmentInput = Form::datetime([
    'id' => 'appointment',
    'name' => 'appointment',
    'class' => 'g-input',
    'min' => date('Y-m-d\TH:i'), // Not earlier than now
    'step' => 1800 // Every 30 minutes
]);

// Color picker
$colorInput = Form::color([
    'id' => 'theme_color',
    'name' => 'theme_color',
    'value' => '#3498db'
]);

// Range slider
$volumeRange = Form::range([
    'id' => 'volume',
    'name' => 'volume',
    'min' => 0,
    'max' => 100,
    'value' => 50,
    'step' => 1
]);
*/

Currency and Number Format

// Currency input (Thai Baht)
$priceInput = Form::currency([
    'id' => 'price',
    'name' => 'price',
    'class' => 'g-input',
    'placeholder' => '0.00',
    'min' => 0,
    'step' => 0.01,
    'currency' => 'THB'
]);

// Integer input
$quantityInput = Form::integer([
    'id' => 'quantity',
    'name' => 'quantity',
    'class' => 'g-input',
    'min' => 1,
    'max' => 999,
    'value' => 1
]);

Buttons

// Submit button
$submitButton = Form::submit([
    'id' => 'submit_btn',
    'class' => 'button save large',
    'value' => 'Save Data'
]);

// Reset button
$resetButton = Form::reset([
    'id' => 'reset_btn',
    'class' => 'button cancel',
    'value' => 'Clear Data'
]);

// Custom button
$customButton = Form::button([
    'id' => 'custom_btn',
    'class' => 'button action',
    'value' => 'Execute',
    'onclick' => 'handleCustomAction()'
]);

Html Class - Managing HTML Elements

Creating Complete Forms

use Kotchasan\Html;

// Create main form
$form = Html::create('form', [
    'id' => 'user_form',
    'class' => 'setup_frm',
    'method' => 'post',
    'action' => 'index.php/demo/save',
    'autocomplete' => 'off',
    'ajax' => true,
    'token' => true
]);

// Add fieldset
$personalInfo = $form->add('fieldset', [
    'title' => 'Personal Information',
    'titleClass' => 'icon-user'
]);

// Add field groups
$nameGroup = $personalInfo->add('groups');

// First name
$nameGroup->add('label', [
    'for' => 'first_name',
    'innerHTML' => 'First Name <span class="required"></span>'
]);
$nameGroup->add('input', [
    'type' => 'text',
    'id' => 'first_name',
    'name' => 'first_name',
    'class' => 'g-input',
    'maxlength' => 50,
    'required' => true
]);

// Last name
$nameGroup->add('label', [
    'for' => 'last_name',
    'innerHTML' => 'Last Name <span class="required"></span>'
]);
$nameGroup->add('input', [
    'type' => 'text',
    'id' => 'last_name',
    'name' => 'last_name',
    'class' => 'g-input',
    'maxlength' => 50,
    'required' => true
]);

Input Groups and Row Groups

// Input groups for field grouping
$contactGroup = $personalInfo->add('inputgroups');

// Email
$emailRow = $contactGroup->add('groups');
$emailRow->add('label', [
    'for' => 'email',
    'innerHTML' => 'Email <span class="required">*</span>'
]);
$emailRow->add('input', [
    'type' => 'email',
    'id' => 'email',
    'name' => 'email',
    'class' => 'g-input',
    'required' => true,
    'pattern' => '[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$'
]);

// Phone number
$phoneRow = $contactGroup->add('groups');
$phoneRow->add('label', [
    'for' => 'phone',
    'innerHTML' => 'Phone Number'
]);
$phoneRow->add('input', [
    'type' => 'tel',
    'id' => 'phone',
    'name' => 'phone',
    'class' => 'g-input',
    'pattern' => '[0-9]{9,10}'
]);

Radio Groups and Checkbox Groups

// Radio groups
$genderGroup = $personalInfo->add('radiogroups', [
    'label' => 'Gender <span class="required">*</span>',
    'labelClass' => 'g-input',
    'name' => 'gender',
    'options' => [
        'M' => 'Male',
        'F' => 'Female',
        'O' => 'Not specified'
    ],
    'value' => 'M' // Default value
]);

// Checkbox groups
$skillsGroup = $personalInfo->add('checkboxgroups', [
    'label' => 'Skills',
    'labelClass' => 'g-input',
    'name' => 'skills[]',
    'options' => [
        'php' => 'PHP',
        'javascript' => 'JavaScript',
        'mysql' => 'MySQL',
        'css' => 'CSS/HTML',
        'python' => 'Python'
    ],
    'value' => ['php', 'mysql'] // Default selected
]);
// Menu button with dropdown
$actionMenu = $form->add('menubutton', [
    'class' => 'button action',
    'text' => 'Select Action',
    'options' => [
        [
            'text' => 'Save and Send',
            'onclick' => 'submitForm("send")'
        ],
        [
            'text' => 'Save Draft',
            'onclick' => 'submitForm("draft")'
        ],
        '-', // separator
        [
            'text' => 'Export PDF',
            'onclick' => 'exportPDF()'
        ],
        [
            'text' => 'Export Excel',
            'onclick' => 'exportExcel()'
        ]
    ]
]);

Using CKEditor

// Rich text editor
$descriptionEditor = $form->add('ckeditor', [
    'id' => 'description',
    'name' => 'description',
    'height' => 300,
    'language' => 'en',
    'toolbar' => [
        ['Bold', 'Italic', 'Underline'],
        ['NumberedList', 'BulletedList'],
        ['Link', 'Unlink'],
        ['Image', 'Table'],
        ['Source']
    ],
    'value' => '<p>Initial content</p>'
]);

JavaScript Management

// Add JavaScript to form
$form->script('
    // Basic validation
    document.getElementById("user_form").addEventListener("submit", function(e) {
        var firstName = document.getElementById("first_name").value;
        var lastName = document.getElementById("last_name").value;

        if (!firstName.trim() || !lastName.trim()) {
            alert("Please enter first and last name");
            e.preventDefault();
            return false;
        }

        // Email format validation
        var email = document.getElementById("email").value;
        var emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
        if (email && !emailPattern.test(email)) {
            alert("Invalid email format");
            e.preventDefault();
            return false;
        }

        return true;
    });
');

HtmlTable Class - Creating Tables

Basic Table Creation

use Kotchasan\HtmlTable;

// Create new table
$table = new HtmlTable([
    'id' => 'users_table',
    'class' => 'data fullwidth',
    'border' => 0
]);

// Add caption
$table->addCaption('System Users List');

// Add header
$table->addHeader([
    'ID',
    'Full Name',
    'Email',
    'Status',
    'Join Date',
    'Actions'
]);

// Add data rows
$users = [
    [
        'id' => 1,
        'name' => 'John Doe',
        'email' => 'john@example.com',
        'status' => 'active',
        'created' => '2024-01-15',
        'actions' => ['edit', 'delete']
    ],
    [
        'id' => 2,
        'name' => 'Jane Smith',
        'email' => 'jane@example.com',
        'status' => 'inactive',
        'created' => '2024-01-20',
        'actions' => ['edit', 'delete']
    ]
];

foreach ($users as $user) {
    // Create action buttons
    $actions = [];
    if (in_array('edit', $user['actions'])) {
        $actions[] = '<a href="edit.php?id=' . $user['id'] . '" class="icon-edit" title="Edit"></a>';
    }
    if (in_array('delete', $user['actions'])) {
        $actions[] = '<a href="javascript:void(0)" onclick="confirmDelete(' . $user['id'] . ')" class="icon-delete" title="Delete"></a>';
    }

    // Status badge
    $statusBadge = $user['status'] === 'active'
        ? '<span class="badge green">Active</span>'
        : '<span class="badge red">Inactive</span>';

    $table->addRow([
        $user['id'],
        $user['name'],
        $user['email'],
        $statusBadge,
        date('m/d/Y', strtotime($user['created'])),
        implode(' ', $actions)
    ]);
}

// Add footer (if needed)
$footerRow = \Kotchasan\TableRow::create();
$footerRow->addCell('Total ' . count($users) . ' records', [
    'colspan' => 6,
    'class' => 'center'
]);
$table->addFooter($footerRow);

Complex Data Table

// Sales report table
$salesTable = new HtmlTable([
    'id' => 'sales_report',
    'class' => 'data responsive fullwidth'
]);

$salesTable->addCaption('Monthly Sales Report');

// Multi-level header
$salesTable->addHeader([
    ['text' => 'Month', 'rowspan' => 2],
    ['text' => 'Sales', 'colspan' => 3],
    ['text' => 'Comparison', 'rowspan' => 2]
]);

$salesTable->addHeader([
    'Target',
    'Actual',
    'Percentage'
]);

// Sales data
$salesData = [
    ['January', 100000, 95000, 95.0, '+5%'],
    ['February', 120000, 135000, 112.5, '+12%'],
    ['March', 110000, 108000, 98.2, '-2%']
];

foreach ($salesData as $data) {
    $row = [];
    $row[] = $data[0]; // Month
    $row[] = number_format($data[1]); // Target
    $row[] = number_format($data[2]); // Actual

    // Percentage with color based on performance
    $percentage = $data[3];
    $percentageClass = $percentage >= 100 ? 'text-success' : 'text-warning';
    $row[] = '<span class="' . $percentageClass . '">' . $percentage . '%</span>';

    // Comparison
    $comparison = $data[4];
    $comparisonClass = str_starts_with($comparison, '+') ? 'text-success' : 'text-danger';
    $row[] = '<span class="' . $comparisonClass . '">' . $comparison . '</span>';

    $salesTable->addRow($row);
}

// Add summary row
$totalRow = \Kotchasan\TableRow::create(['class' => 'summary']);
$totalRow->addCell('Total', ['class' => 'bold']);
$totalRow->addCell(number_format(330000), ['class' => 'bold']);
$totalRow->addCell(number_format(338000), ['class' => 'bold']);
$totalRow->addCell('<span class="text-success bold">102.4%</span>');
$totalRow->addCell('<span class="text-success bold">+2.4%</span>');
$salesTable->addFooter($totalRow);

Grid Class - Managing Grid Layout

Using Grid System

use Kotchasan\Grid;

// Create grid container
$grid = new Grid();
$grid->setCols(12); // 12-column system

// Add content to grid
$content = '
<div class="ggrid">
    <div class="block4 tablet6 mobile12">
        <div class="card">
            <h3>User Statistics</h3>
            <div class="stat-number">1,234</div>
            <div class="stat-text">Total Users</div>
        </div>
    </div>

    <div class="block4 tablet6 mobile12">
        <div class="card">
            <h3>Today\'s Sales</h3>
            <div class="stat-number">$45,680</div>
            <div class="stat-text">Up 12%</div>
        </div>
    </div>

    <div class="block4 tablet12 mobile12">
        <div class="card">
            <h3>New Orders</h3>
            <div class="stat-number">28</div>
            <div class="stat-text">Pending Confirmation</div>
        </div>
    </div>
</div>';

// Responsive grid layout
$responsiveLayout = '
<div class="ggrid">
    <!-- Main content -->
    <div class="block8 tablet12 mobile12">
        <div class="content-area">
            <h2>Main Content</h2>
            <p>Website content goes here...</p>
        </div>
    </div>

    <!-- Sidebar -->
    <div class="block4 tablet12 mobile12">
        <div class="sidebar">
            <h3>Sidebar Menu</h3>
            <ul>
                <li><a href="#section1">Topic 1</a></li>
                <li><a href="#section2">Topic 2</a></li>
                <li><a href="#section3">Topic 3</a></li>
            </ul>
        </div>
    </div>
</div>';

Real-world Usage

User Registration Form

use Kotchasan\Html;
use Kotchasan\Form;

class RegistrationForm
{
    public static function create()
    {
        // Create main form
        $form = Html::create('form', [
            'id' => 'registration_form',
            'class' => 'setup_frm',
            'method' => 'post',
            'action' => 'index.php/register/save',
            'autocomplete' => 'off',
            'ajax' => true,
            'token' => true
        ]);

        // Personal information
        $personalSection = $form->add('fieldset', [
            'title' => 'Personal Information',
            'titleClass' => 'icon-user'
        ]);

        // Name fields
        $nameGroup = $personalSection->add('groups');
        $nameGroup->add('label', [
            'for' => 'first_name',
            'innerHTML' => 'First Name <span class="required"></span>'
        ]);
        $nameGroup->add('input', [
            'type' => 'text',
            'id' => 'first_name',
            'name' => 'first_name',
            'class' => 'g-input',
            'maxlength' => 50,
            'required' => true,
            'autofocus' => true
        ]);

        $nameGroup->add('label', [
            'for' => 'last_name',
            'innerHTML' => 'Last Name <span class="required"></span>'
        ]);
        $nameGroup->add('input', [
            'type' => 'text',
            'id' => 'last_name',
            'name' => 'last_name',
            'class' => 'g-input',
            'maxlength' => 50,
            'required' => true
        ]);

        // Email and password
        $credentialGroup = $personalSection->add('groups');
        $credentialGroup->add('label', [
            'for' => 'email',
            'innerHTML' => 'Email <span class="required"></span>'
        ]);
        $credentialGroup->add('input', [
            'type' => 'email',
            'id' => 'email',
            'name' => 'email',
            'class' => 'g-input',
            'required' => true
        ]);

        $credentialGroup->add('label', [
            'for' => 'password',
            'innerHTML' => 'Password <span class="required"></span>'
        ]);
        $credentialGroup->add('input', [
            'type' => 'password',
            'id' => 'password',
            'name' => 'password',
            'class' => 'g-input',
            'minlength' => 8,
            'required' => true
        ]);

        // Gender
        $genderGroup = $personalSection->add('radiogroups', [
            'label' => 'Gender <span class="required"></span>',
            'labelClass' => 'g-input',
            'name' => 'gender',
            'options' => [
                'M' => 'Male',
                'F' => 'Female',
                'O' => 'Not specified'
            ]
        ]);

        // Birth date
        $birthdateGroup = $personalSection->add('groups');
        $birthdateGroup->add('label', [
            'for' => 'birthdate',
            'innerHTML' => 'Birth Date'
        ]);
        $birthdateGroup->add('input', [
            'type' => 'date',
            'id' => 'birthdate',
            'name' => 'birthdate',
            'class' => 'g-input',
            'max' => date('Y-m-d')
        ]);

        // Contact information
        $contactSection = $form->add('fieldset', [
            'title' => 'Contact Information',
            'titleClass' => 'icon-phone'
        ]);

        // Phone
        $phoneGroup = $contactSection->add('groups');
        $phoneGroup->add('label', [
            'for' => 'phone',
            'innerHTML' => 'Phone Number'
        ]);
        $phoneGroup->add('input', [
            'type' => 'tel',
            'id' => 'phone',
            'name' => 'phone',
            'class' => 'g-input',
            'pattern' => '[0-9]{9,10}',
            'placeholder' => '0812345678'
        ]);

        // Address
        $addressGroup = $contactSection->add('groups');
        $addressGroup->add('label', [
            'for' => 'address',
            'innerHTML' => 'Address'
        ]);
        $addressGroup->add('textarea', [
            'id' => 'address',
            'name' => 'address',
            'class' => 'g-input',
            'rows' => 3,
            'placeholder' => 'Current address'
        ]);

        // Province
        $provinceGroup = $contactSection->add('groups');
        $provinceGroup->add('label', [
            'for' => 'province',
            'innerHTML' => 'Province'
        ]);
        $provinceGroup->add('select', [
            'id' => 'province',
            'name' => 'province',
            'class' => 'g-select',
            'options' => self::getProvinceOptions()
        ]);

        // Terms and conditions
        $termsSection = $form->add('fieldset', [
            'title' => 'Terms and Conditions',
            'titleClass' => 'icon-verfied'
        ]);

        $termsGroup = $termsSection->add('groups');
        $termsGroup->add('label', [
            'for' => 'accept_terms',
            'innerHTML' => 'Accept <a href="terms.php" target="_blank">Terms and Conditions</a> <span class="required"></span>'
        ]);
        $termsGroup->add('input', [
            'type' => 'checkbox',
            'id' => 'accept_terms',
            'name' => 'accept_terms',
            'value' => '1',
            'required' => true
        ]);

        $newsletterGroup = $termsSection->add('groups');
        $newsletterGroup->add('label', [
            'for' => 'newsletter',
            'innerHTML' => 'Receive Newsletter'
        ]);
        $newsletterGroup->add('input', [
            'type' => 'checkbox',
            'id' => 'newsletter',
            'name' => 'newsletter',
            'value' => '1',
            'checked' => true
        ]);

        // Submit buttons
        $submitGroup = $form->add('groups', ['class' => 'submit']);
        $submitGroup->add('input', [
            'type' => 'submit',
            'class' => 'button save large',
            'value' => 'Register'
        ]);
        $submitGroup->add('input', [
            'type' => 'reset',
            'class' => 'button cancel',
            'value' => 'Clear'
        ]);

        // Add JavaScript validation
        $form->script('
            // Real-time validation
            document.getElementById("email").addEventListener("blur", function() {
                checkEmailAvailability(this.value);
            });

            document.getElementById("password").addEventListener("input", function() {
                checkPasswordStrength(this.value);
            });

            // Email availability check
            function checkEmailAvailability(email) {
                if (email.length > 0) {
                    // Ajax request to check email
                    fetch("index.php/register/checkEmail", {
                        method: "POST",
                        headers: {"Content-Type": "application/json"},
                        body: JSON.stringify({email: email})
                    })
                    .then(response => response.json())
                    .then(data => {
                        const emailField = document.getElementById("email");
                        if (data.available) {
                            emailField.setCustomValidity("");
                        } else {
                            emailField.setCustomValidity("This email is already taken");
                        }
                    });
                }
            }

            // Password strength check
            function checkPasswordStrength(password) {
                const strengthMeter = document.getElementById("password_strength");
                let strength = 0;
                let message = "";

                if (password.length >= 8) strength++;
                if (/[a-z]/.test(password)) strength++;
                if (/[A-Z]/.test(password)) strength++;
                if (/[0-9]/.test(password)) strength++;
                if (/[^A-Za-z0-9]/.test(password)) strength++;

                switch(strength) {
                    case 0:
                    case 1:
                        message = "Weak";
                        break;
                    case 2:
                    case 3:
                        message = "Medium";
                        break;
                    case 4:
                    case 5:
                        message = "Strong";
                        break;
                }

                if (strengthMeter) {
                    strengthMeter.innerHTML = message;
                    strengthMeter.className = "password-strength " + message.toLowerCase();
                }
            }
        ');

        return $form->render();
    }

    private static function getProvinceOptions()
    {
        // Example province list
        return [
            '' => 'Select Province',
            'Bangkok' => 'Bangkok',
            'Chiang Mai' => 'Chiang Mai',
            'Chiang Rai' => 'Chiang Rai',
            'Khon Kaen' => 'Khon Kaen',
            'Nakhon Ratchasima' => 'Nakhon Ratchasima',
            'Phuket' => 'Phuket',
            'Surat Thani' => 'Surat Thani'
        ];
    }
}

Best Practices

1. Naming and Organization

// ✅ Use meaningful names
$userRegistrationForm = Html::create('form', [
    'id' => 'user_registration_form',
    'class' => 'setup_frm user-form'
]);

// ❌ Avoid meaningless names
$form1 = Html::create('form', ['id' => 'f1']);

// ✅ Group related fields
$personalInfo = $form->add('fieldset', ['title' => 'Personal Information']);
$contactInfo = $form->add('fieldset', ['title' => 'Contact Information']);
$preferences = $form->add('fieldset', ['title' => 'Preferences']);

2. Accessibility and UX

// ✅ Use appropriate labels
$emailGroup->add('label', [
    'for' => 'email',
    'innerHTML' => 'Email <span class="required" aria-label="Required">*</span>'
]);

// ✅ Add ARIA attributes
$form->add('input', [
    'type' => 'password',
    'id' => 'password',
    'name' => 'password',
    'aria-describedby' => 'password_help',
    'aria-required' => 'true'
]);

// ✅ Helpful placeholders
$form->add('input', [
    'type' => 'tel',
    'placeholder' => '0XX-XXX-XXXX',
    'pattern' => '[0-9]{3}-[0-9]{3}-[0-9]{4}'
]);

3. Error Handling

// ✅ Client-side validation
$form->script('
    function validateForm() {
        const email = document.getElementById("email");
        const password = document.getElementById("password");

        // Custom validation
        if (!isValidEmail(email.value)) {
            showError(email, "Invalid email format");
            return false;
        }

        if (password.value.length < 8) {
            showError(password, "Password must be at least 8 characters");
            return false;
        }

        return true;
    }

    function showError(field, message) {
        field.setCustomValidity(message);
        field.reportValidity();
    }
');

// ✅ Server-side validation feedback
if (isset($errors['email'])) {
    $emailInput->setAttribute('class', 'g-input error');
    $emailInput->setAttribute('title', $errors['email']);
}

4. Performance

// ✅ Load JavaScript when needed
$form->script('
    // Load library when needed
    if (typeof DatePicker === "undefined") {
        loadScript("/js/datepicker.js", function() {
            initDatePicker("#birthdate");
        });
    }
');

// ✅ Use lazy loading for tables
$table->setAttribute('data-lazy', 'true');
$table->setAttribute('data-source', 'index.php/api/users');

5. Security

// ✅ Add CSRF token
$form = Html::create('form', [
    'token' => true, // Automatically add CSRF token
    'method' => 'post'
]);

// ✅ Escape user input
$userInput = htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');

// ✅ Validate file uploads
$fileUpload = Form::file([
    'accept' => 'image/jpeg,image/png,image/gif',
    'max-size' => '2MB'
]);

Using Kotchasan's Form, Html, HtmlTable, and Grid classes helps create forms and tables efficiently, quickly, and securely, with full support for cross-device compatibility.