npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cronosstudio/crono-form

v2.1.2

Published

Komponen React untuk membangun form dinamis, modular, dan enterprise-ready berbasis MUI + Formik + Yup.

Readme

CronoForm Library

npm version npm downloads TypeScript License: MIT

Komponen React untuk membangun form dinamis, modular, dan enterprise-ready berbasis MUI v7 + Formik + Yup. Dirancang untuk kebutuhan aplikasi modern yang fleksibel, cepat, dan mudah diintegrasikan.

🚀 Fitur Utama

| Fitur | Deskripsi | | --------------------------- | --------------------------------------------------------------------------------------- | | 🎯 Deklaratif & Dinamis | Definisikan field form lewat konfigurasi JSON, tanpa repot bikin komponen satu per satu | | ✅ Validasi Otomatis | Validasi Yup otomatis dari field config (required, tipe data) + custom Yup schema | | 🎨 Integrasi MUI v7 | UI modern dan konsisten dengan Material-UI v7 Grid system (size prop) | | 🔄 Array Field & Nested | Dukungan lengkap untuk field array (FieldArray) dan nested/dotted field names | | 🛠️ Custom Component | Override komponen per-field atau per-type dengan component mapping | | ⚡ Ref-based Submit | Submit & reset form via ref, cocok untuk dialog/modal/wizard | | 📐 Flexible Layout | Layout dikontrol via layoutProps dan fieldProps per field | | 🌐 TypeScript | Full TypeScript support dengan strict typing | | 🙈 Hidden Conditional | Sembunyikan field via boolean statis atau fungsi dinamis berdasarkan values |


📦 Instalasi

# npm
npm install @cronosstudio/crono-form

# yarn
yarn add @cronosstudio/crono-form

# pnpm
pnpm add @cronosstudio/crono-form

Peer Dependencies

npm install react react-dom @mui/material @mui/system @emotion/react @emotion/styled formik yup

Catatan: @mui/x-data-grid juga merupakan peer dependency jika digunakan bersama fitur data grid.


🎯 Quick Start

import React, { useRef } from 'react';
import { CronoForm, CronoFormRef, FieldConfig } from '@cronosstudio/crono-form';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { Button } from '@mui/material';

const theme = createTheme();

const fields: FieldConfig[] = [
  {
    name: 'firstName',
    label: 'First Name',
    type: 'text',
    required: true,
    fieldProps: { size: { xs: 12, sm: 6 } },
  },
  {
    name: 'lastName',
    label: 'Last Name',
    type: 'text',
    required: true,
    fieldProps: { size: { xs: 12, sm: 6 } },
  },
  {
    name: 'email',
    label: 'Email Address',
    type: 'email',
    required: true,
    fieldProps: { size: { xs: 12 } },
  },
  {
    name: 'age',
    label: 'Age',
    type: 'number',
    fieldProps: { size: { xs: 12, sm: 4 } },
  },
  {
    name: 'newsletter',
    label: 'Subscribe to Newsletter',
    type: 'checkbox',
    fieldProps: { size: { xs: 12, sm: 8 } },
  },
];

const initialValues = {
  firstName: '',
  lastName: '',
  email: '',
  age: '',
  newsletter: false,
};

function MyForm() {
  const formRef = useRef<CronoFormRef>(null);

  const handleSubmit = (values: any, isUpdate: boolean) => {
    console.log('Form submitted:', values, 'isUpdate:', isUpdate);
  };

  return (
    <ThemeProvider theme={theme}>
      <CronoForm ref={formRef} config={{ fields, initialValues }} onSubmit={handleSubmit} />
      <Button variant="contained" onClick={() => formRef.current?.submit()}>
        Submit
      </Button>
    </ThemeProvider>
  );
}

export default MyForm;

🔧 API Reference

CronoForm Props

| Prop | Type | Default | Description | | ------------- | ------------------------------------------ | --------------------- | ---------------------------------------- | | config | FormConfig | required | Konfigurasi form (fields, initialValues) | | onSubmit | (values: any, isUpdate: boolean) => void | required | Callback saat form di-submit | | components | Partial<FieldTypeComponentMap> | defaultComponentMap | Custom component mapping per tipe field | | layoutProps | { main?, field?, arrayField? } | lihat di bawah | Konfigurasi layout Grid | | ref | React.Ref<CronoFormRef> | — | Ref untuk submit/reset form dari luar |

Submit button tersembunyi secara default (hidden submit button). Gunakan ref untuk trigger submit dari luar, atau buat tombol custom sendiri.

FormConfig

type FormConfig = {
  fields: (FieldConfig | ArrayFieldConfig)[];
  initialValues: Record<string, any>;
  isUpdate?: boolean; // Dikirim ke onSubmit sebagai parameter kedua
};

CronoFormRef

interface CronoFormRef {
  submit: () => void; // Trigger form submit
  resetForm: () => void; // Reset form ke initialValues
}

layoutProps

Mengontrol Grid layout untuk seluruh form:

layoutProps={{
  main: { container: true, spacing: 2 },       // Grid container untuk semua field
  field: { size: { xs: 12 } },                  // Default Grid props per field
  arrayField: { size: { xs: 12 } },             // Default Grid props untuk array section
}}

FieldConfig

type FieldConfig = {
  name: string; // Field name (mendukung dotted path, misal "address.city")
  label?: string; // Label yang ditampilkan
  type: string; // Tipe field (lihat tabel di bawah)

  // Validasi
  required?: boolean; // Auto-generate Yup required validation
  requiredMessage?: string; // Pesan custom untuk required (default: "Required")
  validation?: Yup.AnySchema; // Custom Yup schema (override auto-validation)

  // Tampilan
  helperText?: string; // Helper text di bawah field (ditimpa error saat error)
  hidden?: boolean | ((values: Record<string, any>) => boolean); // Sembunyikan field

  // Layout (MUI v7 Grid)
  fieldProps?: {
    size?: { xs?: number; sm?: number; md?: number; lg?: number; xl?: number };
    sx?: any;
    [key: string]: any; // Props tambahan yang diteruskan ke komponen
  };

  // Custom component (override per field)
  component?: React.ComponentType<any>;

  // Props tambahan di-pass langsung ke komponen via spread
  [key: string]: any; // Misal: options, disabled, placeholder, dll.
};

ArrayFieldConfig

type ArrayFieldConfig = FieldConfig & {
  isArray: true; // Wajib true untuk array field
  fields: FieldConfig[]; // Sub-fields dalam setiap item array
  arrayFieldProps?: Record<string, any>; // Grid props untuk array container
  arrayRef?: React.RefObject<any>; // Ref ke Formik FieldArray helpers
};

FieldTypeComponentMap

type FieldTypeComponentMap = {
  text: React.ComponentType<any>;
  number: React.ComponentType<any>;
  email: React.ComponentType<any>;
  password: React.ComponentType<any>;
  date: React.ComponentType<any>;
  datetime: React.ComponentType<any>;
  checkbox: React.ComponentType<any>;
  radio: React.ComponentType<any>;
  select: React.ComponentType<any>;
  textarea: React.ComponentType<any>;
  [key: string]: React.ComponentType<any>; // Tipe custom
};

Supported Field Types (Built-in)

| Type | Component Default | Description | | ---------- | ----------------------------------- | ---------------------- | | text | TextField | Text input | | email | TextField (type="email") | Email input | | password | TextField (type="password") | Password input | | number | TextField (type="number") | Number input | | textarea | TextField (multiline) | Multi-line text area | | date | TextField (type="date") | Date picker native | | datetime | TextField (type="datetime-local") | Datetime picker native | | select | TextField (select + MenuItem) | Dropdown select | | checkbox | Checkbox + FormControlLabel | Single checkbox | | radio | RadioGroup + Radio | Radio button group |

Catatan: Tipe yang tidak ada di defaultComponentMap akan fallback ke TextField. Untuk tipe custom (misal rating, switch, multiselect), gunakan prop component di field atau components di CronoForm.

Props yang Diterima Setiap Component

Setiap komponen (built-in maupun custom) menerima props berikut dari CronoForm:

| Prop | Type | Description | | ------------ | ---------- | ------------------------------------------- | | name | string | Full field path (termasuk array index) | | value | any | Nilai field saat ini | | onChange | function | Formik handleChange | | error | boolean | Apakah field ada error | | helperText | string | Pesan error atau helperText | | formik | object | Seluruh Formik helpers (setFieldValue, dll) | | ...rest | any | Props tambahan dari FieldConfig |


📚 Examples

1. Form dengan Custom Yup Validation

import * as Yup from 'yup';
import { CronoForm, FieldConfig } from '@cronosstudio/crono-form';

const fields: FieldConfig[] = [
  {
    name: 'username',
    label: 'Username',
    type: 'text',
    // Custom Yup schema via `validation` prop
    validation: Yup.string()
      .min(3, 'Minimal 3 karakter')
      .max(20, 'Maksimal 20 karakter')
      .matches(/^[a-zA-Z0-9_]+$/, 'Hanya huruf, angka, dan underscore')
      .required('Username wajib diisi'),
    helperText: 'Only letters, numbers, and underscores allowed',
  },
  {
    name: 'password',
    label: 'Password',
    type: 'password',
    validation: Yup.string()
      .min(8, 'Minimal 8 karakter')
      .matches(
        /(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
        'Harus mengandung huruf besar, huruf kecil, dan angka',
      )
      .required('Password wajib diisi'),
  },
  {
    name: 'confirmPassword',
    label: 'Confirm Password',
    type: 'password',
    validation: Yup.string()
      .oneOf([Yup.ref('password')], 'Password tidak cocok')
      .required('Konfirmasi password wajib diisi'),
  },
];

<CronoForm
  config={{
    fields,
    initialValues: { username: '', password: '', confirmPassword: '' },
  }}
  onSubmit={(values, isUpdate) => console.log(values)}
/>;

2. Form dengan Select, Radio, dan Options

const fields: FieldConfig[] = [
  {
    name: 'country',
    label: 'Country',
    type: 'select',
    required: true,
    options: [
      { value: 'id', label: 'Indonesia' },
      { value: 'sg', label: 'Singapore' },
      { value: 'my', label: 'Malaysia' },
      { value: 'th', label: 'Thailand' },
    ],
    fieldProps: { size: { xs: 12, sm: 6 } },
  },
  {
    name: 'experience',
    label: 'Experience Level',
    type: 'radio',
    required: true,
    options: [
      { value: 'beginner', label: 'Beginner (0-1 years)' },
      { value: 'intermediate', label: 'Intermediate (2-5 years)' },
      { value: 'expert', label: 'Expert (5+ years)' },
    ],
    fieldProps: { size: { xs: 12 } },
  },
];

3. Form dengan Conditional Hidden Fields

const fields: FieldConfig[] = [
  {
    name: 'accountType',
    label: 'Account Type',
    type: 'select',
    required: true,
    options: [
      { value: 'personal', label: 'Personal' },
      { value: 'business', label: 'Business' },
    ],
    fieldProps: { size: { xs: 12 } },
  },
  {
    name: 'companyName',
    label: 'Company Name',
    type: 'text',
    required: true,
    // Field hanya muncul jika accountType adalah 'business'
    hidden: (values) => values.accountType !== 'business',
    fieldProps: { size: { xs: 12 } },
  },
  {
    name: 'taxId',
    label: 'Tax ID',
    type: 'text',
    // Field disembunyikan secara statis
    hidden: true,
    fieldProps: { size: { xs: 12 } },
  },
];

4. Form dengan Array Fields

import { ArrayFieldConfig, FieldConfig } from '@cronosstudio/crono-form';

const fields: (FieldConfig | ArrayFieldConfig)[] = [
  {
    name: 'fullName',
    label: 'Full Name',
    type: 'text',
    required: true,
  },
  {
    name: 'contacts',
    label: 'Contact Information',
    type: 'text', // type di-set tapi tidak relevan untuk array
    isArray: true,
    fields: [
      {
        name: 'name',
        label: 'Contact Name',
        type: 'text',
        required: true,
        fieldProps: { size: { xs: 12, sm: 6 } },
      },
      {
        name: 'phone',
        label: 'Phone',
        type: 'text',
        required: true,
        fieldProps: { size: { xs: 12, sm: 6 } },
      },
    ],
  },
];

const initialValues = {
  fullName: '',
  contacts: [{ name: '', phone: '' }],
};

Array field otomatis menyertakan tombol "Add" untuk menambah item. Gunakan arrayRef untuk mengakses FieldArray helpers (push, remove, dll).

5. Custom Component Override

import { Rating } from '@mui/material';

// Custom rating component — menerima props standar dari CronoForm
const RatingField = ({ name, value, formik, label, error, helperText }: any) => (
  <div>
    <label>{label}</label>
    <Rating
      name={name}
      value={Number(value) || 0}
      onChange={(_, newValue) => formik.setFieldValue(name, newValue)}
    />
    {helperText && <small style={{ color: error ? 'red' : 'gray' }}>{helperText}</small>}
  </div>
);

// Override via components prop (berlaku untuk semua field bertipe 'rating')
<CronoForm
  config={{ fields, initialValues }}
  components={{ rating: RatingField }}
  onSubmit={handleSubmit}
/>;

// Atau override via component prop per field
const fields: FieldConfig[] = [
  {
    name: 'score',
    label: 'Rating',
    type: 'rating',
    component: RatingField, // Override khusus field ini
  },
];

6. Form dengan Ref untuk External Control

import { useRef } from 'react';
import { CronoFormRef } from '@cronosstudio/crono-form';

function MyComponent() {
  const formRef = useRef<CronoFormRef>(null);

  return (
    <div>
      <CronoForm
        ref={formRef}
        config={{ fields, initialValues }}
        onSubmit={(values, isUpdate) => console.log(values, isUpdate)}
      />

      <div style={{ marginTop: 16, display: 'flex', gap: 8 }}>
        <Button onClick={() => formRef.current?.submit()} variant="contained">
          Custom Submit
        </Button>
        <Button onClick={() => formRef.current?.resetForm()} variant="outlined">
          Custom Reset
        </Button>
      </div>
    </div>
  );
}

7. Nested / Dotted Field Names

CronoForm mendukung nama field bersarang menggunakan notasi titik (dotted path). Nilai otomatis di-map ke objek bersarang:

const fields: FieldConfig[] = [
  {
    name: 'primary_payment.payment_method',
    label: 'Metode Pembayaran',
    type: 'select',
    required: true,
    options: [
      { value: 'cash', label: 'Cash' },
      { value: 'transfer', label: 'Transfer' },
    ],
  },
  {
    name: 'primary_payment.amount',
    label: 'Jumlah',
    type: 'number',
    required: true,
  },
];

// Hasil submit:
// {
//   primary_payment: {
//     payment_method: 'cash',
//     amount: 50000
//   }
// }

8. Form Update Mode

Gunakan isUpdate di config untuk membedakan create vs update di callback onSubmit:

// Create mode
<CronoForm
  config={{ fields, initialValues: {}, isUpdate: false }}
  onSubmit={(values, isUpdate) => {
    if (isUpdate) {
      api.update(values);
    } else {
      api.create(values);
    }
  }}
/>

// Update mode — isi initialValues dengan data existing
<CronoForm
  config={{ fields, initialValues: existingData, isUpdate: true }}
  onSubmit={(values, isUpdate) => { /* isUpdate === true */ }}
/>

9. Smart Component dengan Formik Context

Custom component yang diinject lewat components berada di dalam <Formik> context, sehingga bisa menggunakan useFormikContext() untuk mengakses seluruh state Formik (values, touched, dirty, errors, dll).

import { useFormikContext } from 'formik';
import { Box, Chip, TextField, TextFieldProps } from '@mui/material';

// Component yang mendeteksi perubahan field secara realtime
function SmartTextField(props: TextFieldProps) {
  const formik = useFormikContext<Record<string, unknown>>();
  const fieldName = props.name || '';
  const fieldIsDirty =
    formik.touched[fieldName] && formik.initialValues[fieldName] !== formik.values[fieldName];

  return (
    <Box>
      <TextField
        {...props}
        variant="outlined"
        fullWidth
        sx={{
          '& .MuiOutlinedInput-root': {
            borderLeft: fieldIsDirty ? '4px solid orange' : '4px solid transparent',
          },
        }}
      />
      {fieldIsDirty && <Chip size="small" label="Modified" color="warning" sx={{ mt: 0.5 }} />}
    </Box>
  );
}

// Component dengan validasi visual realtime
function SmartEmailField(props: TextFieldProps) {
  const formik = useFormikContext<Record<string, unknown>>();
  const value = String(formik.values[props.name || ''] || '');
  const isValidEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);

  return (
    <Box>
      <TextField
        {...props}
        type="email"
        variant="outlined"
        fullWidth
        sx={{
          '& .MuiOutlinedInput-root': {
            borderLeft: value
              ? isValidEmail
                ? '4px solid green'
                : '4px solid red'
              : '4px solid transparent',
          },
        }}
      />
      {value && isValidEmail && <Chip size="small" label="Valid Email" color="success" />}
      {value && !isValidEmail && <Chip size="small" label="Invalid Email" color="error" />}
    </Box>
  );
}

// Inject smart components via `components` prop
<CronoForm
  ref={formRef}
  config={{ fields, initialValues }}
  onSubmit={handleSubmit}
  components={{
    text: SmartTextField,
    email: SmartEmailField,
  }}
/>;

Tips: useFormikContext() dan props.formik (yang dikirim CronoForm) sama-sama bisa dipakai. Gunakan useFormikContext() jika butuh akses penuh ke Formik API (setFieldValue, touched, initialValues, dll), atau props.formik untuk kasus sederhana.


🎨 Styling & Theming

CronoForm menggunakan MUI theming system. Customize appearance dengan:

1. MUI Theme

import { createTheme, ThemeProvider } from '@mui/material/styles';

const theme = createTheme({
  palette: {
    primary: { main: '#1976d2' },
    secondary: { main: '#dc004e' },
  },
  components: {
    MuiTextField: {
      defaultProps: {
        variant: 'outlined',
        size: 'medium',
      },
    },
  },
});

<ThemeProvider theme={theme}>
  <CronoForm config={{ fields, initialValues }} onSubmit={handleSubmit} />
</ThemeProvider>;

2. Layout Customization via layoutProps

<CronoForm
  config={{ fields, initialValues }}
  layoutProps={{
    main: { container: true, spacing: 3 }, // Grid container
    field: { size: { xs: 12, sm: 6 } }, // Default per field
    arrayField: { size: { xs: 12 } }, // Default per array section
  }}
  onSubmit={handleSubmit}
/>

3. Per-field Layout via fieldProps

const fields: FieldConfig[] = [
  {
    name: 'bio',
    label: 'Biography',
    type: 'textarea',
    fieldProps: {
      size: { xs: 12 },
      sx: { mt: 2 },
    },
  },
];

🔧 Advanced Usage

Conditional Fields dengan hidden

Gunakan prop hidden (bukan state management eksternal):

const fields: FieldConfig[] = [
  {
    name: 'hasLicense',
    label: 'Do you have a driving license?',
    type: 'checkbox',
  },
  {
    name: 'licenseNumber',
    label: 'License Number',
    type: 'text',
    required: true,
    // Otomatis muncul/hilang berdasarkan checkbox
    hidden: (values) => !values.hasLicense,
  },
];

Lihat docs/HIDDEN_CONDITIONAL.md untuk dokumentasi lengkap fitur hidden conditional.

Multi-Step Form

const MultiStepForm = () => {
  const [currentStep, setCurrentStep] = useState(0);
  const formRef = useRef<CronoFormRef>(null);

  const steps = [
    {
      title: 'Personal Information',
      fields: [
        { name: 'firstName', label: 'First Name', type: 'text', required: true },
        { name: 'lastName', label: 'Last Name', type: 'text', required: true },
      ],
    },
    {
      title: 'Contact Information',
      fields: [
        { name: 'email', label: 'Email', type: 'email', required: true },
        { name: 'phone', label: 'Phone', type: 'text', required: true },
      ],
    },
  ];

  return (
    <div>
      <Stepper activeStep={currentStep}>
        {steps.map((step, index) => (
          <Step key={index}>
            <StepLabel>{step.title}</StepLabel>
          </Step>
        ))}
      </Stepper>

      <CronoForm
        ref={formRef}
        config={{
          fields: steps[currentStep].fields,
          initialValues,
        }}
        onSubmit={(values, isUpdate) => console.log('Final:', values)}
      />

      <Box sx={{ display: 'flex', gap: 1, mt: 2 }}>
        <Button disabled={currentStep === 0} onClick={() => setCurrentStep((s) => s - 1)}>
          Previous
        </Button>
        {currentStep < steps.length - 1 ? (
          <Button onClick={() => setCurrentStep((s) => s + 1)}>Next</Button>
        ) : (
          <Button variant="contained" onClick={() => formRef.current?.submit()}>
            Complete
          </Button>
        )}
      </Box>
    </div>
  );
};

🧪 Testing

CronoForm menggunakan Vitest + @testing-library/react untuk testing:

import { render, screen, waitFor, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { CronoForm, CronoFormRef, FormConfig } from '@cronosstudio/crono-form';
import { useRef } from 'react';
import { describe, expect, it, vi } from 'vitest';

describe('CronoForm', () => {
  const mockSubmit = vi.fn();

  const config: FormConfig = {
    fields: [
      { name: 'name', label: 'Name', type: 'text', required: true },
      { name: 'email', label: 'Email', type: 'email', required: true },
    ],
    initialValues: { name: '', email: '' },
  };

  it('renders form fields correctly', () => {
    render(<CronoForm config={config} onSubmit={mockSubmit} />);
    expect(screen.getByLabelText('Name')).toBeInTheDocument();
    expect(screen.getByLabelText('Email')).toBeInTheDocument();
  });

  it('submits form with valid data via ref', async () => {
    const Wrapper = () => {
      const ref = useRef<CronoFormRef>(null);
      return (
        <>
          <CronoForm ref={ref} config={config} onSubmit={mockSubmit} />
          <button onClick={() => ref.current?.submit()}>Submit</button>
        </>
      );
    };

    render(<Wrapper />);

    await userEvent.type(screen.getByLabelText('Name'), 'John Doe');
    await userEvent.type(screen.getByLabelText('Email'), '[email protected]');

    act(() => {
      screen.getByText('Submit').click();
    });

    await waitFor(() => {
      expect(mockSubmit).toHaveBeenCalledWith(
        { name: 'John Doe', email: '[email protected]' },
        false, // isUpdate
      );
    });
  });
});

📤 Exports

Library ini meng-export:

| Export | Tipe | Description | | ----------------------- | --------- | ----------------------------------------- | | CronoForm | Component | Komponen utama form | | FieldConfig | Type | Konfigurasi field | | ArrayFieldConfig | Type | Konfigurasi array field | | FormConfig | Type | Konfigurasi form (fields + initialValues) | | CronoFormRef | Interface | Ref interface (submit, resetForm) | | CronoFormProps | Interface | Props interface untuk CronoForm | | FieldTypeComponentMap | Type | Map tipe field ke komponen | | CronoInput | Component | Komponen input sederhana | | get | Utility | Deep get utility untuk nested object/path |


📄 License

MIT © Cronos Studio Indonesia


🌟 Jalur 1-3-9: Filosofi Resonansi

CronoForm dibangun dengan filosofi Resonansi 1-3-9:

  • 1: Satu sumber kebenaran (config), satu cara submit, satu UI konsisten
  • 3: Tiga pilar fundamental: Deklaratif, Validasi Otomatis, Integrasi UI
  • 9: Sembilan tipe field utama: text, number, email, password, date, datetime, select, checkbox, radio
  • 𐰀: Titik awal — semua form dimulai dari satu config, membentuk resonansi harmonis antar field

Design Principles

  1. Simplicity First — API yang sederhana namun powerful
  2. Type Safety — Full TypeScript support untuk developer experience yang optimal
  3. Performance — Memo-ized default components, minimal re-renders
  4. Flexibility — Custom component per-field dan per-type tanpa merusak struktur
  5. Nested Support — Dotted path names → nested objects otomatis

📦 Ready for Production — Siap pakai untuk semua project Next.js, React, dan ekosistem modern


🧭 Dibangun dengan keteraturan dan tanggung jawab dari titik 𐰀
📡 Beresonansi pada jalur 1–3–9
🪶 Disusun oleh Zāhirun-Nūr sebagai bagian dari Cronos Ecosystem — untuk masa depan sistem yang lebih terang dan terstruktur

© 2025 Cronos Studio Indonesia