FormField (Pembungkus Bidang Isian)
Komponen FormField (.mjl-form-field) adalah fondasi utama seluruh formulir pelayanan publik di Kabupaten Majalengka. Komponen ini memastikan setiap kolom masukan memiliki keterkaitan aksesibel yang benar antara label, petunjuk, dan pesan error sesuai standar WCAG 2.1 Level AA.
1. Pratinjau FormField Lengkap
Pastikan 16 digit NIK terdaftar di Disdukcapil Majalengka.
Nomor telepon terlalu pendek. Masukkan minimal 10 digit angka.
2. Struktur Anatomi Komponen
- Label (
.mjl-label): Menjelaskan informasi apa yang harus diisi. Wajib memiliki atributfor="id-input". - Tanda Wajib (
.mjl-label__required): Bintang merah*dengan atributaria-hidden="true"(pembaca layar cukup membaca teks utama). - Input (
.mjl-input): Kolom masukan tempat pengguna mengetik data. - Hint Text (
.mjl-help-text): Teks penjelasan pembantu yang dihubungkan dengan atributaria-describedby. - Error Message (
.mjl-error): Pesan kesalahan yang jelas, solutif, dan menggunakanrole="alert"sehingga langsung dibacakan screen reader saat validasi gagal.
3. Integrasi Vue 3 (@majalengka/vue)
Komponen <MjlFormField> secara otomatis mengaitkan atribut id, for, aria-describedby, dan aria-invalid:
<script setup lang="ts">
import { MjlFormField, MjlInput } from '@majalengka/vue';
import { ref } from 'vue';
const nik = ref('');
const nikError = ref('');
function validateNik() {
if (nik.value.length !== 16) {
nikError.value = 'NIK harus tepat berjumlah 16 digit angka.';
} else {
nikError.value = '';
}
}
</script>
<template>
<MjlFormField
label="Nomor Induk Kependudukan (NIK)"
required
hint="Contoh: 3210123456780001"
:error="nikError"
>
<MjlInput
v-model="nik"
placeholder="Masukkan 16 digit NIK"
maxlength="16"
@blur="validateNik"
/>
</MjlFormField>
</template>
Penggunaan di Berbagai Teknologi
Salin contoh sesuai teknologi proyek Anda. Semua versi memakai class dari @majalengka/css. Komponen siap pakai tersedia di @majalengka/vue; untuk React, Angular, Laravel, dan PHP, contoh di bawah sudah berupa pembungkus kecil yang bisa langsung disalin ke proyek.
<div class="mjl-form-field">
<label class="mjl-label" for="nik">
NIK <span class="mjl-label__required" aria-hidden="true">*</span>
</label>
<input id="nik" name="nik" class="mjl-input mjl-input--error" inputmode="numeric" required
aria-invalid="true" aria-describedby="nik-error">
<p id="nik-error" class="mjl-error-text" role="alert">NIK harus 16 digit angka.</p>
</div>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { MjlFormField, MjlInput } from '@majalengka/vue';
const nik = ref('');
const error = computed(() => (nik.value && !/^\d{16}$/.test(nik.value) ? 'NIK harus 16 digit angka.' : ''));
</script>
<template>
<MjlFormField id="nik" label="NIK" required help-text="Sesuai KTP elektronik." :error-text="error">
<template #default="{ id, hasError, describedBy }">
<MjlInput :id="id" v-model="nik" inputmode="numeric" :has-error="hasError" :described-by="describedBy" />
</template>
</MjlFormField>
</template>
import { useId, useState } from 'react';
export function FieldNik() {
const id = useId();
const [nik, setNik] = useState('');
const error = nik && !/^\d{16}$/.test(nik) ? 'NIK harus 16 digit angka.' : '';
return (
<div className="mjl-form-field">
<label className="mjl-label" htmlFor={id}>
NIK <span className="mjl-label__required" aria-hidden="true">*</span>
</label>
<input id={id} className={`mjl-input ${error ? 'mjl-input--error' : ''}`} inputMode="numeric" required
value={nik} onChange={(e) => setNik(e.target.value)}
aria-invalid={error ? true : undefined} aria-describedby={error ? `${id}-error` : `${id}-help`} />
{error
? <p id={`${id}-error`} className="mjl-error-text" role="alert">{error}</p>
: <p id={`${id}-help`} className="mjl-help-text">Sesuai KTP elektronik.</p>}
</div>
);
}
import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
@Component({
selector: 'app-field-nik',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<div class="mjl-form-field">
<label class="mjl-label" for="nik">NIK <span class="mjl-label__required" aria-hidden="true">*</span></label>
<input id="nik" class="mjl-input" inputmode="numeric" [formControl]="nik"
[class.mjl-input--error]="invalid" [attr.aria-invalid]="invalid || null"
[attr.aria-describedby]="invalid ? 'nik-error' : 'nik-help'" />
@if (invalid) {
<p id="nik-error" class="mjl-error-text" role="alert">NIK harus 16 digit angka.</p>
} @else {
<p id="nik-help" class="mjl-help-text">Sesuai KTP elektronik.</p>
}
</div>
`,
})
export class FieldNik {
nik = new FormControl('', [Validators.required, Validators.pattern(/^\d{16}$/)]);
get invalid() { return this.nik.invalid && this.nik.touched; }
}
{{-- resources/views/components/mjl/field.blade.php --}}
@props(['name', 'label', 'help' => null, 'required' => false])
<div class="mjl-form-field">
<label class="mjl-label" for="{{ $name }}">
{{ $label }} @if ($required)<span class="mjl-label__required" aria-hidden="true">*</span>@endif
</label>
<input id="{{ $name }}" name="{{ $name }}" value="{{ old($name) }}" @required($required)
{{ $attributes->class(['mjl-input', 'mjl-input--error' => $errors->has($name)]) }}
@error($name) aria-invalid="true" aria-describedby="{{ $name }}-error" @else aria-describedby="{{ $name }}-help" @enderror>
@error($name)
<p id="{{ $name }}-error" class="mjl-error-text" role="alert">{{ $message }}</p>
@else
@if ($help)<p id="{{ $name }}-help" class="mjl-help-text">{{ $help }}</p>@endif
@enderror
</div>
{{-- Pemakaian --}}
<x-mjl.field name="nik" label="NIK" help="Sesuai KTP elektronik." required inputmode="numeric" />
<?php
$nik = $_POST['nik'] ?? '';
$error = ($_SERVER['REQUEST_METHOD'] === 'POST' && !preg_match('/^\d{16}$/', $nik)) ? 'NIK harus 16 digit angka.' : '';
?>
<div class="mjl-form-field">
<label class="mjl-label" for="nik">NIK <span class="mjl-label__required" aria-hidden="true">*</span></label>
<input id="nik" name="nik" inputmode="numeric" required
class="mjl-input<?= $error ? ' mjl-input--error' : '' ?>"
value="<?= htmlspecialchars($nik, ENT_QUOTES, 'UTF-8') ?>"
<?= $error ? 'aria-invalid="true" aria-describedby="nik-error"' : '' ?>>
<?php if ($error): ?>
<p id="nik-error" class="mjl-error-text" role="alert"><?= $error ?></p>
<?php endif; ?>
</div>
4. Kaidah Anti-Kaku pada Pesan Error
Hindari bahasa birokratis kaku yang membingungkan warga:
| ❌ Hindari (Birokratis / Kaku) | ✅ Gunakan (Jelas & Solutif) |
|---|---|
| "Format isian parameter input tidak valid." | "Format email salah. Contoh yang benar: [email protected]" |
| "Data wajib diisi sesuai regulasi perundang-undangan." | "Mohon masukkan nama lengkap sesuai KTP." |
| "Ukuran berkas melebihi kuota ambang batas." | "Ukuran foto maksimal 2MB. Silakan kompres foto Anda terlebih dahulu." |