{"id":13532308,"url":"https://github.com/kajetansw/solar-forms","last_synced_at":"2025-10-13T12:27:09.304Z","repository":{"id":36995964,"uuid":"460996610","full_name":"kajetansw/solar-forms","owner":"kajetansw","description":"Forms library for SolidJS inspired by Angular's reactive forms.","archived":false,"fork":false,"pushed_at":"2022-06-19T20:58:51.000Z","size":424,"stargazers_count":39,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-06T12:37:01.523Z","etag":null,"topics":["forms","reactive","solid","solidjs"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kajetansw.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-02-18T20:30:32.000Z","updated_at":"2024-07-14T12:15:08.000Z","dependencies_parsed_at":"2022-08-08T18:31:44.928Z","dependency_job_id":null,"html_url":"https://github.com/kajetansw/solar-forms","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/kajetansw/solar-forms","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kajetansw%2Fsolar-forms","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kajetansw%2Fsolar-forms/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kajetansw%2Fsolar-forms/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kajetansw%2Fsolar-forms/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kajetansw","download_url":"https://codeload.github.com/kajetansw/solar-forms/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kajetansw%2Fsolar-forms/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279015060,"owners_count":26085643,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-13T02:00:06.723Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["forms","reactive","solid","solidjs"],"created_at":"2024-08-01T07:01:09.926Z","updated_at":"2025-10-13T12:27:09.255Z","avatar_url":"https://github.com/kajetansw.png","language":"TypeScript","funding_links":[],"categories":["📦 Components \u0026 Libraries"],"sub_categories":["Form"],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"docs/logo.svg\" align=\"center\"/\u003e\n\u003c/div\u003e \n\n\u003ch2 align=\"center\"\u003eSolar Forms\u003c/h2\u003e\n\n\u003cp align=\"center\"\u003e\n  Forms library for \u003ca href=\"https://www.solidjs.com/\"\u003eSolidJS\u003c/a\u003e\n  inspired by \u003ca href=\"https://angular.io/\"\u003eAngular\u003c/a\u003e's reactive forms.\n\u003c/p\u003e \n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.npmjs.com/package/solar-forms\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/v/solar-forms.svg\" alt=\"npm version\" height=\"18\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/solar-forms\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/dm/solar-forms.svg\" alt=\"downloads\" height=\"18\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/kajetansw/solar-forms\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/l/solar-forms.svg\" alt=\"MIT license\" height=\"18\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n```tsx\nimport { createFormGroup, formGroup, Validators as V } from 'solar-forms';\n\nconst Registration = ({ onSubmit }: Props) =\u003e {\n  const fg = createFormGroup({\n    email: ['', { validators: [V.required] }],\n    name: '',\n    password: ['', { validators: [V.required] }],\n    acceptTerms: [false, { validators: [V.is(true)] }]\n  });\n  const [form, setForm] = fg.value;\n  const validAll = fg.validAll;\n\n  return (\n    \u003c\u003e\n      \u003cform use:formGroup={fg}\u003e\n        \u003clabel htmlFor=\"email\"\u003eEmail\u003c/label\u003e\n        \u003cinput id=\"email\" type=\"email\" formControlName=\"email\" /\u003e\n        \n        \u003clabel htmlFor=\"name\"\u003eName (optional)\u003c/label\u003e\n        \u003cinput id=\"name\" type=\"text\" formControlName=\"name\" /\u003e\n        \n        \u003clabel htmlFor=\"password\"\u003ePassword\u003c/label\u003e\n        \u003cinput id=\"password\" type=\"password\" formControlName=\"password\" /\u003e\n        \n        \u003clabel htmlFor=\"acceptTerms\"\u003eAccept terms\u003c/label\u003e\n        \u003cinput id=\"acceptTerms\" type=\"checkbox\" formControlName=\"acceptTerms\" /\u003e\n        \n        \u003cbutton type=\"submit\" disabled={!validAll()} onClick={onSubmit}\u003e\n          Submit\n        \u003c/button\u003e\n      \u003c/form\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n\n## About\n\nSolar Forms allows you to create **reactive and type-safe** state for your form controls. It lets you take over \nform controls and **access key information** like control's current value, whether it's disabled, valid, etc. \nas SolidJS signals. Form controls can also be pre-configured with validator functions, ensuring your \nform won't be marked as valid unless **all data is correct**.\n\n\u003e ### ⚠️ This library is still in very early stages of development. It is not encouraged to use it in production applications. Although we encourage you to try it out and give some feedback!\n\n## Features\n\n- Create form group as a set of related controls that you can manage.\n- Use form control properties like `value`, `disabled`, `dirty` and `touched`.\n- Use form group properties like `disabledAll`, `dirtyAll` and `touchedAll`.\n- Pre-configure form controls with built-in or custom validator functions to ensure you have all information you need before the form is submitted.\n- Check if a single form control or an entire form group is valid with `valid` and `validAll` properties.\n- Access validation errors with `errors` form control property.\n- Access all form group and form control properties as SolidJS signals.\n- Create nested form control structures.\n\n\n## Installation\n\n```shell\n# using npm\nnpm install solar-forms\n\n# using yarn\nyarn add solar-forms\n```\n\n\u003e If you encounter any issues when setting up Solar Forms, try consulting our\n\u003e [FAQ](#faq) section!\n\n# Documentation\n\n- [Online examples](#online-examples)\n- [Getting started](#getting-started)\n  * [Creating form group](#creating-form-group)\n  * [Binding our form group to the `form` element using `formGroup` directive](#binding-our-form-group-to-the-form-element-using-formgroup-directive)\n  * [Accessing form control values at any time](#accessing-form-control-values-at-any-time)\n  * [Managing `disabled` form control property](#managing-disabled-form-control-property)\n  * [Managing `disabledAll` form group property](#managing-disabledall-form-group-property)\n  * [Managing `dirty` form control property](#managing-dirty-form-control-property)\n  * [Managing `dirtyAll` form group property](#managing-dirtyall-form-group-property)\n  * [Managing `touched` form control property](#managing-touched-form-control-property)\n  * [Managing `touchedAll` form group property](#managing-touchedall-form-group-property)\n  * [Validating form controls](#validating-form-controls)\n    + [Setting up form control validators](#setting-up-form-control-validators)\n    + [Accessing the `valid` form control property](#accessing-the-valid-form-control-property)\n    + [Accessing the `validAll` form group property](#accessing-the-validall-form-group-property)\n    + [Accessing validation errors](#accessing-validation-errors)\n    + [Built-in validators](#built-in-validators)\n      * [`required` validator](#required-validator)\n      * [`min` validator](#min-validator)\n      * [`max` validator](#max-validator)\n      * [`minLength` validator](#minlength-validator)\n      * [`maxLength` validator](#maxlength-validator)\n      * [`is` validator](#is-validator)\n      * [`isAnyOf` validator](#isanyof-validator)\n      * [`email` validator](#email-validator)\n      * [`pattern` validator](#pattern-validator)\n  * [Binding form controls to different types of `\u003cinput\u003e` elements](#binding-form-controls-to-different-types-of-input-elements)\n    + [Type of \"text\"](#type-of-text)\n    + [Type of \"email\"](#type-of-email)\n    + [Type of \"password\"](#type-of-password)\n    + [Type of \"tel\"](#type-of-tel)\n    + [Type of \"url\"](#type-of-url)\n    + [Type of \"number\"](#type-of-number)\n    + [Type of \"range\"](#type-of-range)\n    + [Type of \"date\"](#type-of-date)\n    + [Type of \"datetime-local\"](#type-of-datetime-local)\n    + [Type of \"time\"](#type-of-time)\n    + [Type of \"checkbox\"](#type-of-checkbox)\n    + [Type of \"radio\"](#type-of-radio)\n  * [Binding form controls to `\u003cselect\u003e` element](#binding-form-controls-to-select-element)\n  * [Binding form controls to `\u003ctextarea\u003e` element](#binding-form-controls-to-textarea-element)\n  * [Form control errors](#form-control-errors)\n    + [Form control name does not match any key from form group](#form-control-name-does-not-match-any-key-from-form-group)\n    + [Form control type does not match the type of an input element](#form-control-type-does-not-match-the-type-of-an-input-element)\n  * [Nested form groups](#nested-form-groups)\n- [FAQ](#faq)\n  * [I'm getting `Uncaught ReferenceError: formGroup is not defined`](#im-getting-uncaught-referenceerror-formgroup-is-not-defined)\n  * [I'm getting type errors when defining `use:formGroup`, `formGroupName` and `formControlName` attributes](#im-getting-type-errors-when-defining-useformgroup-formgroupname-and-formcontrolname-attributes)\n- [Roadmap](#roadmap)\n- [Support](#support)\n- [Contribution guidelines](#contribution-guidelines)\n- [Inspirations](#inspirations)\n\n## Online examples\n\n- [Registration form example](https://stackblitz.com/edit/solid-vite-km8l2n?file=src/index.tsx)\n\n## Getting started\n\n### Creating form group\n\nOne of main elements of Solar Forms is `createFormGroup` function, that lets you\ninstantiate form group that you can use to manage your form:\n\n```tsx\nconst fg = createFormGroup({\n  // ➡️ Registering form control under `firstName` name \n  firstName: 'John',\n});\n```\n\n`createFormGroup` has a single argument and that is an object representing structure\nof your form. In the case above we define the `firstName` form control and we set\nan initial value to it.\n\nLater you'll see just how much we can expand your form group and which properties\nof it we'll be able to manage!\n\n\n### Binding our form group to the `form` element using `formGroup` directive\n\nSecond of the main concepts of Solar Forms is the `formGroup` [SolidJS directive](https://www.solidjs.com/tutorial/bindings_directives), \nthat allows you to bind your form group to your `form` HTML element:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  // 1️⃣ Form control named `firstName`\n  firstName: 'John',\n});\n\n// 2️⃣ Binding form group to the form using `use:formGroup`\nreturn (\n  \u003cform use:formGroup={fg}\u003e\n    \u003clabel htmlFor=\"firstName\"\u003eFirst name\u003c/label\u003e\n    {/* 3️⃣ Binding form control to the input element using `formControlName` property */}\n    \u003cinput id=\"firstName\" type=\"text\" formControlName=\"firstName\" /\u003e\n  \u003c/form\u003e\n);\n```\n\nUnder the hood, the `formGroup` directive sets up the entire set of [SolidJS signals](https://www.solidjs.com/tutorial/introduction_signals)\nand initiates form control properties.\n\nStill, our form group needs to know which form controls belong to which form inputs.\nThis is why we bind those together by assigning `formControlName` property to form inputs\nwith the same name, as defined for the form control.\n\n\n### Accessing form control values at any time\n\nAfter you set up your form group the way shown above, you can access your form control\nvalues using the `value` signal:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  firstName: 'John',\n});\n// ➡️ Accessing form group `value` signal\nconst [form, setForm] = fg.value;\n\nreturn (\n  \u003cform use:formGroup={fg}\u003e\n    \u003clabel htmlFor=\"firstName\"\u003eFirst name\u003c/label\u003e\n    \u003cinput id=\"firstName\" type=\"text\" formControlName=\"firstName\" /\u003e\n  \u003c/form\u003e\n);\n```\n\nThe `value` signal contains tuple of reactive value for our form group and a setter\nfunction. You'll see this pattern along the way of learning about the rest of form control properties:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  firstName: 'John',\n});\nconst [form, setForm] = fg.value;\n\n// 1️⃣ Accessing form groups's reactive value\nconst logForm = () =\u003e console.log(JSON.stringify(form()));\n\nreturn (\n  \u003c\u003e\n    \u003cform use:formGroup={fg}\u003e\n      \u003clabel htmlFor=\"firstName\"\u003eFirst name\u003c/label\u003e\n      \u003cinput id=\"firstName\" type=\"text\" formControlName=\"firstName\" /\u003e\n    \u003c/form\u003e\n\n    {/* 2️⃣ Clicking this logs \"{\"firstName\":\"John\"}\" */}\n    \u003cbutton onClick={logForm}\u003e\n      Log form value\n    \u003c/button\u003e\n  \u003c/\u003e\n);\n```\n\nA setter function allows us to set the form controls' values at will:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  firstName: 'John',\n});\nconst [form, setForm] = fg.value;\n\n// ➡️ Changing form control value\nconst changeName = () =\u003e setForm({ ...form(), firstName: 'Tom' });\n\nreturn (\n  \u003c\u003e\n    \u003cform use:formGroup={fg}\u003e\n      \u003clabel htmlFor=\"firstName\"\u003eFirst name\u003c/label\u003e\n      \u003cinput id=\"firstName\" type=\"text\" formControlName=\"firstName\" /\u003e\n    \u003c/form\u003e\n\n    \u003cbutton onClick={changeName}\u003e\n      Change first name\n    \u003c/button\u003e\n  \u003c/\u003e\n);\n```\n\nAs an argument, setter functions for form controls accept new form group value object, \nor a setter callback:\n\n```tsx\nconst fg = createFormGroup({\n  firstName: 'John',\n});\nconst [form, setForm] = fg.value;\n\n// 1️⃣ Update form value by passing value object\nconst changeName1 = () =\u003e setForm({ ...form(), firstName: 'Tom' });\n// 2️⃣ Update form value by passing setter callback\nconst changeName2 = () =\u003e setForm(f =\u003e ({ ...f, firstName: 'Tom' }));\n```\n\n\n### Managing `disabled` form control property\n\nWhen defining your form group, you can mark individual form controls as disabled by default.\nYou can do that by passing a tuple of default value and form control config, instead of \na single value:\n\n```tsx\nconst fg = createFormGroup({\n  // With this, the `firstName` form control is marked as disabled by default\n  firstName: ['John', { disabled: true }],\n});\n```\n\nAs you may have already realised, if you do not implicitly mark control as disabled, only by passing\nonly a default value, the form control is enabled by default:\n\n```tsx\nconst fg = createFormGroup({\n  // The `firstName` form control is set as enabled by default\n  firstName: 'John',\n});\n```\n\nYou can access the `disabled` state of the form controls by using a specific form group signal:\n\n```tsx\nconst fg = createFormGroup({\n  firstName: ['John', { disabled: true }],\n});\n// Accessing the reactive enabled/disabled state of form controls \n// and a setter function under `disabled` property\nconst [disabled, setDisabled] = fg.disabled;\n```\n\nUnder the hood, the `disabled` state is bound to your form elements, so that any change to form\ncontrol state is reflected in the UI:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  firstName: ['John', { disabled: true }],\n});\nconst [disabled, setDisabled] = fg.disabled;\n\nreturn (\n  \u003c\u003e\n    \u003cform use:formGroup={fg}\u003e\n      \u003clabel htmlFor=\"firstName\"\u003eFirst name\u003c/label\u003e\n      {/* ➡️ This form element is now disabled */}\n      \u003cinput id=\"firstName\" type=\"text\" formControlName=\"firstName\" /\u003e\n    \u003c/form\u003e\n  \u003c/\u003e\n);\n```\n\nAlso, any update to the `disabled` form control state is reflected in the UI as well:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  firstName: ['John', { disabled: true }],\n});\nconst [disabled, setDisabled] = fg.disabled;\n\nconst enableFirstName = () =\u003e setDisabled(d =\u003e ({ ...d, firstName: false }));\n\nreturn (\n  \u003c\u003e\n    \u003cform use:formGroup={fg}\u003e\n      \u003clabel htmlFor=\"firstName\"\u003eFirst name\u003c/label\u003e\n      {/* 1️⃣ This form element is disabled on initialization */}\n      \u003cinput id=\"firstName\" type=\"text\" formControlName=\"firstName\" /\u003e\n    \u003c/form\u003e\n\n    {/* 2️⃣ After clicking this button, the `firstName` form input becomes enabled */}\n    \u003cbutton onClick={enableFirstName}\u003e\n      Enable first name\n    \u003c/button\u003e\n  \u003c/\u003e\n);\n```\n\n\n### Managing `disabledAll` form group property\n\nIf you value information on whether your entire form is disabled (meaning, every form input\nelement is disabled), you can use the `disabledAll` form group property, which also is\na SolidJS signal:\n\n```tsx\nconst fg = createFormGroup({\n  firstName: ['John', { disabled: true }],\n  lastName: 'Smith',\n});\n// Accessing information on whether the entire form is disabled or enabled\nconst [disabledAll, setDisabledAll] = fg.disabledAll;\n```\n\nThis aggregates all `disabled` form control properties under one `boolean` value:\n\n```tsx\nconst fg = createFormGroup({\n  firstName: ['John', { disabled: true }],\n  lastName: 'Smith',\n});\nconst [disabledAll, setDisabledAll] = fg.disabledAll;\n\n// Logs `false`, because the `lastName` form control is enabled\nconsole.log(disabledAll());\n```\n\nYou can also set your entire form as enabled or disabled using the `setDisabledAll` setter function:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  firstName: ['John', { disabled: true }],\n  lastName: 'Smith',\n});\nconst [disabledAll, setDisabledAll] = fg.disabledAll;\n\n// 1️⃣ Set all form elements as disabled with this single call\nconst disableForm = () =\u003e setDisabledAll(true);\n\nreturn (\n  \u003c\u003e\n    \u003cform use:formGroup={fg}\u003e\n      \u003clabel htmlFor=\"firstName\"\u003eFirst name\u003c/label\u003e\n      \u003cinput id=\"firstName\" type=\"text\" formControlName=\"firstName\" /\u003e\n      \u003clabel htmlFor=\"lastName\"\u003eLast name\u003c/label\u003e\n      \u003cinput id=\"lastName\" type=\"text\" formControlName=\"lastName\" /\u003e\n    \u003c/form\u003e\n\n    {/* 2️⃣ After clicking this button, all form elements become disabled */}\n    \u003cbutton onClick={disableForm}\u003e\n      Disable form\n    \u003c/button\u003e\n  \u003c/\u003e\n);\n```\n\n\u003e ⚠️ As a contrary to `value` or `disabled` **form control properties**, `disabledAll` is a **form group property**,\nas it represents state of the entire form, not its individual elements.\n\nAs an argument, the setter function for `disabledAll` property accepts boolean or a setter callback:\n\n```tsx\nconst fg = createFormGroup({\n  firstName: 'John',\n});\nconst [disabledAll, setDisabledAll] = fg.disabledAll;\n\n// 1️⃣ Update `disabledAll` state by passing boolean\nconst disableForm1 = () =\u003e setDisabledAll(true);\n// 2️⃣ Update `disabledAll` state by passing setter callback\nconst disableForm2 = () =\u003e setDisabledAll(d =\u003e !d);\n```\n\n\n### Managing `dirty` form control property\n\nAfter you define your form group, it tracks whether the user has already\nchanged the form input value from UI. That's what the `dirty` form control property is for:\n\n```tsx\nconst fg = createFormGroup({\n  firstName: 'John',\n});\n// Accessing the reactive `dirty` state of form controls \n// and a setter function under the `dirty` signal\nconst [dirty, setDirty] = fg.dirty;\n```\n\nUnder the hood, every form control is marked as \"pristine\" (as an opposite to \"dirty\") on initialization , \nso all `dirty` values for form controls are `false` by default:\n\n```tsx\nconst fg = createFormGroup({\n  firstName: 'John',\n});\nconst [dirty, setDirty] = fg.dirty;\n\n// 1️⃣ Logs `false`, as user did not update the form input yet\nconsole.log(dirty().firstName);\n```\n\nWhenever the user changes the form input value from UI, the `dirty` property for the form control\nis set to `true`:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  firstName: '',\n});\nconst [dirty, setDirty] = fg.dirty;\n\nconst logDirtyForFirstName = () =\u003e console.log(dirty().firstName);\n\nreturn (\n  \u003c\u003e\n    \u003cform use:formGroup={fg}\u003e\n      \u003clabel htmlFor=\"firstName\"\u003eFirst name\u003c/label\u003e\n      {/* 1️⃣ Imagine user updating their first name from UI */}\n      \u003cinput id=\"firstName\" type=\"text\" formControlName=\"firstName\" /\u003e\n    \u003c/form\u003e\n\n    {/* 2️⃣ After the update, clicking this button logs `true` */}\n    \u003cbutton onClick={logDirtyForFirstName}\u003e\n      Log\n    \u003c/button\u003e\n  \u003c/\u003e\n);\n```\n\nYou can also mark your form controls as \"dirty\" or \"pristine\" (as an opposite to \"dirty\") \nprogrammatically as well:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  firstName: '',\n});\nconst [dirty, setDirty] = fg.dirty;\n\nconst markFirstNameAsPristine = () =\u003e setDirty(d =\u003e ({ ...d, firstName: false }));\nconst logDirtyForFirstName = () =\u003e console.log(dirty().firstName);\n\nreturn (\n  \u003c\u003e\n    \u003cform use:formGroup={fg}\u003e\n      \u003clabel htmlFor=\"firstName\"\u003eFirst name\u003c/label\u003e\n      {/* 1️⃣ Imagine user updates their first name from UI */}\n      \u003cinput id=\"firstName\" type=\"text\" formControlName=\"firstName\" /\u003e\n    \u003c/form\u003e\n\n    {/* 2️⃣ After the update, clicking this button marks the `firstName` */}\n    {/* form control as \"pristine\" again */}\n    \u003cbutton onClick={markFirstNameAsPristine}\u003e\n      Change dirty\n    \u003c/button\u003e\n    {/* 3️⃣ Clicking this button logs `false`, as we set the property to it ourselves */}\n    \u003cbutton onClick={logDirtyForFirstName}\u003e\n      Log\n    \u003c/button\u003e\n  \u003c/\u003e\n);\n```\n\n### Managing `dirtyAll` form group property\n\nSimilarly to `disabledAll`, there is a `dirtyAll` form group property aggregating all form controls'\n`dirty` properties under one `boolean` value. `dirtyAll` holds information whether the entire form\n(meaning every form control in the form) is \"dirty\":\n\n```tsx\nconst fg = createFormGroup({\n  firstName: 'John',\n  lastName: 'Smith',\n});\n// 1️⃣ Accessing information on whether the entire form is dirty\nconst [dirtyAll, setDirtyAll] = fg.dirtyAll;\n\n// 2️⃣ Logs `false`, because all form controls weren't updated from UI\nconsole.log(dirtyAll());\n```\n\nYou can also set your entire form as \"dirty\" or \"pristine\" (as an opposite to \"dirty\")\nusing the `setDirtyAll` setter function:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  firstName: 'Johm',\n  lastName: 'Smith',\n});\nconst [dirtyAll, setDirtyAll] = fg.dirtyAll;\n\n// 1️⃣ Sets all form elements as \"dirty\" with a single call\nconst changeAllToDirty = () =\u003e setDirtyAll(true);\nconst logDirtyAll = () =\u003e console.log(dirtyAll());\n\nreturn (\n  \u003c\u003e\n    \u003cform use:formGroup={fg}\u003e\n      \u003clabel htmlFor=\"firstName\"\u003eFirst name\u003c/label\u003e\n      \u003cinput id=\"firstName\" type=\"text\" formControlName=\"firstName\" /\u003e\n      \u003clabel htmlFor=\"lastName\"\u003eLast name\u003c/label\u003e\n      \u003cinput id=\"lastName\" type=\"text\" formControlName=\"lastName\" /\u003e\n    \u003c/form\u003e\n\n    {/* 2️⃣ After clicking this button, all form elements are marked as \"dirty\" */}\n    \u003cbutton onClick={changeAllToDirty}\u003e\n      Change all to dirty\n    \u003c/button\u003e\n\n    {/* 3️⃣ After the update, clicking this button logs `true` */}\n    \u003cbutton onClick={logDirtyAll}\u003e\n      Log dirtyAll\n    \u003c/button\u003e\n  \u003c/\u003e\n);\n```\n\nAs an argument, the setter function for `dirtyAll` property accepts `boolean` or a setter callback:\n\n```tsx\nconst fg = createFormGroup({\n  firstName: 'John',\n});\nconst [dirtyAll, setDirtyAll] = fg.dirtyAll;\n\n// 1️⃣ Update `dirtyAll` state by passing boolean\nconst markAllAsDirty1 = () =\u003e setDirtyAll(true);\n// 2️⃣ Update `dirtyAll` state by passing setter callback\nconst markAllAsDirty2 = () =\u003e setDirtyAll(d =\u003e !d);\n```\n\n\n### Managing `touched` form control property\n\nAfter you define your form group, it tracks whether the user has already\ntriggered a [blur event](https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event) \non the form input value. With this you can track whether the user has already been focused on\na specific form input element, or not. That's what the `touched` form control property is for:\n\n```tsx\nconst fg = createFormGroup({\n  firstName: 'John',\n});\n// 1️⃣ Accessing the reactive \"touched\" state of form controls \n// and a setter function under the `touched` signal\nconst [touched, setTouched] = fg.touched;\n```\n\nUnder the hood, every form control is marked as \"untouched\" on initialization, so\nall `touched` values for form controls are `false` by default:\n\n```tsx\nconst fg = createFormGroup({\n  firstName: 'John',\n});\nconst [touched, setTouched] = fg.touched;\n\n// 1️⃣ Logs `false`, as there hasn't been a \"blur\" event triggered yet on the `firstName` form input \nconsole.log(touched().firstName);\n```\n\nWhenever user switches from the specific form control to another (triggering the \"blur\" event), \nthe `touched` property for the form control is set to `true`:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  firstName: 'John',\n  lastName: 'John',\n});\nconst [touched, setTouched] = fg.touched;\n\nconst logTouchedForFirstName = () =\u003e console.log(touched().firstName);\n\nreturn (\n  \u003c\u003e\n    \u003cform use:formGroup={fg}\u003e\n      \u003clabel htmlFor=\"firstName\"\u003eFirst name\u003c/label\u003e\n      {/* 1️⃣ Imagine user switching from this form input... */}\n      \u003cinput id=\"firstName\" type=\"text\" formControlName=\"firstName\" /\u003e\n      \u003clabel htmlFor=\"lastName\"\u003eLast name\u003c/label\u003e\n      {/* 2️⃣ ... to this one */}\n      \u003cinput id=\"lastName\" type=\"text\" formControlName=\"lastName\" /\u003e\n    \u003c/form\u003e\n\n    {/* 3️⃣ After the \"blur\" event has been triggered, clicking this button logs `true` */}\n    \u003cbutton onClick={logTouchedForFirstName}\u003e\n      Log\n    \u003c/button\u003e\n  \u003c/\u003e\n);\n```\n\nYou can also mark your form controls as \"touched\" or \"untouched\" programmatically:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  firstName: 'John',\n  lastName: 'John',\n});\nconst [touched, setTouched] = fg.touched;\n\nconst changeTouchedForFirstName = () =\u003e setTouched(d =\u003e ({ ...d, firstName: false }));\nconst logTouchedForFirstName = () =\u003e console.log(touched().firstName);\n\nreturn (\n  \u003c\u003e\n    \u003cform use:formGroup={fg}\u003e\n      \u003clabel htmlFor=\"firstName\"\u003eFirst name\u003c/label\u003e\n      {/* 1️⃣ Imagine user switching from this form input... */}\n      \u003cinput id=\"firstName\" type=\"text\" formControlName=\"firstName\" /\u003e\n      \u003clabel htmlFor=\"lastName\"\u003eLast name\u003c/label\u003e\n      {/* 2️⃣ ... to this one */}\n      \u003cinput id=\"lastName\" type=\"text\" formControlName=\"lastName\" /\u003e\n    \u003c/form\u003e\n\n    {/* 3️⃣ After the \"blur\" event has been triggered, clicking this */}\n    {/* switches `touched` to `false` again */}\n    \u003cbutton onClick={changeTouchedForFirstName}\u003e\n      Change touched\n    \u003c/button\u003e\n    {/* 4️⃣ Clicking this button logs `false`, as we set the property to it ourselves */}\n    \u003cbutton onClick={logTouchedForFirstName}\u003e\n      Log\n    \u003c/button\u003e\n  \u003c/\u003e\n);\n```\n\n\n### Managing `touchedAll` form group property\n\nSimilarly to `disabledAll` and `dirtyAll`, there is a `touchedAll` form group property aggregating \nall form controls' `touched` properties under one `boolean` value. `touchedAll` holds information \nwhether the entire form (meaning every form control in the form) is \"touched\":\n\n```tsx\nconst fg = createFormGroup({\n  firstName: 'Johm',\n  lastName: 'Smith',\n});\n// 1️⃣ Accessing information on whether the entire form is \"touched\"\nconst [touchedAll, setTouchedAll] = fg.touchedAll;\n\n// 2️⃣ Logs `false`, because \"blur\" event wasn't triggered for all form inputs\nconsole.log(touchedAll());\n```\n\nYou can also mark your entire form as \"touched\" or \"untouched\" using the `setTouchedAll` setter \nfunction:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  firstName: 'Johm',\n  lastName: 'Smith',\n});\nconst [touchedAll, setTouchedAll] = fg.touchedAll;\n\n// 1️⃣ Sets all form elements as \"touched\" with a single call\nconst changeAllToTouched = () =\u003e setTouchedAll(true);\nconst logTouchedAll = () =\u003e console.log(touchedAll());\n\nreturn (\n  \u003c\u003e\n    \u003cform use:formGroup={fg}\u003e\n      \u003clabel htmlFor=\"firstName\"\u003eFirst name\u003c/label\u003e\n      \u003cinput id=\"firstName\" type=\"text\" formControlName=\"firstName\" /\u003e\n      \u003clabel htmlFor=\"lastName\"\u003eLast name\u003c/label\u003e\n      \u003cinput id=\"lastName\" type=\"text\" formControlName=\"lastName\" /\u003e\n    \u003c/form\u003e\n\n    {/* 2️⃣ After clicking this button, all form elements are marked as \"touched\" */}\n    \u003cbutton onClick={changeAllToTouched}\u003e\n      Change all to touched\n    \u003c/button\u003e\n\n    {/* 3️⃣ After the update, clicking this button logs `true` */}\n    \u003cbutton onClick={logTouchedAll}\u003e\n      Log touchedAll\n    \u003c/button\u003e\n  \u003c/\u003e\n);\n```\n\nAs an argument, the setter function for `touchedAll` property accepts `boolean` or a setter callback:\n\n```tsx\nconst fg = createFormGroup({\n  firstName: 'John',\n});\nconst [touchedAll, setTouchedAll] = fg.touchedAll;\n\n// 1️⃣ Update `touchedAll` state by passing boolean\nconst markAllAsDirty1 = () =\u003e setTouchedAll(true);\n// 2️⃣ Update `touchedAll` state by passing setter callback\nconst markAllAsDirty2 = () =\u003e setTouchedAll(d =\u003e !d);\n```\n\n\n### Validating form controls\n\n#### Setting up form control validators\n\nAs you've probably worked with forms before (as a developer or a user), you realised that not every \nuser input should be valid. We do not accept emails with wrong format, empty passwords, terms checkbox\nnot ticked or dates of birth that make you over 200 years old.\n\nWith Solar Forms you can take control over your user's input and define how your form controls\nshould be validated. You can do that by using [built-in](#built-in-validators) or custom validator functions:\n\n```tsx\nimport { FormControl, ValidatorFn } from 'solar-forms';\n\nconst required: ValidatorFn = (formControl: FormControl) =\u003e \n  formControl.value ? null : { required: true };\n```\n\nThis is a very simple example of how to define custom `required` validator function for your\nform controls. Here we are checking whether the value is falsy (but remember that `0` is also falsy!)\nand if it is, we return the record with validation error. If the value is valid, we return\n`null`, meaning there are no validation errors.\n\n`FormControl` and `ValidatorFn` types seem important here, so let's take a look at their definitions:\n\n```tsx\nexport interface FormControl {\n  value: string | number | boolean | Date | null;\n  disabled: boolean;\n  touched: boolean;\n  dirty: boolean;\n}\n\nexport interface ValidatorFn {\n  (control: FormControl): ValidationErrors | null;\n}\n\nexport type ValidationErrors = {\n  [key: string]: unknown;\n};\n```\n\nAs you can see, when defining a validator function, you can use various data about your\nform control that may be important to you: its current value and whether it is disabled, touched or dirty.\n\nWith validator function defined, you can pre-configure your form controls with it when creating \nyour form group:\n\n```tsx\nconst required = (formControl) =\u003e\n  formControl.value ? null : { required: true };\n\nconst fg = createFormGroup({\n  // Adding validator(s) to your form control\n  password: ['', { validators: [required] }],\n});\n```\n\nAs you see, you can add validator(s) to your form control by passing a list of validator functions \nto the config object under the `validators` key.\n\n\n#### Accessing the `valid` form control property\n\nAfter you define your form group, it tracks whether the form control values are valid or invalid.\nThat's what the `valid` form control property is for:\n\n```tsx\nconst required = (formControl) =\u003e\n  formControl.value ? null : { required: true };\n\nconst fg = createFormGroup({\n  password: ['', { validators: [required] }],\n});\n// Accessing the reactive \"valid\" state of form controls \nconst valid = fg.valid;\n```\n\nUnder the hood, every form control is marked as valid or invalid on initialization, based on\nwhether the form control value passes the all validator functions. \n\n```tsx\nconst required = (formControl) =\u003e\n  formControl.value ? null : { required: true };\n\nconst fg = createFormGroup({\n  password: ['', { validators: [required] }],\n});\nconst valid = fg.valid;\n\n// Logs `false` as the value is required and it's an empty string on initialization\nconsole.log(valid().password);\n```\n\nAfter changing form control values, (either with UI or using value setter functions), the validation \nfunctions are run again against form control values and the `valid` state is updated accordingly.\n\n```tsx\n// Component definition\n\nconst required = (formControl) =\u003e\n  formControl.value ? null : { required: true };\n\nconst fg = createFormGroup({\n  password: ['', { validators: [required] }],\n});\nconst valid = fg.valid;\n\nconst logValidForPassword = () =\u003e console.log(valid().password);\n\nreturn (\n  \u003c\u003e\n    \u003cform use:formGroup={fg}\u003e\n      \u003clabel htmlFor=\"password\"\u003ePassword\u003c/label\u003e\n      {/* 1️⃣ Imagine user typing their password */}\n      \u003cinput id=\"password\" type=\"password\" formControlName=\"password\" /\u003e\n    \u003c/form\u003e\n\n    {/* 2️⃣ After user types their password, clicking this button logs `true` */}\n    \u003cbutton onClick={logValidForPassword}\u003e\n      Log\n    \u003c/button\u003e\n  \u003c/\u003e\n);\n```\n\n\n#### Accessing the `validAll` form group property\n\n`valid` form control property also has the corresponding form group property - `validAll`.\nIt represents aggregated data on all form controls being valid or not. That means you \ncan use `validAll` accessor to check whether the whole form is valid or not:\n\n```tsx\nconst required = (formControl) =\u003e\n  formControl.value ? null : { required: true };\n\nconst fg = createFormGroup({\n  name: '',\n  password: ['', { validators: [required] }],\n});\n// 1️⃣ Accessing the reactive \"validAll\" state of form controls \nconst validAll = fg.validAll;\n\n// 2️⃣ Logs `false` as one of form control values is required and it's \n// an empty string on initialization\nconsole.log(validAll());\n```\n\nWhen all form controls are valid (all form controls' validator functions pass), `validAll` returns\n`true` as well:\n\n```tsx\n// Component definition\n\nconst required = (formControl) =\u003e\n  formControl.value ? null : { required: true };\n\nconst fg = createFormGroup({\n  name: '',\n  password: ['', { validators: [required] }],\n});\nconst validAll = fg.validAll;\n\nconst logValidAll = () =\u003e console.log(validAll());\n\nreturn (\n  \u003c\u003e\n    \u003cform use:formGroup={fg}\u003e\n      \u003clabel htmlFor=\"name\"\u003eName (optional)\u003c/label\u003e\n      \u003cinput id=\"name\" type=\"text\" formControlName=\"name\" /\u003e\n      \u003clabel htmlFor=\"password\"\u003ePassword\u003c/label\u003e\n      {/* 1️⃣ Imagine user typing their password */}\n      \u003cinput id=\"password\" type=\"password\" formControlName=\"password\" /\u003e\n    \u003c/form\u003e\n\n    {/* 2️⃣ After user types their password, clicking this button logs `true` */}\n    \u003cbutton onClick={logValidAll}\u003e\n      Log\n    \u003c/button\u003e\n  \u003c/\u003e\n);\n```\n\n\n#### Accessing validation errors\n\nAt the same time, form group tracks all validation errors at a given time with the `errors`\nform control property:\n\n```tsx\nconst required = (formControl) =\u003e\n  formControl.value ? null : { required: true };\n\nconst fg = createFormGroup({\n  password: ['', { validators: [required] }],\n});\n// Accessing the reactive \"errors\" state of form controls \nconst errors = fg.errors;\n```\n\nOn initialization and on every form value update validator functions are run against form control\nvalues and the `errors` form control property is updated accordingly:\n\n```tsx\nconst required = (formControl) =\u003e\n  formControl.value ? null : { required: true };\n\nconst fg = createFormGroup({\n  name: '',\n  password: ['', { validators: [required] }],\n});\nconst errors = fg.errors;\n\n// 1️⃣ Logs \"{\"required\":\"This is required!\"}\" as the value is required \n// and it's an empty string on initialization\nconsole.log(JSON.stringify(errors().password));\n\n// 2️⃣ Logs \"null\" as the value has no validators, so it's valid by default \nconsole.log(JSON.stringify(errors().name));\n```\n\n`error` form control property holds an object with all validation errors aggregated under one\nobject.\n\n\u003e ⚠️ Because of the fact, that `error` form control property holds an object with all validation \n\u003e errors aggregated under one record, it is advised to name keys for your validation errors object in \n\u003e a unique way when creating your custom validator functions.\n\u003e \n\u003e Doing otherwise may result in overwriting your keys inside the `ValidationErrors` record.  \n\n\n#### Built-in validators\n\nAs you've learned, form group accepts list of validators that match the\n`ValidatorFn` interface. This means you can write custom validators\nfor your own use cases.\n\nAs an alternative, you can use built-in validators provided by Solar Forms.\nHere's a brief introduction:\n\n##### `required` validator\n\nValidator that requires the control have a non-empty value \n(`null` and`''` are treated as empty values).\n\n```tsx\nimport { createFormGroup, Validators as V } from 'solar-forms';\n\nconst fg = createFormGroup({\n  firstName: ['', { validators: [V.required] }],\n  lastName: ['Smith', { validators: [V.required] }],\n});\nconst errors = fg.errors;\n\n// ➡️ Logs `{ required: true }`\nconsole.log(errors().firstName);\n// ➡️ Logs `null`\nconsole.log(errors().lastName);\n```\n\n##### `min` validator\n\nValidator that requires the control's value to be greater than \nor equal to the provided number.\n\n```tsx\nimport { createFormGroup, Validators as V } from 'solar-forms';\n\nconst fg = createFormGroup({\n  minorAge: [16, { validators: [V.min(21)] }],\n  adultAge: [30, { validators: [V.min(21)] }],\n});\nconst errors = fg.errors;\n\n// ➡️ Logs `{ min: true }`\nconsole.log(errors().minorAge);\n// ➡️ Logs `null`\nconsole.log(errors().adultAge);\n```\n\n##### `max` validator\n\nValidator that requires the control's value to be less than \nor equal to the provided number.\n\n```tsx\nimport { createFormGroup, Validators as V } from 'solar-forms';\n\nconst fg = createFormGroup({\n  invalidAmount: [30, { validators: [V.max(10)] }],\n  validAmount: [5, { validators: [V.max(10)] }],\n});\nconst errors = fg.errors;\n\n// ➡️ Logs `{ max: true }`\nconsole.log(errors().invalidAmount);\n// ➡️ Logs `null`\nconsole.log(errors().validAmount);\n```\n\n##### `minLength` validator\n\nValidator that requires the length of the control's \nstring-based value's length to be greater than or equal \nto the provided minimum length.\n\n```tsx\nimport { createFormGroup, Validators as V } from 'solar-forms';\n\nconst fg = createFormGroup({\n  invalidPassword: ['grfr', { validators: [V.minLength(8)] }],\n  validPassword: ['wdnaw#@!udnwahe3w@#$@!', { validators: [V.minLength(8)] }],\n});\nconst errors = fg.errors;\n\n// ➡️ Logs `{ minLength: true }`\nconsole.log(errors().invalidPassword);\n// ➡️ Logs `null`\nconsole.log(errors().validPassword);\n```\n\n##### `maxLength` validator\n\nValidator that requires the length of the control's \nstring-based value's length to be lower than or equal \nto the provided maximum length.\n\n```tsx\nimport { createFormGroup, Validators as V } from 'solar-forms';\n\nconst fg = createFormGroup({\n  invalidInput: ['qwertyuiopasdfg', { validators: [V.maxLength(10)] }],\n  validInput: ['qwerty', { validators: [V.maxLength(10)] }],\n});\nconst errors = fg.errors;\n\n// ➡️ Logs `{ maxLength: true }`\nconsole.log(errors().invalidInput);\n// ➡️ Logs `null`\nconsole.log(errors().validInput);\n```\n\n##### `is` validator\n\nValidator that requires the value of the form control\nto be equal to the provided value.\n\n```tsx\nimport { createFormGroup, Validators as V } from 'solar-forms';\n\nconst fg = createFormGroup({\n  invalidCaptcha: ['q1q1q1', { validators: [V.is('q1w2e3')] }],\n  validCaptcha: ['q1w2e3', { validators: [V.is('q1w2e3')] }],\n  nonAcceptedTerms: [false, { validators: [V.is(true)] }],\n  acceptedTerms: [true, { validators: [V.is(true)] }],\n});\nconst errors = fg.errors;\n\n// ➡️ Logs `{ is: true }`\nconsole.log(errors().invalidCaptcha);\n// ➡️ Logs `null`\nconsole.log(errors().validCaptcha);\n// ➡️ Logs `{ is: true }`\nconsole.log(errors().nonAcceptedTerms);\n// ➡️ Logs `null`\nconsole.log(errors().acceptedTerms);\n```\n\n##### `isAnyOf` validator\n\nValidator that requires the value of the form control\nto be equal to one of provided values.\n\n```tsx\nimport { createFormGroup, Validators as V } from 'solar-forms';\n\nconst fg = createFormGroup({\n  invalidCountry: ['Poland', { validators: [V.isAnyOf(['Spain', 'France', 'Germany'])] }],\n  validCountry: ['Spain', { validators: [V.isAnyOf(['Spain', 'France', 'Germany'])] }],\n});\nconst errors = fg.errors;\n\n// ➡️ Logs `{ isAnyOf: true }`\nconsole.log(errors().invalidCountry);\n// ➡️ Logs `null`\nconsole.log(errors().validCountry);\n```\n\n##### `email` validator\n\nValidator that requires the value of the form control\nto have a valid email format.\n\n```tsx\nimport { createFormGroup, Validators as V } from 'solar-forms';\n\nconst fg = createFormGroup({\n  invalidEmail: ['test', { validators: [V.email] }],\n  validEmail: ['test@test.com', { validators: [V.email] }],\n});\nconst errors = fg.errors;\n\n// ➡️ Logs `{ email: true }`\nconsole.log(errors().invalidEmail);\n// ➡️ Logs `null`\nconsole.log(errors().validEmail);\n```\n\n##### `pattern` validator\n\nValidator that requires the value of the form control\nto have a format matching provided regular expression.\n\n```tsx\nimport { createFormGroup, Validators as V } from 'solar-forms';\n\nconst regexp = /^([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/gm;\nconst fg = createFormGroup({\n  invalidHour: ['test', { validators: [V.pattern(regexp)] }],\n  validHour: ['12:00:00', { validators: [V.pattern(regexp)] }],\n});\nconst errors = fg.errors;\n\n// ➡️ Logs `{ pattern: { requiredPattern: ..., actualValue: 'test' } }`\nconsole.log(errors().invalidHour);\n// ➡️ Logs `null`\nconsole.log(errors().validHour);\n```\n\n\n### Binding form controls to different types of input elements\n\nThere are [a lot of possible types for an HTML input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types)\nand Solar Forms allows you to work with HTML input elements of every major type:\n\n- text\n- email\n- password\n- tel\n- url\n- number\n- range\n- date\n- datetime-local\n- time\n- checkbox\n- radio\n\n\n#### Type of `text`\n\nThis is the most basic input element - a string-based input element type. Accordingly, you can\ndefine your form control's default value as `string` or `null`:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  // 1️⃣ Default value is set here as string\n  name: '',\n});\n\nreturn (\n  \u003cform use:formGroup={fg}\u003e\n    \u003clabel htmlFor=\"name\"\u003eName\u003c/label\u003e\n    {/* 2️⃣ Here we define the `type` attribute of the input element */}\n    \u003cinput id=\"name\" type=\"text\" formControlName=\"name\" /\u003e\n  \u003c/form\u003e\n);\n```\n\n#### Type of `email`\n\nAnother string-based input element type. For the corresponding form control you can\ndefine the default value as `string` or `null`:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  // 1️⃣ Default value is set here as string\n  email: '',\n});\n\nreturn (\n  \u003cform use:formGroup={fg}\u003e\n    \u003clabel htmlFor=\"email\"\u003eEmail\u003c/label\u003e\n    {/* 2️⃣ Here we define the `type` attribute of the input element */}\n    \u003cinput id=\"email\" type=\"email\" formControlName=\"email\" /\u003e\n  \u003c/form\u003e\n);\n```\n\n#### Type of `password`\n\nAnother string-based input element type. For the corresponding form control you can\ndefine the default value as `string` or `null`:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  // 1️⃣ Default value is set here as string\n  password: '',\n});\n\nreturn (\n  \u003cform use:formGroup={fg}\u003e\n    \u003clabel htmlFor=\"password\"\u003ePassword\u003c/label\u003e\n    {/* 2️⃣ Here we define the `type` attribute of the input element */}\n    \u003cinput id=\"password\" type=\"password\" formControlName=\"password\" /\u003e\n  \u003c/form\u003e\n);\n```\n\n#### Type of `tel`\n\nAnother string-based input element type. For the corresponding form control you can\ndefine the default value as `string` or `null`:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  // 1️⃣ Default value is set here as string\n  tel: '',\n});\n\nreturn (\n  \u003cform use:formGroup={fg}\u003e\n    \u003clabel htmlFor=\"tel\"\u003eTel\u003c/label\u003e\n    {/* 2️⃣ Here we define the `type` attribute of the input element */}\n    \u003cinput id=\"tel\" type=\"tel\" formControlName=\"tel\" /\u003e\n  \u003c/form\u003e\n);\n```\n\n#### Type of `url`\n\nAnother string-based input element type. For the corresponding form control you can\ndefine the default value as `string` or `null`:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  // 1️⃣ Default value is set here as string\n  url: '',\n});\n\nreturn (\n  \u003cform use:formGroup={fg}\u003e\n    \u003clabel htmlFor=\"url\"\u003eURL\u003c/label\u003e\n    {/* 2️⃣ Here we define the `type` attribute of the input element */}\n    \u003cinput id=\"url\" type=\"url\" formControlName=\"url\" /\u003e\n  \u003c/form\u003e\n);\n```\n\n#### Type of `number`\n\nThe most basic number-based type of input element. In this case, for the corresponding form control \nyou can define the default value as `number` or `null`:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  // 1️⃣ Default value is set here as number\n  age: 0,\n});\n\nreturn (\n  \u003cform use:formGroup={fg}\u003e\n    \u003clabel htmlFor=\"age\"\u003eURL\u003c/label\u003e\n    {/* 2️⃣ Here we define the `type` attribute of the input element */}\n    \u003cinput id=\"age\" type=\"number\" formControlName=\"age\" /\u003e\n  \u003c/form\u003e\n);\n```\n\n#### Type of `range`\n\nAnother number-based type of input element. For the corresponding form control \nyou can define the default value as `number` or `null`:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  // 1️⃣ Default value is set here as number\n  skillLevel: 0,\n});\n\nreturn (\n  \u003cform use:formGroup={fg}\u003e\n    \u003clabel htmlFor=\"skillLevel\"\u003eSkill level\u003c/label\u003e\n    {/* 2️⃣ Here we define the `type` attribute of the input element */}\n    \u003cinput id=\"skillLevel\" type=\"range\" formControlName=\"skillLevel\" /\u003e\n  \u003c/form\u003e\n);\n```\n\n#### Type of `date`\n\nA date-based type of input element. In this case, for the corresponding form control\nyou can define the default value as `Date` object, `string`, `number` or `null`:\n\n\u003e ⚠️ If you wish to bind string values to the `date` form control, remember about using\n\u003e [proper date text formats](https://developer.mozilla.org/en-US/docs/Web/HTML/Date_and_time_formats#format_of_a_valid_date_string).\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  // 1️⃣ Default values are set here\n  dateDate: new Date(),\n  dateString: new Date().toISOString().split('T')[0],\n  dateNumber: new Date().getTime(),\n});\n\nreturn (\n  \u003cform use:formGroup={fg}\u003e\n    \u003clabel htmlFor=\"dateDate\"\u003eDate [Date]\u003c/label\u003e\n    {/* 2️⃣ Here we define the `type` attribute of the input element */}\n    \u003cinput id=\"dateDate\" type=\"date\" formControlName=\"dateDate\" /\u003e\n    \n    \u003clabel htmlFor=\"dateString\"\u003eDate [string]\u003c/label\u003e\n    \u003cinput id=\"dateString\" type=\"date\" formControlName=\"dateString\" /\u003e\n    \n    \u003clabel htmlFor=\"dateNumber\"\u003eDate [number]\u003c/label\u003e\n    \u003cinput id=\"dateNumber\" type=\"date\" formControlName=\"dateNumber\" /\u003e\n  \u003c/form\u003e\n);\n```\n\n#### Type of `datetime-local`\n\nA date-based type of input element. In this case, for the corresponding form control\nyou can define the default value as `string`, `number` or `null`:\n\n\u003e ⚠️ If you wish to bind `string` values to the `datetime-local` form control, remember about using\n\u003e [proper date text formats](https://developer.mozilla.org/en-US/docs/Web/HTML/Date_and_time_formats#format_of_a_valid_date_string).\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  // 1️⃣ Default values are set here\n  dateString: new Date().toISOString().split('.')[0],\n  dateNumber: new Date().getTime(),\n});\n\nreturn (\n  \u003cform use:formGroup={fg}\u003e\n    \u003clabel htmlFor=\"dateString\"\u003eDate [string]\u003c/label\u003e\n    {/* 2️⃣ Here we define the `type` attribute of the input element */}\n    \u003cinput id=\"dateString\" type=\"datetime-local\" formControlName=\"dateString\" /\u003e\n    \n    \u003clabel htmlFor=\"dateNumber\"\u003eDate [number]\u003c/label\u003e\n    \u003cinput id=\"dateNumber\" type=\"datetime-local\" formControlName=\"dateNumber\" /\u003e\n  \u003c/form\u003e\n);\n```\n\n#### Type of `time`\n\nA time-based type of input element. In this case, for the corresponding form control\nyou can define the default value as `Date`, `string`, `number` or `null`:\n\n\u003e ⚠️ If you wish to bind `string` values to the `time` form control, remember about using\n\u003e [proper time text formats](https://developer.mozilla.org/en-US/docs/Web/HTML/Date_and_time_formats#time_strings).\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  // 1️⃣ Default values are set here\n  timeDate: new Date(),\n  timeString: '00:00:00',\n  timeNumber: new Date().getTime(),\n});\n\nreturn (\n  \u003cform use:formGroup={fg}\u003e\n    \u003clabel htmlFor=\"timeDate\"\u003eDate [Date]\u003c/label\u003e\n    {/* 2️⃣ Here we define the `type` attribute of the input element */}\n    \u003cinput id=\"timeDate\" type=\"time\" formControlName=\"timeDate\" /\u003e\n    \n    \u003clabel htmlFor=\"timeString\"\u003eDate [string]\u003c/label\u003e\n    \u003cinput id=\"timeString\" type=\"time\" formControlName=\"timeString\" /\u003e\n    \n    \u003clabel htmlFor=\"timeNumber\"\u003eDate [number]\u003c/label\u003e\n    \u003cinput id=\"timeNumber\" type=\"time\" formControlName=\"timeNumber\" /\u003e\n  \u003c/form\u003e\n);\n```\n\n#### Type of `checkbox`\n\nA boolean-based type of input element. For the corresponding form control\nyou can define the default value as `boolean` or `null`:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  // 1️⃣ Default values are set here\n  acceptTerms: false,\n});\n\nreturn (\n  \u003cform use:formGroup={fg}\u003e\n    \u003clabel htmlFor=\"acceptTerms\"\u003eDate [Date]\u003c/label\u003e\n    {/* 2️⃣ Here we define the `type` attribute of the input element */}\n    \u003cinput id=\"acceptTerms\" type=\"checkbox\" formControlName=\"acceptTerms\" /\u003e\n  \u003c/form\u003e\n);\n```\n\n#### Type of `radio`\n\nA string-based type of input element, where you can choose one of pre-defined set of options. \nFor the corresponding form control you can define the default value as `string` or `null`:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  // 1️⃣ Default value is set here\n  // 2️⃣ Imagine you can have 3 options here: \"engineering\", \"testing\" and \"product\"\n  team: null,\n});\n\nreturn (\n  \u003cform use:formGroup={fg}\u003e\n    {/* 3️⃣ Choosing one of 3 options sets form control value to */}\n    {/* a value assigned to specific radio input */}\n    \u003cinput type=\"radio\" id=\"radioEngineering\" name=\"team\" value=\"engineering\" formControlName=\"team\" /\u003e\n    \u003clabel htmlFor=\"radioEngineering\"\u003eEngineering\u003c/label\u003e\n    \u003cinput type=\"radio\" id=\"radioProduct\" name=\"team\" value=\"product\" formControlName=\"team\" /\u003e\n    \u003clabel htmlFor=\"radioProduct\"\u003eProduct\u003c/label\u003e\n    \u003cinput type=\"radio\" id=\"radioTesting\" name=\"team\" value=\"testing\" formControlName=\"team\" /\u003e\n    \u003clabel htmlFor=\"radioTesting\"\u003eTesting\u003c/label\u003e\n  \u003c/form\u003e\n);\n```\n\n### Binding form controls to `\u003cselect\u003e` element\n\nYou can bind `string` values to the `\u003cselect\u003e` element. You can change value\nof the element by choosing one of the predefined options:\n\n```tsx\ntype CountryOption = '' | 'Poland' | 'Spain' | 'Germany';\n\n// Component definition\n\nconst fg = createFormGroup({\n  // 1️⃣ Default value is set here\n  country: '' as CountryOption,\n});\n\nreturn (\n  \u003cform use:formGroup={fg}\u003e\n    {/* 2️⃣ Choosing one of available options sets form control value to */}\n    {/* a value assigned to specific \u003coption\u003e element */}\n    \u003clabel htmlFor=\"country-select\"\u003e\n      Country\n      \u003cselect name=\"country\" id=\"country-select\" formControlName=\"country\"\u003e\n        \u003coption value=\"\"\u003e--Please choose an option--\u003c/option\u003e\n        \u003coption value=\"Poland\"\u003ePoland\u003c/option\u003e\n        \u003coption value=\"Spain\"\u003eSpain\u003c/option\u003e\n        \u003coption value=\"Germany\"\u003eGermany\u003c/option\u003e\n      \u003c/select\u003e\n    \u003c/label\u003e\n  \u003c/form\u003e\n);\n```\n\n### Binding form controls to `\u003ctextarea\u003e` element\n\n`textarea` element is a string-based form control. You can\ndefine your form control's default value as `string` or `null`:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  // 1️⃣ Default value is set here\n  bio: '',\n});\n\nreturn (\n  \u003cform use:formGroup={fg}\u003e\n    \u003clabel htmlFor=\"bio\"\u003eBio\u003c/label\u003e\n    {/* 2️⃣ Here we bind form control to form group */}\n    \u003ctextarea name=\"bio\" id=\"bio\" formControlName=\"bio\" /\u003e\n  \u003c/form\u003e\n);\n```\n\n\n### Form control errors\n\nTo ensure that the form group defined by us matches the form structure in our template, some\nadditional runtime checks were implemented.\n\n#### Form control name does not match any key from form group\n\nIn case we'd made a mistake while connecting a form group key with the form input element using \n`formControlName` HTML attribute, we would get a runtime error informing us of the mistake:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  firstName: 'John',\n});\n\nreturn (\n  \u003c\u003e\n    \u003cform use:formGroup={fg}\u003e\n      \u003clabel for=\"firstName\"\u003eFirst name\u003c/label\u003e\n      \u003cinput id=\"firstName\" type=\"text\" formControlName=\"company\" /\u003e\n    \u003c/form\u003e\n  \u003c/\u003e\n);\n```\n\nThis one results in throwing a custom `FormControlInvalidKeyError` error with a message:\n```\n\"company\" form control name does not match any key from the form group.\n```\n\n#### Form control type does not match the type of an input element \n\nIn case we'd made a mistake when initializing the value of a form control in our form group, that\nwasn't supposed to be used with a given HTML element, we would get a runtime error informing \nus of the mistake:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  // 1️⃣ Here we initialize `firstName` form control value as `string`\n  firstName: 'John',\n});\n\nreturn (\n  \u003c\u003e\n    \u003cform use:formGroup={fg}\u003e\n      \u003clabel for=\"firstName\"\u003eFirst name\u003c/label\u003e\n      {/* 2️⃣ But here we use input element with type \"number\" - types mismatch */}\n      \u003cinput id=\"firstName\" type=\"number\" formControlName=\"firstName\" /\u003e\n    \u003c/form\u003e\n  \u003c/\u003e\n);\n```\n\nThis one results in throwing a custom `FormControlInvalidTypeError` error with a message:\n```\nValue of the \"firstName\" form control is expected to be of type [number] but the type was [string].\n```\n\n### Nested form groups\n\nYou can define nested form groups, by placing a nested record in your form group schema:\n\n```tsx\nconst fg = createFormGroup({\n  firstName: 'John',\n  // Here we create a nested form group\n  address: {\n    city: '',\n    postalNumber: null\n  }\n});\n```\n\nThis makes composing complex form models easier to maintain and logically group together.\n\nWhen building complex forms, managing the different areas of information is easier in smaller \nsections. Using nested form groups lets you break large forms groups into smaller, \nmore manageable ones, e.g. for styling or domain purposes.\n\nTo represent nested form groups in your template, you must wrap the form input elements\nfor that nested form group in another element, e.g. `div` and declare a `formGroupName` attribute:\n\n```tsx\n// Component definition\n\nconst fg = createFormGroup({\n  firstName: 'John',\n  address: {\n    city: '',\n    postalNumber: null\n  }\n});\n\nreturn (\n  \u003c\u003e\n    \u003cform use:formGroup={fg}\u003e\n      \u003clabel for=\"firstName\"\u003eFirst name\u003c/label\u003e\n      \u003cinput id=\"firstName\" type=\"text\" formControlName=\"firstName\" /\u003e\n      \n      \u003cdiv formGroupName=\"address\"\u003e\n        \u003clabel for=\"city\"\u003eCity\u003c/label\u003e\n        \u003cinput id=\"city\" type=\"text\" formControlName=\"city\" /\u003e\n        \n        \u003clabel for=\"postalNumber\"\u003ePostal number\u003c/label\u003e\n        \u003cinput id=\"postalNumber\" type=\"number\" formControlName=\"postalNumber\" /\u003e\n      \u003c/div\u003e\n    \u003c/form\u003e\n  \u003c/\u003e\n);\n```\n\nAll rules and features apply to the nested form groups as well:\n- accessing the `value`, `disabled`, `dirty`, `touched`, `valid` and `errors` signals\n- pre-configuring nested form control with `disabled` and `validators`\n- using proper HTML input elements with proper form control types\n- runtime type checking for proper using of form group keys and values\n\n\n## FAQ\n\n### I'm getting `Uncaught ReferenceError: formGroup is not defined`\n\nIf you encounter this problem, make sure you have following `vite-plugin-solid` options turned on:\n\n```typescript\n// vite.config.ts\n\nimport { defineConfig } from 'vite';\nimport solidPlugin from 'vite-plugin-solid';\n\nexport default defineConfig({\n  // Enable following `vite-plugin-solid` config option:\n  plugins: [solidPlugin({ typescript: { onlyRemoveTypeImports: true } })],\n});\n```\n\nSolution for the problem was found in this \n[answer for similar issue for SolidJS directives](https://github.com/solidjs/solid/issues/569#issuecomment-882721883).\n\n\n###  I'm getting type errors when defining `use:formGroup`, `formGroupName` and `formControlName` attributes\n\nIf you encounter TypeScript type errors when using the `formGroup` directive and new attributes \nwith your HTML elements, try [extending SolidJS's JSX namespace](https://www.solidjs.com/docs/latest/api#use%3A___):\n\n```tsx\ndeclare module 'solid-js' {\n  namespace JSX {\n    interface Directives {\n      formGroup?: {};\n    }\n\n    interface InputHTMLAttributes\u003cT\u003e {\n      formControlName?: string;\n    }\n\n    interface SelectHTMLAttributes\u003cT\u003e {\n      formControlName?: string;\n    }\n\n    interface HTMLAttributes\u003cT\u003e {\n      formGroupName?: string;\n    }\n  }\n}\n```\n\nThis will allow you to bind Solar's form group to your form elements without TypeScript type errors\nrelated to new HTML attributes.\n\n\n## Roadmap\n\n- [x] Creating and exporting [built-in validator functions](https://angular.io/api/forms/Validators) for common usage\n- [x] Support for `\u003cselect\u003e` element\n- [ ] Support for `\u003ctextarea\u003e` element\n- [ ] Defining and using [form arrays](https://angular.io/guide/reactive-forms#creating-dynamic-forms)\n- [ ] Support for [async validators](https://angular.io/api/forms/AsyncValidatorFn)\n- [ ] Documentation for API\n\n\n## Support\n\nIf you want to say thank you and/or support development of Solar Forms:\n\n1. Add a GitHub Star to the project.\n2. Tweet about the project on your Twitter.\n3. Write about it on Medium, Dev.to or personal blog.\n\n\n## Contribution guidelines\n\n🚧 Under construction! 🚧\n\n\n## Inspirations\n\nThis library is heavily inspired by [Angular's reactive forms](https://angular.io/guide/reactive-forms)\nalthough it was adapted to match more \"hook-like\" or \"signal-like\" form of accessing form group state.\n\nMany thanks to all people who contributed to growth of Angular's reactive forms over the years! 🙏\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkajetansw%2Fsolar-forms","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkajetansw%2Fsolar-forms","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkajetansw%2Fsolar-forms/lists"}