--- url: /introduction.md --- # Introduction ## What is Regle? If you've ever built forms and wrote repetitive validation logic, struggling with complex error states, or losing type safety along the way, Regle is the perfect solution. Regle is a type-safe, headless form validation library that lets you write validation rules that mirror your data structure. Think of it as the perfect evolution of Vuelidate, but with modern TypeScript support and a more intuitive API. ## Why Choose Regle? * **🔒 Type Safe**: Full TypeScript inference means autocomplete everywhere and catch errors at compile time * **🌳 Model-Based**: Your validation tree matches your data model—no mental gymnastics required * **🔌 Headless**: Works with any UI framework, CSS library, or design system * **🔍 Devtools**: Built-in Vue devtools extension for easy debugging and testing. * **📦 Modular**: Use built-in rules or create custom ones that fit your exact needs * **⚡ Performance**: Efficient reactivity system that only updates what changed * **🛠 Developer Experience**: If you've used Vuelidate, you'll feel right at home ## Basic example Here's a real form that you can copy and use right away: ```vue twoslash [App.vue] ``` From `r$`, you can build any UI you want. The validation logic is completely separate from your presentation layer. **Live Result:** ## What's Next? Ready to dive deeper? Here's your learning path: 1. **[Installation](/introduction/installation)** - Get Regle set up in your project 2. **[Core Concepts](/core-concepts/)** - Understand how `useRegle` works 3. **[Built-in Rules](/core-concepts/rules/built-in-rules)** - Explore all available validation rules 4. **[Examples](/examples/simple)** - See Regle in action with real-world scenarios :::tip Coming from Vuelidate? Regle's API is intentionally similar to Vuelidate's. Check out our [comparison guide](/introduction/comparisons#vuelidate) to see what's changed and what's stayed the same. ::: --- --- url: /introduction/installation.md --- # Installation ## Prerequisites Required * [Vue](https://vuejs.org/) `3.4+`. * [Typescript](https://www.typescriptlang.org/) `5.1+`. * Compatible with plain javascript. * Text Editor with Vue syntax support. * [VSCode](https://code.visualstudio.com/) is recommended, along with the [official Vue extension](https://marketplace.visualstudio.com/items?itemName=Vue.volar). Optional * [Nuxt](https://nuxt.com/) * Nuxt `3.2.0+`, and check docs for [Nuxt module](/integrations/nuxt) * [Pinia](https://pinia.vuejs.org/) * Pinia `2.2.5+` Schema libraries: [Docs](/integrations/schemas-libraries) * [Zod](https://zod.dev/) `3.24+`. * [Valibot](https://valibot.dev/) `1+`. * [ArkType](https://arktype.io/) `2+` * Any library using the [Standard Schema Spec](https://standardschema.dev/) ::: code-group ```sh [pnpm] pnpm add @regle/core @regle/rules ``` ```sh [npm] npm install @regle/core @regle/rules ``` ```sh [yarn] yarn add @regle/core @regle/rules ``` ```sh [bun] bun add @regle/core @regle/rules ``` ::: ## Devtools To enable devtools, you need to install the Regle plugin in your app. :::tip If you use the `@regle/nuxt` module, the devtools will be automatically enabled. ::: ```ts [main.ts] import { createApp } from 'vue'; import { RegleVuePlugin } from '@regle/core'; import App from './App.vue'; const app = createApp(App); app.use(RegleVuePlugin); // <-- app.mount('#app'); ``` You can provide options to the RegleVuePlugin to customize global config. See [Global configuration](/advanced-usage/global-config#declarative) for more details. ## MCP server Regle offers an MCP server that can be used to get documentation and autocomplete for Regle. You can install it using the following configurations: * [Cursor](/integrations/mcp-server#cursor) * [Claude Desktop](/integrations/mcp-server#claude-desktop) --- --- url: /introduction/devtools.md description: Debug your validation tree with Regle devtools --- # Regle Devtools Regle offers a devtools extension for [Vue Devtools](https://devtools.vuejs.org/) to help you debug your validation tree. ![Regle Devtools Screenshot](/screenshots/devtools.png) ## Installation To enable devtools, you need to install the Regle plugin in your app. :::tip If you use the `@regle/nuxt` module, the devtools will be automatically enabled. ::: ```ts [main.ts] import { createApp } from 'vue'; import App from './App.vue'; import { RegleVuePlugin } from '@regle/core'; const app = createApp(App); app.use(RegleVuePlugin); // <-- app.mount('#app'); ``` ## Usage Regle devtools can inspect every variation of `useRegle`: * `useRegle` * `useRules` * `useRegleSchema` * `useScopedRegle` * `useScopedRegleSchema` You can inspect every nested properties and rules of the `r$` instance. :::warning Rules details inspection is not available for `useRegleSchema` ::: ### Actions You can perform actions on the `r$` instance by clicking on the actions buttons in the devtools. ![Devtools Actions Screenshot](/screenshots/devtools-actions.png) * Validate: Validate the `r$` instance (with `$validate` method) * Reset validation state: Reset the validation state of the `r$` instance (with `$reset` method) * Restore to original state: Restore the `r$` instance to the original state (with `$reset` method) ## Providing custom `r$` ids to devtools By default, the devtools will use a generic name to display the `r$` instance. You can provide a custom name to the `useRegle` composable to display a more descriptive name in the devtools. ```ts [App.vue] import { useRegle } from '@regle/core'; const { r$ } = useRegle({ name: '' }, { name: { required } }, { id: 'my-form' }); ``` ## Devtools demo You can go in any of the [StackBlitz examples](/examples/index) and open the devtools by clicking on the "Open Devtools" button in the bottom middle of the page. ### Vite Devtools Integration Regle devtools also integrate cleanly with [Vite](https://vitejs.dev/) when you're running your app in development mode. You should see the Regle panel show up automatically in the Vite devtools if you've installed the plugin correctly. You will see the Regle icon showing in the devtools. Just click on it! --- --- url: /introduction/comparisons.md --- # Comparison with other form libraries ## Vuelidate Regle is successor of Vuelidate. As a long-time user of Vuelidate, I was inspired to create Regle after the project was discontinued. Both libraries share a similar API and developer experience (DX), including: * Data-based validation * Unified reactivity * Simple declaration Regle builds upon these features and adds several improvements: * 100% type safety * Autocomplete * Zod/Valibot support (with more integrations planned) * Global config * Improved API in some areas, such as rules declaration, `$each`, `validationGroups`, `$validate` ## VeeValidate Regle is a good VeeValidate alternative. VeeValidate is primarily focused on being component-centric. It now also offers Composition API helpers. Its API is less declarative compared to Regle, making it challenging to handle large forms or manage form state within a Pinia store. While VeeValidate supports typed schemas using libraries like Zod, Yup, or Valibot, this comes at the cost of losing some of VeeValidate's native features. In contrast, when using Zod with Regle, you retain all the features available in the default @regle/rules, ensuring a consistent developer experience. ## Tanstack Forms I love Tanstack products and what they're doing is so great for the JS community, especially making their tools framework agnostic. As for Tanstack Forms, I feel the API for Vue keeps too much syntax logic from the React counterpart. It doesn't take advantage of the Vue composition API enough. Tanstack forms also relies on DOM components, Regle doesn't. Regle is a more lightweight and less boilerplate alternative to Tanstack Forms. You can compare the [Regle playground](https://play.reglejs.dev) and the [Tanstack Forms Vue playground](https://tanstack.com/form/latest/docs/framework/vue/examples/simple?panel=code) to see that Regle is much more readable and uses way less code to do the same thing. ## Formkit & VueForms Formkit and VueForms are centered around DOM components. Regle is headless and data-driven, so you can work with your state anywhere you want. Working exclusively with a data-driven model enables stronger type safety and a better developer experience. Regle is an alternative to Formkit and VueForms if you don't want the UI and validation to be tied. ## Formwerk From Formwerk website: `[Formwerk] is ideal for those building internal UI design systems or UI libraries intended for other developers.`. The focus is not on validation, but on building a scalable UI around the form fields, so it differs completely in usage. Formwerk also offers the same feature to validate forms using Standard Schema Spec (Zod, Valibot, Arktype) --- --- url: /introduction/migrate-from-vuelidate.md description: Migrate to Regle with ease --- # Migrate from Vuelidate Migrating from Vuelidate is really simple. Regle API is similar to Vuelidate's one on purpose, so the mental model stays the same. Regle type safety will ensure you make no mistakes while making the migration. :::tip There is a dedicated **`regle-migrate-vuelidate`** [Agent Skill](/integrations/agent-skills) that walks an AI coding agent through this migration step by step. Install it with `npx skills add victorgarciaesgi/regle`. ::: ## Imports ```ts import { useVuelidate } from '@vuelidate/core'; // [!code --] import { required } from '@vuelidate/validators'; // [!code --] import { useRegle } from '@regle/core'; // [!code ++] import { required } from '@regle/rules'; // [!code ++] ``` ```ts const v$ = useVuelidate(rules, state, options); // [!code --] const { r$ } = useRegle(state, rules, options); // [!code ++] ``` ## Helpers ```ts import { helpers } from '@vuelidate/validators'; // [!code --] import { withMessage, withParams, withAsync, isEmpty, ... } from '@regle/rules'; // [!code ++] ``` Helpers which have been renamed: * `req` -> `isFilled` * `len` -> `getSize` * `regex` -> `matchRegex` * `forEach` -> Deleted, you can use `$each` directly. * `unwrap` -> use `toValue` from [Vue](https://vuejs.org/api/reactivity-utilities#tovalue) * Parameters are automatically unwrapped when using `createRule` ## Displaying errors Vuelidate: ```vue ``` Regle: ```vue ``` ### `withMessage` Order of parameters are swapped ```ts const rule = helpers.withMessage('This field cannot be empty', required) // [!code --] const rule = withMessage(required, 'This field cannot be empty') // [!code ++] ``` ### `withParams` You can create rules with parameters with [createRule](/core-concepts/rules/reusable-rules#createrule) helper ```ts const contains = (param) => // [!code --] helpers.withParams( // [!code --] { type: 'contains', value: param }, // [!code --] (value) => !helpers.req(value) || value.includes(param) // [!code --] ) // [!code --] const contains = createRule({ // [!code ++] validator(value: Maybe, param: Maybe) { // [!code ++] return isEmpty(value) || value.includes(param); // [!code ++] }, // [!code ++] message: ({$params: [param]}) => `Value must contain ${param}`, // [!code ++] }) // [!code ++] ``` ## Properties Some properties have been renamed * `$model` -> `$value` * `$response` -> `$metadata` [Using metadata from rules](/advanced-usage/rule-metadata#using-metadata-from-rules) * `$externalResults` -> `$externalErrors` ### Accessing nested fields ```ts v$.nested.child.$error // [!code --] r$.nested.child.$error // [!code ++] ``` ## Collections See [docs for validating arrays](/common-usage/collections) ```ts const v$ = useVuelidate({ // [!code --] collection: { // [!code --] $each: helpers.forEach({ // [!code --] name: { // [!code --] required // [!code --] } // [!code --] }) // [!code --] } // [!code --] }, {collection: [{name: ''}]}) // [!code --] const { r$ } = useRegle({ collection: [{name: ''}]}, { // [!code ++] collection: {// [!code ++] $each: {// [!code ++] name: {// [!code ++] required// [!code ++] }// [!code ++] }// [!code ++] }// [!code ++] })// [!code ++] ``` ## Methods See [docs for type safe output](/typescript/type-safe-output) ```ts const result = await v$.$validate(); // [!code --] const { valid, data } = await r$.$validate(); // [!code ++] ``` ## Custom messages If you used to declare this kind of helper methods with Vuelidate: ```ts import {helpers, required, numeric, minLength} from '@vuelidate/validators'; export const requiredValidator = helpers.withMessage( 'This field is required.', required ); export const numericValidator = helpers.withMessage( 'Please enter a valid value.', numeric ); export const minLengthValidator = (value) => helpers.withMessage( ({ $model, $params }) => `Please enter a value greater than or equal to ${$params.max}.`, minLength(value) ); ``` You can remove it and configure it with [global config](/advanced-usage/global-config#replace-built-in-rules-messages). :::tip If you use Nuxt , check the [Nuxt module documentation](/integrations/nuxt) for even easier error message sharing. ::: ```ts import { defineRegleConfig } from '@regle/core'; import { withMessage, minLength, required, numeric } from '@regle/rules'; const { useRegle: useCustomRegle } = defineRegleConfig({ rules: () => ({ required: withMessage(required, 'This field is required.'), numeric: withMessage(numeric, 'Please enter a valid value.'), minLength: withMessage(minLength, ({ $value, $params: [max] }) => { return `Minimum length is ${max}. Current length: ${$value?.length}`; }) }) }) const { r$ } = useCustomRegle({ name: '' }, { name: { required, numeric, minLength: minLength(6) } }) ``` ## Nested component validation ****Nested component**** validation is replaced by ****Scoped validation****. See [docs for scoped validation](/advanced-usage/scoped-validation) for more details ```ts // [scoped-config.ts] import { useScopedRegle, useCollectScope, useRegle } from '@regle/core'; // [!code ++] // Parent.vue const v$ = useVuelidate(); // [!code --] const v$ = useVuelidate({}, {}, {$scope: 'foo'}); // [!code --] const { r$ } = useCollectScope(); // [!code ++] const { r$ } = useCollectScope('foo'); // [!code ++] // Child.vue const v$ = useVuelidate(validations, state); // [!code --] const v$ = useVuelidate(validations, state, { $scope: false }); // [!code --] const v$ = useVuelidate(validations, state, { $scope: 'foo' }); // [!code --] const v$ = useVuelidate(validations, state, { $stopPropagation: true }); // [!code --] const { r$ } = useScopedRegle(state, validations); // [!code ++] const { r$ } = useRegle(state, validations); // [!code ++] const { r$ } = useScopedRegle(state, validations, {namespace: 'foo'}); // [!code ++] const { r$ } = useScopedRegle(state, validations); // [!code ++] ``` ## Validation groups ```ts const rules = { // [!code --] number: { isEven },// [!code --] nested: {// [!code --] word: { required: v => !!v }// [!code --] },// [!code --] $validationGroups: {// [!code --] firstGroup: ['number', 'nested.word']// [!code --] }// [!code --] }// [!code --] const v$ = useVuelidate(rules, ...);// [!code --] const { r$ } = useRegle(..., { // [!code ++] number: {isEven},// [!code ++] nested: {// [!code ++] word: { required: v => !!v }// [!code ++] }// [!code ++] }, {// [!code ++] validationGroups: (fields) => ({// [!code ++] firstGroup: [fields.number, fields.nested.word]// [!code ++] })// [!code ++] })// [!code ++] r$.$groups.firstGroup// [!code ++] ``` --- --- url: /core-concepts.md --- # Understanding `useRegle` `useRegle` is the heart of Regle. It's the composable that transforms your data and validation rules into a powerful, reactive validation system. Think of it as a bridge between your form data and your validation logic. You give it your state and rules, and it gives you back everything you need. ## The Big Picture Here's how `useRegle` works at a high level: ```vue [App.vue] ``` Let's break down each piece: :::tip Regle only works with the [Composition API](https://vuejs.org/guide/extras/composition-api-faq). There's no plan to support the Options or Class API—the Composition API's reactivity system is essential for Regle to work. ::: ## State: Your Form Data The first parameter is your form data—the actual values users will be entering. Regle is flexible about how you define this state. ### Different Ways to Define State **Raw Object** (simplest approach): ```ts const { r$ } = useRegle( { name: '', email: '', age: 0 }, /* rules */ ) ``` **Reactive Object** (when you need the state elsewhere): ```ts const formData = reactive({ name: '', email: '', age: 0 }); const { r$ } = useRegle(formData, /* rules */) // You can bind to either formData or r$.$value ``` **Ref Object** (for complex scenarios): ```ts const formData = ref({ name: '', email: '', age: 0 }); const { r$ } = useRegle(formData, /* rules */) ``` **Mixed Refs** (when individual fields need to be refs): ```ts const formData = { name: ref(''), email: ref(''), age: ref(0) } const { r$ } = useRegle(formData, /* rules */) ``` **Single Value** (for validating just one field): ```ts const email = ref(''); const { r$ } = useRegle(email, /* rules */) ``` ## Rules: Your Validation Logic The second parameter defines how your data should be validated. The beautiful thing about Regle is that your rules structure mirrors your data structure exactly. ### Basic Rules Declaration ```ts import { required, email, minLength } from '@regle/rules'; const { r$ } = useRegle( { user: { name: '', email: '' }, message: '' }, { user: { name: { required, minLength: minLength(2) }, email: { required, email } }, message: { required, minLength: minLength(10) } } ) ``` See how the rules structure matches the data structure? This makes it easy to understand and maintain. ### Dynamic Rules object Sometimes your validation rules need to change based on other values or conditions. Regle handles this elegantly: ```ts [Inline] import { useRegle } from '@regle/core'; // The rule object will not react to computed changes useRegle({ name: '' }, { name: { required } }) ``` ```ts [Getter] import { useRegle } from '@regle/core'; // The rules can now detect computed properties inside the object useRegle({ name: '' }, () => ({ name: { required } })) ``` ```ts [Computed] import { inferRules } from '@regle/core'; const state = ref({name: ''}); // inferRules preserves TypeScript autocompletion const rules = computed(() => { return inferRules(state, { name: { required } }) }) const { r$ } = useRegle(state, rules); ``` ### Available Rules Regle comes with a comprehensive set of built-in rules: ```ts import { required, email, minLength, maxLength, numeric, between, url, regex, // ... and many more } from '@regle/rules'; ``` Check out the [complete list of built-in rules](/core-concepts/rules/built-in-rules) to see everything available. :::tip Type-First Validation If you prefer to define your validation schema first (like with Zod) and infer your TypeScript types from it, check out [Standard Schema usage](/common-usage/standard-schema). ::: ## The stored result : `r$` When you call `useRegle`, you get back an object containing `r$`—your validation state. If you've used Vuelidate before, `r$` works similarly to `v$`. `r$` is a reactive object that contains everything you need to build your form UI: ### Common `r$` Properties If you’ve used Vuelidate before, useRegle behaves similarly to `v$`. `r$` is a reactive object containing the values, errors, dirty state and all the necessary validations properties you'll need to display information. You can find all the [available properties here](/core-concepts/validation-properties) ```vue twoslash [App.vue] ``` --- --- url: /core-concepts/rules.md description: >- Rules are the building blocks of validation in Regle. Learn how to create reusable and inline rules. --- # Rules Rules are the core building block of Regle (and also its name). A rule takes a value (and optional parameters) as input and returns a validation result as output. The **result** can be either: * A `boolean` * An object containing at least `{ $valid: boolean }` (allowing you to attach [metadata](/advanced-usage/rule-metadata)) ## Reusable rules with `createRule` The recommended way to write rules in Regle is with `createRule`. It provides a structured definition that includes the validator logic, an error message, and optional configuration — all in one place. ```ts import { createRule, type Maybe } from '@regle/core'; import { isFilled } from '@regle/rules'; export const mustBe = createRule({ validator(value: Maybe, expected: string) { if (isFilled(value)) { return value === expected; } return true; }, message: ({ $params: [expected] }) => `The value must be '${expected}'`, }); // Rule can now accept reactive parameters const expected = ref('foo'); const { r$ } = useRegle({ name: '' }, { name: { mustBe: mustBe(expected), // or mustBe: mustBe(() => expected.value), }, }); ``` Rules created with `createRule` can accept reactive parameters, return custom metadata, run async logic, and more. :::tip Head to the [Reusable rules](/core-concepts/rules/reusable-rules) page for the full guide on `createRule`, including parameters, reactivity, async rules, and metadata. ::: ## Inline rules For quick, one-off validations, you can write rules directly as inline functions. The function receives the current field value as its first argument. Simple rule: ```ts const { r$ } = useRegle({ name: '' }, { name: { simpleRule: (value) => value === 'regle', }, }) ``` Async rule: ```ts const { r$ } = useRegle({ name: '' }, { name: { asyncRule: async (value) => await someAsyncCall(), }, }) ``` Rule with metadata: ```ts const { r$ } = useRegle({ name: '' }, { name: { metadataRule: (value) => ({ $valid: value === 'regle', foo: 'bar', }), }, }) ``` ## Adding error messages Any rule — inline or reusable — can be wrapped with the `withMessage` helper to associate an error message with it. ```ts import { withMessage } from '@regle/rules'; const { r$ } = useRegle({ name: '' }, { name: { foo: withMessage((value: Maybe) => value === 'foo', "Value must be 'foo'"), }, }) ``` :::tip Learn more about `withMessage` and other wrappers in the [Rule wrappers](/core-concepts/rules/rule-wrappers) section. ::: ## Handling `optional` and `required` rules In Regle (borrowed from Vuelidate), all rules are **optional by default**. This means a rule will only run when the field has a value. The only exceptions are: * `required` * `checked` * `literal` This separation keeps the validation logic clean: you define *how* a field should be validated independently from *whether* it's mandatory. When writing custom rules, follow the same convention by checking if the value is filled before validating: ```ts import { isFilled } from '@regle/rules'; const { r$ } = useRegle({ name: '' }, { name: { mustBeFoo: (value) => { return isFilled(value) && value === 'foo'; }, }, }) ``` --- --- url: /core-concepts/rules/reusable-rules.md description: >- Create reusable, type-safe validation rules with createRule — supporting parameters, reactivity, async logic, and metadata. --- # Reusable rules ## `createRule` To create reusable rules, it’s recommended to use `createRule`. This utility simplifies defining the rule’s type, parameters, active state as well as track reactive dependencies automatically. Example: Recreating a simple `required` rule ```ts import { createRule } from '@regle/core'; import { isFilled } from '@regle/rules'; export const required = createRule({ validator: (value: unknown) => { return isFilled(value); }, message: 'This field is required', }); ``` ## Available options: ### `validator` ***Type***: `(value, ...params?) => boolean | {$valid: boolean, [x: string]: any}` *required* The `validator` function determines whether the field is valid. You can write it in the same way as an inline rule. ### `message` ***Type***: `string | string[] | (metadata) => (string | string[])` *required* This will define what error message you assign to your rule. It can be a string or a function receiving the value, params and metadata as parameters. ### `type` ***Type***: `string` *optional* Specifies the type of validator. This is useful when multiple rules share the same target, such as `required` and `requiredIf`. ### `active` ***Type***: `boolean | (metadata) => boolean` *optional* Defines the `$active` state of the rule, indicating whether the rule is currently being validated. This can be computed dynamically. For more details, see [Parameters and active mode](#parameters-and-active-mode). ### `async` ***Type***: `boolean` *optional* If your validator function is not written with `async await` syntax, you can enforce the rule to be async with this parameter. ### `tooltip` ***Type***: `string | string[] | (metadata) => (string | string[])` *optional* Use `tooltip` to display non-error-related messages for your field. These tooltips are aggregated and made accessible via `xxx.$tooltips`. This is useful for providing additional information or guidance. ## Reactive parameters With `createRule` you can easily define a rule that will depend on external reactive parameters. ### Declaration When declaring your validator, **Regle** will detect that your rule requires parameters and transform it into a function declaration: ```ts import { createRule, type Maybe } from '@regle/core'; export const myValidator = createRule({ validator: (value: Maybe, arg: number) => { return true; }, message: ({ $params: [arg] }) => { return 'This field is invalid'; } }); ``` The parameters detection also works with optional and spread parameters ```ts import { createRule, type Maybe } from '@regle/core'; export const myValidator = createRule({ validator: (value: Maybe, optionalArg?: number, otherOptional?: string) => { return true; }, message: ({ $params: [optionalArg, otherOptional] }) => { return 'This field is invalid'; } }); const {r$} = useRegle({foo: ''}, { foo: { // Can be used inline if first parameter is optional myValidator, // or myValidator: myValidator(5), // or myValidator: myValidator(5, 'foo'), } }) ``` :::warning While adding spread parameters `...anyOtherArg` is supported, keep in mind that it will receive every parameters, even the ones injected by a parent modifier like `applyIf`, `and`, `or` etc.. So it's not advised to use it ::: ### Reactivity The real advantage of using `createRule` is that it automatically registers parameters as reactive dependencies. This means your rule works seamlessly with plain values, refs, or getter functions. ```ts const max = ref(5); useRegle({name: ''},{ name: { // Plain value rule1: myValidator(5), // Ref rule2: myValidator(max), // Getter value rule3: myValidator(() => max.value) } }) ``` :::warning If you pass a raw value as a parameter, it will only be reactive if all your rules are declared as a computed or a getter function ```ts const state = ref({name: ''}) const max = ref(5); // ❌ Not reactive useRegle(state, { name: { maxLength: maxLength(max.value), ...(max.value === 3 && { required, }) } }) // ✅ Reactive useRegle(state, () => ({ name: { maxLength: maxLength(max.value), ...(max.value === 3 && { required, }) } })) // ✅ Reactive const rules = computed(() => ({ name: { maxLength: maxLength(max.value), ...(max.value === 3 && { required, }) } })) useRegle(state, rules); ``` ::: ### Default values You can set default value directly in the rule declaration. Be sure to type it correctly. There will be difference in implementation if you want to access the parameter value in the message factory function as a function scope is unreadable from outside its scope. You'll have to return explicitly the default value in the validator function, and declare the parameter as metadata. ```ts export const myValidator = createRule({ validator: (value: Maybe, arg: number = 0) => { return { $valid: value === arg, arg, }; }, message: ({ arg }) => `The value must be ${arg}`, }); ``` ### Active property The `active` property option is a field that will provide the rule an `on/off` behaviour. Some rules have conditional validation properties, such as `requiredIf` or any rule using `applyIf`. This property allows you to declare the active state of the rule. It will then be exposed in the `$active` rule property and be used to reflect the validation to your user. ```ts import { createRule, useRegle } from '@regle/core'; const myConditionalRule = createRule({ validator(value: unknown, param: string) { // Params like `condition` will always be unwrapped here // no need to check if it's a value, a ref or a getter function if (param === "Yes") { return isFilled(value); } return true; }, active({ $params: [condition] }) { return condition === 'Yes'; }, }); ``` Usage in a component: ```vue ``` ### Recreating `requiredIf` rule ::: code-group ```vue [Form.vue] ``` ::: Result: ## Async rules Async rules are useful for server-side validations or computationally expensive local checks. They update the `$pending` state whenever invoked. ```vue [App.vue] ``` ## Metadata Like in inline rules, you can return any data from your validator function as long as it returns an object containing at least `$valid: boolean`. It can be useful for returning computed data from the validator, or in async function to process api result, or api errors. ```ts twoslash {8} import { createRule } from '@regle/core'; export const example = createRule({ validator: (value) => { if (value === 'regle') { return { $valid: false, foo: 'bar' } } return true; }, message({foo}) { // ^? return 'Error example'; }, }); ``` --- --- url: /core-concepts/rules/built-in-rules.md description: >- Reference for all built-in validation rules available in the @regle/rules package. --- # Built-in rules All built-in rules are available through the `@regle/rules` package. Don't forget to install it if you haven't: ::: code-group ```sh [pnpm] pnpm add @regle/rules ``` ```sh [npm] npm install @regle/rules ``` ```sh [yarn] yarn add @regle/rules ``` ```sh [bun] bun add @regle/rules ``` ::: :::tip Every built-in rule will check if the value of the field is set before checking if it's valid. This allow to have rules even if the field is not required. ::: ## `alpha` ***Params*** * `allowSymbols?: MaybeRefOrGetter` Allows only alphabetic characters. ```ts import { alpha } from '@regle/rules'; const { r$ } = useRegle({ name: '' }, { name: { alpha, // or alpha: alpha({ allowSymbols: true }), }, }) ``` ## `alphaNum` ***Params*** * `allowSymbols?: MaybeRefOrGetter` Allows only alphanumeric characters. ```ts import { useRegle } from '@regle/core'; import { alphaNum } from '@regle/rules'; const { r$ } = useRegle({ name: '' }, { name: { alphaNum, // or alphaNum: alphaNum({ allowSymbols: true }), }, }); ``` ## `atLeastOne` ***Params*** * `keys?: MaybeRefOrGetter` - Optional list of keys to check. If not provided, checks if the object has at least one filled property. ***Works with*** * `Record | object` Checks if at least one key is filled in the object. Useful for object-level validation with `$self`. ```ts import { atLeastOne } from '@regle/rules'; const { r$ } = useRegle({ user: { firstName: '', lastName: '' } }, { user: { $self: { // Check if any property is filled atLeastOne, // or check specific keys atLeastOne: atLeastOne(['firstName', 'lastName']), }, }, }) ``` ## `between` ***Params*** * `min: Ref | number | () => number` * `max: Ref | number | () => number` * `options?: {allowEqual?: boolean}` Checks if a number is in specified bounds. `min` and `max` are both inclusive. ```ts import { between } from '@regle/rules'; const maxCount = ref(6); const { r$ } = useRegle({ count: 0 }, { count: { between: between(1, 6), between: between(1, maxCount, {allowEqual: false}), between: between(() => maxCount.value, 10) }, }) ``` ## `boolean` Requires a value to be a native boolean type. Mainly used for typing. ```ts import {type InferInput} from '@regle/core'; import { boolean } from '@regle/rules'; const rules = { checkbox: { boolean }, } const state = ref>({}); ``` ## `checked` Requires a boolean value to be `true`. This is useful for checkbox inputs. ### Note This rule does not need `required` to be set, it will assert the value is set. ```ts import { checked } from '@regle/rules'; const { r$ } = useRegle({ confirm: false }, { confirm: { checked }, }) ``` ## `contains` ***Params*** * `contain: Ref | string | () => string` Checks if the string contains the specified substring. ```ts import { contains } from '@regle/rules'; const { r$ } = useRegle({ bestLib: '' }, { bestLib: { contains: contains('regle') }, }) ``` ## `containsSpecialCharacter` ***Params*** * `minCharactersCount?: Ref | number | () => number` Requires a string to contain at least a number of special characters. ```ts import { containsSpecialCharacter } from '@regle/rules'; const { r$ } = useRegle({ password: '' }, { password: { containsSpecialCharacter, // or with a custom minimum containsSpecialCharacter: containsSpecialCharacter(2), }, }) ``` ## `containsUppercase` ***Params*** * `minUppercaseCount?: Ref | number | () => number` Requires a string to contain at least a number of uppercase letters. ```ts import { containsUppercase } from '@regle/rules'; const { r$ } = useRegle({ password: '' }, { password: { containsUppercase, // or with a custom minimum containsUppercase: containsUppercase(2), }, }) ``` ## `date` Requires a value to be a native Date constructor. Mainly used for typing. ```ts import {type InferInput} from '@regle/core'; import { date } from '@regle/rules'; const rules = { birthday: { date }, } const state = ref>({}); ``` ## `dateAfter` ***Params*** * `after: Ref | string | Date | () => string | Date` * `options?: {allowEqual?: boolean}` Checks if the date is after the given parameter. ```ts import { dateAfter } from '@regle/rules'; const today = ref(new Date()); const { r$ } = useRegle({ birthday: null as Date | null }, { birthday: { dateAfter: dateAfter(today), // or dateAfter: dateAfter(today, { allowEqual: false }), }, }) ``` ## `dateBefore` ***Params*** * `before: Ref | string | Date | () => string | Date` * `options?: {allowEqual?: boolean}` Checks if the date is before the given parameter. ```ts import { dateBefore } from '@regle/rules'; const today = ref(new Date()); const { r$ } = useRegle({ birthday: null as Date | null }, { birthday: { dateBefore: dateBefore(today), // or dateBefore: dateBefore(today, { allowEqual: false }), }, }) ``` ## `dateBetween` ***Params*** * `before: Ref | string | Date | () => string | Date` * `after: Ref | string | Date | () => string | Date` * `options?: {allowEqual?: boolean}` Checks if the date falls between the specified bounds. ```ts import { dateBetween } from '@regle/rules'; const before = ref(new Date()); const after = ref(new Date(2030, 3, 1)); const { r$ } = useRegle({ birthday: null as Date | null }, { birthday: { dateBetween: dateBetween(before, after), // or dateBetween: dateBetween(before, after, { allowEqual: false }), }, }) ``` ## `decimal` Allows positive and negative decimal numbers. ```ts import { decimal } from '@regle/rules'; const { r$ } = useRegle({ price: 0 }, { price: { decimal }, }) ``` ## `domain` Validates domain names only (for example `example.com` or `sub.example.com`). ```ts import { domain } from '@regle/rules'; const { r$ } = useRegle({ siteDomain: '' }, { siteDomain: { domain }, }) ``` ## `email` Validates email addresses. Always verify on the server to ensure the address is real and not already in use. ```ts import { email } from '@regle/rules'; const { r$ } = useRegle({ email: '' }, { email: { email }, }) ``` ## `emoji` Validates emojis. ```ts import { emoji } from '@regle/rules'; const { r$ } = useRegle({ emoji: '' }, { emoji: { emoji }, }) ``` ## `endsWith` ***Params*** * `end: Ref | string | () => string` Checks if the string ends with the specified substring. ```ts import { endsWith } from '@regle/rules'; const { r$ } = useRegle({ firstName: '' }, { firstName: { endsWith: endsWith('foo') }, }) ``` ## `exactDigits` ***Params*** * `count: Ref | number | () => number` Requires the input value to have a strict specified number of digits. ```ts import { exactDigits } from '@regle/rules'; const exactValue = ref(6); const { r$ } = useRegle({ digits: '' }, { digits: { exactDigits: exactDigits(6), // or with ref exactDigits: exactDigits(exactValue), // or with getter exactDigits: exactDigits(() => exactValue.value) }, }) ``` ## `exactLength` ***Params*** * `count: Ref | number | () => number` ***Works with*** * `Array | Record | string | number` Requires the input value to have a strict specified length. ```ts import { exactLength } from '@regle/rules'; const exactValue = ref(6); const { r$ } = useRegle({ name: '' }, { name: { exactLength: exactLength(6), exactLength: exactLength(exactValue), exactLength: exactLength(() => exactValue.value) }, }) ``` ## `exactValue` ***Params*** * `count: Ref | number | () => number` Requires a field to have a strict numeric value. ```ts import { exactValue } from '@regle/rules'; const exactCount = ref(6); const { r$ } = useRegle({ count: 0 }, { count: { exactValue: exactValue(6), exactValue: exactValue(exactCount), exactValue: exactValue(() => exactCount.value) }, }) ``` ## `file` Requires a value to be a native File constructor. Mainly used for typing. ```ts import { file } from '@regle/rules'; const rules = { file: { file }, } const state = ref>({}); ``` ## `fileType` Requires a value to be a file with a specific type. ```ts import { fileType } from '@regle/rules'; const { r$ } = useRegle({ file: null as File | null }, { file: { fileType: fileType(['image/png', 'image/jpeg']) }, }) ``` ## `hexadecimal` Validates hexadecimal values. ```ts import { hexadecimal } from '@regle/rules'; const { r$ } = useRegle({ hexadecimal: '' }, { hexadecimal: { hexadecimal }, }) ``` ## `hostname` Validates hostnames. ```ts import { hostname } from '@regle/rules'; const { r$ } = useRegle({ siteHost: '' }, { siteHost: { hostname }, }) ``` ## `httpUrl` ***Params*** * `options?: {protocol?: RegExp}` Validates HTTP URLs. ```ts import { httpUrl } from '@regle/rules'; const { r$ } = useRegle({ bestUrl: '' }, { bestUrl: { httpUrl }, // or with custom protocol validation bestUrl: { httpUrl: httpUrl({ protocol: /^https$/ }) }, }) ``` ## `integer` Allows only integers (positive and negative). ```ts import { integer } from '@regle/rules'; const { r$ } = useRegle({ count: 0 }, { count: { integer }, }) ``` ## `ipv4Address` Validates IPv4 addresses in dotted decimal notation *127.0.0.1*. ```ts import { ipv4Address } from '@regle/rules'; const { r$ } = useRegle({ address: '' }, { address: { ipv4Address }, }) ``` ## `literal` Validates literal values. ### Note This rule does not need `required` to be set, it will assert the value is set. ```ts import { literal } from '@regle/rules'; const { r$ } = useRegle({ value: '' }, { value: { literal: literal('foo') }, }) ``` ## `lowercase` Validates lowercase strings. ```ts import { lowercase } from '@regle/rules'; const { r$ } = useRegle({ name: '' }, { name: { lowercase }, }) ``` ## `macAddress` ***Params*** * `separator?: string | Ref | () => string` Validates MAC addresses. Call as a function to specify a custom separator (e.g., ':' or an empty string for 00ff1122334455). ```ts import { useRegle } from '@regle/core'; import { macAddress } from '@regle/rules'; const { r$ } = useRegle({ address: '' }, { address: { macAddress, // or macAddress: macAddress('-') }, }); ``` ## `maxFileSize` Requires a value to be a file with a maximum size. ```ts import { maxFileSize } from '@regle/rules'; const { r$ } = useRegle({ file: null as File | null }, { file: { maxFileSize: maxFileSize(10_000_000) }, // 10 MB }) ``` ## `maxLength` ***Params*** * `max: Ref | number | () => number` * `options?: {allowEqual?: boolean}` ***Works with*** * `Array | Record | string | number` Requires the input value to have a maximum specified length, inclusive. ```ts import { maxLength } from '@regle/rules'; const maxValue = ref(6); const { r$ } = useRegle({ name: '' }, { name: { maxLength: maxLength(6), maxLength: maxLength(maxValue), maxLength: maxLength(() => maxValue.value) }, }) ``` ## `maxValue` ***Params*** * `min: Ref | number | () => number` * `options?: {allowEqual?: boolean}` Requires a field to have a specified maximum numeric value. ```ts import { maxValue } from '@regle/rules'; const maxCount = ref(6); const { r$ } = useRegle({ count: 0 }, { count: { maxValue: maxValue(6), maxValue: maxValue(maxCount, {allowEqual: false}), maxValue: maxValue(() => maxCount.value) }, }) ``` ## `minFileSize` Requires a value to be a file with a minimum size. ```ts import { minFileSize } from '@regle/rules'; const { r$ } = useRegle({ file: null as File | null }, { file: { minFileSize: minFileSize(1_000_000) }, // 1 MB }) ``` ## `minLength` ***Params*** * `min: Ref | number | () => number` * `options?: {allowEqual?: boolean}` ***Works with*** * `Array | Record | string | number` Requires the input value to have a minimum specified length, inclusive. ```ts import { minLength } from '@regle/rules'; const minValue = ref(6); const { r$ } = useRegle({ name: '' }, { name: { minLength: minLength(6), minLength: minLength(minValue), minLength: minLength(() => minValue.value) }, }) ``` ## `minValue` ***Params*** * `min: Ref | number | () => number` * `options?: {allowEqual?: boolean}` ***Works with*** * `number` Requires a field to have a specified minimum numeric value. ```ts import { minValue } from '@regle/rules'; const minCount = ref(6); const { r$ } = useRegle({ count: 0 }, { count: { minValue: minValue(6), minValue: minValue(minCount, {allowEqual: false}), minValue: minValue(() => minCount.value) }, }) ``` ## `nativeEnum` Validate against a native Typescript enum value. Similar to Zod's `nativeEnum` ```ts import { nativeEnum } from '@regle/rules'; enum Foo { Bar, Baz } const { r$ } = useRegle({ type: '' }, { type: { nativeEnum: nativeEnum(Foo) }, }) ``` ## `number` Requires a value to be a native number type. Mainly used for typing. ```ts import { number } from '@regle/rules'; const rules = { count: { number }, } const state = ref>({}); ``` ## `numeric` Allows only numeric values (including numeric strings). ```ts import { numeric } from '@regle/rules'; const { r$ } = useRegle({ count: 0 }, { count: { numeric }, }) ``` ## `oneOf` Allow only one of the values from a fixed Array of possible entries. ***Params*** * `options: MaybeRefOrGetter>` ```ts import { oneOf } from '@regle/rules'; const foodEnum = { Fish: 'Fish', Meat: 'Meat', Bone: 'Bone', } as const; const { r$ } = useRegle({ aliment: 'Fish' }, { aliment: { oneOf: oneOf(['Fish', 'Meat', 'Bone']), // or oneOf: oneOf(foodEnum), }, }) ``` ## `regex` ***Params*** * `regexps: MaybeRefOrGetter` Checks if the value matches one or more regular expressions. ```ts import { regex } from '@regle/rules'; const { r$ } = useRegle({ name: '' }, { name: { regex: regex(/^foo/), regex: regex([/^bar/, /baz$/]), }, }) ``` ## `required` Requires non-empty data. Checks for empty arrays and strings containing only whitespaces. ```ts import {required} from '@regle/rules'; const {r$} = useRegle({name: ''}, { name: {required}, }) ``` ## `requiredIf` ***Params*** * `condition: Ref | unknown | () => unknown` - the property to base the `required` validator on. Requires non-empty data, only if provided data property, ref, or a function resolves to `true`. ```ts import { requiredIf } from '@regle/rules'; const form = ref({ name: '', condition: false }); const conditionRef = ref(false); const { r$ } = useRegle(form, { name: { required: requiredIf(() => form.value.condition), required: requiredIf(conditionRef), }, }) ``` ## `requiredUnless` ***Params*** * `condition: Ref | unknown | () => unknown` - the property to base the `required` validator on. Requires non-empty data, only if provided data property, ref, or a function resolves to `false`. ```ts import { requiredUnless } from '@regle/rules'; const form = ref({ name: '', condition: false }); const conditionRef = ref(false); const { r$ } = useRegle(form, { name: { required: requiredUnless(() => form.value.condition), required: requiredUnless(conditionRef) }, }) ``` ## `sameAs` ***Params*** * `target: unknown` Checks if the value matches the specified property or ref. ```ts import { sameAs } from '@regle/rules'; const form = ref({ password: '', confirmPassword: '', }); const { r$ } = useRegle(form, { confirmPassword: { sameAs: sameAs(() => form.value.password), } }) ``` ## `startsWith` ***Params*** * `start: Ref | string | () => string` Checks if the string starts with the specified substring. ```ts import { startsWith } from '@regle/rules'; const { r$ } = useRegle({ bestLib: '' }, { bestLib: { startsWith: startsWith('regle') }, }) ``` ## `string` Requires a value to be a native string type. Mainly used for typing ```ts import {type InferInput} from '@regle/core'; import { string } from '@regle/rules'; const rules = { firstName: { string }, } const state = ref>({}); ``` ## `type` Define the input type of a rule. No runtime validation.\ Override any input type set by other rules. ```ts import {type InferInput} from '@regle/core'; import { type } from '@regle/rules'; const rules = { firstName: { type: type() }, } const state = ref>({}); ``` ## `uppercase` Validates uppercase strings. ```ts import { uppercase } from '@regle/rules'; const { r$ } = useRegle({ name: '' }, { name: { uppercase }, }) ``` ## `url` ***Params*** * `options?: {protocol?: RegExp}` Validates URLs. ```ts import { url } from '@regle/rules'; const { r$ } = useRegle({ bestUrl: '' }, { bestUrl: { url }, // or with custom protocol validation bestUrl: { url: url({ protocol: /^https?$/ }) }, }) ``` --- --- url: /core-concepts/rules/rules-properties.md description: >- Rule properties are computed values or methods available in every nested rule status --- # Rule properties Rule properties are computed values or methods available in every nested rule status (including `regle`) Let's take a look at a simple example to explain the different properties. ```vue twoslash ``` ## Computed properties for rules ### `$valid` * Type: `readonly boolean` Indicates the state of validation for this validator. ### `$pending` * Type: `readonly boolean` If the rule is async, indicates if it's currently pending. Always `false` if it's synchronous. ### `$message` * Type: `readonly string | string[]` Returns the computed error message or messages for the current rule. ### `$active` * Type: `readonly boolean` Indicates whether or not the rule is enabled (for rules like `requiredIf`) ### `$metadata` * Type `RegleRuleMetadataDefinition` Contains the metadata returned by the validator function. ### `$type` * Type: `readonly string` The name of the rule type. ### `$validator` * Type: `readonly (value, ...metadata) => boolean | {$valid: true, [x:string]: any}` Returns the original rule validator function. ### `$path` * Type: `readonly string[]` Returns the current path of the rule (used internally for tracking) ## Common methods for rules ### `$parse` * Type: `() => Promise` Run the rule validator and compute its properties like `$message` and `$active` ### `$reset` * Type: `() => void` Reset the `$valid`, `$metadata` and `$pending` states --- --- url: /core-concepts/rules/rule-wrappers.md description: >- Rule wrappers let you customize or upgrade your rules by injecting or replacing some properties. --- # Rule wrappers Rule wrappers let you customize or upgrade your rules by injecting or replacing some properties. ## Built-in wrappers ### `withMessage` The withMessage wrapper lets you associate an error message with a rule. Pass your rule as the first argument and the error message as the second. ```ts twoslash {5-13} // @noErrors import { useRegle, type InlineRuleDeclaration, type Maybe, type MaybeInput } from '@regle/core'; // ---cut--- import { withMessage } from '@regle/rules'; const customRuleInlineWithMetaData = ((value: Maybe) => ({ $valid: value === 'regle', foo: 'bar' as const })) satisfies InlineRuleDeclaration; const { r$ } = useRegle({ name: '' }, { name: { // Inline functions can be also written... inline customRule1: withMessage((value: MaybeInput) => !!value, "Custom Error"), customRule2: withMessage(customRuleInlineWithMetaData, "Custom Error"), // You can also access the current value and metadata with a getter function customRule3: withMessage( customRuleInlineWithMetaData, ({ $value, foo }) => `Custom Error: ${$value} ${foo}` // ^? ), } }) ``` Every error can be accessed in the `r$` object. In either `$errors` (if the field is dirty) or `$silentErrors` properties. In this case: * `r$.$errors.name` * `r$.name.$errors` ### `withParams` The withParams wrapper allows your rule to depend on external parameters, such as a reactive property in your component or store. By default, useRegle observes changes automatically when rules are defined using getter functions or computed properties. ```ts /** Non reactive rules */ useRegle({}, { /* rules */}) ``` ⬇️ ```ts useRegle({}, () => ({ /* rules */ })) // or const rules = computed(() => ({/* rules */ })) useRegle({}, rules) ``` However, sometimes dependencies cannot be tracked automatically, use `withParams` to manually define them: ```ts twoslash {7-9} // @noErrors import { useRegle } from '@regle/core'; import { ref } from 'vue'; // ---cut--- import { withParams } from '@regle/rules'; const base = ref('foo'); const { r$ } = useRegle({ name: '' }, { name: { customRule: withParams((value, param) => value === param, [base]), // or customRule: withParams((value, param) => value === param, [() => base.value]), } }) ``` ### `withAsync` `withAsync` works like `withParams`, but is specifically designed for async rules that depend on external values. ```ts import { withAsync } from '@regle/rules'; const base = ref('foo'); const { r$ } = useRegle({ name: '' }, { name: { customRule: withAsync(async (value, param) => { await someAsyncCall(param) }, [base]) } }) ``` ### `withTooltip` The `withTooltip` wrapper allows you to display additional messages for your field that aren’t necessarily errors. Tooltips are aggregated and accessible via `xxx.$tooltips`. ```ts import { withTooltip } from '@regle/rules'; const { r$ } = useRegle({ password: '' }, { password: { strong: withTooltip( (value) => !!value && value.length >= 8, 'Password should be at least 8 characters' ), } }) // Access tooltips in template: r$.password.$tooltips ``` ## Chaining wrappers You can combine multiple wrappers to create more powerful and flexible rules while keeping everything typed correctly. ```ts twoslash {9-14} // @noErrors import { useRegle } from '@regle/core'; import { ref } from 'vue'; const someAsyncCall = async (param: string) => await Promise.resolve(true); // ---cut--- import { withAsync, withMessage } from '@regle/rules'; const base = ref(1); const { r$ } = useRegle({ name: '' }, { name: { customRule: withMessage( withAsync( async (value, param) => await someAsyncCall(param), [base] ), ({$value, $params: [param] }) => `Custom error: ${$value} != ${param}` // ^? ), }, } ); ``` --- --- url: /core-concepts/rules/rules-operators.md description: Regle provides tools to combine and operate on different rules --- # Rules operators Regle provides tools to combine and operate on different rules. It includes the following built-in operators, available in `@regle/rules`: * `and` * `or` * `xor` * `not` * `applyIf` * `assignIf` * `pipe` These operators work with any rules you provide, but combining rules with incompatible input types may lead to errors. :::tip All operators are compatible with wrappers ::: ## `and` The `and` operator combines multiple rules and validates successfully only if all provided rules are valid. ```ts import { useRegle } from '@regle/core'; import { and, startsWith, endsWith, withMessage } from '@regle/rules'; const { r$ } = useRegle( { regex: '' }, { regex: { myError: withMessage( and(startsWith('^'), endsWith('$')), ({ $params: [start, end] }) => `Regex should start with "${start}" and end with "${end}"` ), }, } ); ``` Result: ## `or` The `or` operator validates successfully if at least one of the provided rules is valid. ```ts twoslash import { useRegle } from '@regle/core'; import { or, startsWith, endsWith, withMessage } from '@regle/rules'; const { r$ } = useRegle( { regex: '' }, { regex: { myError: withMessage( or(startsWith('^'), endsWith('$')), ({ $params: [start, end] }) => `Field should start with "${start}" or end with "${end}"` ), }, } ); ``` Result: ## `xor` The `xor` operator (exclusive or) validates successfully if **exactly one** of the provided rules is valid. It fails when none or more than one rule passes. ```ts import { useRegle } from '@regle/core'; import { xor, contains, withMessage } from '@regle/rules'; const { r$ } = useRegle( { code: '' }, { code: { myError: withMessage( xor(contains('A'), contains('B')), ({ $params: [charA, charB] }) => `Field should contain either "${charA}" or "${charB}", but not both` ), }, } ); ``` Result: ## `not` The `not` operator passes when the provided rule fails and fails when the rule passes. It can be combined with other rules. ```ts import { useRegle } from '@regle/core'; import { not, required, sameAs, withMessage } from '@regle/rules'; import { ref } from 'vue'; const form = ref({ oldPassword: '', newPassword: '' }); const { r$ } = useRegle(form, { oldPassword: { required, }, newPassword: { notEqual: withMessage( not(sameAs(() => form.value.oldPassword)), 'Your passwords must be different' ), }, }); ``` Result: ## `applyIf` The `applyIf` operator is similar to `requiredIf`, but it can be used with any rule. It simplifies conditional rule declarations. ```ts import { useRegle } from '@regle/core'; import { minLength, applyIf } from '@regle/rules'; import { ref } from 'vue'; const condition = ref(false); const { r$ } = useRegle({name: ''}, { name: { minLength: applyIf(condition, minLength(6)) }, }); ``` Result: ## `assignIf` The `assignIf` is a shorthand for conditional destructuring assignment. It allows to apply multiple rules to a field conditionally. ```ts import { useRegle } from '@regle/core'; import { required, email, minLength, assignIf } from '@regle/rules'; import { ref } from 'vue'; const condition = ref(false); const { r$ } = useRegle(ref({ name: '', email: '' }), { name: assignIf(condition, { required, minLength: minLength(4) }), email: { email }, }); ``` Result: ## `pipe` The `pipe` operator chains multiple rules together sequentially. Each rule only runs if all previous rules have passed. This is useful when you want to validate in a specific order and avoid running expensive validations (like async checks) until simpler ones pass. ```ts import { useRegle } from '@regle/core'; import { pipe, required, minLength, email } from '@regle/rules'; const { r$ } = useRegle( { email: '' }, { email: pipe(required, minLength(5), email), } ); // minLength only runs if required passes // email only runs if both required and minLength pass ``` Result: :::tip When to use `pipe` vs `and` * Use **`and`** when all rules should run simultaneously and you want all errors at once * Use **`pipe`** when rules should run sequentially and later rules depend on earlier ones passing (e.g., don't check email format until the field has enough characters) ::: The `pipe` operator also works with async rules. Subsequent rules will wait for async validators to complete before running: ```ts import { pipe, required, withAsync } from '@regle/rules'; const checkEmailAvailable = withAsync(async (value) => { const response = await fetch(`/api/check-email?email=${value}`); return response.ok; }); const { r$ } = useRegle( { email: '' }, { email: pipe(required, email, checkEmailAvailable), } ); // checkEmailAvailable only runs after required and email pass ``` ### Debouncing async validators When using `pipe` with async validators, you can configure a debounce delay to prevent too many API calls while the user is typing. Use the array syntax with an options object as the second argument: ```ts import { pipe, required, email, withAsync } from '@regle/rules'; const checkEmailAvailable = withAsync(async (value) => { const response = await fetch(`/api/check-email?email=${value}`); return response.ok; }); const { r$ } = useRegle( { email: '' }, { email: pipe( [required, email, checkEmailAvailable], { debounce: 300 } // Wait 300ms after last input before running async validators ), } ); ``` Result: The debounce option only affects async validators in the pipe. Synchronous validators (`required`, `email` in the example above) run immediately, while the async validator (`checkEmailAvailable`) will be debounced. :::info Default debounce When using `pipe` with async validators, the default debounce delay is **200ms**. You can override this by passing a custom `debounce` value in the options. ::: --- --- url: /core-concepts/rules/validations-helpers.md description: >- Utility functions for writing custom rules — type guards, size helpers, and value coercion. --- # Validations helpers When writing custom rules, some checks or validations can become tedious, especially when handling values that might be null, undefined, or unset. It's also a best practice to verify whether a field is "filled" before proceeding with validation. To simplify this process, Regle provides a set of utility functions to assist in creating custom rules. These utilities can be accessed via: ```ts import { isFilled, isEmpty, getSize, ... } from '@regle/rules'; ``` ## Runtime and Type guards ### `isFilled` ***Params*** * `value: unknown` * `considerEmptyArrayInvalid = true` This is almost a must have for optional fields. It checks if any value you provided is defined (including arrays and objects). You can base your validator result on this. `isFilled` also acts as a type guard. By default, it considers empty array as `false`. You can override this behaviour with the `considerEmptyArrayInvalid` ```ts import { createRule } from '@regle/core'; import { isFilled } from '@regle/rules'; const rule = createRule({ validator(value: unknown) { if (isFilled(value)) { return check(value); } return true; }, message: 'Error' }) ``` ### `isEmpty` ***Params*** * `value: unknown` * `considerEmptyArrayInvalid = true` This is the inverse of `isFilled`. It will check if the value is in any way empty (including arrays and objects) `isEmpty` also acts as a type guard. By default, it considers empty array as `true`. You can override this behaviour with the `considerEmptyArrayInvalid` ```ts import { createRule, type Maybe } from '@regle/core'; import { isEmpty } from '@regle/rules'; const rule = createRule({ validator(value: Maybe) { if (isEmpty(value)) { return true; } return check(value); }, message: 'Error' }) ``` ### `isNumber` This is a type guard that will check if the passed value is a real `Number`. This also returns false for `NaN`, so this is better than `typeof value === "number"`. ```ts import { createRule, type Maybe } from '@regle/core'; import { isFilled, isNumber } from '@regle/rules'; const rule = createRule({ validator(value: Maybe) { if (isFilled(value) && isNumber(value)) { return checkNumber(value); } return true; }, message: 'Error' }) ``` ### `isDate` This is a useful helper that can check if the provided value is a Date, it is used internally for `date` rules. This can also check strings. ```ts import { createRule, type Maybe } from '@regle/core'; import { isFilled, isDate } from '@regle/rules'; const rule = createRule({ validator(value: Maybe) { if (isFilled(value) && isDate(value)) { return checkDate(value); } return true; }, message: 'Error' }) ``` ## Operations utils ### `getSize` This helper determines the size of strings, numbers, arrays, and objects. ```ts import { createRule, type Maybe } from '@regle/core'; import { isFilled, getSize } from '@regle/rules'; const rule = createRule({ validator(value: Maybe>) { if (isFilled(value)) { return getSize(value) > 6; } return true; }, message: 'Error' }) ``` ### `matchRegex` This utility can take multiple regular expressions as arguments. It checks the input's validity and tests it against the provided regex patterns. ```ts import { createRule, type Maybe } from '@regle/core'; import { isFilled, matchRegex } from '@regle/rules'; const regex = createRule({ validator(value: Maybe, regexps: RegExp[]) { if (isFilled(value)) { return matchRegex(value, ...regexps); } return true; }, message: 'Error' }) ``` ## Coerce utils ### `toNumber` This utility converts any string (or number) into a number using the `Number` constructor. :::warning This helper returns `NaN` if the input cannot be coerced, which is technically still a number. It can be safe to also check for `isNaN` additionally. ::: ```ts import { createRule, type Maybe } from '@regle/core'; import { isFilled, toNumber } from '@regle/rules'; const minValue = createRule({ validator(value: Maybe, min: number) { if (isFilled(value)) { const num = toNumber(value); return !isNaN(num) && num >= min; } return true; }, message: ({ $params: [min] }) => `Value must be at least ${min}`, }); ``` ### `toDate` This utility will coerce any string, number or Date value into a Date using the `Date` constructor. ```ts import { createRule, type Maybe } from '@regle/core'; import { isFilled, toDate } from '@regle/rules'; const afterToday = createRule({ validator(value: Maybe) { if (isFilled(value)) { const date = toDate(value); return date > new Date(); } return true; }, message: 'Date must be in the future', }); ``` --- --- url: /core-concepts/validation-properties.md description: >- Validation properties are computed values or methods available for every nested rule status --- # Validation properties Validation properties are computed values or methods available for every nested rule status, including `r$` and `regle`. Let's take a look at a simple example to explain the different properties. ```vue twoslash ``` ## Computed properties for fields ### `$invalid` * Type: `readonly boolean` Indicates whether the field is invalid. It becomes `true` if any associated rules return `false`. ### `$correct` * Type: `readonly boolean` This is not the opposite of `$invalid`. Correct is meant to display UI validation report. This will be `true` only if: * The field have at least one active rule * Is dirty and not empty * Passes validation ### `$dirty` * Type: `readonly boolean` Indicates whether a field has been validated or interacted with by the user at least once. It's typically used to determine if a message should be displayed to the user. You can change this flag manually using the `$touch` and `$reset` methods. The `$dirty` flag is considered true if the current model has been touched or if all its children are dirty. ### `$anyDirty` * Type: `readonly boolean` Similar to `$dirty`, with one exception. The `$anyDirty` flag is considered true if given model was touched or any of its children are `$anyDirty` which means at least one descendant is `$dirty`. ### `$edited` * Type: `readonly boolean` Indicates whether a field has been touched and if the value is different than the initial one. ### `$anyEdited` * Type: `readonly boolean` Similar to `$edited`, with one exception. The $anyEdited flag is considered true if given model was edited or any of its children are $anyEdited which means at least one descendant is `$edited`. ### `$value` * Type: `TValue` (The current property value type) A reference to the original validated model. It can be used to bind your form with `v-model`. ### `$silentValue` * Type: `TValue` (The current property value type) `$value` variant that will not "touch" the field and update the value silently, running only the rules, so you can easily swap values without impacting user interaction. ### `$initialValue` * Type: `TValue` Initial value of the field. This value will be set to the current `$value` when using `$reset`. ### `$originalValue` * Type: `TValue` Original value of the field. This value is the unmutated state that was passed to the form when it was initialized. This value will not be mutated when using `$reset`. ### `$pending` * Type: `readonly boolean` Indicates if any async rule for the field is currently running. Always `false` for synchronous rules. ### `$ready` * Type: `readonly boolean` Indicates whether the field is ready for submission. Equivalent to `!$invalid && !$pending`. ### `$error` * Type: `readonly boolean` Convenience flag to easily decide if a message should be displayed. Equivalent to `$dirty && !$pending && $invalid`. ### `$errors` * Type: `readonly string[]` Collection of all the error messages, collected for all children properties and nested forms. Only contains errors from properties where $dirty equals `true`. ### `$silentErrors` * Type: `readonly string[]` Collection of all the error messages, collected for all children properties. ### `$issues` * Type: `RegleFieldIssue[]` Collect all metadata of validators (errors, messages etc). Only contains metadata from properties where $dirty equals true. Structured external issues are also exposed here with their metadata. ### `$name` * Type: `readonly string` Return the current key name of the field. ## Common methods for fields ### `$validate` * Type: `(forceValues?: TState) => Promise>` Sets all properties as dirty, triggering all rules. It returns a promise that will either resolve to `false` or a Headless copy of your form state. Values that had the `required` rule will be transformed into a non-nullable value (type only). #### `forceValues` parameter The first argument is optional and can be used to assign a new state before validating. It's equivalent to use `r$.$value = x` and `r$.$validate();`. ### `$validateSync` * Type: `(forceValues?: TState) => boolean` Validates the form synchronously without waiting for async rules. This method: * Does **NOT** wait for async validation results (async rules are skipped and assumed valid) * Returns a `boolean` directly instead of a `Promise` Use this when you need immediate validation feedback without side effects, such as for real-time UI feedback or form gating logic. ```ts // Basic usage const isValid = r$.$validateSync(); // Field-level validation const isEmailValid = r$.email.$validateSync(); // Use with form submission gating function handleSubmit() { if (r$.$validateSync()) { // Proceed with submission } } ``` :::warning Since `$validateSync` skips async rules, use `$validate()` when you need to ensure all validations (including async) have passed before submission. ::: ### `$extractDirtyFields` * Type: `(filterNullishValues = true) => DeepPartial` Will return a copy of your state with only the fields that are dirty. By default it will filter out nullish values or objects, but you can override it with the first parameter `$extractDirtyFields(false)`. ### `$touch` * Type: `() => void` Marks the field and all nested properties as `$dirty`. ### `$reset` * Type: `(options?: ResetOptions) => void` Reset the validation status to a pristine state while keeping the current state. The current state is treated as the new initial state. :::tip For more information about the `$reset` method, check the [resetting forms section](/common-usage/reset-form) ::: ### `$clearExternalErrors` * Type: `() => void` Clears the `$externalErrors` state back to an empty object. ### `$setExternalErrors` * Type: `(errors: RegleExternalErrorTree | Record) => void` Sets external errors from either a nested object or dot-path keys. This method is available on the root `r$`, and works with or without passing the `externalErrors` option to `useRegle`. ```ts r$.$setExternalErrors({ email: ['Email already exists'], 'user.firstName': ['First name already exists'], }); ``` ### `$clearExternalIssues` * Type: `() => void` Clears the `$externalIssues` state back to an empty object. ### `$setExternalIssues` * Type: `(issues: RegleExternalIssueTree | Record) => void` Sets structured external issues from either a nested object or dot-path keys. The full issue objects are available in `$issues`, and each `$message` is also exposed in `$errors`. This method is mutually exclusive with `$setExternalErrors`: setting one clears the other. ```ts r$.$setExternalIssues({ email: [ { $message: 'Email already exists', code: 'EMAIL_TAKEN', }, ], }); ``` ### `$addRules` * Type: `(rules: RegleRuleDecl) => void` Adds runtime rules to the current field status. This is useful when a child component receives a `RegleFieldStatus` and needs to attach its own field-specific validation rules. :::warning This is convenient for children components to add rules to their parent field status, but you will lose a bit of type safety. ::: ```ts const { r$ } = useRegle(form, { password: {}, }); r$.password.$addRules({ required, minLength: minLength(8), }); ``` ### `addRules` (deprecated) * Type: `(rules: RegleRuleDecl) => void` :::warning Deprecated Use `$addRules` instead. This alias will be removed in a future version. ::: ## Specific properties for fields ### `$rules` * Type: `Record` This is reactive tree containing all the declared rules of your field. To know more about the rule properties check the [rules properties section](/core-concepts/rules/rules-properties) ### `$silentIssues` * Type: `RegleFieldIssue[]` Collect all metadata of validators (errors, messages etc). ## Specific properties for nested objects ### `$fields` * Type: `Record` This represents all the children of your object. You can access any nested child at any depth to get the relevant data you need for your form. ### `$self` * Type: `RegleFieldStatus` Represents the status of the object itself. You can have validation rules on the object like `required`, this field represents the isolated status of the object. ## Specific properties for collections Check documentation for [collections here](/common-usage/collections) ### `$each` * Type: `Array` This will store the status of every item in your collection. Each item will be a field you can access, or map on it to display your elements. ### `$self` * Type: `RegleFieldStatus` Represents the status of the collection itself. You can have validation rules on the array like `minLength`, this field represents the isolated status of the collection. --- --- url: /core-concepts/displaying-errors.md --- # Displaying errors Regle is a headless library, allowing you to display error messages in any way you choose. You can also use its internal state to apply classes or trigger behaviors dynamically. ## Showing errors messages You can display your errors by iterating though `r$.xxx.$errors`, `xxx` being the field you need to check. You can also access `r$.$errors.xxx` or `r$.$silentErrors.xxx`. ```vue twoslash [App.vue] ``` Result: ## Display custom error messages To display custom error messages, you can use the [withMessage](/core-concepts/rules/rule-wrappers#withmessage) helper.\ You have access to additional data like parameters or rule status to write your message. :::tip If you fall into this case: * You have a lot of forms in your app * You want to share translations easily between your forms Consider using [defineRegleConfig](/advanced-usage/global-config#replace-built-in-rules-messages) instead. ::: ```vue [App.vue] ``` ## i18n and translations Regle is library agnostic so you can use any i18n library freely, and there is nothing specific to configure, it will just work out of the box. ```vue ``` ## Applying an error and valid class ```vue [App.vue] ``` Result: ## Get errors by path If you need to access errors for a specific field using a dot-notation path, you can use the `getErrors` utility. This is useful when you need to programmatically access errors or when building reusable input components. ```ts twoslash import { getErrors, useRegle } from '@regle/core'; import { required, email } from '@regle/rules'; const { r$ } = useRegle( { user: { email: '' }, contacts: [{ name: '' }] }, { user: { email: { required, email } }, contacts: { $each: { name: { required } } } } ); await r$.$validate(); // Access nested errors with dot notation const emailErrors = getErrors(r$, 'user.email'); // ['This field is required'] // Access collection item errors const contactErrors = getErrors(r$, 'contacts.$each.0.name'); // ['This field is required'] ``` :::tip The path parameter is **type-safe** - TypeScript will autocomplete available paths and show an error if you try to access a path that doesn't exist or isn't a field with errors. ::: ## Get issues by path Similar to `getErrors`, the `getIssues` utility returns detailed validation issues including metadata like the rule name and custom properties. ```ts twoslash import { getIssues, useRegle } from '@regle/core'; import { required, minLength } from '@regle/rules'; const { r$ } = useRegle( { user: { name: '' } }, { user: { name: { required, minLength: minLength(3) } } } ); await r$.$validate(); const nameIssues = getIssues(r$, 'user.name'); // [{ // $message: 'This field is required', // $property: 'name', // $rule: 'required', // $type: 'required' // }] ``` ## Display flat errors If you want to display the complete list of errors of a form, or the total count of errors, you can use the `flatErrors` utility. It will return an array of error strings. ```ts import { flatErrors, useRegle } from '@regle/core'; import { email, minLength, required } from '@regle/rules'; const { r$ } = useRegle( { name: '', level0: { email: 'bar' } }, { name: { required, minLength: minLength(5) }, level0: { email: { email }, }, } ); r$.$validate(); const flattenErrors = flatErrors(r$.$errors); // [ // "This field is required", // "Value must be an valid email address" // ] ``` ### `includePath` option This helper also include an option to have the path of the property and returns the issues in Standard Schema Issue format. ```ts import { flatErrors, useRegle } from '@regle/core'; import { email, minLength, required } from '@regle/rules'; const { r$ } = useRegle( { name: '', level0: { email: 'bar' } }, { name: { required, minLength: minLength(5) }, level0: { email: { email }, }, } ); r$.$validate(); const flattenErrors = flatErrors(r$.$errors, {includePath: true}); // [ // { message: "This field is required", path: ["name"] }, // { message: "Value must be an valid email address", path: ["level0", "email"]} // ] ``` --- --- url: /core-concepts/modifiers.md description: >- Modifiers allow you to control the behavior and settings of validation rules in your application --- # Modifiers Modifiers allow you to control the behavior and settings of validation rules in your application. They can be applied globally to all fields or customized per field. ## Deep modifiers Deep modifiers are specified as the third argument of the `useRegle` composable. They apply recursively to all fields within your state. ```ts const { r$ } = useRegle({}, {}, { /* modifiers */ }) ``` ### `autoDirty` **Type**: `boolean` **Default**: `true` Automatically set the dirty state to `true` when the value changes. ### `immediateDirty` **Type**: `boolean | 'eager' | 'non-empty' | 'lazy-non-empty'` **Default**: `false` Set the dirty state to `true` when the form is initialized. * `true` or `'eager'`: touch the whole form on mount. * `'non-empty'`: touch the whole form on mount only if an **active** field (a field with rules) has a non-empty initial value. Inactive fields — fields without rules — are ignored, so a prefilled field with no rules will not trigger a touch. * `'lazy-non-empty'`: touch only the fields that are non-empty on mount. ### `disabled` **Type**: `boolean` **Default**: `false` Temporarily pauses Regle reactivity and validation computation. When `disabled` is `true`, state changes still happen, but validation status does not update until you enable it again. ```ts import { ref } from 'vue'; import { useRegle } from '@regle/core'; import { required } from '@regle/rules'; const disabled = ref(false); const { r$ } = useRegle( { name: '' }, { name: { required } }, { disabled } ); disabled.value = true; r$.$value.name = 'John'; // value updates, validation is paused disabled.value = false; // validation reactivity resumes ``` ### `silent` **Type**: `boolean` **Default**: `false` Regle Automatically tracks changes in the state for all nested rules. If set to `true`, you must manually call `$touch` or `$validate` to display errors. ### `lazy` **Type**: `boolean` **Default**: `false` Usage: When set to false, tells the rules to be called on init, otherwise they are lazy and only called when the field is dirty. ### `debounce` **Type**: `number` (ms) **Default**: `200` (for fields with async rules) Number of milliseconds every async field should wait before executing its rules. It applies to every async field in your form. The per-field [`$debounce`](#debounce-1) modifier always takes priority over this global value. Set to `0` to disable the default debounce on async rules. ### `externalErrors` **Type**: `RegleExternalErrorTree` Pass an object, matching your error state, that holds external validation errors. These can be from a backend validations or something else. Check the [External errors](/common-usage/external-errors) section for more details. ### `externalIssues` **Type**: `RegleExternalIssueTree` Pass an object, matching your state, that holds structured external validation issues. Each issue keeps its metadata in `$issues`, and its `$message` is also exposed through `$errors`. `externalIssues` and `externalErrors` are mutually exclusive. Setting one clears the other. Check the [External errors and issues](/common-usage/external-errors) section for more details. ### `rewardEarly` **Type**: `boolean` **Default**: `false` **Side effect**: disable `$autoDirty` when `true`. Enables the `reward-early-punish-late` mode of Regle. This mode will not set fields as invalid once they are valid, unless manually triggered by `$validate` method. This has no effect when `autoDirty` is set to `true` (the default). Set `autoDirty: false` for `rewardEarly` to take effect. ### `clearExternalErrorsOnChange` **Type**: `boolean` **Default**: `true` This mode is similar to `rewardEarly`, but only applies to external errors. Setting it to `false` will keep the server errors and issues until `$clearExternalErrors` or `$clearExternalIssues` is called. ### `clearExternalErrorsOnValidate` **Type**: `boolean` **Default**: `false` This mode is similar to `clearExternalErrorsOnChange`, but for the `$validate` and `$validateSync` methods. Setting it to `false` will keep the server errors and issues until `$clearExternalErrors` or `$clearExternalIssues` is called manually, or `externalErrors` / `externalIssues` is set again. ### `validationGroups` **Type**: `(fields) => Record` Validation groups let you merge field properties under one, to better handle validation status. You will have access to your declared groups in the `r$.$groups` object. ```ts twoslash // @noErrors import { ref } from 'vue'; // ---cut--- import { useRegle } from '@regle/core'; import { required } from '@regle/rules'; const { r$ } = useRegle({ email: '', user: { firstName: '' } }, { email: { required }, user: { firstName: { required }, } }, { validationGroups: (fields) => ({ group1: [fields.email, fields.user.firstName] }) }) r$.$groups.group1. // ^| ``` ## Per-field modifiers Per-field modifiers allow to customize more precisely which behavior you want for each field. ```ts twoslash // @noErrors import { useRegle } from '@regle/core'; // ---cut--- const { r$ } = useRegle({ name: '' }, { name: { $ } // ^| }) ``` `$autoDirty` `$lazy`, `$silent`, `$immediateDirty` and `$rewardEarly` work the same as the deep modifiers. ### `$debounce` Type: `number` (ms) This let you declare the number of milliseconds the rule needs to wait before executing. Useful for async or heavy computations. It takes priority over the global [`debounce`](#debounce) deep modifier. :::tip All async rules have a default debounce of `200ms`, you can disable or modify this setting with `$debounce` ::: ### `$isEdited` Type: `(currentValue: MaybeInput, initialValue: MaybeInput, defaultHandlerFn: (currentValue: unknown, initialValue: unknown) => boolean) => boolean` Override the default `$edited` property handler. Useful to handle custom comparisons for complex object types. :::warning It's highly recommended to use this modifier with the [`markStatic`](/advanced-usage/immutable-constructors) helper to handle immutable constructors. ::: ```ts import { markStatic, useRegle } from '@regle/core'; import { Decimal } from 'decimal.js'; import { required } from '@regle/rules'; const { r$ } = useRegle({ decimal: markStatic(new Decimal(1)) }, { decimal: { required, $isEdited(currentValue, initialValue, defaultHandlerFn) { if (currentValue != null && initialValue != null) { return currentValue.toNearest(0.01).toString() !== initialValue.toNearest(0.01).toString(); } // fallback to the default handler return defaultHandlerFn(currentValue, initialValue); }, }, }) ``` ## Array specific modifiers This modifiers are only impacting Array collections. ```ts const { r$ } = useRegle({ collection: [] }, { collection: { /** Deep modifiers */ } }) ``` ### `$deepCompare` Type: `boolean` Default: `false` Allow deep compare of array children to compute the `$edited` property. It's disabled by default for performance reasons. --- --- url: /integrations/nuxt.md description: Enhance the Regle experience with the Nuxt module --- # Nuxt Adding the Nuxt module enables auto-imports for selected exports. Run the following command in your Nuxt application: ```bash npx nuxi module add regle ``` Or do it manually :::code-group ```sh [pnpm] pnpm add @regle/core @regle/rules @regle/nuxt ``` ```sh [npm] npm install @regle/core @regle/rules @regle/nuxt ``` ```sh [yarn] yarn add @regle/core @regle/rules @regle/nuxt ``` ```sh [bun] bun add @regle/core @regle/rules @regle/nuxt ``` ::: You can then declare the module inside your `nuxt.config.ts`. ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@regle/nuxt'] }) ``` ## `setupFile` The Regle Nuxt module allow you to define a global configuration plugin to provide all your forms with the same translations, options and custom rules. ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@regle/nuxt'], regle: { setupFile: '~/regle-config.ts' } }) ``` ```ts [app/regle-config.ts] import { defineRegleNuxtPlugin } from '@regle/nuxt/setup'; import { defineRegleConfig } from '@regle/core'; import { required, withMessage } from '@regle/rules'; export default defineRegleNuxtPlugin(() => { return defineRegleConfig({ rules: () => { const {t} = useI18n(); return { required: withMessage(required, t('general.required')), customRule: myCustomRule, } }, }); }); ``` This will inject the following composables to your auto-imports and `#imports`, loaded with your custom error messages and rules: * `useRegle` * `inferRules` * `useCollectScope` * `useScopedRegle` * `type RegleFieldStatus` ```vue [app.vue] {2} ``` ## No options If no setup file is provided, the following auto-imports will be added to your app. * `@regle/core` * useRegle * inferRules * createRule * defineRegleConfig * extendRegleConfig * createVariant * narrowVariant * useScopedRegle * useCollectScope * `@regle/rules` Note: Built-in rules are not auto-injected to minimize the risk of name conflicts. * withAsync * withMessage * withParams * withTooltip * `@regle/schemas` (if present) * useRegleSchema * inferSchema * withDeps * defineRegleSchemaConfig --- --- url: /integrations/schemas-libraries.md --- # Schemas libraries (Zod, Valibot, ...) Regle supports the [Standard Schema Spec](https://standardschema.dev/). This means any Standard Schema compliant RPC library can be used with Regle. Official list of supported libraries: * Zod [docs](https://zod.dev/) `3.24+`. * Valibot [docs](https://valibot.dev/) `1+`. * ArkType [docs](https://arktype.io/) `2+` * Any library following the [Standard Schema Spec](https://standardschema.dev/) ::: code-group ```sh [pnpm] pnpm add @regle/schemas ``` ```sh [npm] npm install @regle/schemas ``` ```sh [yarn] yarn add @regle/schemas ``` ```sh [bun] bun add @regle/schemas ``` ::: ## Usage Instead of using the core `useRegle`, use `useRegleSchema` export from `@regle/schemas`. :::code-group ```ts [Zod] import { useRegleSchema } from '@regle/schemas'; import { z } from 'zod'; const { r$ } = useRegleSchema({ name: '' }, z.object({ name: z.string().min(1) })) ``` ```ts [Valibot] import { useRegleSchema } from '@regle/schemas'; import * as v from 'valibot'; const { r$ } = useRegleSchema({ name: '' }, v.object({ name: v.pipe(v.string(), v.minLength(3)) })) ``` ```ts [ArkType] import { useRegleSchema } from '@regle/schemas'; import { type } from 'arktype'; const { r$ } = useRegleSchema({ name: '' }, type({ name: "string > 1" })) ``` ::: :::warning Limitations from the core behaviour Using schema libraries uses a different mechanism than the core "rules" one. Regle will parse the entire tree instead of doing it per-field. That means that properties or methods are not available in nested values: * `$validate` (only at root) * `$pending` (only at root) One other limitation is you won't have access to any children `$rules`, so checking if a field is required with `xx.$rules.required.active` is not possible with schemas. ::: ## Computed schema You can also have a computed schema that can be based on other state values. :::warning When doing refinements or transform, Vue can't track what the schema depends on because you're in a function callback. Same way as `withParams` from `@regle/rules`, you can use the `withDeps` helper to force dependencies on any schema ::: :::code-group ```ts [Zod] import { useRegleSchema, inferSchema, withDeps } from '@regle/schemas'; import { z } from 'zod'; import { ref, computed } from 'vue'; type Form = { firstName?: string; lastName?: string } const form = ref
({ firstName: '', lastName: '' }) const schema = computed(() => inferSchema(form, z.object({ firstName: z.string(), /** * Important to keep track of the dependency change * Without it, the validator wouldn't run if `firstName` changed */ lastName: withDeps( z.string().refine((v) => v !== form.value.firstName, { message: "Last name can't be equal to first name", }), [() => form.value.firstName] ), })) ); const { r$ } = useRegleSchema(form, schema); ``` ```ts [Valibot] import { useRegleSchema, inferSchema, withDeps} from '@regle/schemas'; import * as v from 'valibot'; import { ref, computed } from 'vue'; type Form = { firstName?: string; lastName?: string } const form = ref({ firstName: '', lastName: '' }) const schema = computed(() => inferSchema(form, v.object({ firstName: v.string(), /** * Important to keep track of the dependency change * Without it, the validator wouldn't run if `firstName` changed */ lastName: withDeps( v.pipe( v.string(), v.check((v) => v !== form.value.firstName, "Last name can't be equal to first name") ), [() => form.value.firstName] ), })) ) const { r$ } = useRegleSchema(form, schema); ``` ::: ## `syncState` By default, Regle doesn't allow any transforms on the state. Modifiers like `default`, `catch` or `transform` will not impact the validation. If you want to allow the schema to update your form state you can use the `syncState` option.\ The state will only be patched is the parse is successful. ```ts type RegleSchemaBehaviourOptions = { syncState?: { /** * Applies every transform on every update to the state */ onUpdate?: boolean; /** * Applies every transform only when calling `$validate` */ onValidate?: boolean; }; }; ``` Usage: ```vue ``` ## Type safe output Similar to the main `useRegle` composable, `r$.$validate` also returns a type-safe output using Zod type schema parser. :::code-group ```ts [Zod] import { useRegleSchema, inferSchema } from '@regle/schemas'; import { z } from 'zod'; import { ref, computed } from 'vue'; type Form = { firstName?: string; lastName?: string } const form = ref({ firstName: '', lastName: '' }) const schema = computed(() => inferSchema(form, z.object({ firstName: z.string().optional(), lastName: z.string().min(1).refine(v => v !== form.value.firstName, { message: "Last name can't be equal to first name" }), }))) const { r$ } = useRegleSchema(form, schema); async function submit() { const { valid, data } = await r$.$validate(); if (valid) { console.log(data); } } ``` ```ts [Valibot] import { useRegleSchema, inferSchema } from '@regle/schemas'; import * as v from 'valibot'; import { ref, computed } from 'vue'; type Form = { firstName?: string; lastName?: string } const form = ref({ firstName: '', lastName: '' }) const schema = computed(() => { return inferSchema(form, v.object({ firstName: v.optional(v.string()), lastName: v.pipe( v.string(), v.minLength(3), v.check((v) => v !== form.value.firstName, "Last name can't be equal to first name") ) })) }) const { r$ } = useRegleSchema(form, schema); async function submit() { const { valid, data } = await r$.$validate(); if (valid) { console.log(data); } } ``` ```ts [ArkType] import { useRegleSchema, inferSchema } from '@regle/schemas'; import { type } from 'arktype'; import { ref, computed } from 'vue'; type Form = { firstName?: string; lastName?: string } const form = ref({ firstName: '', lastName: '' }) const schema = computed(() => { return inferSchema(form, type({ 'firstName?': 'string', lastName: 'string > 3', }).narrow((data, ctx) => { if (data.firstName !== data.lastName) { return true; } return ctx.reject({ expected: 'different than firstName', path: ['lastName'], }); })) }) const { r$ } = useRegleSchema(form, schema); async function submit() { const { valid, data } = await r$.$validate(); if (valid) { console.log(data); } } ``` ::: --- --- url: /integrations/mcp-server.md description: Integrate Regle MCP with your favorite AI assistant --- # MCP Server [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) is an open standard that enables AI assistants to interact with external tools and data sources. Regle offers an MCP server that can be used to get documentation and autocomplete in your favorite AI assistant editor. This allows your AI assistant to understand Regle's API and help you write validation rules more effectively. The MCP server provides the following features: * Create form validation rules * Search documentation * Get precise information on any rule * Create custom rules * API information on every Regle helper ## Cursor Or add to your `.cursor/mcp.json` ```json { "mcpServers": { "regle": { "command": "npx", "args": ["@regle/mcp-server"] } } } ``` ## Claude Code For Claude Code, run the following command: ```bash claude mcp add-json regle --scope project '{"command":"npx","args":["-y","@regle/mcp-server"]}' ``` ## Claude Desktop For Claude Desktop, add the following to your `claude_desktop_config.json`: ```json { "mcpServers": { "regle": { "command": "npx", "args": ["-y", "@regle/mcp-server"] } } } ``` --- --- url: /integrations/agent-skills.md description: Install Regle Agent Skills for your AI coding agent --- # Agent Skills [Agent Skills](https://skills.sh/) are reusable capabilities for AI coding agents. They provide procedural knowledge that helps agents write Regle validation code more effectively. Regle provides six skills covering core usage, validation rules, advanced patterns, schema integration, TypeScript, and migrating from Vuelidate. ## Install ```bash npx skills add victorgarciaesgi/regle ``` This installs all Regle skills and configures them for your AI agent. ## Available skills ### `regle` Core Regle usage: * `useRegle` composable (state, rules, `r$` object) * Validation properties (`$invalid`, `$dirty`, `$error`, `$errors`, `$pending`) * Displaying errors (`getErrors`, `flatErrors`) * Modifiers (`autoDirty`, `lazy`, `silent`, `rewardEarly`, `validationGroups`) ### `regle-rules` Validation rules: * Built-in validation rules (`required`, `email`, `minLength`, etc.) * Custom rules (`createRule`, inline rules, async rules) * Rule wrappers (`withMessage`, `withParams`, `withAsync`, `withTooltip`) * Rule operators (`and`, `or`, `xor`, `not`, `pipe`, `applyIf`, `assignIf`) ### `regle-advanced` Advanced patterns: * Collections (`$each`, array validation) * Async validation (`$pending`, debouncing) * Server errors (`externalErrors`, dot-path errors) * Reset forms (`$reset` options) * Global configuration (`defineRegleConfig`, i18n) * Variants (`createVariant`, `narrowVariant`, discriminated unions) * Scoped validation (`useScopedRegle`, `useCollectScope`) * Merge multiple Regles (`mergeRegles`) * Object self validation (`$self`) ### `regle-schemas` Schema integration: * Schema libraries (`useRegleSchema` with Zod, Valibot, ArkType) * Standard Schema spec (`useRules`, `refineRules`, `InferInput`) ### `regle-typescript` TypeScript integration: * Type-safe output (`$validate` return type, `InferSafeOutput`) * Typing rules (`inferRules`, `RegleComputedRules`) * Typing component props (`InferRegleRoot`, `RegleFieldStatus`) ### `regle-migrate-vuelidate` Step-by-step migration guide to port Vuelidate forms (`useVuelidate`) to Regle (`useRegle` / `useScopedRegle`), covering imports, renamed helpers and properties, rules, validation properties, scoped validation, and parent-child form propagation. See [Migrate from Vuelidate](/introduction/migrate-from-vuelidate). --- --- url: /common-usage/collections.md description: API usage of $each --- # Validating arrays ## Declaring rules for collections Your forms may often include validations for collections where you need to validate multiple items sharing a nested structure. This can be easily achieved using `$each` in the rules declaration. You can also add validations for the field containing the array itself. :::warning Due to a JavaScript limitation with [Primitives](https://developer.mozilla.org/en-US/docs/Glossary/Primitive), it's recommended to use only arrays of objects. Primitives (Strings, Numbers etc...) are immutable, so they can't be modified to add a tracking ID (which is how Regle works for collections). ::: ```ts const form = ref<{ collection: { name: string }[] }>({ collection: [] }) const { r$ } = useRegle(form, { collection: { $each: { name: { required }, } } }) ``` ## Displaying collection errors For collections, the best way to display errors is to bind your list to the `$each` linked to your state. In this example, `r$.collection.$each`. Alternatively, you can map your errors using `r$.$errors.collection.$each`. ```vue twoslash ``` Result: :::warning If your array is empty, Regle can't know if it's supposed to be considered a field or a collection, only type-wise. Be sure to declare even an `$each` object in the client rules to tell Regle that the array is to be treated as a collection. ```ts const { r$ } = useRegle({collection: [] as {name: string}}, { collection: { $each: {} }, }) ``` ::: ## Validating the array independently Sometimes, you may want to validate not only each field in every element of the array but also the array itself, such as its size. You can do this just like you would with a normal field. Errors can be displayed either using `r$.$errors.[field].$self` or `r$.[field].$self.$errors`. ```ts import { useRegle } from '@regle/core'; const form = ref<{ collection: Array<{ name: string }> }>({ collection: [{ name: '' }], }); const { r$ } = useRegle(form, { collection: { $rewardEarly: true, minLength: minLength(4), $each: { name: { required }, }, }, }); ``` Result: ## Accessing the current item state In each item of your collection, you may have a validation that depends on another property of the item. You can access the current item's state and index by providing a function callback to `$each`. ```ts import { useRegle } from '@regle/core'; const form = ref({ collection: [{ name: '', condition: false }], }); const { r$ } = useRegle(form, { collection: { $each: (item, index) => ({ name: { required: requiredIf(() => item.value.condition) }, }), }, }); ``` Result: ## Providing a custom key to track items By default, Regle generates a random ID to track your items and maintain their state through mutations. This ID is stored in `$id` and can be used in Vue as a `key` for rendering. You can also provide your own key to the rule for custom tracking: ```ts import { useRegle } from '@regle/core'; const form = ref({ collection: [{ name: '', uuid: '28xja83' }], }); const { r$ } = useRegle(form, { collection: { $each: (item) => ({ $key: item.value.uuid, name: { required }, }), }, }); ``` --- --- url: /common-usage/usage-with-pinia.md description: 'Since Regle is headless, you can use it anywhere in your app' --- # Usage with Pinia Since Regle is headless, you can use it anywhere in your app — whether in a composable or a store. Using a Pinia store is an excellent way to avoid prop drilling with multiple properties while maintaining type inference seamlessly across your components. ## Using regle in a Pinia store ::: code-group ```ts [demo.store.ts] import { required, minLength, email } from '@regle/rules'; import { defineStore } from 'pinia'; import { useRegle } from '@regle/core'; export const useDemoStore = defineStore('demo-store', () => { const { r$ } = useRegle({ email: '' }, { email: { required, minLength: minLength(4), email } }) return { r$ } }) ``` ```vue [ComponentA.vue] ``` ```vue [ComponentB.vue] ``` ::: Component A: Component B: ## Avoid hydration issues If you use `store.$dispose()` or Nuxt in SSR mode, you may encounter this error: ``` Uncaught TypeError: 'set' on proxy: trap returned falsish for property 'xxx' ``` This is because Pinia tries to hydrate the stateful property `r$`. To avoid this, you can use [skipHydrate](https://pinia.vuejs.org/api/pinia/functions/skipHydrate.html#skipHydrate-) ```ts [pinia.store.ts] import { skipHydrate } from 'pinia'; export const usePiniaStore = defineStore('pinia-store', () => { const {r$} = useRegle(/** */) return { r$: skipHydrate(r$) }; }); ``` :::info If you are using Nuxt, you can use the `@regle/nuxt` module that will automatically skip the hydration of the `r$` property. ::: --- --- url: /common-usage/async-validation.md --- # Async validation A common pattern in forms is to asynchronously check for a value on the server side. Async rules can perform these tasks, and they update the `$pending` state whenever invoked. :::tip By default, all async rules will be debounced by `200ms`. It can be overridden with the `$debounce` modifier. ::: ## Inline Async rule To declare an async rule, you simply have to use the `async await` syntax. ```ts const myAsyncRule = async (value: Maybe) => { if (isFilled(value)) { return await someStuff(); } return true; } ``` If your rule doesn't use `async await` syntax, but still returns a `Promise`, you have to use the `withAsync` helper when using your rule in your form. Otherwise, Regle can't know it's an async rule. ## Async using `createRule` In the same way of an inline rule, your validator function must be using `async await` to be declared as an async rule. ```ts const myAsyncRule = createRule({ async validator(value: Maybe) { if (isFilled(value)) { return await someStuff(); } return true; }, message: 'Error' }) ``` ## `$pending` property Every time you update the `$value` of the field using an async rule, the `$pending` [validation property](/core-concepts/validation-properties#pending) will be updated. The `$error` status depends on `$pending` so a field cannot be errored if it's still pending. This can be used to display a loading icon and a custom message indicating that an operation is taking time. ## Full example ```vue [App.vue] ``` --- --- url: /common-usage/reset-form.md description: How to reset forms and fields --- # Reset forms Regle offers multiple options to reset a form. It depends on your use case and what you want to achieve. It can be either: * Only resetting the validation state ($dirty, $invalid, $pending etc..) * Only resetting the form state ($value) and keeping the validation state * Resetting the form state to a given state and keeping the validation state * Resetting the form state to a given state and clearing the validation state * Reset both form state and validation state to a pristine state ## Basic usage, with `$reset` method The `$reset` method is available on every nested instance of field of a form. So you can reset fields individually or the whole form. ### Options The `$reset` method accepts an options object with the following properties: #### `toInitialState` * **Type:** `boolean` * **Description:**\ Reset validation status and reset form state to its initial state.\ Initial state is different from the original state, as it can be mutated when using `$reset`. This serves as the base comparison for `$edited`.\ ⚠️ This doesn't work if the state is a `reactive` object. #### `toOriginalState` * **Type:** `boolean` * **Description:**\ Reset validation status and reset form state to its original state.\ Original state is the unmutated state that was passed to the form when it was initialized. #### `toState` * **Type:** `TState` or `() => TState` * **Description:**\ Reset validation status and reset form state to the given state. Also sets the new state as the new initial state. #### `clearExternalErrors` * **Type:** `boolean` * **Description:**\ Clears the `$externalErrors` and `$externalIssues` state back to an empty object. ```ts r$.$reset(); // Only reset validation state, set the initialValue as the current value ``` ```ts r$.$reset({ toInitialState: true }); // Reset validation state and form state to initial state ``` ```ts r$.$reset({ toOriginalState: true }); // Reset validation state and form state to original state ``` ```ts r$.$reset({ toState: { email: 'test@test.com' } }); // Reset validation state and form state to the given state ``` ```ts r$.$reset({ clearExternalErrors: true }); // Clear $externalErrors and $externalIssues state ``` ## Example ```vue ``` Result: --- --- url: /common-usage/standard-schema.md description: Use Regle as a Standard Schema library --- # Standard Schema Regle implements the [Standard Schema](https://standardschema.dev/) specification. This means that you can use Regle on any third party package that supports the Standard Schema spec. Regle can also use itself as a schema library when using `useRegleSchema`. ## Usage ```ts import { useRegle } from '@regle/core'; import { required } from '@regle/rules'; const { r$ } = useRegle({ name: '' }, { name: { required } }) const result = await r$.['~standard'].validate({ name: '' }); console.log(result.issues); ``` ### Schema only usage ```ts import { useRules } from '@regle/core'; import { required, string } from '@regle/rules'; const schema = useRules({ name: { string, required } }) const result = await schema['~standard'].validate({ name: '' }); ``` ## Composition usage ```vue ``` ### `InferInput` `InferInput` is an utility type that can produce a object state from any rules object. It will try to extract the possible state type that a rule may have, prioritizing rules that have a strict input type. Some rules may have `unknown` type because it could apply to any value. To cover this, there is now type-helpers rules to help you type your state from the rules: `type`, `string`, `number`, `boolean`, `date`. :::info Some types like `numeric` will feel weird as it's typed `string | number`, it's normal as the rule can also validate numeric strings. You can enforce the type by applying `number` rule to it. ::: ```ts twoslash import {ref} from 'vue'; // ---cut--- import { defineRules, type InferInput} from '@regle/core'; import { required, string, numeric, type } from '@regle/rules'; /* defineRules is not required, but it helps you catch errors in structure */ const rules = defineRules({ firstName: { required, string }, count: { numeric }, enforceType: { required, type: type<'FOO' | 'BAR'>()} }) type State = InferInput; // ^? ``` ## `useRules` `useRules` is a composable that allows you to write your rules in a more declarative way. It works exactly like `useRegle`, but it doesn't accept a state parameter, it will create an empty state from the rules. ```ts twoslash import { useRules, type InferInput } from '@regle/core'; import { required, string } from '@regle/rules'; const r$ = useRules({ name: { required, string }, }); ``` Result: ## `refineRules` Regle is state first because in real world forms, rules can depend a state values.\ This make it a problem for dynamic rules as it would make a cyclic type error when trying to use the state inside the rules. To cover this case and inspired by Zod's `refine`, Regle provides a `refineRules` helper to write dynamic rules that depend on the state, while making it possible to access a typed state. Anything returned by the rule refine function will override what's defined in the default rules. ```ts twoslash import {ref} from 'vue'; // ---cut--- import { refineRules, type InferInput} from '@regle/core'; import { required, string, sameAs } from '@regle/rules'; const rules = refineRules({ password: { required, string }, }, (state) => ({ confirmPassword: { required, sameAs: sameAs(() => state.value.password) } }) ) type State = InferInput; // ^? ``` --- --- url: /common-usage/external-errors.md description: Handle server side errors with Regle --- # External errors and issues Regle handles only client side errors. But some validation may need to be submitted to a server and returned to the client. To handle this, you can use the `externalErrors` modifier, or `externalIssues` when the server returns structured metadata. It matches the structure of your form, but you can also use dot path to define the errors. ## Basic usage ```ts import { type RegleExternalErrorTree, useRegle } from '@regle/core' const form = reactive({ email: '', name: { pseudo: '', }, }) const externalErrors = ref>({}); const { r$ } = useRegle( form, { email: { required }, name: { pseudo: { required } }, }, { externalErrors, } ); async function submit() { const {valid} = await r$.$validate(); if (valid) { r$.$setExternalErrors({ email: ["Email already exists"], name: { pseudo: ["Pseudo already exists"] }, }) } } ``` Use `externalIssues` when you need to keep metadata on each server issue. Its `$message` is still reflected in `$errors`, while the full object is available from `$issues`. ```ts import { type RegleExternalIssueTree, useRegle } from '@regle/core' const externalIssues = ref>({}); const { r$ } = useRegle(form, rules, { externalIssues }); r$.$setExternalIssues({ email: [ { $message: 'Email already exists', $property: 'email', code: 'EMAIL_TAKEN', }, ], }); ``` ### Typing issue metadata Server payloads often include extra fields (`code`, `field`, i18n keys, and so on). Regle exposes them on `$issues` while still using `$message` for `$errors`. Augment `RegleIssueCustomMetadata` once (for example in `regle.d.ts` next to your app entry). Keys you add are merged into `RegleFieldIssue` and typed on field `$issues`, including issues coming from `externalIssues`. `RegleExternalFieldIssue` only requires `$message`. Built-in fields such as `$property` and your custom metadata stay optional when you build the object passed to `externalIssues` or `$setExternalIssues`. ```ts [regle.d.ts] declare module '@regle/core' { interface RegleIssueCustomMetadata { code?: string; } } ``` ```ts const externalIssues = ref>({ email: [{ $message: 'Email already exists', code: 'EMAIL_TAKEN' }], }); // After Regle applies the issue: r$.email.$issues[0].code; // string | undefined r$.email.$errors[0]; // 'Email already exists' ``` `externalErrors` and `externalIssues` are mutually exclusive. Setting one clears the other, so the latest server payload is the source of truth. Result: :::warning If you're working with collections and server-only validations, you'll have to at least specify an empty `$each` object in the client rules to tell Regle that the array is to be treated as a collection ```ts const { r$ } = useRegle({collection: []}, { collection: { $each: {} }, }, { externalErrors }) ``` ::: ## Dot path errors `externalErrors` and `externalIssues` can also be used to handle dot path errors. It can be handy for some backend frameworks that return errors with dot path. ```ts import { useRegle } from '@regle/core'; const form = reactive({ email: '', name: { pseudo: '', }, collection: [{name: ''}] }) const externalErrors = ref>({}); const { r$ } = useRegle(form, {}, { externalErrors }) async function submit() { const {valid} = await r$.$validate(); if (valid) { r$.$setExternalErrors({ email: ["Email already exists"], "name.pseudo": ["Pseudo already exists"], "collection.0.name": ["Name already exists"] }) } } ``` ## Without the `externalErrors` option `r$.$setExternalErrors` also works when you don't provide the `externalErrors` option. Regle stores those errors internally. ```ts const form = reactive({ email: '', name: { pseudo: '', }, }) const { r$ } = useRegle(form, { email: { required }, name: { pseudo: { required } }, }) async function submit() { const { valid } = await r$.$validate(); if (!valid) return; r$.$setExternalErrors({ email: ['Email already exists'], 'name.pseudo': ['Pseudo already exists'], }); } ``` ## Clearing errors By default, when you set the external errors, Regle will keep them until the form is validated or modified again. This behavior can be modified with these options: ### `clearExternalErrors` ```ts r$.$reset({ clearExternalErrors: false }); ``` ### `clearExternalErrorsOnValidate` ```ts import { useRegle } from '@regle/core'; const { r$ } = useRegle(form, {}, { externalErrors, clearExternalErrorsOnValidate: true }) ``` ### `clearExternalErrorsOnChange` ```ts import { useRegle } from '@regle/core'; const { r$ } = useRegle(form, {}, { externalErrors, clearExternalErrorsOnChange: false }) ``` ### `$clearExternalErrors()` You can also clear the errors manually by calling the `$clearExternalErrors` method. ```ts r$.$clearExternalErrors(); ``` For structured issues, use the matching methods and options: ```ts r$.$setExternalIssues({ email: [{ $message: 'Email already exists', code: 'EMAIL_TAKEN' }], }); r$.$clearExternalIssues(); r$.$reset({ clearExternalErrors: true }); ``` `clearExternalErrorsOnValidate`, `clearExternalErrorsOnChange`, and `clearExternalErrors` also apply to structured external issues. --- --- url: /advanced-usage/rule-metadata.md description: Rule validator functions can return more than just a boolean --- # Rules metadata Rule validator functions can return more than just a boolean. It can return any object as long as it returns an object containing at least `$valid: boolean`. This additional data can be utilized by your `message` handler, `active` handler, or any other part of your application that has access to the regle instance. ```ts twoslash // @noErrors import {withMessage} from '@regle/rules'; import {useRegle} from '@regle/core'; const inlineRule = withMessage((value: unknown) => { return { $valid: true, myCustomMetadata: 100 } }, ({myCustomMetadata}) => `Hello ${myCustomMetadata}`) // ^? const { r$ } = useRegle({name: ''}, { name: {inlineRule} }) r$.name.$rules.inlineRule.$metadata. // ^| ``` ## Using metadata in `createRule` You can use `createRule` to define your custom rules. Let's explore a real-world example by creating a password strength validator. :::code-group ```ts twoslash include strongPassword [strongPassword.ts] // @module: esnext // @filename strongPassword.ts // ---cut--- import { createRule, Maybe } from '@regle/core'; import { isFilled } from '@regle/rules'; import { passwordStrength, type Options } from 'check-password-strength'; export const strongPassword = createRule({ validator(value: Maybe, options?: Options) { if (isFilled(value)) { const result = passwordStrength(value, options); return { $valid: result.id > 1, result, }; } return { $valid: true }; }, message({ result }) { return `Your password is ${result?.value}`; }, }); ``` ```vue twoslash [ComponentA.vue] ``` ::: Result: --- --- url: /advanced-usage/global-config.md --- # Global configuration If your app includes multiple forms, it can be helpful to define a global configuration that centralizes your custom validators, error messages, and modifiers. This eliminates the need to declare these settings repeatedly for every `useRegle` call, improving both code consistency and developer experience with features like autocompletion and type checking. Regle offers two ways to define your global configuration: * **Composable** — use `defineRegleConfig` to create a custom `useRegle` composable that encapsulates your configuration. Best for scoped or modular setups. * **Declarative** — use `defineRegleOptions` with the `RegleVuePlugin` to provide configuration at the app level via Vue's plugin system. Best for app-wide defaults that apply everywhere, including the default `useRegle`. ## Composable {#composable} `defineRegleConfig` creates and returns a custom `useRegle` composable (along with `inferRules` and `useRules`) that has your configuration baked in. This is ideal when you want strong typing and autocompletion for your custom rules. ### Replace built-in rules messages Each `@regle/rules` rule provides a default error message. You may not want to call `withMessage` every time you need to use one with a custom error message. `defineRegleConfig` allows you to redefine the default messages of built-in rules. ```ts import { defineRegleConfig } from '@regle/core'; import { withMessage, minLength, required } from '@regle/rules'; const { useRegle: useCustomRegle } = defineRegleConfig({ rules: () => ({ required: withMessage(required, 'You need to provide a value'), minLength: withMessage(minLength, ({ $value, $params: [min] }) => { return `Minimum length is ${min}. Current length: ${$value?.length}`; }) }) }) const { r$ } = useCustomRegle({ name: '' }, { name: { required, minLength: minLength(6) } }) ``` Result: :::tip If you use Nuxt, check out the [Nuxt module](/integrations/nuxt) for a even better DX.\ It provides a way to add your custom global config to your auto-imports. ::: #### i18n You can also use any i18n library directly inside the config. ```ts import { defineRegleConfig } from '@regle/core'; import { withMessage, minLength, required } from '@regle/rules'; import { useI18n } from 'vue-i18n'; const { useRegle: useCustomRegle } = defineRegleConfig({ rules: () => { const { t } = useI18n() return { required: withMessage(required, t('general.required')), minLength: withMessage(minLength, ({ $value, $params: [max] }) => { return t('general.minLength', {max}); }) } } }) ``` ### Declare new rules While `useRegle` allows you to use any rule key, adding custom rules to the global configuration provides autocompletion and type checking. This improves maintainability and consistency across your application. ```ts twoslash const someAsyncCall = async () => await Promise.resolve(true); // ---cut--- // @noErrors import { defineRegleConfig, createRule, type Maybe } from '@regle/core'; import { withMessage, isFilled } from '@regle/rules'; const asyncEmail = createRule({ async validator(value: Maybe) { if (!isFilled(value)) { return true; } const result = await someAsyncCall(); return result; }, message: 'Email already exists', }); const { useRegle: useCustomRegle } = defineRegleConfig({ rules: () => ({ asyncEmail }) }) const { r$ } = useCustomRegle({ name: '' }, { name: { asy // ^| } }) ``` ### Declare modifiers You can include global modifiers in your configuration to automatically apply them wherever you use the `useRegle` composable. This avoids repetitive declarations and keeps your code clean. ```ts import { defineRegleConfig } from '@regle/core'; import { withMessage, minLength, required } from '@regle/rules'; export const { useRegle: useCustomRegle } = defineRegleConfig({ modifiers: { autoDirty: false, silent: true, lazy: true, rewardEarly: true, // Applies to every async field. A per-field `$debounce` still takes priority. debounce: 500 } }) ``` ### Export scoped `inferRules` helper `defineRegleConfig` also returns a scoped `inferRules` helper, similar to the one exported from `@regle/core`, but that will autocomplete and check your custom rules. For information about `inferRules`, check [Typing rules docs](/typescript/typing-rules) ```ts import { defineRegleConfig } from '@regle/core'; import { withMessage, minLength, required } from '@regle/rules'; export const { useRegle, inferRules } = defineRegleConfig({/* */}) ``` ### Extend global config It's also possible to add additional config to an already created custom `useRegle`. With `extendRegleConfig`, you can recreate a custom one with a existing composable as an input. ```ts twoslash // @noErrors import { defineRegleConfig, extendRegleConfig, createRule } from '@regle/core'; import { withMessage, required } from '@regle/rules'; const { useRegle: useCustomRegle } = defineRegleConfig({ rules: () => ({ customRule: withMessage(required, 'Custom rule'), }) }) const {useRegle: useExtendedRegle} = extendRegleConfig(useCustomRegle, { rules: () => ({ customRuleExtended: withMessage(required, 'Custom rule 2'), }) }) useExtendedRegle({name: ''}, { name: { custom // ^| } }) ``` ## Declarative {#declarative} The declarative approach uses `defineRegleOptions` combined with the `RegleVuePlugin` to provide global configuration at the Vue app level. The configuration is injected via Vue's `provide/inject` mechanism, so it applies to every `useRegle` call in your application — including the default one from `@regle/core`. This is especially useful when you want app-wide defaults without needing to import a custom composable everywhere. ### Setup Pass your options as the second argument to `app.use`: ```ts [main.ts] import { createApp } from 'vue'; import { RegleVuePlugin, defineRegleOptions } from '@regle/core'; import { withMessage, required, minLength } from '@regle/rules'; import App from './App.vue'; const options = defineRegleOptions({ rules: () => ({ required: withMessage(required, 'You need to provide a value'), minLength: withMessage(minLength, ({ $value, $params: [min] }) => { return `Minimum length is ${min}. Current length: ${$value?.length}`; }) }), modifiers: { autoDirty: false, }, shortcuts: { fields: { $isRequired: (field) => field.$rules.required?.$active ?? false, }, }, }); const app = createApp(App); // Add the options to the RegleVuePlugin app.use(RegleVuePlugin, options); app.mount('#app'); ``` Now, every `useRegle` call in your app will automatically use the configured rules and modifiers — no need to import a custom composable. ```vue ``` ### Type augmentation To get full type-safety and autocompletion with the declarative approach, you can augment Regle's interfaces using TypeScript module augmentation. This lets the default `useRegle` composable know about your custom rules and shortcut properties. ```ts [regle.config.ts] import { createRule } from '@regle/core'; const customRule = createRule({ validator: (value: unknown) => value === 'custom', message: 'Custom rule', }); declare module '@regle/core' { interface CustomRules { customRule: typeof customRule; } interface CustomFieldProperties { $isRequired: boolean; } interface CustomNestedProperties { $isEmpty: boolean; } interface CustomCollectionProperties { $isEmpty: boolean; } } ``` ### Combining with composable configuration If both the plugin options and a `defineRegleConfig` composable are used, the composable's configuration takes precedence and is merged on top of the plugin's. This lets you set app-wide defaults via the plugin while still allowing scoped overrides where needed. ## Override default behaviors You can override the default behaviors of Regle processors by using the `overrides` property. This works with both the composable and declarative approaches. ### `isEdited` Override the default `$edited` property handler. Useful to handle custom comparisons for complex object types. :::warning It's highly recommended to use this override with the [`markStatic`](/advanced-usage/immutable-constructors) helper to handle immutable constructors. ::: ```ts import { defineRegleConfig } from '@regle/core'; import { Decimal } from 'decimal.js'; export const { useRegle: useCustomRegle } = defineRegleConfig({ overrides: { isEdited(currentValue, initialValue, defaultHandlerFn) { if (currentValue instanceof Decimal && initialValue instanceof Decimal) { return currentValue.toNearest(0.01).toString() !== initialValue.toNearest(0.01).toString(); } // fallback to the default handler return defaultHandlerFn(currentValue, initialValue); }, }, }) // Or with the declarative approach import { defineRegleOptions } from '@regle/core'; const options = defineRegleOptions({ overrides: { isEdited: (currentValue, initialValue, defaultHandlerFn) => { return currentValue !== initialValue; }, }, }); app.use(RegleVuePlugin, options); ``` --- --- url: /advanced-usage/self-validation.md description: Validate objects directly using $self to apply rules at the object level --- # Object self validation By default, Regle validates the nested properties of an object. But sometimes you need to validate the object itself, to perform cross-field validation that depends on multiple properties. The `$self` property allows you to apply validation rules directly to an object, giving you full control over object-level validation. ## Basic usage Use `$self` to define rules that validate the object itself rather than its nested properties. ```ts twoslash import { ref } from 'vue'; import { useRegle, type Maybe } from '@regle/core'; import { required, isFilled, minLength, withMessage } from '@regle/rules'; type User = { firstName: string; lastName: string; }; const state = ref<{ user: User }>({ user: { firstName: '', lastName: '', }, }); const { r$ } = useRegle(state, { user: { $self: { // Custom rule: at least one name must be filled atLeastOneName: withMessage( (value: Maybe) => isFilled(value?.firstName) || isFilled(value?.lastName), 'At least one name is required' ), }, firstName: { minLength: minLength(3) }, lastName: { minLength: minLength(3) }, }, }); ``` In this example the `atLeastOneName` custom rule validates that at least one of `firstName` or `lastName` is filled. ## Accessing `$self` status The `$self` status is accessible through `r$.fieldName.$self` and provides all standard field status properties: ```vue ``` Result: --- --- url: /advanced-usage/variants.md description: Define variants rules for your discriminated unions --- # Variants or discriminated unions Your form may not be linear, and have multiple fields that depends on a condition or a toggle. It can be complex and become a mess when trying to organise your types around it. Regle variants offer a way to simply declare and use this discriminated unions, while keeping all fields correctly types and also runtime safe. ## `createVariant` The first step to Regle variants is to have a type that includes a discriminated variant. ```ts twoslash include form-types type FormStateLoginType = | {type: 'EMAIL', email: string} | {type: 'GITHUB', username: string} | {type?: undefined} type FormState = { firstName?: string; lastName?: string; } & FormStateLoginType ``` Here your state can have two possible outcomes, but with classic rules it's hard to handle fields statuses as they can always be undefined. The solution to this is to first declare your variant-related rules inside `createVariant` like this: ```ts twoslash include main import {ref} from 'vue'; // @include: form-types // ---cut--- import { useRegle, createVariant} from '@regle/core'; import { literal, required, email } from '@regle/rules'; const state = ref({}) // ⚠️ Use getter syntax for your rules () => {} or a computed one const {r$} = useRegle(state, () => { /** * Here you create you rules variations, see each member as a `OR` * `type` here is the discriminant * * Depending of the value of `type`, Regle will apply the corresponding rules. */ const variant = createVariant(state, 'type', [ {type: { literal: literal('EMAIL')}, email: { required, email }}, {type: { literal: literal('GITHUB')}, username: { required }}, {type: { required }}, ]); return { firstName: {required}, // Don't forget to return the computed rules ...variant.value, }; }) ``` ## `narrowVariant` In your form, you'll need to use type narrowing to access your field status somehow. For this you'll have to discriminate the `$fields` depending on value. As the status uses deeply nested properties, this will not be possible with a standard guard `if (value === "EMAIL")`. In your template or script, you can use Regle's `narrowVariant` helper to narrow the fields to the value. Let's take the previous example again: ```vue twoslash ``` Result: ### Nested variants All the above also works for nested variants ```ts twoslash include nested-types type FormState = { firstName?: string; lastName?: string; login: | {type: 'EMAIL', email: string} | {type: 'GITHUB', username: string} | {type?: undefined} } ``` :::warning The first argument of `createVariant` needs to be reactive. For nested values, use getter syntax. ::: ```ts twoslash include nested-regle import {ref, defineComponent} from 'vue'; const Errors = defineComponent({}); // @include: nested-types // ---cut--- import { useRegle, createVariant} from '@regle/core'; import { literal, required, email } from '@regle/rules'; const state = ref({ firstName: '', login: {} }) const {r$} = useRegle(state, () => { const loginVariant = createVariant(() => state.value.login, 'type', [ {type: { literal: literal('EMAIL')}, email: { required, email }}, {type: { literal: literal('GITHUB')}, username: { required }}, {type: { required}}, ]); return { firstName: {required}, login: loginVariant.value }; }) ``` In the component: ```vue twoslash ``` ## `variantToRef` A use case is also to have a narrowed **Ref** ready to be used and isn't tied to a block scope. Like in the root of a script setup component where you're sure only one variant is possible. Having a `variantToRef` helper prevents you from creating custom `computed` methods, which would make you lose the `v-model` compatibilities of the `.$value`. The **ref** will be reactive and already typed as the variant you defined, while still needing to be checked for nullish. :::code-group ```vue twoslash [Github.vue] ``` ```ts twoslash [form.store.ts] import {ref} from 'vue'; import { defineStore, skipHydrate} from 'pinia'; import { useRegle, createVariant} from '@regle/core'; import { literal, required, email } from '@regle/rules'; // @include: form-types export const useFormStore = defineStore('form', () => { const state = ref({}); const {r$} = useRegle(state, () => { const variant = createVariant(state, 'type', [ {type: { literal: literal('EMAIL')}, email: { required, email }}, {type: { literal: literal('GITHUB')}, username: { required }}, {type: { required}}, ]); return { firstName: {required}, ...variant.value, }; }) return { r$: skipHydrate(r$), } }) ``` ::: ### `unsafeAssertion` option When using `variantToRef` in a component it happens that the assertion is done by the parent component, which means you know the variant assertion will always be valid in the entire component. For this case you can pass an option to assert that the variant is always defined. ```ts import { variantToRef } from '@regle/core'; const variant$ = variantToRef(r$, 'type', 'EMAIL', { unsafeAssertion: true }); // ^ Removes the `undefined` ``` --- --- url: /advanced-usage/scoped-validation.md description: >- Scoped validation in Regle is made to port Vuelidate's nested component validation --- # Scoped validation Scoped validation in Regle is made to port Vuelidate's `nested component validation`. Problems with Vuelidate's approach: * Performances * Not declarative * Usage (too magic for the user) * Type safety * Restricted to DOM * Have to play with `$scope` and `$stopPropagation` to avoid unwanted behaviour Regle's solution solves all these problems ## Collecting validation with `useCollectScope` and `useScopedRegle` ### `useScopedRegle` `useScopedRegle` is a clone of `useRegle`, but with the difference that every time it's used and updated, its state will be collected by the same scope created using `createScopedUseRegle`. Every time it's called, a instance will be added for `useCollectScope` to collect. It can be called multiple times at any place, not only on components, as it's not restricted by DOM. ### `useCollectScope` This composable allow you to retrieve every Regle instances created using the sibling composable `useScopedRegle`. Children properties like `$value` and `$errors` will not be objects, and are converted into arrays instead. You will also have access to every validation properties like `$error`, `$invalid` etc... :::code-group ```vue [Parent.vue] ``` ```vue [Child1.vue] ``` ```vue [Child2.vue] ``` ::: Result: ## Multiple scopes If you want to create your own separated scope, you can use `createScopedUseRegle` helper method. It's advised to change the name of this composable to avoid conflicts or issues. ```ts [scoped-config.ts] import { createScopedUseRegle } from '@regle/core'; export const { useScopedRegle, useCollectScope } = createScopedUseRegle(); export const { useScopedRegle: useContactsRegle, useCollectScope: useCollectContacts } = createScopedUseRegle(); ``` ## Namespaces inside scopes Each scope can collect a specific namespace. Giving a namespace name will collect only the children with the same namespace name. The namespace can be reactive, so it will update every time it changes. In this example, only the components using the same scope and namespace will be collected. :::code-group ```vue [Parent.vue] ``` ```vue [Child1.vue] ``` ```vue [Child2.vue] ``` You can also collect multiple namespaces at once by passing an array of namespace names to the `useCollectScope` function. ```ts const { r$ } = useCollectScope(['contacts', 'persons']); ``` ::: ## Inject global config If you have a global config already registered, simply pass it as a parameter to your `createScopedUseRegle` function. ```ts twoslash [scoped-config.ts] import { createScopedUseRegle, defineRegleConfig } from '@regle/core'; import { required, withMessage } from '@regle/rules'; const { useRegle } = defineRegleConfig({ rules: () => ({ custom: withMessage(required, 'Custom error'), }), }); export const { useScopedRegle, useCollectScope } = createScopedUseRegle({customUseRegle: useRegle}); // @noErrors const {r$} = useScopedRegle({name: ''}, { name: { cus // ^| } }) ``` ## Custom store for instances By default collected instances are stored in a local ref. You can provide your own store ref. ```ts import { createScopedUseRegle, type ScopedInstancesRecordLike } from '@regle/core'; // Having a default const myCustomStore = ref({}); const { useScopedRegle, useCollectScope } = createScopedUseRegle({customStore: myCustomStore}); ``` ## Collect instances in a Record By default collected instances are stored in a readonly array. If you want to store your nested instances in a record it's possible with the `asRecord` option. This will **require** every nested `useScopeRegle` to provide a parameter `id`. :::code-group ```ts [scoped-config.ts] import { createScopedUseRegle } from '@regle/core'; export const { useScopedRegle: useScopedRegleItem, useCollectScope: useCollectScopeRecord } = createScopedUseRegle({ asRecord: true }); ``` ```vue [Parent.vue] ``` ```vue [Child1.vue] ``` ```vue [Child2.vue] ``` ::: ## Manually dispose or register a scope entry `useScopedRegle` also returns two methods: `dispose` and `register`. You can then programmatically handle if your component is collected from inside. ```vue ``` ## Manual typing :::warning Use with care, only if you're 100% sure of what return type your collected types will have. The order of the collected values can change depending on if they added/deleted. This is here for convenience but not advised. ::: ```ts twoslash import { useCollectScope } from '@regle/core'; const { r$ } = useCollectScope<[{ foo: string }]>(); const { valid, data } = await r$.$validate(); // ^? ``` ## Testing If you write unit test for a component using scoped validation, you may encounter problems with the tests failing. To solve this, mount your component with the `attachTo` option set to the document element. ```ts import { mount } from '@vue/test-utils'; import ComponentUsingCollectScope from './ComponentUsingCollectScope.vue'; const wrapper = mount(ComponentUsingCollectScope, { attachTo: document.documentElement, }); ``` --- --- url: /advanced-usage/extend-properties.md description: >- Regle offers a way to extend the default validation properties with defineRegleConfig --- # Extend properties Regle offers a way to extend the default validation properties with `defineRegleConfig`. For more information about global config [check here](/advanced-usage/global-config) ## Extending field properties ```ts twoslash import { required } from '@regle/rules'; // @noErrors // ---cut--- import { defineRegleConfig } from '@regle/core'; const { useRegle } = defineRegleConfig({ shortcuts: { fields: { $isRequired: (field) => field.$rules.required?.$active ?? false; } } }); const { r$ } = useRegle({ name: '' }, { name: { required } }) r$.name.$isRe // ^| ``` ## Extending nested object properties ```ts twoslash import { required } from '@regle/rules'; // @noErrors // ---cut--- import { defineRegleConfig } from '@regle/core'; const { useRegle } = defineRegleConfig({ shortcuts: { nested: { $isEmpty: (nest) => Object.keys(nest.$fields).length === 0; } } }); const { r$ } = useRegle({ user: {} } as { user: { firstName?: string, lastName?: string } }, { user: { firstName: {required} } }) r$.user.$is // ^| ``` ## Extending collections properties ```ts twoslash import { required } from '@regle/rules'; // @noErrors // ---cut--- import { defineRegleConfig } from '@regle/core'; const { useRegle } = defineRegleConfig({ shortcuts: { collections: { $isArrayEmpty: (collection) => collection.$each.length === 0; } } }); const { r$ } = useRegle({ projects: [{ name: '' }] }, { projects: { $each: { name: { required } } } }) r$.projects.$is // ^| ``` ## Typing shortcuts in component props When defining shortcuts, it can be hard to type props in common Input components. For this Regle provides a type helper that can ease the declaration of these props. :::code-group ```ts twoslash include config [config.ts] // @module: esnext // @filename config.ts // ---cut--- import { defineRegleConfig } from '@regle/core'; export const { useRegle: useCustomRegle } = defineRegleConfig({ shortcuts: { fields: { $test: () => true, }, }, }); ``` ```vue twoslash [myInput.vue] ``` ::: --- --- url: /advanced-usage/merge-regles.md --- # Merge multiple Regles If you need to combine multiple Regle instances into one, it's possible with the `mergeRegles` helper. ## When to use `mergeRegles` is useful when: * You have forms split across multiple components and need to validate them together * You want to compose smaller, reusable form sections into a larger form * Different parts of your form have different validation lifecycles but need unified submission ## Basic usage The helper returns an output similar to the main `r$`, while still being able to call `$touch`, `$validate`, or `$reset` on all merged instances at once. All types are preserved, giving you full autocomplete support. ```ts twoslash import {required, numeric, email} from '@regle/rules'; // ---cut--- // @noErrors import { mergeRegles, useRegle } from '@regle/core'; const { r$ } = useRegle({email: ''}, { email: { required, email }, }); const { r$: otherR$ } = useRegle({firstName: ''}, { firstName: { required }, }); const r$Merged = mergeRegles({ r$, otherR$ }); r$Merged.$value.otherR$. // ^| ``` Result: ## Accessing merged data You can access each merged instance's data through the merged object: ```ts // Access values r$Merged.$value.r$.email r$Merged.$value.otherR$.firstName // Access validation states r$Merged.r$.email.$valid r$Merged.otherR$.firstName.$error // Access errors r$Merged.$errors.r$.email r$Merged.$errors.otherR$.firstName ``` ## Global operations The merged instance exposes global methods that operate on all child instances: ```ts // Validate all merged forms await r$Merged.$validate() // Reset all merged forms r$Merged.$reset({ toInitialState: true }) // Touch all fields r$Merged.$touch() ``` :::tip The merged instance's `$valid`, `$error`, and `$pending` properties reflect the combined state of all child instances. ::: --- --- url: /advanced-usage/immutable-constructors.md description: Use markStatic to handle immutable constructors --- # Handling immutable constructors Regle works by tracking changes in the state and updating the validation rules accordingly. This works great for objects and arrays, but not for immutable constructors (like `Decimal` from `decimal.js` or `Moment` from `moment.js`, etc...). This constructors will be interpreted as regular objects and their properties treated as nested fields. ## Default Usage To handle these cases, you can use the `markStatic` helper to mark the value as static and treat the constructor as a regular raw Field. ```vue ``` ## Schema Usage When using Regle with `@regle/schemas`, you will have to also declare the static constructor in the schema. ```ts import { markStatic } from '@regle/core' import { useRegleSchema } from '@regle/schemas' import { z } from 'zod' const StaticDecimal = markStatic(Decimal) const schema = z.object({ decimal: z.instanceof(StaticDecimal).refine((value) => value.toNumber() > 10), }) const { r$ } = useRegleSchema({ decimal: new StaticDecimal(0) }, schema) ``` ## `isStatic` helper You can use the `isStatic` helper to check if a value is a static value. ```ts import { isStatic } from '@regle/core'; const result = isStatic(r$.$value.decimal); // true ``` ## `UnwrapStatic` type helper You can use the `UnwrapStatic` type to unwrap a static value. ```ts import { type UnwrapStatic } from '@regle/core'; type value = UnwrapStatic; // Decimal ``` ## `isRegleStatic` type helper You can use the `isRegleStatic` type helper to check if a value is a static value. ```ts import { type isRegleStatic } from '@regle/core'; type isStatic = isRegleStatic; // true ``` --- --- url: /typescript/type-safe-output.md --- # Type safe output What would be the benefit of building a validation library without a type safe output? Inspired by the `Zod` parse output type, `Regle` will also infer your validator types to know which properties are ***guaranteed*** to be defined. ## `$validate` Using `r$.$validate` will asynchronously run and wait for all your validators to finish, and will return an object containing the status of your form. ```ts const { valid, data, errors, issues } = await r$.$validate(); ``` If your **result** is `true`, the **data** will be type safe. It will check the rules to get the ones that ensure the value is set (`required`, `literal`, `checked`) It will not work with `requiredIf` or `requiredUnless`, because we can't know the condition at build time. ```ts twoslash import { useRegle } from '@regle/core'; import { ref, type Ref, computed } from 'vue'; import { required } from '@regle/rules'; // ---cut--- type Form = { firstName?: string; lastName?: string; } const form = ref({ firstName: '', lastName: '' }) const { r$ } = useRegle(form, { lastName: { required }, }); async function submit() { const { valid, data, errors, issues } = await r$.$validate(); if (valid) { console.log(data); // ^? } } ``` ### `InferSafeOutput` You can also statically infer the safe output from any `r$` instance. ```ts twoslash import { ref, type Ref, computed } from 'vue'; import { required } from '@regle/rules'; // ---cut--- import { useRegle, InferSafeOutput } from '@regle/core'; type Form = { firstName?: string; lastName?: string; } const form: Ref = ref({ firstName: '', lastName: '' }) const { r$ } = useRegle(form, { lastName: { required }, }); type FormRequest = InferSafeOutput; // ^? ``` --- --- url: /typescript/typing-props.md --- # Typing props Forms often span multiple components, and splitting your logic across components is a common practice. Regle offers tools to help type your props correctly, ensuring type safety and improving developer experience. The best way to manage a centralized form state with inferred types is by using a Pinia store. Learn more in the Usage with Pinia guide [explained here](/common-usage/usage-with-pinia). If you cannot use Pinia, here are the alternative approaches. ## Typing component props As Regle's types are complex and based on both your state and your rules, it's hard to replicate manually. `@regle/core` exports all its utility types, it can be long to explain each one of them, so we'll show the simplest way to type your props. To avoid juggling with complex generic types, you can declare your form in a composable inside a file outside your component, and use this composable to type your props. :::code-group ```ts twoslash include useMyForm [useMyForm.ts] import { useRegle } from '@regle/core'; import { email, maxValue, minLength, numeric, required } from '@regle/rules'; export function useMyForm() { return useRegle( { email: '', user: { firstName: '', lastName: '' } }, { email: { required, email: email }, user: { firstName: { required, minLength: minLength(6), }, lastName: { minLength: minLength(6), }, }, } ); } ``` ```vue twoslash [Parent.vue] ``` ```vue twoslash [Child.vue] ``` ::: :::tip `InferRegleRoot` also works with `@regle/schemas` ::: ### Manually typing your state and rules If you happen to have no way of importing the type, you can still use `RegleRoot` type, that will allow you to manually type your state and rules. ```vue [MyForm.vue] ``` :::warning While this can be useful, be warned that `r$` will be missing the rule properties. ::: ## Typing a field prop It's possible that you have a `MyInput` like component that contains your business logic. You may want to pass regle computed properties to this component to display useful information to the user. Here's how you can do it: :::code-group ```vue [MyInput.vue] ``` ```vue [myForm.vue] ``` ::: ## Typing a field prop with global configuration :::code-group ```ts twoslash include config [config.ts] // @module: esnext // @filename config.ts import { withMessage } from '@regle/rules'; // ---cut--- import { defineRegleConfig } from '@regle/core'; export const { useRegle: useCustomRegle } = defineRegleConfig({ rules: () => ({ strongPassword: withMessage(() => true, 'test') }), shortcuts: { fields: { $test: () => true } } }); ``` ```vue twoslash [MyPassword.vue] ``` ::: ## Enforcing rules for a specific component On your common Input component, you can also enforce a rule to be present in the field. ```vue twoslash [MyPassword.vue] ``` ### For a custom configurations :::code-group ```ts twoslash include config [config.ts] // @module: esnext // @filename config.ts import { withMessage } from '@regle/rules'; // ---cut--- import { defineRegleConfig } from '@regle/core'; export const { useRegle: useCustomRegle } = defineRegleConfig({ rules: () => ({ strongPassword: withMessage(() => true, 'test') }), shortcuts: { fields: { $test: () => true } } }); ``` ```vue twoslash [MyPassword.vue] ``` ::: --- --- url: /typescript/typing-rules.md --- # Typing rules schema ## Computed rules When defining your rules schema in a separate computed property, you may lose autocompletion and detailed type checking for your rules. While useRegle will still perform type checks, the feedback will be less informative. To avoid this, it is recommended to use the `inferRules` utility. This ensures that the rules schema aligns perfectly with the state, preventing extra or mismatched properties. ### Example: Using inferRules The inferRules utility requires that your state be declared independently of useRegle. ```ts twoslash // @noErrors import { computed, ref } from 'vue'; // ---cut--- import { inferRules, useRegle } from '@regle/core'; const form = ref({ name: '' }); const rules = computed(() => inferRules(form, { n // ^| }) ); const { r$ } = useRegle(form, rules); ``` ### Example: Typing Without inferRules If you prefer not to use inferRules, you can type your rules explicitly using RegleComputedRules. To ensure type safety while retaining type inference, use the satisfies operator. ```ts twoslash // @noErrors import { computed, ref } from 'vue'; // ---cut--- import { useRegle, type RegleComputedRules } from '@regle/core'; const form = ref({ name: '' }); const rules = computed(() => ({ name: { required: () => true } } satisfies RegleComputedRules)) const { r$ } = useRegle(form, rules); ``` :::tip If you don’t want to write your rules in a separate computed property, you can still define them inline. ```ts const min = ref(1); useRegle({ name: '' }, () => ({ name: { minLength: minLength(min.value) } })) ``` ::: ## Typing external nested properties `inferRules` can be used for any nested properties and chained inside the same schema. ```ts twoslash // @noErrors import { inferRules, useRegle } from '@regle/core'; import { required } from '@regle/rules'; import { computed, ref } from 'vue'; const form = ref({ name: { firstName: '', lastName: '' } }); const nameRules = computed(() => inferRules(form.value.name, { firstName: { required }, lastName: { r }, // ^| }) ); const rules = computed(() => inferRules(form, { name: nameRules.value, }) ); const { r$ } = useRegle(form, rules); ``` :::tip `Zod` and `Valibot` also have their own `inferSchema` exported. ::: ## Typing external nested field `inferRules` can also accept plain values to autocomplete the available rules. ```ts twoslash import { inferRules, useRegle } from '@regle/core'; import { required } from '@regle/rules'; import { computed, ref } from 'vue'; const form = ref({ name: { firstName: '', lastName: '' } }); const firstNameRules = computed(() => inferRules(form.value.name.firstName, { required, }) ); const rules = computed(() => inferRules(form, { name: { firstName: firstNameRules.value, }, }) ); const { r$ } = useRegle(form, rules); ``` --- --- url: /cheat-sheet.md description: Quick reference for common Regle patterns and usage scenarios --- # Regle Cheat Sheet Quick reference for common Regle patterns and usage scenarios. ## Basic Setup ```ts import { useRegle } from '@regle/core' import { required, email, minLength } from '@regle/rules' const { r$ } = useRegle( { name: '', email: '' }, { name: { required, minLength: minLength(2) }, email: { required, email } } ) ``` ## Essential Properties | Property | Description | Example | |----------|-------------|---------| | `r$.$value` | Form data (reactive) | `r$.$value.email` | | `r$.$correct` | Form is dirty and valid | `