--- title: Internationalization (i18n) description: A guide in my new Starlight docs site. --- # Internationalization (i18n) Formwerk composables treat internationalization as a first class concern. It's not just about translating the labels and messages. It's mainly about catering to specifics of each locale audience and giving them the best experience possible. If you didn't work with internationalization before, you might be surprised how the web still misses the mark in many cases. Here are a few examples: - The `input[type="number"]` only accepts latin numerals. This causes issues for those with non-latin locales and keyboard layouts. - Some keyboard layouts lack the semantically correct commas, and decimal separators, yet users use identical symbols that usually fails validation. - Most keyboard shortcuts, especially left/right arrow keys do not respect the directionality of the locale. - The HTML5 constrained validation API generates messages in the browser's default language, not the website's. Formwerk composables take all of this into account and more. In this page we'll go over the details of how Formwerk handles internationalization and how you can benefit from it. ## Not an i18n library Formwerk is not an i18n library. It does not provide a way to translate the labels and messages. But it does its best to offer the best experience it can with Vue's ecosystem. You can use a dedicated i18n library in tandem with Formwerk like [vue-i18n](https://vue-i18n.intlify.dev/). ## Locale Config Formwerk exposes a `locale` configuration option that you can set with the `configure` function. This is the main option Formwerk uses to determine locale and [directionality](#directionality). ```ts import { configure } from '@formwerk/core'; configure({ locale: 'en-US', }); ``` The default locale is `en-US`. ### Locale auto-detection By default Formwerk will auto-detect the locale using the html tag's `lang` attribute. It will not use the browser's locale since it may be different from the user's preferred language or the website's target audience. There is no need to disable this feature since it detects the locale right when Formwerk is imported. So if you set the locale with the `configure` function, it will always respect that setting. The auto-detection is not available in some environments like server-side rendering (SSR). In those cases you'll need to set the locale manually with the `configure` function. ## Directionality Formwerk determines the directionality of the locale currently set. It does so using the [Intl.Locale](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale) API. This determines the default `dir` property of Formwerk components. You can always override it with the `dir` prop on each composable or you can turn the auto-detection off with the `detectDirection` option. ```ts import { configure } from '@formwerk/core'; configure({ detectDirection: false, }); ``` Not all composables react to the direction change, it is up to you to style them accordingly. But the functionality of some components may change as result, especially components that utilize the arrow keys (e.g: sliders). ## Usage with Vue i18n Formwerk can be used with [vue-i18n](https://vue-i18n.intlify.dev/). Usually it is a good idea to set the locale in the app's main file or entry point where you initialize vue-i18n. Formwerk accepts a reactive locale value and will automatically update the direction whenever the locale changes. ```ts import { createI18n } from 'vue-i18n'; import { configure } from '@formwerk/core'; const i18n = createI18n({ locale: 'en-US', }); configure({ // Reactive if legacy: false in vue-i18n options locale: i18n.global.locale, }); ``` ## Usage with Nuxt i18n Since there is no `html` tag in SSR environments, Formwerk will not be able to auto-detect the locale. You'll need to set it manually with the `configure` function. For Nuxt, you would be using [Nuxt i18n](https://i18n.nuxtjs.org/) module. It is recommended to set the locale in a [plugin](https://nuxt.com/docs/guide/directory-structure/plugins) to ensure it happens before Formwerk is used anywhere. ### Using the locale ref Like with Vue i18n, Formwerk will automatically update the directionality whenever the locale changes if you pass a reactive locale value. So you only have one source of truth to manage. ```ts import { configure } from '@formwerk/core'; export default defineNuxtPlugin((nuxtApp) => { configure({ locale: nuxtApp.$i18n.locale, }); }); ``` ### Using Nuxt Hooks Alternatively, you can listen to the `i18n:localeSwitched` hook to update the locale and directionality whenever the locale changes with `setLocale` [returned from `useI18n`](https://i18n.nuxtjs.org/docs/guide/lang-switcher). ```ts import { configure } from '@formwerk/core'; export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hook('i18n:localeSwitched', ({ newLocale }) => { configure({ locale: newLocale, }); }); }); ``` ## HTML Validation Language Mismatch HTML5 validation messages are generated in the browser's default language, not the website's. This means that if the user's browser is set to a different language than the website's target audience, the validation messages will be in the wrong language. Unfortunately, there is no way to change the language of the validation messages after they are generated and there is no sufficient JS API to influence the message text. We are considering either building a tiny i18n layer just for this or rely on 3rd party to do it. For now, the recommended workaround is to disable HTML5 validation messages generation and rely on [Standard Schemas](/guides/forms/validation#standard-schema-validation/) to provide the validation messages. You still will be able to pass the `required` and other HTML validation attributes but they will not affect the validity or the messages once disabled. --- --- title: Resources description: A list of resources for Formwerk, including articles, videos, podcasts, and more. --- # Resources If you have any resources that you think would be useful for the Formwerk community, please let us know and we'll add them to this page. You can submit your resources by posting in our [Discord server](https://discord.gg/gQ7wqpvT5X). --- --- title: Server Side Rendering description: How well does Formwerk work with server side rendering? --- # Server Side Rendering Server side rendering, or SSR, is the process of rendering components on the server and serve them as HTML initially, rather than rendering them on the client. Static Site Generation (SSG) is a similar approach, it renders pages to HTML files at build time rather than on each request. These techniques can help improve perceived loading performance and SEO. Formwerk is designed to work with server side rendering. It does not rely on any client-side only APIs and should work in any environment that supports Vue.js SSR like Nuxt and Astro. ## Label and Input ID Generation Formwerk builds on top of `useId` function exposed by Vue.js `>= v3.5.0` to generate unique IDs for labels and inputs. This is why Vue.js `v3.5.0` is the minimum required version for Formwerk. ## Nuxt Example Formwerk works fully out of the box with Nuxt, import and use the composables in your Nuxt pages or components. The validation here is aggressive, it is up to you to decide when to show the error message. Keep in mind that this is an inefficient use of Formwerk, here you only gain validation state and the form API, since most UI libraries already have their accessibility and interactions built in. But, if you need to, this is how you could do it. ## Usage ### Disabled For custom fields, it is up to you to implement the disabled state. We don't know what disabled means for your field, so we leave it up to you to implement it. The disabled state is returned from `useCustomField` as `isDisabled` and it is one of the props accepted by the `useCustomField` composable. It behaves the same as other fields' disabled state, if a parent form or form group is disabled, the field will be disabled. What Formwerk does however, is it will not validate the field when it is disabled and it will not submit the field when it is disabled either. ### Readonly For custom fields, it is up to you to implement the readonly state. We don't know what readonly means for your field, so we leave it up to you to implement it. Readonly fields are validated and submitted, but they do not accept user input. The field is still focusable, and the value is copyable in the case of text fields. So use this as your north star, make sure the field is focusable, but not editable. ## API ### Props These are the properties that can be passed to the `useCustomField` composable. ### Returns These are the properties in the object returned by the `useCustomField` composable. --- --- title: File Fields description: Learn how to build accessible file field Vue components with Formwerk. --- # File Fields import AnatomyCanvas from '@components/AnatomyCanvas.vue'; import AnatomyFileField from '@components/AnatomyFileField.vue'; import MdxRepl from '@components/MdxRepl.vue'; import MdxTableAPI from '@components/MdxTableAPI.vue'; import DropzonePartial from './_partials/_dropzone.mdx'; import FileFieldPartial from './_partials/_fileField.mdx'; File fields are a type of input field that allows users to submit or upload files. ## Features - Uses `input` element. - Labeling, Descriptions, and error messages are automatically linked to their corresponding elements. - Validation support with native HTML constraint validation or [Standard Schema](https://github.com/standard-schema/standard-schema) validation. - Support for `v-model` binding. - Basic upload handling support. - Multiple file selection support. - Drag and drop support for "dropzone" components. - Auto previews for images and videos. ## Anatomy ## Building a File Field Component You can start by importing the `useFileField` composable and using it in your file field component. The `useFileField` composable returns binding objects for the elements shown in the [anatomy](#anatomy). You will use `v-bind` to bind them to the corresponding DOM elements.
```vue ```
## Building a Dropzone Component You can use `useFileField` to build a "dropzone" component that allows users to drag and drop files to upload them. In this case, we can use the `FileEntry` component to render the file entry, the `FileEntry` component exposes various slot props to manage, and preview the file entry if it is an image or a video.
```vue ```
## Validation ### HTML Constraints You can use the following properties to validate the file field with native HTML constraint validation: | Name | Type | Description | | ---------- | --------- | ----------------------------------- | | `required` | `boolean` | Whether the file field is required. | Here is an example of how to use the `required` property to make the file field required.
```vue ```
### Standard Schema You can use the `useFileField` composable to validate the file field with [Standard Schema](https://github.com/standard-schema/standard-schema). In this example, we are validating the file size to be 1MB maximum.
```vue ```
### Mixed Validation All file fields created with Formwerk support mixed validation, which means you can use both HTML constraints and Standard Schema validation to validate the field, and they work seamlessly together. Note that HTML constraints are validated first, so any errors from the HTML constraints will be displayed first. Then, once all HTML constraints are satisfied, the Standard Schema is validated. Here we are mixing both the `required` HTML constraint and the Zod size validation.
```vue ```
## Usage ### Disabled Use `disabled` to mark fields as non-interactive. Disabled fields are not validated and are not submitted.
```vue ```
One important thing is to no forget the `dropzoneProps` binding object, which contains the listeners needed to make the dropzone work. You can also build your own `FileEntry` component with the `useFileEntry` composable. This dropzone component isn't multiple by default, but you can use the `multiple` prop to allow users to select multiple files. ### Multiple Files You can use the `multiple` prop to allow users to select multiple files.
```vue ```
### Allowing Picking Directories You can use the `allowDirectory` prop to allow users to pick directories if the file is `multiple`.
```vue ```
### Uploading Files The `useFileField` composable accepts an `onUpload` handler that is called with the file to be uploaded. The `onUpload` handler receives a `FileUploadContext` object with the following properties: The upload handler should return a string value that will be swapped with the file in the form when it submits. The string value is usually an identifier that your server can use to identify the file.
```vue ```
### Initial Data At the moment, `useFileField` doesn't support initial data because you cannot really set a "file" value to an `input` element due to security restrictions. There are a few ideas we have like: - Given a URL, we reconstruct the file object and set it as the initial value. - Create a "fake" file object that we can set as the initial value. We are open to suggestions, join our [Discord server](https://discord.gg/sT73ZGUy7X) or [open an issue](https://github.com/formwerk/formwerk/issues) if you have any ideas! ## API ### useFileField #### Props #### Returns ### useFileEntry #### Props #### Returns --- --- title: Date Fields description: Learn how to build accessible date field Vue components with Formwerk. --- # Date Fields import Kbd from '@components/KeyboardKey.vue'; import AnatomyCanvas from '@components/AnatomyCanvas.vue'; import AnatomyDateField from '@components/AnatomyDateField.vue'; import MdxRepl from '@components/MdxRepl.vue'; import DateFieldPartial from './_partials/_dateFieldPartial.mdx'; import CalendarPartial from './_partials/_calendarPartial.mdx'; import MdxTableAPI from '@components/MdxTableAPI.vue'; import DateFieldPartialWithCalendar from './_partials/_dateFieldPartialWithCalendar.mdx'; import PreviewCard from '@components/PreviewCard.vue'; Date fields are a type of input field that allows users to enter a date. They are a common feature in many web applications, and Formwerk provides a `useDateTimeField` composable that can be used to create date fields in your application. ## Features - Labeling, Descriptions, and error messages are automatically linked to the date field elements. - Support for multiple calendars (e.g: `islamic-umalqura`, `japanese`, etc). - Supports minimum and maximum date boundaries. - Validation support with native HTML constraints or [Standard Schemas](https://github.com/standard-schema/standard-schema). - Auto directionality based on the locale. - Focus management and auto navigation for date segments. - Support for `v-model` binding. - Comprehensive keyboard shortcuts support. ### Keyboard Features | Key | Description | | -------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | | Decrements selected segment by 1. | | | Increments selected segment by 1. | | | Moves the focus to the previous segment. | | | Moves the focus to the next segment. | | | Clears the current segment. | | | Clears the current segment. | | | Moves the focus to the next segment or next element in the tab index order if it is the last segment. | ## Anatomy ## Building a Date Field Component Just like the [native HTML date field](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date), the date field in Formwork is compromised of date segments, each segment represents a part of the date/time value (e.g: day, month, year, hour, minute, etc). The segments are generated for you automatically based on the `formatOptions` you pass to the `useDateTimeField` composable. You will need to bind the prop objects returned by the composable to the corresponding DOM elements in the [anatomy](#anatomy). If you do not pass the `formatOptions` prop, the date field will use the default format options for the locale of your app.
```vue ```
The `DateTimeSegment` component is used to render each segment of the date field. We provide one for convenience that you can use. You can also build your own with `useDateTimeSegment` composable. ```vue ``` ## Validation ### HTML Constraints You can use the following properties to validate the date field with native HTML constraint validation: | Name | Type | Description | | ---------- | --------- | ------------------------------------- | | `min` | `Date` | The minimum date that can be entered. | | `max` | `Date` | The maximum date that can be entered. | | `required` | `boolean` | Whether the date field is required. |
```vue ```
### Standard Schema `useDateTimeField` supports [Standard Schema](https://github.com/standard-schema/standard-schema) validation through the `schema` prop. This includes multiple providers like [Zod](https://zod.dev/), [Valibot](https://valibot.dev/), [Arktype](https://arktype.dev/), and more. In this example, we are validating that the date field is between January 1st, 2025 and December 31st, 2025.
```vue ```
### Mixed Validation All date fields created with Formwerk support mixed validation, which means you can use both HTML constraints and Standard Schema validation to validate the field, and they work seamlessly together. Note that HTML constraints are validated first, so any errors from the HTML constraints will be displayed first. Then, once all HTML constraints are satisfied, the Standard Schema is validated.
```vue ```
## Usage ### Disabled Use the `disabled` prop to disable the date field. Disabled date fields are not validated and are not submitted. If you need to prevent the user from interacting with the field while still allowing it to submit, consider using `readonly` instead.
```vue ```
### Readonly Use the `readonly` prop to make the date field read-only. Read-only date fields are still validated and submitted.
```vue ```
### Min and Max You can pass `min` and `max` props to set the minimum and maximum dates that can be entered, not only they are used for validation but they affect the date segments that are available for selection and interaction.
```vue ```
Notice in the previous examples, some parts of the date field are disabled. This is because when providing both a `min` and a `max`, Formwerk checks the possibilities of the date segments, and if a segment has only one possible value then it automatically sets it and disables it. Just like the native `input[type="date"]` element. ### Format Options You can pass any `Intl.DateTimeFormatOptions` to the `formatOptions` prop to customize the date field's display format.
```vue ```
### Locale and Calendar Support The `calendar` prop can be used to specify which calendar to use, along with the `locale` prop to set the locale of the date field. You can use `@internationalized/date` to create calendar objects. In this example, we will use the `islamic-umalqura` calendar along with the `ar-SA` locale.
```vue ```
## Usage with Calendar Component You can use the `DateField` component with the `Calendar` component to allow users to select a date. First check out the [Calendar](/guides/fields/calendars/) guide to build your own calendar component so that you can use it in the next example.
```vue ```
## Examples These are some examples of date fields built with Formwerk.
## API ### useDateField #### Props These are the properties that can be passed to the `useDateField` composable. #### Returns These are the properties in the object returned by the `useDateField` composable. ### useDateSegment #### Props These are the properties that can be passed to the `useDateSegment` composable. #### Returns These are the properties in the object returned by the `useDateTimeSegment` composable. --- --- title: Hidden Fields description: Learn how to use hidden fields in Formwerk. --- # Hidden Fields import AnatomyCanvas from '@components/AnatomyCanvas.vue'; import AnatomyHiddenField from '@components/AnatomyHiddenField.vue'; import MdxRepl from '@components/MdxRepl.vue'; import MdxTableAPI from '@components/MdxTableAPI.vue'; Hidden fields let you include data that cannot be seen or modified by users when a form is submitted. A good example is a CSRF token or an ID for the content being submitted. As such, they do not have most of the features of other fields. ## Features - Offers a `useHiddenField` composable and a renderless `HiddenField` component. ## Anatomy ## Hidden Field Component Unlike other fields, you can use the `HiddenField` component to create a hidden field without having to create your own components. This is because we view the hidden field as a utility field for declaring values declaratively. ```vue ``` Alternatively, you can use the `useHiddenField` composable to create a custom hidden field component. ```vue ``` To demonstrate how hidden fields work, we will use a `form` in the following example. We will try to keep it as simple as possible, but you can check the [form guide](/guides/forms/) for more information.
```vue ```
## Validation Hidden fields do not support validation, although their [API](#api) supports showing any possible error messages that the form might have for the field. The main reason for this is that hidden fields are, well, hidden. That means the user cannot interact with them, and as such, you should ensure they always have valid values; otherwise, it will be confusing to the user. ## Usage ### Disabled Marking a hidden field as disabled will make it non-submittable, meaning its value will not be included in the form data when the form is submitted.
```vue ```
## API ### Props These are the properties that can be passed to the `useHiddenField` composable and the `HiddenField` component. ### Returns These are the properties in the object returned by the `useHiddenField` composable. --- --- title: Number Fields description: Learn how to build accessible and i18n-ready number field Vue components with Formwerk. --- # Number Fields import AnatomyCanvas from '@components/AnatomyCanvas.vue'; import AnatomyNumberField from '@components/AnatomyNumberField.vue'; import MdxRepl from '@components/MdxRepl.vue'; import MdxTableAPI from '@components/MdxTableAPI.vue'; import Kbd from '@components/KeyboardKey.vue'; import MouseWheel from '@components/MouseWheel.vue'; import PreviewCard from '@components/PreviewCard.vue'; import NumberFieldPartial from './_partials/_numberField.mdx'; Number fields are a common field in many forms. They include built-in validation to reject non-numerical input. Additionally, the browser may provide stepper arrows to let the user increase and decrease the value with a configurable step amount. The Number field is usually used for number values rather than numeric values. For example while a credit card number is numeric, you should not use a number field for it. Instead you should use it for values that are meant to be consumed as a number like units, prices and percentages. The native HTML number input and most other implementations do not offer a good experience. Here are a couple of common issues: - **Lack of proper internationalization:** Mainly, the lack of support for other numeral systems like the Arabic numerals `(٠١٢٣٤٥٦٧٨٩)` are a pain to work with. Users often have to switch keyboard languages/layout to enter numbers and even then it is not perfect. Keyboards don't always have all the necessary characters for decimals and thousands separators. This means the number input is not accessible for the global audience. - **No formatting support:** this includes grouping `,` and displaying units and currencies and other simple masking features. Formwerk tries to address all these issues and more by utilizing the `Intl.NumberFormat` API. It provides a solid foundation for building number fields that are accessible and easy to use for users all over the world. ## Features - Supports using the `input` element as a base with `type="text"` (don't worry, we add the `inputmode` automatically for accessability and mobile keyboards). - Labeling, descriptions, error message displays are automatically linked to input and label elements with `aria-*` attributes. - Formatting and parsing numbers with the `Intl.NumberFormat` API depending on the site locale or the user's preferred language. - Support for multiple numeral systems (Latin, Arabic, and Chinese). - Support for `Intl.NumberFormat` units and currencies. - Support for the [Spinbutton ARIA pattern](https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/) for increment/decrement buttons. - Support for `min`, `max` and `step` attributes. - Validation support with native HTML constraint validation or [Standard Schema](https://github.com/standard-schema/standard-schema) validation. - Rejects non-numerical input characters and any incoming key presses that would make the number invalid. - Support for `v-model` binding. - Supported Keyboard features: | Key | Description | | ---------------------------------------------- | -------------------------------------------------------------------- | | | increment the value by the step amount. | | | decrement the value by the step amount. | | | increment the value by larger multiple of the step amount. | | | decrement the value by larger multiple of the step amount. | | | set the value to the min value if provided, otherwise has no effect. | | | set the value to the max value if provided, otherwise has no effect. | | | increment the value by the step amount. | | | decrement the value by the step amount. | ## Anatomy ## Building a Number Field Component You can start by importing the `useNumberField` composable and using it in your number field component. The `useNumberField` composable returns binding objects for the elements shown in the [anatomy](#anatomy), you will use `v-bind` to bind them to the corresponding DOM elements.
```vue ```
Notice that we imported the `NumberFieldProps` in the previous example. This is recommended to use as your component prop types. Not only you get type safety for your component out of it but also it handles the reactivity aspects of the props so you don't have to. You are free to extend it with your own props or omit the ones you don't need. ## Validation ### HTML Constraints You can use the following native HTML validation properties to validate the number field: | Name | Type | Description | | ---------- | --------- | --------------------------------------- | | `max` | `number` | The maximum value for the number field. | | `min` | `number` | The minimum value for the number field. | | `required` | `boolean` | Whether the number field is required. | | `step` | `number` | The step amount for the number field. | Here is an example of how to use the `max` and `min` properties to limit the number field value between 0 and 100. Assuming you have a `NumberField` component like the one shown above, you can use it like this:
```vue ```
### Standard Schema `useNumberField` also supports [Standard Schema](https://github.com/standard-schema/standard-schema) validation through the `schema` prop. This includes multiple providers like [Zod](https://zod.dev/), [Valibot](https://valibot.dev/), [Arktype](https://arktype.dev/), and more.
```vue ```
### Mixed Validation While it is unlikely that you need both HTML constraints and Standard Schemas to validate a number field, Formwerk supports mixed validation, which means you can use both HTML constraints and Standard Schemas to validate the field and define step amount and min/max values and they work seamlessly together. Note that HTML constraints are validated first, so any errors from the HTML constraints will be displayed first. Then once all HTML constraints are satisfied, the Standard Schema is validated.
```vue ```
## Usage ### Disabled Use disabled to mark fields as non-interactive, disabled fields are not validated and are not submitted. If you need to prevent the user from interacting with the field while still allowing it to submit, consider using `readonly` instead.
```vue ```
### Readonly Readonly fields are validated and submitted, but they do not accept user input. The field is still focusable and the value is copyable. For more info, check the [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly).
```vue ```
### Formatting and Units You can use the `formatOptions` prop to format the number field value. It accepts all [options](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#options) that are supported by the `Intl.NumberFormat` API.
```vue ```
### i18n Aside from [formatting](#formatting-and-units) you can also use any numeral system supported by the `Intl.NumberFormat` API. Like Arabic, and Chinese. The number field also accepts a `locale` prop to change the locale of the number field. Usually you should not want to pass it manually but for demonstration purposes it is shown below. Actually, here are 3 fields each with a different numeral system bound to the same value, and you get the parsed value with either of them.
```vue ```
### RTL The number field doesn't really need much for RTL support, however the `dir` prop can be used to set the direction of the field for convenience.
```vue ```
## Examples These are some examples of number fields built with Formwerk.
## API ### Props These are the properties that can be passed to the `useNumberField` composable. ### Returns These are the properties in the object returned by the `useNumberField` composable. --- --- title: OTP Fields description: Learn how to build accessible OTP field Vue components with Formwerk. --- # OTP Fields import AnatomyCanvas from '@components/AnatomyCanvas.vue'; import AnatomyOtp from '@components/AnatomyOtp.vue'; import MdxTableAPI from '@components/MdxTableAPI.vue'; import Kbd from '@components/KeyboardKey.vue'; import OtpFieldPartial from './_partials/_otpField.mdx'; import MdxRepl from '@components/MdxRepl.vue'; OTP fields are used to allow users to input a one-time password or a code. Usually used for 2FA (MFA) or authentication purposes. ## Features - Use `input` or any other element to create an OTP field and its slots. - Labels, descriptions, and error message displays are automatically linked to input and label elements with `aria-*` attributes. - Validation support with native HTML constraint validation or [Standard Schema](https://github.com/standard-schema/standard-schema) validation. - Support for `v-model` binding. - Supports masking (hiding) the entered characters with a custom character. - Supports prefixes (e.g. `F-{code}`). - Supports custom length. - Supports both numeric and alphanumeric OTPs. - Comprehensively supports keyboard navigation. - Auto management of focus during user interaction. - Handles paste events. - Auto submit on completion. ### Keyboard Features | Key | Description | | -------------------------------------------- | --------------------------------------------------------------------- | | | Moves the focus to the next OTP slot. | | | Moves the focus to the previous OTP slot. | | | Clears the current OTP slot and moves the focus to the previous slot. | | | Moves the focus to the next OTP slot. | | | Moves the focus to the next OTP slot. | ## Anatomy ## Building an OTP Field Component First, import the `useOtpField` composable and use it in your OTP field component. The `useOtpField` composable returns binding objects for the elements shown in the [anatomy](#anatomy). You will use `v-bind` to bind them to the corresponding DOM elements.
```vue ```
For your convenience, Formwerk already implements an OTP Slot component that you can use in your OTP field directly, but you can still build your own with `useOtpSlot`. ## Validation ### HTML Constraints You can use the following properties to validate the OTP field with native HTML constraint validation: | Name | Type | Description | | ---------- | --------- | ----------------------------------- | | `required` | `boolean` | Whether the text field is required. |
```vue ```
### Standard Schema `useOtpField` also supports [Standard Schema](https://github.com/standard-schema/standard-schema) validation through the `schema` prop. This includes multiple providers like [Zod](https://zod.dev/), [Valibot](https://valibot.dev/), [Arktype](https://arktype.dev/), and more.
```vue ```
### Mixed Validation All OTP fields created with Formwerk support mixed validation, which means you can use both HTML constraints and Standard Schema validation to validate the field, and they work seamlessly together. Note that HTML constraints are validated first, so any errors from the HTML constraints will be displayed first. Then, once all HTML constraints are satisfied, the Standard Schema is validated.
```vue ```
This makes schemas lighter; however, we recommend sticking to one or the other per form for maintainability. If you need to disable the native validation, you can do so by setting the `disableHtmlValidation` prop to `true`. ## Usage ### Disabled Use `disabled` to mark fields as non-interactive. Disabled fields are not validated and are not submitted. If you need to prevent the user from interacting with the field while still allowing it to submit, consider using `readonly` instead.
```vue ```
### Readonly Use `readonly` to mark fields as non-editable. Readonly fields are still validated and submitted.
```vue ```
### Accepting Specific Characters The OTP field accepts a `accept` prop to specify the type of characters that can be entered. You can have one of the following values: | Value | Description | | -------------- | --------------------------------------------------------------------------------------------------- | | `alphanumeric` | Alphanumeric characters are accepted (i.e: English letters and numbers), this is the default value. | | `numeric` | Only numeric characters are accepted. | | `all` | All characters are accepted. |
```vue ```
### Masking Characters The OTP field accepts a `mask` prop to specify the character to use for masking the entered characters, your model and submitted values be the actual values. The mask prop can either be a boolean which will apply a masked default, or a character that will be used to mask the entered value.
```vue ```
### Prefix OTP fields can have a prefix, prefixes cannot be changed or edited by the user.
```vue ```
### Custom Length OTP fields accept a `length` prop to specify the number of OTP slots. By default the length is 6 without a prefix, and with a prefix it will be 4.
```vue ```
### onCompleted Handler The OTP field accepts an `onCompleted` handler to be notified when the user has filled all the OTP slots with valid characters.
```vue ```
### RTL At this time, OTP fields do not support RTL (right-to-left) text direction. This is mainly because we want to get more feedback on this, from personal experience OTP codes are still LTR even in RTL web apps. Feel free to open an issue on [GitHub](https://github.com/formwerkjs/formwerk/issues) if you have any feedback on this. ## API ### useOtpField #### Props These are the properties that can be passed to the `useOtpField` composable. #### Returns These are the properties in the object returned by the `useOtpField` composable. ### useOtpSlot #### Props These are the properties that can be passed to the `useOtpSlot` composable. #### Returns These are the properties in the object returned by the `useOtpSlot` composable. --- --- title: Radio Buttons description: Learn how to build accessible Vue.js radio button components with Formwerk. --- # Radio Buttons import AnatomyCanvas from '@components/AnatomyCanvas.vue'; import AnatomyRadio from '@components/AnatomyRadio.vue'; import MdxRepl from '@components/MdxRepl.vue'; import MdxTableAPI from '@components/MdxTableAPI.vue'; import Kbd from '@components/KeyboardKey.vue'; import RadioGroupPartial from './_partials/_radioGroup.mdx'; import RadioInputPartial from './_partials/_radioInput.mdx'; import RadioItemPartial from './_partials/_radioItem.mdx'; > A radio group is a set of checkable buttons, known as radio buttons, where no more than one of the buttons can be checked at a time. Some implementations may initialize the set with all buttons in the unchecked state to force the user to check one of the buttons before moving past a certain point in the workflow. Radios in HTML do not have a "group" concept, but they get grouped implicitly by the "name" attribute. This isn't the case in Vue and most UI libraries, as they are grouped by the model name they mutate. Formwerk follows the "group" concept to provide a consistent API for radio fields regardless of whether they are bound to the same model or if they have the same name or not. This means radios are a compound field, meaning they require more than one composable to work properly, and by extension, you need to build more than one component to make them work. For radios, you will use the `useRadioGroup` and `useRadio` composables to build radio components. ## Features You can build radio components using either the native HTML `input[type="radio"]` elements or other HTML elements. We provide the behavior, state, and accessibility implementation for both cases with the same API and features. The following features are implemented: - Support for either `input[type="radio"]` or custom HTML elements as a base element for the radio component. - Labeling, descriptions, and error message displays are automatically linked to input and label elements with `aria-*` attributes. - Form management, data collection, and validation with native HTML5 validation or [Standard Schema](https://github.com/standard-schema/standard-schema) validation. - Support for orientation with `horizontal` and `vertical` values. - `v-model` support for radio groups. - Supported Keyboard features: | Key | Description | | -------------------------------------------- | -------------------------------------------------------------------------------- | | | Focuses the next radio item in the group. | | | Focuses the next radio item in the group. In RTL, focuses the previous item. | | | Focuses the previous radio item in the group. | | | Focuses the previous radio item in the group. In RTL, focuses the next item. | | | Focuses the selected item in the group. If none selected, focuses the first one. | | | Selects the focused radio item. | ## Anatomy ## Building a Radio Group Component The `useRadioGroup` provides the behavior, state, and accessibility implementation for group components. Unlike checkboxes, radio components **MUST** be grouped by a radio group component. This is why we will start by building a `RadioGroup` component as a prerequisite. We will be using this component in the following examples throughout this page. ## Building a Radio Component With the Radio Group component built, we can now build a `RadioItem` component. You will be using the `useRadio` composable to build it. You can use either the native HTML `input[type="radio"]` element or custom HTML elements. It doesn't matter which one you choose; both have the same exact API, and Formwerk does the work needed for each case behind the scenes. ```ts import { type RadioProps, useRadio } from '@formwerk/core'; const props = defineProps(); const { labelProps, inputProps } = useRadio(props); ``` The most important part is to bind the `inputProps` object to the base element, the element that you consider to be the radio button. We also provide the `RadioProps` type for you to use as your component props. You are not required to use it, but it is recommended to make use of the full feature set of the `useRadio` composable and, by extension, your component. With the basics out of the way, let's build a radio component with two common variations. ### With `input` element as a base You can use the `useRadio` composable to build a radio component with the `input` element as a base.
```vue ```
The style-ability of the last example is limited to the styling capabilities of the native `input` element. To work around that, check the [styling section](#styling). ### With custom HTML element as a base For unlimited styling freedom, you don't have to use the `input` element. With the same API, you can use custom HTML elements as a binding target for the `inputProps` object. In the following example, we are using a `span` element as a base element for the radio. Try keyboard navigation, clicking, focusing, and other interactions to see how it behaves.
```vue ```
## Validation Radio components support validation with native HTML5 constraints or [Standard Schema](https://github.com/standard-schema/standard-schema) validation. However, the `useRadioGroup` is the one that accepts validation props. ### HTML Constraint Validation The following properties are supported on `useRadioGroup` and `useRadio` that use the `input` element as a base. Custom elements do not support these properties. | Name | Type | Description | | ---------- | --------- | ------------------------------------- | | `required` | `boolean` | Whether the number field is required. |
```vue ```
### Standard Schema `useRadioGroup` also supports [Standard Schema](https://github.com/standard-schema/standard-schema) validation through the `schema` prop. This includes multiple providers like [Zod](https://zod.dev/), [Valibot](https://valibot.dev/), [Arktype](https://arktype.dev/), and more. Here is an example of using zod as a Standard Schema to validate the radio group. We will be using the radio item component from the previous examples.
```vue ```
## Usage ### Disabled You can disable individual radio items or the whole group with the `disabled` prop on either. Disabled radio items are not focusable. Disabled groups are not submitted and are not validated. We made use of the styled radio component that we created above to make it clearer that the radio items are disabled.
```vue ```
If you need to prevent the user from interacting with the group while still allowing it to submit, consider using `readonly` instead. ### Readonly Only available on the group, the `readonly` prop prevents the user from interacting with the group while still allowing it to submit and be validated.
```vue ```
### Orientation Radio groups accept an `orientation` prop that can be set to `horizontal` or `vertical`. The orientation does not affect the focus order, but you can use it to layout the radio items in a row or column with CSS. There is no default value assumed for the orientation, but if it is provided, the group element will have an `aria-orientation` attribute set to the value of the prop. So you can use that to style it.
```vue ```
### RTL The radio group accepts a `dir` prop that can be set to `ltr` or `rtl`. Unlike the orientation, the `dir` prop affects the focus order of the radio items as the Left and Right arrow keys will navigate the items in the opposite direction.
```vue ```
## Radio button Value Type and Generics By default, the value of a radio button is a string. You can change the value type by passing in the generic type to the `RadioProps` or `RadioGroupProps` types. The following example shows how to change the value type to a string. ```ts import { useRadio, type RadioProps } from '@formwerk/core'; const props = defineProps>(); // ... ``` But ideally, to make sure your component is type-safe and as flexible as possible, you should use generics on the component itself. ```vue ``` You can do the same for the `RadioGroup` components as well. ```vue ``` ## API Most of the values expressed below are wrapped in `Ref` as they are reactive values. ### Radio Group #### Props These are the properties that can be passed to the `useRadioGroup` composable. #### Returns These are the properties in the object returned by the `useRadioGroup` composable. ### Radio #### Props These are the properties that can be passed to the `useRadio` composable. #### Returns These are the properties in the object returned by the `useRadio` composable. --- --- title: Search Fields description: Learn how to build accessible search field Vue components with Formwerk. --- # Search Fields import AnatomyCanvas from '@components/AnatomyCanvas.vue'; import AnatomySearchField from '@components/AnatomySearchField.vue'; import Kbd from '@components/KeyboardKey.vue'; import MdxRepl from '@components/MdxRepl.vue'; import MdxTableAPI from '@components/MdxTableAPI.vue'; import SearchFieldPartial from './_partials/_searchField.mdx'; > Input elements of type search are text fields designed for the user to enter search queries into. These are functionally identical to text inputs but may be styled differently by the user agent. Search fields have extra behaviors and use-cases that set them apart from regular text fields. This composable provides the behavior, state, and accessibility implementation for search fields. A couple of behaviors set this apart from regular text fields: - The text content can be cleared with the clear button or a keyboard shortcut. - They are usually used without a parent `form` element and sometimes without a `submit` button. So they can be submitted with the `Enter` keyboard key on their own. You can find more information about the differences [here](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/search#differences_between_search_and_text_types). ## Features - Uses `input[type="search"]` element as a base. - Labeling, descriptions, and error message displays are automatically linked to input and label elements with `aria-*` attributes. - Support for a custom clear button. - Validation support with native HTML constraint validation or [Standard Schema](https://github.com/standard-schema/standard-schema) validation. - Support for `v-model` binding. - Supported Keyboard features: | Key | Description | | --------------------------------- | --------------------------------------------------------------------------------------------------- | | | Clears the input value. | | | If in a form, submits the form. Otherwise, submits the input with the `onSubmit` event or callback. | ## Anatomy ## Building a Search Field Component The `useSearchField` composable returns binding objects for the elements shown in the [anatomy](#anatomy). You will use `v-bind` to bind them to the corresponding DOM elements.
```vue ```
Notice that we imported the `SearchFieldProps` in the previous example. This is recommended to use as your component prop types. Not only do you get type safety for your component out of it, but it also handles the reactivity aspects of the props so you don't have to. You are free to extend it with your own props or omit the ones you don't need. ## Validation ### HTML Constraints You can use the following native HTML validation properties to validate the search field: | Name | Type | Description | | ----------- | ------------------ | ------------------------------------ | | `maxLength` | `number` | The maximum length of characters. | | `minLength` | `number` | The minimum length of characters. | | `required` | `boolean` | Whether the text field is required. | | `pattern` | `string \| RegExp` | A regular expression for validation. | Here is an example of how to use the `maxLength` and `minLength` properties to limit the text length between 3 and 18 characters. Assuming you have a `SearchField` component like the one shown above, you can use it like this:
```vue ```
### Standard Schema `useSearchField` also supports [Standard Schema](https://github.com/standard-schema/standard-schema) validation through the `schema` prop. This includes multiple providers like [Zod](https://zod.dev/), [Valibot](https://valibot.dev/), [Arktype](https://arktype.dev/), and more.
```vue ```
### Mixed Validation All search fields created with Formwerk support mixed validation, which means you can use both HTML constraints and Standard Schemas to validate the field, and they work seamlessly together. Note that HTML constraints are validated first, so any errors from the HTML constraints will be displayed first. Then, once all HTML constraints are satisfied, the Standard Schema is validated.
```vue ```
This makes schemas lighter; however, we recommend sticking to one or the other per form for maintainability. If you need to disable the native validation, you can do so by setting the `disableHtmlValidation` prop to `true`.
```vue ```
You can also disable it globally for all fields. For more information, check out the [Validation](/guides/forms/validation/) guide. ## Usage ### Disabled You can disable the search field by setting the `disabled` prop to `true`. Disabled fields are not editable, will not be validated, and will not be submitted with the form. If you need to prevent the user from interacting with the field while still allowing it to submit, consider using `readonly` instead.
```vue ```
### Readonly Readonly fields are validated and submitted, but they do not accept user input. The field is still focusable, and the value is copyable. For more info, check the [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly).
```vue ```
### RTL The search field doesn't really need much for RTL support; however, the `dir` prop can be used to set the direction of the field for convenience.
```vue ```
### Submitting Search fields can be used without a parent `form` element and sometimes without a `submit` button. The `useSearchField` accepts an `onSubmit` callback that is called when the user presses the `Enter` key on the field.
```vue ```
Note that empty search fields can be submitted, so you might want to validate the field before submitting. ## API ### Props These are the properties that can be passed to the `useSearchField` composable. ### Returns These are the properties in the object returned by the `useSearchField` composable. --- --- title: Select Fields description: Learn how to build accessible select field Vue components with Formwerk. --- # Select Fields import AnatomyCanvas from '@components/AnatomyCanvas.vue'; import AnatomySelect from '@components/AnatomySelect.vue'; import MdxRepl from '@components/MdxRepl.vue'; import MdxTableAPI from '@components/MdxTableAPI.vue'; import Kbd from '@components/KeyboardKey.vue'; import OptionPartial from './_partials/_option.mdx'; import OptionGroupPartial from './_partials/_optionGroup.mdx'; import SelectPartial from './_partials/_select.mdx'; Select fields are very common form fields. They allow the user to select one or more options from a list of options. The native `select` element does a lot in terms of interactivity and accessibility. However, it leaves a lot to be desired in terms of styling and customization. Formwerk tries to address that by providing the same accessability and interactive behaviors to your custom select component so you don't have to compromise for the sake of styling. Note that [customizable selects](https://developer.chrome.com/blog/rfc-customizable-select) are coming soon to the browser and Formwerk will leverage that when it becomes available. ## Features - Labeling, descriptions, error message displays are automatically linked to input and label elements with `aria-*` attributes. - Support for single/multiple selections. - Support for option groups. - Support for option searching with starting characters. - First-class Support for `[popover]` popups for dropdowns/menus. - Generic typing support for options. - Validation support with native HTML constraints or [Standard Schemas](https://github.com/standard-schema/standard-schema). - Support for `v-model` binding. - Supported Keyboard features: When focusing the trigger element, and menu is closed: | Key | Description | | ------------------------------------------ | ----------------------- | | | Opens the options menu. | | | Opens the options menu. | | | Opens the options menu. | | | Opens the options menu. | When focusing an option in the menu, and menu is open: | Key | Description | | -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | | Selects the option, if multiple toggles the option selection state. | | | Selects the option, if multiple toggles the option selection state. | | | Focuses the next option if available. | | | Focuses the previous option if available. | | + | Focuses the next option and selects it. | | + | Focuses the previous option and selects it. | | | Focuses the first option. | | + | Focuses the first option. If multiple is enabled, selects all options between the last focused option and the first option. | | | Focuses the first option. | | + | Focuses the first option. If multiple is enabled, selects all options between the last focused option and the first option. | | | Focuses the last option. | | + | Focuses the last option. If multiple is enabled, selects all options between the last focused option and the last option. | | | Focuses the last option. | | + | Focuses the last option. If multiple is enabled, selects all options between the last focused option and the last option. | | + | If multiple is enabled, selects all options. If all are already selected, deselects all options. | Most people aren't aware of these keyboard shortcuts, but they do make a big difference in the user experience for keyboard users. ## Anatomy ## Building a Select Field The select field is one of the most complex fields with multiple moving parts. It is what Formwerk considers a "compound component" because it is made up of an ecosystem of components that work together to create a single field. From the anatomy diagram above, you can see that the core select field is made up of the following parts: - **Trigger:** A trigger element that is used to open the options menu, doubles as the selected value display. - **Listbox:** An options menu that contains the list of options. - **Option:** An option component that represents each option in the list. - **Option Group:** Not illustrated above, an option group that groups options together based on some categorization. Formwerk handles all of these parts and abstracts them into the following component ecosystem: - **Select**: Contains the trigger and listbox parts. You will use `useSelect` to build it. - **Option**: Represents an option in the list. You will use `useOption` to build it. - **OptionGroup**: Represents a group of options in the list. You will use `useOptionGroup` to build it. Notice that the listbox is not a separate component. This is because popups today can be done in different ways, so Formwerk while offers the open state along with some accessability attributes, it does not have a specific listbox component implementation. But we offer out of the box support for [Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API) if you happen to use one for the listbox element. ### Building an Option Component We can start by using `useOption` to create our option component. This component will be responsible for rendering each option in the list. Notice that we are using the `OptionProps` type to define the props for our component. This type is generic and allows you to specify the type of the option value. In this case, we are using `any` to represent any type of value. The `label` is available inside `OptionProps` and we will display it to the user. ### Building a Select Component Next, you will use `useSelect` to create our select component. This component will be responsible for rendering the trigger and listbox parts. The `useSelect` composable returns binding objects for some of the elements shown in the [anatomy](#anatomy), you will use `v-bind` to bind them to the corresponding DOM elements.
```vue ```
### Building an Option Group Component Building an option group component is similar to building an option component. You will use `useOptionGroup` to create the component.
```vue ```
## Validation ### HTML Constraints You can use the following properties to validate the date field with native HTML constraint validation: | Name | Type | Description | | ---------- | --------- | ------------------------------------- | | `required` | `boolean` | Whether the select field is required. |
```vue ```
### Standard Schema `useSelect` supports [Standard Schema](https://github.com/standard-schema/standard-schema) validation through the `schema` prop. This includes multiple providers like [Zod](https://zod.dev/), [Valibot](https://valibot.dev/), [Arktype](https://arktype.dev/), and more.
```vue ```
### Mixed Validation All select fields created with Formwerk support mixed validation, which means you can use both HTML constraints and Standard Schema validation to validate the field, and they work seamlessly together. Note that HTML constraints are validated first, so any errors from the HTML constraints will be displayed first. Then, once all HTML constraints are satisfied, the Standard Schema is validated.
```vue ```
## Usage ### Multiple Select The `useSelect` composable accepts a `multiple` prop, it adjusts behaviors to what users expect out of a multi-select field.
```vue ```
### Disabled Use disabled to mark fields as non-interactive, disabled fields are not validated and are not submitted. If you need to prevent the user from interacting with the field while still allowing it to submit, consider using `readonly` instead.
```vue ```
You can also mark individual options as disabled by passing a `disabled` prop to the `OptionItem` component. Note that disabled options are skipped from the focus order when using shortcuts or the search functionality.
```vue ```
### Readonly Readonly fields are validated and submitted, but they do not accept user input. The field is still focusable and the popup is still openable. For more info, check the [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly).
```vue ```
## Styling When styling the select field, you need to be aware of the following... #### Option attributes When an option is selected, it will receive the `aria-selected="true"` attribute if the select field is a single choice select. If the select field is a multi-choice select, the selected options will receive the `aria-checked="true"` attribute instead. #### Trigger attributes The trigger element will receive the `aria-expanded` attribute. So you can style the trigger according to the popover state if needed. It will also receive the `aria-activedescendant` attribute when an option is focused. #### ListBox attributes The listbox element will receive the `aria-multiselectable` attribute if the select field is a multi-choice select. ## API ### Option #### Props These are the properties that can be passed to the `useOption` composable. #### Returns These are the properties in the object returned by the `useOption` composable. ### Option Group #### Props These are the properties that can be passed to the `useOptionGroup` composable. #### Returns These are the properties in the object returned by the `useOptionGroup` composable. ### Select #### Props These are the properties that can be passed to the `useSelect` composable. #### Returns These are the properties in the object returned by the `useSelect` composable. --- --- title: Sliders description: Learn how to build accessible slider Vue components with Formwerk. --- # Sliders import AnatomySlider from '@components/AnatomySlider.vue'; import AnatomyCanvas from '@components/AnatomyCanvas.vue'; import MdxRepl from '@components/MdxRepl.vue'; import MdxTableAPI from '@components/MdxTableAPI.vue'; import Kbd from '@components/KeyboardKey.vue'; import PreviewCard from '@components/PreviewCard.vue'; import SliderThumb from './_partials/_sliderThumb.mdx'; import Slider from './_partials/_slider.mdx'; import SliderMultiple from './_partials/_sliderMultiple.mdx'; > A slider is an input where the user selects a value from within a given range. Sliders typically have a slider thumb that can be moved along a bar, rail, or track to change the value of the slider. ## Features - Labeling, descriptions, and error message displays are automatically linked to input and label elements with `aria-*` attributes. - `v-model` support for binding the value of the slider and the individual thumbs. - Multi-thumb support with auto value clamping. - Support for `min`, `max`, and `step` attributes. - Support for both horizontal and vertical orientations. - Support for both LTR and RTL directions. - Validation with [Standard Schema](https://github.com/standard-schema/standard-schema). - Interactive behaviors: - Clicking the track element sets the value of the slider or the nearest suitable thumb to the clicked position. - Dragging the thumb element changes the value of the slider. - Supported keyboard interactions: | Key | Description | | ----------------------------------------- | ------------------------------------------------------------------------------------------ | | | Increments the slider value of the currently focused thumb. In RTL, it decrements instead. | | | Decrements the slider value of the currently focused thumb. In RTL, it increments instead. | | | Increments the slider value of the currently focused thumb. | | | Decrements the slider value of the currently focused thumb. | | | Sets the slider value of the currently focused thumb to the minimum possible value. | | | Sets the slider value of the currently focused thumb to the maximum possible value. | | | Increments the slider value of the currently focused thumb by a large step. | | | Decrements the slider value of the currently focused thumb by a large step. | ## Anatomy ## Building a thumb component Every slider needs at least one thumb to represent the current value of the slider. This means the slider in Formwerk is a compound component similar to radio buttons. First, let's build a thumb component that will be used in the slider later. We will keep styling to a minimum by using a simple circle SVG. You will be using the `useSliderThumb` composable to build the thumb component. There is nothing to show yet because we need to build the slider component. ## Building a slider component You will be using the `useSlider` composable to build the slider component. The `useSlider` composable returns binding objects for the elements shown in the [anatomy](#anatomy). You will use `v-bind` to bind them to the corresponding DOM elements. The Slider is what we consider a fully custom component, meaning it doesn't have an underlying `input` base element that you can use. While the `input[type="range"]` may be a suitable candidate, it doesn't scale to support the wide range (pun intended) of use-cases that developers expect of slider inputs today.
```vue ```
Notice that in order to model the slider progress visually, we used the utility composable `useThumbMetadata` to calculate the percentage of the thumb position. ## Validation Because sliders in Formwerk are a fully custom component, they don't support any HTML validation attributes. You can, however, use [Standard Schema](https://github.com/standard-schema/standard-schema) to validate the value of the slider. ### Standard Schema The `useSlider` composable accepts a `schema` prop that is an instance of a Standard Schema. This includes multiple providers like [Zod](https://zod.dev/), [Valibot](https://valibot.dev/), [Arktype](https://arktype.dev/), and more.
```vue ```
## Usage ### Multiple Thumbs You can have as many thumbs as you want inside a slider. Just add another `` component, and this will be automatically handled for you: - Min/Max clamping for each thumb. - Value conversion to an array instead of a single number.
```vue ```
Note that we did change some CSS from the previous examples in order to color the track properly, but it is up to you how you want to do that in any way you want. ### Disabled Use `disabled` to mark sliders as non-interactive. Disabled sliders are not validated and are not submitted.
```vue ```
You can also disable any individual `Thumb` by passing `disabled` to it as well. But it won't prevent validation/submission of the slider. ### Readonly Readonly sliders are validated and submitted, but they do not accept user input. The slider thumbs would still be focusable. For more info, check the [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly).
```vue ```
### RTL The `useSlider` composable accepts a `dir` property. You can set it to `RTL`, and it will handle thumb positioning automatically along with inverting the horizontal arrow keys (left/right arrows).
```vue ```
### Orientation `useSlider` also accepts an `orientation` prop. You can set it to either `horizontal` (default) or `vertical`. Formwerk will handle most things in either orientation in terms of interaction and thumb positioning. But slider layout is left to you to do. Here is an example that uses the `[aria-orientation]` attribute that is applied automatically. We will use it to flip the slider we initially created with some custom styles.
```vue ```
### Discrete Slider Values You can also use discrete slider values by passing an `options` prop to the slider. This is useful for non-numeric values like a rating slider. When doing this, the slider min/max will be set to the first and last option, and the step will be set to the difference between the first and last option divided by the number of options you want to show. This also means the `step` prop is ignored when `options` is used.
```vue ```
### Generic Types The slider's `SliderProps` accepts a generic type parameter that allows you to build generically typed slider components. ```vue ``` This gives you auto type inference for the slider's value even when using the `options` prop for any type of value. ## Styling You probably have noticed that the `trackProps` and `thumbProps` contain some minor styling properties. These are the bare minimum to get the slider working, and they are automatically added for you. If you are interested in knowing what properties are added, here is a list: - `trackProps` style properties: - `container-type`: is set to `size` or `inline-size` depending on the orientation. You should NOT override this property. - `position`: is set to `relative`. You can override this to anything but `static`. - `thumbProps` style properties: - `position`: is set to `absolute`. You should NOT override this property. - `translate`: used to position the thumb. You should NOT override this property. - `will-change`: is set to `translate`. You should NOT override this property. - Any inset position properties like `top`, `left`, `right`, `bottom` are set to `0`. You should NOT override these properties. We were very careful to not add any easily overridden properties to the `trackProps` and `thumbProps` to avoid any conflicts with your custom styles. Other than all of that, you can use any styling solution you want, whether it's [TailwindCSS](https://tailwindcss.com/) or plain CSS. #### Thumb attributes The thumb element will receive the `aria-orientation` attribute, which is the same as the slider's orientation, so you can style tooltips or other UI elements according to the slider's orientation. #### Slider attributes The slider element will receive the `aria-orientation` attribute, which is the prop you pass to the slider. ## Examples
## API ### Thumb #### Props These are the properties that can be passed to the `useSliderThumb` composable. #### Returns These are the properties in the object returned by the `useSliderThumb` composable. ### Slider #### Props These are the properties that can be passed to the `useSlider` composable. #### Returns These are the properties in the object returned by the `useSlider` composable. #### `useThumbMetadata` returns Additionally, `useThumbMetadata` returns a computed object with the following properties: --- --- title: Switches description: Learn how to build accessible switch Vue components with Formwerk. --- # Switches import AnatomyCanvas from '@components/AnatomyCanvas.vue'; import AnatomySwitch from '@components/AnatomySwitch.vue'; import MdxRepl from '@components/MdxRepl.vue'; import MdxTableAPI from '@components/MdxTableAPI.vue'; import Kbd from '@components/KeyboardKey.vue'; import SwitchPartial from './_partials/_switch.mdx'; import SwitchCustomPartial from './_partials/_switchCustom.mdx'; > A switch is an input widget that allows users to choose one of two values: on or off. Switches are similar to checkboxes and toggle buttons, which can also serve as binary inputs. One difference, however, is that switches can only be used for binary input. The switch field has enough unique behaviors and use-cases that justify it having its own composable. The binary state of a switch means it shouldn't be used to represent "required" inputs where the switch needs to be "on". It is a user preference that can be turned off. ## Features - You can use an `input[type="checkbox"]` element as a base or any custom element. - Labeling, descriptions, and error message displays are automatically linked to input and label elements with `aria-*` attributes. - Support for custom `on` and `off` values. - Validation support with native HTML constraint validation or [Standard Schema](https://github.com/standard-schema/standard-schema) validation. - Support for `v-model` binding. - Supported keyboard features: | Key | Description | | ---------------------------------- | -------------------------- | | | Toggles the switch on/off. | | | Toggles the switch on/off. | ## Anatomy ## Building a Switch component The `useSwitch` composable provides the necessary props and methods to build a switch component. It returns binding objects for the elements shown in the [anatomy](#anatomy). You will use `v-bind` to bind them to the corresponding DOM elements. There are two ways to build a switch component: - With an `input[type="checkbox"]` as a base element. - Without an `input` element, like a `div` or a `button`. We will review the two ways to build a switch component in the following examples. ### With `input[type="checkbox"]` base element We will add some styling to the next example because switches don't look like one unless we style them. Otherwise, it would just look like a checkbox.
```vue ```
### Without input elements Similar to the previous example, we can achieve the same look and behavior with a `div` element or a `button` element. In this example, we will use an `svg` element to closely resemble a switch. We will borrow those SVG paths from [Phosphor Icons](https://phosphoricons.com/).
```vue ```
## Validation ### HTML Constraints While ideally, the switch field should not be validatable, you can still use the `required` attribute if you choose to use an `input[type="checkbox"]` as a base element. We make no assumptions about how you want to use the switch field. | Name | Type | Description | | ---------- | --------- | ------------------------------------- | | `required` | `boolean` | Whether the number field is required. |
```vue ```
### Standard Schemas `useSwitch` also supports [Standard Schema](https://github.com/standard-schema/standard-schema) validation through the `schema` prop. This includes multiple providers like [Zod](https://zod.dev/), [Valibot](https://valibot.dev/), [Arktype](https://arktype.dev/), and more.
```vue ```
### Mixed Validation While it is unlikely that you need both HTML constraints and Standard Schemas to validate a switch, Formwerk supports mixed validation, which means you can use both HTML constraints and Standard Schemas to validate the switch, and they will work together. Note that HTML constraints are validated first, so any errors from the HTML constraints will be displayed first. Then, once all HTML constraints are satisfied, the Standard Schema is validated.
```vue ```
If you need to disable the native validation, you can do so by setting the `disableHtmlValidation` prop to `true`. You can also disable it globally for all fields. For more information, check out the [Validation](/guides/forms/validation/) guide.
```vue ```
## Usage ### Disabled Use `disabled` to mark the switch as non-interactive. Disabled switches are not validated and will not be submitted. If you need to prevent the user from interacting with the field while still allowing it to submit, consider using `readonly` instead.
```vue ```
### Readonly Readonly switches are validated and submitted, but they do not accept user input. The switch is still focusable, and the value is copyable. For more info, check the [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly).
```vue ```
### Custom On/Off values You can customize the `on` and `off` values of the switch component by passing the `trueValue` and `falseValue` props. They can be anything you want.
```vue ```
## API ### Props These are the properties that can be passed to the `useSwitch` composable. ### Returns These are the properties in the object returned by the `useSwitch` composable. --- --- title: Text Fields description: Learn how to build accessible text field Vue components with Formwerk. --- # Text Fields import AnatomyCanvas from '@components/AnatomyCanvas.vue'; import AnatomyTextField from '@components/AnatomyTextField.vue'; import MdxRepl from '@components/MdxRepl.vue'; import MdxTableAPI from '@components/MdxTableAPI.vue'; import TextFieldPartial from './_partials/_textField.mdx'; import TextAreaPartial from './_partials/_textarea.mdx'; Text fields are used to allow users to input plain text into a form. Text fields are implemented with the `input` element for a single line of text or the `textarea` element for multiple lines of text. ## Features - Uses `input` or `textarea` elements as a base. - Labels, descriptions, and error message displays are automatically linked to input and label elements with `aria-*` attributes. - Validation support with native HTML constraint validation or [Standard Schema](https://github.com/standard-schema/standard-schema) validation. - Support for `v-model` binding. ## Anatomy ## Building a Text Field Component You can start by importing the `useTextField` composable and using it in your text field component. The `useTextField` composable returns binding objects for the elements shown in the [anatomy](#anatomy). You will use `v-bind` to bind them to the corresponding DOM elements.
```vue ```
Notice that we imported the `TextFieldProps` in the previous example. This is recommended to use as your component prop types. Not only do you get type safety for your component out of it, but it also handles the reactivity aspects of the props so you don't have to. You are free to extend it with your own props or omit the ones you don't need. ## Building a Text Area Component Instead of using an `input[type="text"]` element, you can switch to using a `textarea` element as a base element instead.
```vue ```
## Validation ### HTML Constraints You can use the following properties to validate the text field with native HTML constraint validation: | Name | Type | Description | | ----------- | ------------------ | ------------------------------------------------------------------------- | | `maxLength` | `number` | The maximum length of characters. | | `minLength` | `number` | The minimum length of characters. | | `required` | `boolean` | Whether the text field is required. | | `pattern` | `string \| RegExp` | A regular expression for validation. Not supported for `textarea` fields. | In addition to the above properties, if you are using the `input` element, you can use the built-in validation for the `type` attribute. | Type | Description | | ------- | -------------------------------- | | `email` | Validates the value as an email. | | `url` | Validates the value as a URL. | Here is an example of how to use the `maxLength` and `minLength` properties to limit the text length between 3 and 18 characters. Assuming you have a `TextField` component like the one shown above, you can use it like this:
```vue ```
### Standard Schema `useTextField` also supports [Standard Schema](https://github.com/standard-schema/standard-schema) validation through the `schema` prop. This includes multiple providers like [Zod](https://zod.dev/), [Valibot](https://valibot.dev/), [Arktype](https://arktype.dev/), and more.
```vue ```
### Mixed Validation All text fields created with Formwerk support mixed validation, which means you can use both HTML constraints and Standard Schema validation to validate the field, and they work seamlessly together. Note that HTML constraints are validated first, so any errors from the HTML constraints will be displayed first. Then, once all HTML constraints are satisfied, the Standard Schema is validated.
```vue ```
This makes schemas lighter; however, we recommend sticking to one or the other per form for maintainability. If you need to disable the native validation, you can do so by setting the `disableHtmlValidation` prop to `true`.
```vue ```
You can also disable it globally for all fields. For more information, check out the [Validation](/guides/forms/validation/) guide. ## Usage ### Disabled Use `disabled` to mark fields as non-interactive. Disabled fields are not validated and are not submitted. If you need to prevent the user from interacting with the field while still allowing it to submit, consider using `readonly` instead.
```vue ```
### Readonly Readonly fields are validated and submitted, but they do not accept user input. The field is still focusable, and the value is copyable. For more info, check the [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly).
```vue ```
### RTL The text field doesn't require much for RTL support; however, the `dir` prop can be used to set the direction of the field for convenience.
```vue ```
## Styling Formwerk does not come with any markup or styling, which is often the part that makes a design system unique. That means you can use any styling solution you want, whether it's [TailwindCSS](https://tailwindcss.com/) or plain CSS, as you have already seen. Note that we make use of the `:user-invalid` pseudo-classes to style the field and control when to show the error messages without any JavaScript. Formwerk leans into native APIs and browser features to provide a more seamless experience for the user. In fact, even if you use Standard Schemas to validate the field, the pseudo-classes will still work. For more information on styling and recommendations, check the [Styling](/extras/styling/) guide. ## API ### Props These are the properties that can be passed to the `useTextField` composable. ### Returns These are the properties in the object returned by the `useTextField` composable. --- --- title: Time Fields description: Learn how to build accessible time field Vue components with Formwerk. --- # Time Fields import Kbd from '@components/KeyboardKey.vue'; import AnatomyTimeField from '@components/AnatomyTimeField.vue'; import AnatomyCanvas from '@components/AnatomyCanvas.vue'; import MdxRepl from '@components/MdxRepl.vue'; import MdxTableAPI from '@components/MdxTableAPI.vue'; import TimeFieldPartial from './_partials/_timeField.mdx'; import PreviewCard from '@components/PreviewCard.vue'; Time fields are a type of input field that allows users to enter a time, usually in the format of `hh:mm`. ## Features - Labeling, Descriptions, and error messages are automatically linked to the time field elements. - Validation support with native HTML constraints or [Standard Schemas](https://github.com/standard-schema/standard-schema). - Support for `v-model` binding. - Comprehensive keyboard shortcuts support. - Focus management and auto navigation for time segments. ### Keyboard Features | Key | Description | | -------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | | Decrements selected segment by 1. | | | Increments selected segment by 1. | | | Moves the focus to the previous segment. | | | Moves the focus to the next segment. | | | Clears the current segment. | | | Clears the current segment. | | | Moves the focus to the next segment or next element in the tab index order if it is the last segment. | ## Anatomy ## Building a Time Field Component Just like the [native HTML time field](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/time), the time field in Formwerk is comprised of time segments, each segment represents a part of the time value (e.g: hour, minute). The segments are generated for you automatically based on the `formatOptions` you pass to the `useTimeField` composable. You will need to bind the prop objects returned by the composable to the corresponding DOM elements in the [anatomy](#anatomy). If you do not pass the `formatOptions` prop, the time field will use an `HH:mm` format.
```vue ```
## Validation ### HTML Constraints You can use the following properties to validate the time field with native HTML constraint validation: | Name | Type | Description | | ---------- | --------- | ------------------------------------- | | `min` | `Date` | The minimum time that can be entered. | | `max` | `Date` | The maximum time that can be entered. | | `required` | `boolean` | Whether the time field is required. |
```vue ```
### Standard Schema `useTimeField` supports [Standard Schema](https://github.com/standard-schema/standard-schema) validation through the `schema` prop. This includes multiple providers like [Zod](https://zod.dev/), [Valibot](https://valibot.dev/), [Arktype](https://arktype.dev/), and more. In this example, we are validating that the time field is between 00:00 and 10:04.
```vue ```
### Mixed Validation All time fields created with Formwerk support mixed validation, which means you can use both HTML constraints and Standard Schema validation to validate the field, and they work seamlessly together.
```vue ```
## Usage ### Disabled Use the `disabled` prop to disable the time field. Disabled time fields are not validated and are not submitted.
```vue ```
### Readonly Use the `readonly` prop to make the time field read-only. Read-only time fields are still validated and submitted. If you need to prevent the user from interacting with the field while still allowing it to submit, consider using `readonly` instead.
```vue ```
### Min and Max You can pass `min` and `max` props to set the minimum and maximum times that can be entered.
```vue ```
### Format Options You can pass a partial of the `Intl.DateTimeFormatOptions` to the `formatOptions` prop to customize the time field's display format. For example, we can switch to 12-hour format.
```vue ```
### Show Seconds Not only you can change the 12-hour format to 24-hour format, you can also control the visibility of the seconds segment with the `formatOptions` as well.
```vue ```
## Examples These are some examples of time fields built with Formwerk.
## API ### useTimeField #### Props These are the properties that can be passed to the `useTimeField` composable. #### Returns These are the properties in the object returned by the `useTimeField` composable. ### useDateSegment #### Props These are the properties that can be passed to the `useDateSegment` composable. #### Returns These are the properties in the object returned by the `useDateTimeSegment` composable. ---