{"id":19522177,"url":"https://github.com/captaincodeman/svelte-form-helper","last_synced_at":"2025-03-03T03:02:52.408Z","repository":{"id":64354148,"uuid":"574578038","full_name":"CaptainCodeman/svelte-form-helper","owner":"CaptainCodeman","description":"Lightweight helpers for form validation with Svelte","archived":false,"fork":false,"pushed_at":"2024-08-28T21:34:48.000Z","size":315,"stargazers_count":56,"open_issues_count":5,"forks_count":0,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-02-24T02:08:28.641Z","etag":null,"topics":["form","form-validation","forms","svelte","validation"],"latest_commit_sha":null,"homepage":"https://captaincodeman.github.io/svelte-form-helper/","language":"Svelte","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/CaptainCodeman.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-05T16:06:31.000Z","updated_at":"2024-11-28T19:14:34.000Z","dependencies_parsed_at":"2024-04-27T19:41:55.393Z","dependency_job_id":"95b8c60a-70ac-40f9-9540-03913f5e9a84","html_url":"https://github.com/CaptainCodeman/svelte-form-helper","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CaptainCodeman%2Fsvelte-form-helper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CaptainCodeman%2Fsvelte-form-helper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CaptainCodeman%2Fsvelte-form-helper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CaptainCodeman%2Fsvelte-form-helper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CaptainCodeman","download_url":"https://codeload.github.com/CaptainCodeman/svelte-form-helper/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241600484,"owners_count":19988713,"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","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":["form","form-validation","forms","svelte","validation"],"created_at":"2024-11-11T00:37:30.701Z","updated_at":"2025-03-03T03:02:52.388Z","avatar_url":"https://github.com/CaptainCodeman.png","language":"Svelte","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Svelte Form Helper\n\nLightweight helper for form validation with Svelte\n\n1.79 KB minified, 919 bytes gzipped (compression level 6)\n\n## Features\n\n- ✅ Tiny size (it could have been called `itsy-bitsy-teenie-weenie-svelte-form-validate-machiney`)\n- ✅ Progressive enhancement of standard form validation\n- ✅ Supports SSR only forms (with JS disabled, or if JS fails to load)\n- ✅ Easy acces to validation state and control over messaging \u0026 styling when JS is enabled\n- ✅ Support dynamic addition / removal of form fields\n- ✅ Aggregate individual field into form-level state\n- ✅ Add appropriate WIA-ARIA accessibility attributes for screen readers\n- ✅ Works great with [SvelteKit Form actions](https://kit.svelte.dev/docs/form-actions)\n- ✅ Supports all [HTMLElements that implement The Constraint Validation API](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#the_constraint_validation_api)\n\n## Example\n\nOnline example coming soon, in the meantime checkout the [Basic Example](https://github.com/CaptainCodeman/svelte-form-helper/blob/master/src/routes/Example.svelte) or the [Component Example](https://github.com/CaptainCodeman/svelte-form-helper/blob/master/src/routes/ExampleHelpers.svelte)\n\n## Usage\n\nThe important thing to remember is that we're not trying to _replace_ or _re-implement_ the browser native form validation, so you won't find JS versions of `required` or `minlength` - we build on top of what the browser already provides, to enhance it. So it's worth being familiar with the [validation attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation) available.\n\nWe also use the native browser [`ValidityState` model](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState) to determine if and why validation failed and use those flags to determine what validation messages to show.\n\n### Install from NPM\n\nInstall using your package manager of choice, e.g.:\n\n    npm install svelte-form-helper\n    pnpm i svelte-form-helper\n    yarn add svelte-form-helper\n\n### Create Form Instance\n\nFirst import the `createForm` factory function in your component `\u003cscript\u003e` block and create a form validator instance from it:\n\n```ts\nimport { createForm } from 'svelte-form-helper'\n\nconst form = createForm()\n```\n\n### Create Field Instance(s)\n\nFields are created using the form instance `.field()` method. An options object can be passed to set:\n\n- `onDirty` - a boolean flag of whether to show validation errors on `input` (default `false`)\n- `onTouched` - a boolean flag of whether to show validation errors on `blur` (default `true`)\n- `validator` - a custom validator function\n\n```ts\nconst name = form.field({ validator: isNameAvailable, onDirty: true })\nconst email = form.field({ onDirty: true })\nconst title = form.field()\n```\n\nNOTE: The field is _always_ validated on `input` and `blur` (i.e. when dirty or touched) so the form validation state is always known and uptodate. This creates a better user experience as the form submit button will be enabled as soon as the form is valid, rather than requiring the user to tab out of the field first.\n\n### Custom Validation Function\n\nThe custom validation function will be called if the field is otherwise valid (i.e. it won't be called if the input is set to `required` but is empty or hasn't yet met a required input length). It should accept a string value parameter and return a message if validation fails or else `null` if the value was valid. The validation function can be async to call a remote endpoint - if the input changes before the previous validation completed, the last one called will always win.\n\n```ts\nasync function isNameAvailable(value: string) {\n  const resp = await fetch('/checkname?name=' + value)\n  return resp.status === 200 ? null : `Name not available`\n}\n```\n\nNOTE: It will be ignored if used with a `HTMLFieldSetElement` (which represents a `\u003cfieldset\u003e` element) as this lacks a `value` property.\n\n### Apply to HTMLFormElement\n\nThe `form` instance is a Svelte `use:action` directive so adding it to the `\u003cform\u003e` tag in the Svelte template associates it with the actual `HTMLFormElement` that is created in the browser:\n\n```svelte\n\u003cform use:form on:submit={onSubmit}\u003e\n```\n\nOn the client the form action will set the `noValidate` property of the form to disable the native browser validation messages and provide us full control to provide and style our own. If JS is not available for any reason, the native browser validation will still be enabled.\n\n### Access Form State\n\nThe `form` instance is _also_ a Svelte Readable Store and provides flags to indicate if the form is:\n\n- `dirty` (_any_ field has been input)\n- `touched` (the user has clicked on or tabbed to _any_ field)\n- `valid` (_all_ of the fields attached to the form are valid)\n\nThe typical use for the state is to enable or disable the form submit button (which can also be reflected in its style to provide feedback to the user). Remember to use the `$` prefix to access the store value itself:\n\n```svelte\n\u003cbutton type=\"submit\" disabled={!$form.valid}\u003eSubmit\u003c/button\u003e\n```\n\nThis flag can also be used to prevent form submission in any `on:submit` event handler.\n\n### Apply to HTMLInputElement(s)\n\nThe individual field instances are also Svelte `use:action` directives and should be added to the corresponding `\u003cinput\u003e` tags in the template to associate them with the actual `HTMLInputElement`s in the browser:\n\n```svelte\n\u003cinput use:name type=\"text\" placeholder=\"unique name\" required minlength=\"5\" maxlength=\"50\"/\u003e\n\n\u003cinput use:email type=\"email\" placeholder=\"email address\" required /\u003e\n```\n\n### CSS `:valid` \u0026 `:invalid` Input Styling\n\nThe current field state is reflected to the HTML Element with `data-show`, `data-dirty` and `data-touched` attributes which can be used to style the input itself. You could apply a green or red border to indicate its valid or invalid state only when touched for instance. Note the reason for not using the `:valid` and `:invalid` CSS pseudo classes alone is that the styles would otherwise be applied to inputs on page load which is not a great user experience.\n\n#### Svelte Style\n\nA Svelte style based on the `data-touched` attribute needs to be made global to prevent it being removed:\n\n\u003cstyle global\u003e\n  input[data-touched]:valid {\n    color: green;\n    border-color: green;\n  }\n\n  input[data-touched]:invalid {\n    color: red;\n    border-color: red;\n  }\n\u003c/style\u003e\n\n#### TailwindCSS\n\nIf using TailwindCSS the styles can be added directly to the input element. e.g. to make the text and border red or green based on the state:\n\n```html\n\u003cinput\n  use:email\n  type=\"email\"\n  placeholder=\"email address\"\n  required\n  class=\"\n    data-[touched]:valid:text-green-700\n    data-[touched]:valid:border-green-700\n    data-[touched]:invalid:text-red-700\n    data-[touched]:invalid:border-red-700\n  \"\n/\u003e\n```\n\nThis can be made tidier by adding a custom variant using a TailwindCSS plugin defined in `tailwind.config.cjs`:\n\n```ts\nconst plugin = require('tailwindcss/plugin')\n\n// rest of config\n\nplugins: [\n  plugin(({ addVariant }) =\u003e {\n    addVariant('touched', '\u0026[data-touched]')\n  }),\n],\n```\n\nThe previous classes applied to the input element can then be simplified to:\n\n```html\n\u003cinput\n  use:email\n  type=\"email\"\n  placeholder=\"email address\"\n  required\n  class=\"\n    touched:valid:text-green-700\n    touched:valid:border-green-700\n    touched:invalid:text-red-700\n    touched:invalid:border-red-700\n  \"\n/\u003e\n```\n\n### Access Field State\n\nEnough about styling the input elements themselves, what about adding additonal validation messages and hints?\n\nThe individual field instances are _also_ Svelte Readable Stores and provide easy access to the validation state of their associated `HTMLInputElement`. This can be used to decide what validation messages or hints to output. Whether the message should be shown is determined by the `show` flag.\n\nThis snippet will output the default validation message that the browser generates but allows control over where it is shown and how it is styled. Note the `id` being set on the message element - this allows the message to be linked to the `HTMLInputElement` by setting the appropriate `aria-invalid` and `aria-describedby` attributes on it (this happens automatically):\n\n```svelte\n{#if $name.show}\n  \u003cdiv id={$name.id} class=\"text-red-700\"\u003e{$name.message}\u003c/div\u003e\n{/if}\n```\n\nBut we also have access to the [`ValidityState` flags](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState) so we're not limited to the message that the browser generates - we can decide exactly what custom message we want to show for each reason:\n\n```svelte\n{#if $name.show}\n  \u003cdiv id={$name.id} class=\"text-red-700\"\u003e\n    {#if $name.valueMissing}Name is required{/if}\n    {#if $name.tooShort}Name must be at least 5 characters{/if}\n    {#if $name.tooLong}Name can't be longer than 50 characters{/if}\n    {#if $name.customError}Name not available{/if}\n  \u003c/div\u003e\n{/if}\n```\n\nNOTE: instead of using the `{#if}` block another approach is to set the `hidden` attribute based on the `show` flag to control whether the validation message is shown:\n\n```svelte\n\u003cdiv id={$name.id} class=\"text-red-700\" hidden={!$name.show}\u003e\n  {#if $name.valueMissing}Name is required{/if}\n  {#if $name.tooShort}Name must be at least 5 characters{/if}\n  {#if $name.tooLong}Name can't be longer than 50 characters{/if}\n  {#if $name.customError}Name not available{/if}\n\u003c/div\u003e\n```\n\n### Validation Component Wrappers\n\nThe use of `{#if}` blocks or `hidden` attributes helps keep the package size down and should be more efficient, but it's also possible to define some Svelte Components to make the outputting easier if preferred:\n\n```svelte\n\u003cinput use:email type=\"email\" placeholder=\"email address\" required /\u003e\n\n\u003cValidation for={email} class=\"text-red-700\"\u003e\n  \u003cHint valueMissing\u003eEmail address is required\u003c/Hint\u003e\n  \u003cHint typeMismatch\u003eNot a valid email address\u003c/Hint\u003e\n\u003c/Validation\u003e\n```\n\n#### Validation.svelte\n\nThe simplest message display just needs to reference the field:\n\n```svelte\n\u003cValidation for={email} class=\"text-red-700\" /\u003e\n```\n\n```svelte\n\u003cscript lang=\"ts\" context=\"module\"\u003e\n  import type { FieldState } from 'svelte-form-helper/field'\n\n  export const key = {}\n  export type Context = {\n    state: Readable\u003cFieldState\u003e\n    clazz: string\n  }\n\u003c/script\u003e\n\n\u003cscript lang=\"ts\"\u003e\n  import type { Readable } from 'svelte/store'\n\n  import { setContext } from 'svelte'\n  export { state as for }\n\n  let state: Readable\u003cFieldState\u003e\n  let clazz = $$props.class\n\n  setContext\u003cContext\u003e(key, { state, clazz })\n\u003c/script\u003e\n\n{#if $state.show}\n  \u003cslot\u003e\u003cdiv id={$state.id} class={clazz}\u003e{$state.message}\u003c/div\u003e\u003c/slot\u003e\n{/if}\n```\n\n#### Hint.svelte\n\nFor separate validation messages per reason, nest one or more `\u003cHint\u003e` components within a `\u003cValidation\u003e` component:\n\n```svelte\n\u003cinput use:email type=\"email\" placeholder=\"email address\" required /\u003e\n\n\u003cValidation for={email} class=\"m-1 text-xs text-red-700\"\u003e\n  \u003cHint valueMissing\u003eEmail address is required\u003c/Hint\u003e\n  \u003cHint typeMismatch\u003eNot a valid email address\u003c/Hint\u003e\n\u003c/Validation\u003e\n```\n\n```svelte\n\u003cscript lang=\"ts\"\u003e\n  import { key } from './Validation.svelte'\n  import type { Context } from './Validation.svelte'\n  import { getContext } from 'svelte'\n\n  export let badInput = false\n  export let customError = false\n  export let patternMismatch = false\n  export let rangeOverflow = false\n  export let rangeUnderflow = false\n  export let stepMismatch = false\n  export let tooLong = false\n  export let tooShort = false\n  export let typeMismatch = false\n  export let valid = false\n  export let valueMissing = false\n\n  const { state, clazz } = getContext\u003cContext\u003e(key)\n\n  // prettier-ignore\n  $: show = ($state.badInput \u0026\u0026 badInput) ||\n            ($state.customError \u0026\u0026 customError) ||\n            ($state.patternMismatch \u0026\u0026 patternMismatch) ||\n            ($state.rangeOverflow \u0026\u0026 rangeOverflow) ||\n            ($state.rangeUnderflow \u0026\u0026 rangeUnderflow) ||\n            ($state.stepMismatch \u0026\u0026 stepMismatch) ||\n            ($state.tooLong \u0026\u0026 tooLong) ||\n            ($state.tooShort \u0026\u0026 tooShort) ||\n            ($state.typeMismatch \u0026\u0026 typeMismatch) ||\n            ($state.valid \u0026\u0026 valid) ||\n            ($state.valueMissing \u0026\u0026 valueMissing)\n\u003c/script\u003e\n\n{#if show}\n  \u003cdiv id={$state.id} class={clazz}\u003e\u003cslot message={$state.message} /\u003e\u003c/div\u003e\n{/if}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcaptaincodeman%2Fsvelte-form-helper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcaptaincodeman%2Fsvelte-form-helper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcaptaincodeman%2Fsvelte-form-helper/lists"}