# Introduction

Learn about what Vale is (and isn't).

Vale is a command-line tool that brings code-like linting to prose. Vale is cross-platform (Windows, macOS, and Linux), written in Go, and available on GitHub.

> *Linting* is the process of ensuring that written work (source code or prose) adheres to a particular style—for example, Python’s [PEP 8](https://www.python.org/dev/peps/pep-0008/) style guide (code) or the Google’s [Documentation Style Guide](https://developers.google.com/style/) (prose).

Before getting into the details of what makes Vale useful, there’s one point that needs clarification: **Vale is not a general-purpose writing aid**.

It doesn’t teach you *how* to write; it’s a tool *for* writers.

More specifically, Vale focuses (primarily) on the style of writing rather than its grammatical correctness—making it fundamentally different from, for example, Grammarly.

![Two authors each use writing tools of their own choosing, producing drafts that Vale then checks against one shared standard.](/files/sh1E5OSRHjLT1XXq2klL)

In other words, Vale focuses on ensuring consistency across multiple authors (according to customizable guidelines) rather than the general “correctness” of a single author’s work.

This distinction is particularly important to understand because Vale doesn’t offer any of its own advice. Instead, it offers a framework for creating and enforcing [custom rules](/topics/styles). Its approach is much more similar to code linters than it is to traditional grammar checkers.

## [Your style, our editor](#your-style-our-editor)

One of Vale’s most important features is its ability to support external styles through its extension system, which only requires some familiarity with the YAML file format (and, optionally, regular expressions).

![Most writing software runs one fixed set of checks to produce one result. Vale runs extension points, so each style produces its own result.](/files/MshQCUTl1r5o2e292yII)

To get a better idea of how this works, let’s look at an example from the [Linode documentation](https://github.com/linode/docs/blob/master/ci/vale/styles/Linode/Terms.yml):

{% code title="Terms.yml" %}

```yaml
# `extends` specifies the extension point you're using. Here, we're
# using `substitution` to ensure correct usage of some technical and
# brand-specifc terminology.
extends: substitution
# `message` allows you to customize the output shown to your users.
message: Use '%s' instead of '%s'.
# We're setting this rule's severity to `error`, which will cause
# CI builds to fail.
level: error
# We're using case-insensitive patterns.
ignorecase: true
swap:
  "(?:LetsEncrypt|Let's Encrypt)": Let's Encrypt
  'node[.]?js': Node.js
  'Post?gr?e(?:SQL)': PostgreSQL
  'java[ -]?scripts?': JavaScript
  linode cli: Linode CLI
  linode manager: Linode Manager
  linode: Linode
  longview: Longview
  nodebalancer: NodeBalancer
```

{% endcode %}

In the above example, we’ve defined a few terms that have a particular capitalization style. If Vale finds an instance of a term that matches a pattern on the left of swap (case-insensitive) but doesn’t exactly match the value on the right, it issues an error. So, for example, `Nodebalancer`, `nodebalancer` or any other variation that doesn’t exactly match `NodeBalancer` will be flagged as an error.

While this example may appear quite simple, it’s possible to achieve fairly high coverage on complete editorial style guides. Check out the [Explorer](https://vale.sh/explorer) for more examples.

## [Syntax- and context-aware linting](#syntax--and-context-aware-linting)

Another feature that separates Vale from other linters is its ability to understand its input at both a syntactic and contextual level.

![A Markdown file with each region labelled by the scope Vale assigns it: heading, link text, list item, and a fenced code block that is skipped.](/files/cS6p3MXPaVGlLi2tG7yE)

This level of understanding gives you fine-grained control over the linting process, including the ability to limit rules to certain sections (e.g., only headings) or ignore sections entirely (block and inline code are ignored by default).

Additionally, since Vale is built on top of an NLP library, you can also target specific segments of text—allowing you to, for example, warn about paragraphs that exceed a certain number of words or sentences that end with prepositions.

## [Tech stack](#tech-stack)

Vale is a 100% open-source, MIT-licensed project that consists of multiple parts:

| Name                                                     | Tech       | Info                                                                                                         |
| -------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------ |
| [`vale`](https://github.com/vale-cli/vale)               | Go         | The main repository containing the Vale command-line interface.                                              |
| [`vale-ls`](https://github.com/vale-cli/vale-ls)         | Rust       | An implementation of the Language Server Protocol (LSP) for the Vale command-line tool.                      |
| [`vale.sh`](https://github.com/vale-cli/vale.sh)         | Svelte     | Website and documentation for the Vale CLI and related projects.                                             |
| [`vale-action`](https://github.com/vale-cli/vale-action) | TypeScript | The official GitHub Action for Vale -- install, manage, and run Vale with ease.                              |
| [`packages`](https://github.com/vale-cli/packages)       | YAML       | A collection of pre-packaged, Vale-compatible style guides and configurations.                               |
| [`vale-native`](https://github.com/vale-cli/vale-native) | Go         | A native messaging host for the Vale CLI: Use your local configurations in Chrome, Firefox, Opera, and Edge. |


# Quickstart

Get Vale running on your project in about five minutes.

This walks through a first working setup. Each step links to the page that covers it properly, so read on where you want the detail.

## [1. Install Vale](#1-install-vale)

Use your package manager—`brew install vale`, `choco install vale`, or one of the others on the [Installation](/topics/installation) page—then check it worked:

```bash
$ vale --version
```

## [2. Create a `.vale.ini`](#2-create-a-valeini)

Vale doesn't ship with opinions of its own. It needs a configuration file saying where to keep styles and which to apply, so create one in the root of your project:

{% code title=".vale.ini" %}

```ini
StylesPath = styles
MinAlertLevel = suggestion

Packages = Microsoft

[*.md]
BasedOnStyles = Vale, Microsoft
```

{% endcode %}

Four things are happening:

* `StylesPath` is the folder Vale will keep downloaded styles in. Add it to your `.gitignore`.
* `MinAlertLevel` is the lowest severity worth reporting.
* `Packages` names what to download—here, Microsoft's writing style guide.
* `[*.md]` applies the styles that follow it to Markdown files. `Vale` is built in; `Microsoft` is the package.

See [.vale.ini](/topics/.vale.ini) for everything the file accepts.

## [3. Download the styles](#3-download-the-styles)

```bash
$ vale sync
```

```
 SUCCESS  Synced 1 package(s) to 'styles'.
```

Run this again whenever you change `Packages`. See [Packages](/keys/packages) for other ways to name one, including a URL or a local path.

## [4. Lint something](#4-lint-something)

```bash
$ vale README.md
```

```
 README.md
 3:1   suggestion  Consider using 'to' instead of 'In order to'.  Microsoft.Wordiness
 3:13  suggestion  Consider using 'use' instead of 'utilize'.     Microsoft.Wordiness

✔ 0 errors, 0 warnings and 2 suggestions in 1 file.
```

You can pass a directory instead of a file, or a glob:

```bash
$ vale docs/
$ vale --glob='*.md' .
```

## [5. Read the output](#5-read-the-output)

Each line is one alert:

```
 3:13  suggestion  Consider using 'use' instead of 'utilize'.     Microsoft.Wordiness
 └─┬─┘ └────┬───┘  └──────────────────┬──────────────────────┘    └────────┬────────┘
 line:col  severity                 message                            rule name
```

The rule name is the useful part: it's `<style>.<rule>`, and it's how you turn one off. To silence that rule for Markdown, name it in your config:

```ini
[*.md]
BasedOnStyles = Vale, Microsoft

Microsoft.Wordiness = NO
```

Severity matters for automation. **Only `error` sets a non-zero exit code**, so a CI job fails on errors and passes with warnings and suggestions:

```bash
$ vale README.md   # 2 suggestions
$ echo $?
0
```

See [MinAlertLevel](/keys/minalertlevel) for changing what gets reported, and [BasedOnStyles](/keys/basedonstyles) for enabling and disabling rules.

## [Setting up with a coding agent](#setting-up-with-a-coding-agent)

If an AI assistant is doing the setup, give it [AGENTS.md](https://vale.sh/AGENTS.md) rather than this page. It covers the same four steps, plus the things that are easy to get wrong without reading further: only `error` sets a non-zero exit code, `--output=JSON` is the format to parse, and a term that trips spell check belongs in a vocabulary rather than in a disabled rule.

Save it in the root of your repository, where most assistants read it automatically.

For task-shaped work — fixing alerts, triaging a first run, adding a vocabulary — there are [agent skills](https://vale.sh/skills) to copy in alongside it. In Claude Code, the skills, an edit-time linting hook, and the Vale CMS MCP server install together as one plugin:

```
/plugin marketplace add vale-cli/agent-tools
/plugin install vale@agent-tools
```

The skills and the hook run the CLI you just installed and need no account. The hook lints each prose file as your assistant writes it and hands back only error-level alerts, so a mistake is fixed in the same turn it was made. The MCP server is the one paid piece — it belongs to [Vale CMS](https://vale.sh/cms) and gives an assistant the engine itself to check a rule against, rather than the docs about it.

## [Where to go next](#where-to-go-next)

* Browse the [Package Explorer](https://vale.sh/explorer) for styles beyond Microsoft.
* Write your own rules with [Styles](/topics/styles) and the [checks](/checks/existence) reference.
* Narrow what Vale reads using [Scopes](/topics/scopes).


# Installation

Get started with Vale in just a few minutes.

## [Pick your platform](#pick-your-platform)

The recommended approach on every platform is a package manager: it puts `vale` on your `$PATH` and keeps you up to date with new releases.

{% tabs %}
{% tab title="macOS" %}
[Homebrew](https://formulae.brew.sh/formula/vale) tracks new releases closely:

```bash
$ brew install vale
```

[MacPorts](https://ports.macports.org/port/vale/) also packages Vale, and may lag behind releases.
{% endtab %}

{% tab title="Windows" %}
[Chocolatey](https://chocolatey.org/packages/vale):

```powershell
> choco install vale
```

[Scoop](https://scoop.sh/#/apps?q=vale):

```powershell
> scoop install vale
```

[winget](https://winstall.app/apps/errata-ai.Vale):

```powershell
> winget install -e --id errata-ai.Vale
```

{% endtab %}

{% tab title="Linux" %}
On Debian and Ubuntu, the [pkg.haus](https://pkg.haus) APT archive ships Vale for stable, testing, and unstable (amd64 and arm64), built from source at release tags. Set up the archive per the instructions on [pkg.haus](https://pkg.haus), then:

```bash
$ sudo apt install vale
```

On Arch Linux, Vale is in the official repositories:

```bash
$ sudo pacman -S vale
```

[Snapcraft](https://snapcraft.io/vale) works across distributions:

```bash
$ sudo snap install vale
```

Many other distributions—Alpine, openSUSE, Void, and more—package Vale in their own repositories; see [the full list](https://repology.org/project/vale/versions).
{% endtab %}

{% tab title="FreeBSD" %}
Vale is in the ports collection as [`textproc/vale`](https://www.freshports.org/textproc/vale/):

```bash
$ pkg install vale
```

There are no official FreeBSD binaries on the releases page, so the port is also the answer for build-from-source setups.
{% endtab %}
{% endtabs %}

## [Installing Vale with a project](#installing-vale-with-a-project)

A system-wide install leaves each contributor on whatever version they happened to get, and your CI on another. Declaring Vale in the project instead pins one version for everyone—which matters because a new release can add rules or change what an existing one matches.

[mise](https://mise-versions.jdx.dev/tools/vale) does this for any project, whatever it's written in:

```bash
$ mise use vale@3.19.0
```

If your project already installs its tools through a language's package manager, Vale is packaged there too. Each of these downloads the same release binaries and puts `vale` on your `$PATH`:

{% tabs %}
{% tab title="npm" %}

```bash
$ npm install --save-dev @vvago/vale
```

Adds [`@vvago/vale`](https://www.npmjs.com/package/@vvago/vale) to `devDependencies`; run it with `npx vale`.
{% endtab %}

{% tab title="PyPI" %}

```bash
$ pip install vale
```

Installs [`vale`](https://pypi.org/project/vale/) into the active environment; pin it in `requirements.txt` or your `pyproject.toml`.
{% endtab %}

{% tab title="conda" %}

```bash
$ conda install conda-forge::vale
```

Installs [`conda-forge/vale`](https://anaconda.org/conda-forge/vale) into the active environment, or list it under `dependencies` in `environment.yml`.
{% endtab %}
{% endtabs %}

For linting in CI, the [Vale GitHub Action](https://github.com/vale-cli/vale-action) installs and runs Vale in one step, and Vale can also run as a [pre-commit hook](/integrations/pre-commit).

## [GitHub Releases](#github-releases)

[Archives of precompiled binaries](https://github.com/vale-cli/vale/releases) are available for Windows, macOS, and Linux (amd64 and arm64). Download the archive for your platform, extract it, and (optionally) add the extracted directory to your `$PATH`.

## [Building from source](#building-from-source)

Vale is a Go program, so `go install` builds it for any platform Go supports—including those without a release archive:

```bash
$ go install github.com/vale-cli/vale/v3/cmd/vale@latest
```

This needs Go 1.25.7 or later and a C compiler, since Vale's source-code parsers are built through cgo. A binary built this way reports its version as `master` rather than the release number.

## [Docker](#docker)

Vale is available on Docker Hub at [jdkato/vale](https://hub.docker.com/r/jdkato/vale):

```bash
$ docker pull jdkato/vale
```

Vale requires three components: a `.vale.ini` config file, a `StylesPath` directory (specified in the config file), and a document or directory to lint.

Here's an example of calling Vale with locally-defined components (assuming `$(pwd)/fixtures/styles/demo` contains a config file):

```bash
$ docker run --rm \
             -v $(pwd)/styles:/styles \
             -v $(pwd)/fixtures/styles/demo:/docs \
             -w /docs \
             jdkato/vale .
```

By default, the image supports HTML, Markdown, AsciiDoc, and reStructuredText content. If you need support for DITA as well, you'll need to add the relevant dependencies—for example,

```dockerfile
# Choose a version to pin:
FROM jdkato/vale:v3.18.0

# Copy a local installation of the DITA Open Toolkit:
COPY bin/dita-ot-3.6 /
ENV PATH="/dita-ot-3.6/bin:$PATH"

ENTRYPOINT ["/bin/vale"]
```

## [A note on community packages](#a-note-on-community-packages)

Outside of Homebrew, the GitHub release archives, pkg.haus, and Docker Hub, packages are community-maintained: they may lag behind releases, and their issues belong with their maintainers. [Repology](https://repology.org/project/vale/versions) tracks which version each repository currently ships.


# .vale.ini

Learn how to configure Vale for your specific needs.

## [Creating a `.vale.ini` File](#creating-a-valeini-file)

After installing Vale, you’ll need to create a `.vale.ini` file in your project’s root directory. This file is used to configure Vale’s behavior and can be used to specify which rules to use, which directories to lint, and more.

The fastest way to get started with Vale is to use the [Config Generator](https://vale.sh/generator) to create a `.vale.ini` configuration file.

Once you have your local `.vale.ini` created in the directory of your choice, run `vale sync` from the command line to initialize it:

```bash
$ cd some-project
# You'll need to create this file
$ cat .vale.ini
...
$ vale sync
...
$ ls styles
...
$ vale README.md
```

Check out our [sample repository](https://github.com/vale-cli/vale-boilerplate) for a complete example of the required components of a Vale configuration.

## [File structure](#file-structure)

Vale’s configuration is read from a `.vale.ini` file. This file is [INI-formatted](https://ini.unknwon.io/docs/intro) and consists of multiple sections: core settings, format associations, and format-specific settings:

```ini
# Core settings appear at the top
# (the "global" section).

[formats]
# Format associations appear under
# the optional "formats" section.

[*]
# Format-specific settings appear
# under a user-provided "glob"
# pattern.
```

### [Core settings](#core-settings)

| Name                                 | Type       | Description                               |
| ------------------------------------ | ---------- | ----------------------------------------- |
| [StylesPath](/keys/stylespath)       | `string`   | Path to all Vale-related resources.       |
| [Packages](/keys/packages)           | `string[]` | List of packages to download and install. |
| [Vocab](/keys/vocabularies)          | `string[]` | List of vocabularies to load.             |
| [MinAlertLevel](/keys/minalertlevel) | `enum`     | Minimum alert level to display.           |
| [IgnoredScopes](/keys/ignoredscopes) | `enum`     | List of inline-level HTML tags to ignore. |
| [SkippedScopes](/keys/skippedscopes) | `enum`     | List of block-level HTML tags to ignore.  |

Core settings appear at the top of the file and apply to the application itself rather than a specific file format.

### [Format associations](#format-associations)

Format associations allow you to associate an “unknown” file extension with a supported one:

```ini
[formats]
txt = md
```

In the example above, we’re telling Vale to treat `.txt` files as Markdown files. Note that this is merely an extension-level substitution and is not a means of adding support for a new file type.

An association changes how Vale *reads* a file, not what it’s called. Sections still match the name on disk, so the example above needs a `[*.txt]` section—`[*.md]` won’t reach those files:

```ini
[formats]
txt = md

[*.{md,txt}]
BasedOnStyles = Vale
```

### [Format-specific settings](#format-specific-settings)

| Name                                         | Type        | Description                                     |
| -------------------------------------------- | ----------- | ----------------------------------------------- |
| [BasedOnStyles](/keys/basedonstyles)         | `string[]`  | List of styles to load.                         |
| [BlockIgnores](/keys/blockignores)           | `string[]`  | List regexes to ignore in block-level content.  |
| [TokenIgnores](/keys/tokenignores)           | `string[]`  | List regexes to ignore in inline-level content. |
| [CommentDelimiters](/keys/commentdelimiters) | `string[2]` | Comment delimiters to replace at runtime.       |
| [Transform](/keys/transform)                 | `string`    | A version 1.0 XSL Transformation (XSLT).        |

Format-specific sections apply their settings only to files that match their associated glob pattern. For example, `[*]` matches all files while `[*.{md,txt}]` only matches files that end with either `.md` or `.txt.`

You can have as many format-specific sections as you’d like and settings defined under a more specific section will override those in `[*]`.

A pattern can name a path as well as an extension, so `[docs/src/*.md]` narrows a section to one part of the project:

```ini
[*.md]
BasedOnStyles = Vale

[docs/src/*.md]
TokenIgnores = (\[?-?@[^\s\]]+\]?)
```

A path pattern is matched against the file as Vale was asked for it, so write it relative to where you run Vale. The section above applies to `vale .`, `vale docs`, and `vale docs/src/page.md` run from the project root; it doesn’t match an absolute path such as `vale /home/me/project/docs/src/page.md`.

See [Globbing](/guides/globbing) for more information on how to use glob patterns with Vale.

## [Search process](#search-process)

{% hint style="warning" %}
You can override the default search process by manually specifying a path using the `--config` option or by defining a `VALE_CONFIG_PATH` environment variable.
{% endhint %}

![Vale looks for a configuration file in this order: the --config option, then VALE\_CONFIG\_PATH, then a .vale.ini searched for from the working directory upwards. The global configuration is always loaded as well, and is read last so it can override the others.](/files/P7ixPPuII3URk693ccMi)

Vale expects its configuration to be in a file named `.vale.ini` or `_vale.ini`. It’ll start looking for this file in the directory that the `vale` command was run from and then search up the file tree until it finds one.

If no ancestor of the current directory has a configuration file, Vale will use a global configuration file (see below).

## [Global configuration](#global-configuration)

In addition to project-specific configurations, Vale also supports a global configuration file. The expected location of the global configuration depends on your operating system:

| OS      | Search Locations                                   |
| ------- | -------------------------------------------------- |
| Windows | `%LOCALAPPDATA%\vale\.vale.ini`                    |
| macOS   | `$HOME/Library/Application Support/vale/.vale.ini` |
| Unix    | `$XDG_CONFIG_HOME/vale/.vale.ini`                  |

(Run the `vale ls-dirs` command to see the exact locations on your system.)

This is different from the other config-defining options (`--config`, `VALE_CONFIG_PATH`, etc.) in that it’s loaded in addition to, rather than instead of, any other configuration sources.

In other words, this config file is *always* loaded and is read after any other sources to allow for project-agnostic customization.

## [Cascading overrides](#cascading-overrides)

Vale’s configuration system supports using multiple configuration files at the same time. Typically, this is done in cases where you are contributing to a project that already has an established configuration but you want to make local changes.

For example, let’s say you’re working on a project that uses the following configuration:

```ini
StylesPath = styles
MinAlertLevel = error

[*.md]
BasedOnStyles = ProjectStyle
```

Now, let’s say you want to add the `write-good` style to your local configuration.

Create a global configuration file—for macOS, this would be `~/Library/Application Support/vale/.vale.ini` (see above for other OSes).

```ini
StylesPath = localpath

Packages = write-good

[*.md]
BasedOnStyles = write-good
```

Now, when you run Vale, it will show results from both the `ProjectStyle` and `write-good` styles locally.

You’ll notice that multi-valued settings (like `BasedOnStyles`) are merged together, while single-valued settings (like `MinAlertLevel`) are overridden.

This allows you to contribute to projects with established styles while still being able to make local changes.


# CLI

Learn about the Vale command-line interface.

The Vale CLI is a powerful tool for linting your content in a variety of formats. To get started, try running with no arguments:

![Running vale with no arguments prints a short usage summary and an example configuration file.](/files/hR7Ho6TgblXzB5CO93Z7)

## [Environment variables](#environment-variables)

The following list of environment variables are supported by the `vale` command-line interface:

| Variable           | Description                                                         |
| ------------------ | ------------------------------------------------------------------- |
| `VALE_CONFIG_PATH` | Override the default search process by specifying a .vale.ini file. |
| `VALE_STYLES_PATH` | Specify the location of the default StylesPath.                     |

You can inspect the current environment variables by running:

```
$ vale ls-vars
```

The exact steps for setting environment variables depend on your operating system, but here are some useful links for [Windows](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/setx) and [macOS](https://support.apple.com/guide/terminal/use-environment-variables-apd382cc5fa-4f58-4449-b20a-41c53c006f8f/mac).

## [CLI options](#cli-options)

| Name               | Description                                                                                                                                                                                            |                                      |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------ |
| `sync`             | <p>Download and install packages. See <a href="/pages/9c0d0f187b18e5c5f584dbcd08f05b78015cb529">Packages</a> for more information.<br><code>$ vale sync</code></p>                                     |                                      |
| `ls-config`        | <p>Print the current configuration options as JSON.<br><code>$ vale ls-config</code></p>                                                                                                               |                                      |
| `ls-metrics`       | <p>Print the computed metrics for the given file. See <a href="/pages/e4e23859394b0d893f81f7d87a16990cfd321a3b">metric</a> for more information.<br><code>$ vale ls-metrics path/to/file</code></p>    |                                      |
| `ls-dirs`          | <p>Print the location of default configuration directories.<br><code>$ vale ls-dirs</code></p>                                                                                                         |                                      |
| `ls-vars`          | <p>Print the supported environment variables.<br><code>$ vale ls-vars</code></p>                                                                                                                       |                                      |
| `--config`         | <p>Override the default configuration search process.<br><code>$ vale --config='path/to/.vale.ini' README.md</code></p>                                                                                |                                      |
| `--ext`            | <p>Assign a file extension to stdin.<br><code>$ echo "</code><em><code>This</code></em><code> is Markdown"                                                                                             | vale --ext=.md</code></p>            |
| `--filter`         | <p>An expression to filter rules by. See <a href="/pages/1ca74c93553907de9f4ef834acdc197c797ea53b">Filters</a> for more information.<br><code>$ vale --filter='"heading" in .Scope' test.md</code></p> |                                      |
| `--glob`           | <p>A glob pattern to match files against. See <a href="/pages/2e2d8e9760081b393c82b5b8f5b13152bec9a645">Globbing</a> for more information.<br><code>$ vale --glob='\*.md' some-dir</code></p>          |                                      |
| `--ignore-syntax`  | <p>Treat all input as plain text.<br><code>$ vale --ignore-syntax README.md</code></p>                                                                                                                 |                                      |
| `--minAlertLevel`  | <p>Set the minimum alert level to display.<br><code>$ vale --minAlertLevel=error README.md</code></p>                                                                                                  |                                      |
| `--no-exit`        | <p>Do not return a non-zero exit code if there are errors.<br><code>$ vale --no-exit README.md</code></p>                                                                                              |                                      |
| `--no-wrap`        | <p>Do not wrap output.<br><code>$ vale --no-wrap README.md</code></p>                                                                                                                                  |                                      |
| `--no-global`      | <p>Do not load the global configuration.<br><code>$ vale --no-global README.md</code></p>                                                                                                              |                                      |
| `--output`         | <p>Change the output format. See <a href="/pages/2d1654ae6eb5e995f8144efc4d126122a2157019">Templates</a> for more information.<br><code>$ vale --output=JSON README.md</code></p>                      |                                      |
| `--path`           | <p>Associate a file path with stdin, so that configuration sections and format detection apply.<br><code>$ cat draft.md                                                                                | vale --path=docs/draft.md</code></p> |
| `--plain-progress` | <p>Log each step instead of drawing a progress bar. Useful for CI logs, which otherwise record only redrawn frames.<br><code>$ vale sync --plain-progress</code></p>                                   |                                      |
| `--version`        | <p>Print the version of Vale.<br><code>$ vale --version</code></p>                                                                                                                                     |                                      |

## [Return codes](#return-codes)

The `vale` CLI returns the following exit codes:

| Code | Description                                                                                  |
| ---- | -------------------------------------------------------------------------------------------- |
| `0`  | No error(s) were found.                                                                      |
| `1`  | Linting error(s) were found. Useful for failing CI builds; can be disabled with `--no-exit`. |
| `2`  | Runtime error(s) occurred.                                                                   |

It will try to respect the value of `--output` when printing to `stderr`. For example:

![Vale reporting an E201 configuration error, showing the offending line of the rule file and the accepted values.](/files/5CAuzDYe94IezD60esVM)


# Styles

Learn about the primary component of Vale's configuration system.

Vale has a powerful extension system that doesn’t require knowledge of any programming language. Instead, it uses collections of individual [YAML](http://yaml.org/) files (or “rules”) to enforce particular writing constructs.

```yaml
# An example rule from the "Microsoft" style.
extends: existence
message: "Don't use end punctuation in headings."
link: https://docs.microsoft.com/en-us/style-guide/punctuation/periods
nonword: true
level: warning
scope: heading
action:
  name: edit
  params:
    - remove
    - '.?!'
tokens:
  - '[a-z0-9][.?!](?:\s|$)'
```

These collections are referred to as *styles* and are organized in a nested folder structure at a user-specified location. For example,

```
$ tree styles
styles/
├── base/
│   ├── ComplexWords.yml
│   ├── SentenceLength.yml
│   ...
├── blog/
│   ├── TechTerms.yml
│   ...
└── docs/
    ├── Branding.yml
```

where *base*, *blog*, and *docs* are your styles that each contain certain rules.

## [Rules](#rules)

{% hint style="warning" %}
Make sure your rule files end in extension `.yml`. Do not end them in `.yaml`, as Vale will not detect them.
{% endhint %}

The building blocks of styles are called *rules* (YAML files ending in `.yml`), which utilize *checks* to perform specific tasks.

The structure of a rule consists header followed by check-specific arguments. Every rule supports the following header fields:

| Name      | Required | Default      | Description                                                                                                                                                                                     |
| --------- | -------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `extends` | Yes      | `N/A`        | <p>The name of the check to extend in the particular rule. See <a href="/pages/dfefb67b73c3bc524a08e7c5d5bf39e1d914dffc">Rules</a> for more information.<br><code>extends: existence</code></p> |
| `message` | Yes      | `N/A`        | <p>The message to display when the rule is triggered. Each extension point has different formatting options.<br><code>message: "Don't use '%s' headings."</code></p>                            |
| `level`   | No       | `suggestion` | <p>The severity of the rule. The available options are <code>suggestion</code>, <code>warning</code>, and <code>error</code>.<br><code>level: warning</code></p>                                |
| `scope`   | No       | `text`       | <p>The scope of the rule. See <a href="/pages/aedfa672e4cd05de03ec08e299e580bfb2f2faf1">Scopes</a> for more information.<br><code>scope: heading</code></p>                                     |
| `link`    | No       | `N/A`        | <p>A URL to associate with the rule. This is useful for providing more information about the rule.<br><code>link: <https://example.com></code></p>                                              |
| `limit`   | No       | `N/A`        | <p>The maximum number of times the rule can be triggered in a single file.<br><code>limit: 3</code></p>                                                                                         |
| `vocab`   | No       | `true`       | <p>If set to false, any active vocabularies will be disabled for the rule.<br><code>vocab: false</code></p>                                                                                     |

## [Checks](#checks)

Each rule *extends* a specific check, which is a built-in function that performs a particular task. For example, the `existence` check ensures that a given pattern is present in the content.

| Name                                     | Description                                                                               |
| ---------------------------------------- | ----------------------------------------------------------------------------------------- |
| [existence](/checks/existence)           | Check for the presence of a specific regex pattern.                                       |
| [substitution](/checks/substitution)     | Replace a regex pattern with a specific string.                                           |
| [occurrence](/checks/occurrence)         | Ensure the presence of a regex pattern a specific number of times.                        |
| [repetition](/checks/repetition)         | Avoid repeating a regex pattern a specific number of times.                               |
| [consistency](/checks/consistency)       | Ensure that a regex pattern is used consistently.                                         |
| [conditional](/checks/conditional)       | Check for the presence of a regex pattern based on a condition.                           |
| [capitalization](/checks/capitalization) | Ensure that a regex pattern is capitalized in a specific way.                             |
| [metric](/checks/metric)                 | Check the readability (or other metrics) of your content using custom formulas.           |
| [spelling](/checks/spelling)             | Spell check using Hunspell-compatible dictionaries.                                       |
| [sequence](/checks/sequence)             | Ensure that a regex pattern is used in a specific order. Supports part-of-speech tagging. |
| [script](/checks/script)                 | Run a custom Tengo script to check your content.                                          |

## [Extending another rule](#extending-another-rule)

{% hint style="info" %}
Rule inheritance requires Vale v3.20.0 or later.
{% endhint %}

An `extends` value containing a dot names a rule rather than a check: the new rule starts from that rule's full definition and lays its own keys on top. Two styles can share one carefully built pattern and disagree only about message, level, or a handful of entries:

```yaml
# House/Hedging.yml
extends: Direct.Hedging
message: "Hedge: '%s'. We state things plainly here."
level: error
```

The parent has to be present on the `StylesPath`, not enabled — inheritance is a file reference, and `vale sync` is what puts the file there.

A bare key replaces the parent's value wholesale. Lists and maps also take overlay edits:

* `key+` appends to the parent's list, or merges into a parent map with the child's entries winning.
* `key-` removes entries from a parent list by their source text, or the named keys from a parent map. Removing something the parent doesn't have is a compile error, so an upstream rename is heard about rather than silently diverged from.

```yaml
# Stricter than the parent: two more phrases, one dropped.
extends: Direct.Hedging
message: "Hedge: '%s'."
tokens+:
  - 'arguably'
  - 'to some extent'
tokens-:
  - 'perhaps'
```

Writing both `key` and `key+` (or `key-`) in one file is an error: that says "replace" and "edit the replacement" at once.

A directory whose name starts with `_` or `.` is skipped at load time but stays visible to `extends`, so a pattern shared by several rules can live in one file without itself becoming a rule:

```
styles/GenZ/
├── _shared/
│   └── Slang.yml   # never loads; Density, Budget, and Presence extend it
├── Budget.yml
├── Density.yml
└── Presence.yml
```

A fragment is validated as the chain's root, so it must carry a `message` — even one no alert will ever show.

## [Nested directories](#nested-directories)

{% hint style="info" %}
Nested rule directories require Vale v3.20.0 or later.
{% endhint %}

A style can organize its rules in subdirectories, and the path joins the rule's name: `Std/dates/TimeFormat.yml` is addressed as `Std.dates.TimeFormat` everywhere a rule name goes — configuration, in-text comments, filters, and output.

```
styles/Std/
├── dates/
│   ├── DateFormat.yml
│   └── TimeFormat.yml
└── SentenceLength.yml
```

As above, directories prefixed with `.` or `_` are inert, so drafts and shared fragments can sit inside a style without loading.

## [Regex](#regex)

Many rules will require the use of regular expressions to match specific patterns in your content. Vale uses [a superset](https://github.com/dlclark/regexp2?tab=readme-ov-file#compare-regexp-and-regexp2) of Go’s [regexp/syntax](https://pkg.go.dev/regexp/syntax) package to provide a powerful and flexible regex engine.

In addition to the standard Go regex syntax, Vale also supports positive lookahead (`(?=re)`), negative lookahead (`(?!re)`), positive lookbehind (`(?<=re)`), and negative lookbehind (`(?<!re)`).

See the [Regex](/guides/regex) guide for more information.

## [Vale](#vale)

Vale comes with a single built-in style named `Vale` that implements a few rules, as described in the table below.

| Name              | Description                                                                                                                             |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `Vale.Spelling`   | Checks for spelling errors in your content. Consumes any Hunspell-compatible dictionaries stored in `<StylesPath>/config/dictionaries`. |
| `Vale.Terms`      | Enforces the current project's accepted [Vocabulary](/keys/vocabularies) terms.                                                         |
| `Vale.Avoid`      | Enforces the current project's rejected [Vocabulary](/keys/vocabularies) terms.                                                         |
| `Vale.Repetition` | Flags repeated words such as "the the" or "and and".                                                                                    |


# Scopes

Learn how Vale decides which parts of a file a rule applies to.

Vale is “markup aware”: it reads a file into the pieces a reader would recognize—headings, paragraphs, list items, links—and lets a rule say which of those pieces it applies to. That choice is the rule’s *scope*.

## [What a scope is](#what-a-scope-is)

Every file Vale reads becomes a sequence of *blocks*. Each block carries a scope made of dot-separated parts that say what the block is and where it came from. A list item in a Markdown file, for example, is `text.list.md`.

A rule’s `scope` is a selector over those parts. It matches a block when **all of its parts appear in the block’s scope**. It isn’t a prefix match or an exact match, and the order you write the parts in doesn’t matter:

| Selector       | Matches `text.list.md`? |
| -------------- | ----------------------- |
| `list`         | Yes                     |
| `list.md`      | Yes                     |
| `md.list`      | Yes—order is irrelevant |
| `text.list`    | Yes                     |
| `list.text.md` | Yes                     |
| `heading`      | No—not one of its parts |

Two things follow. Any selector can be qualified with a file extension to make it format-specific: `paragraph` matches paragraphs everywhere, and `paragraph.rst` matches them only in reStructuredText. And a selector with fewer parts is broader: `text` matches nearly everything, because nearly every scope contains it.

![One Markdown file becomes several scoped sections: the heading is text.heading.md, the paragraph text.md, the link inside it link.md, and the list item text.list.md. A selector matches when every one of its parts appears in the scope, which is why list.md matches a list item but link.text.md matches nothing.](/files/pdA3erDfaDaySmES5Hrz)

Vale classifies files into one of three types—`markup`, `code`, or `text`—and the type determines which parts exist. Within a type, every format shares the same parts, so a rule written for Markdown headings applies to AsciiDoc headings too.

## [Markup](#markup)

The default behavior for markup files is to apply rules to all non-ignored text. For most rules, you don’t need a scope at all.

For rules that target a kind of element, the parser assigns these parts:

| Name             | Description                                                                                                                                                               |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `heading`        | <p>Matches all <code>h{1,...}</code> tags. You can specify an exact level by<br>appending tags—for example, <code>heading.h1</code> matches all <code>h1</code> tags.</p> |
| `table.header`   | Matches all `th` tags.                                                                                                                                                    |
| `table.cell`     | Matches all `td` tags.                                                                                                                                                    |
| `table.caption`  | Matches all `caption` tags.                                                                                                                                               |
| `figure.caption` | Matches all `figcaption` tags.                                                                                                                                            |
| `list`           | Matches all `li` tags.                                                                                                                                                    |
| `blockquote`     | Matches all `blockquote` tags.                                                                                                                                            |
| `alt`            | Matches all alt attributes.                                                                                                                                               |

The supported formats for markup files are:

* [AsciiDoc](/formats/asciidoc)
* [DITA](/formats/dita)
* [HTML](/formats/html) Built-in
* [Markdown](/formats/markdown) Built-in, including [R Markdown](/formats/markdown#r-markdown)
* [MDX](/formats/mdx) Built-in
* [MyST](/formats/myst) Built-in
* [Org](/formats/org) Built-in
* [QDoc](/formats/qdoc) Built-in
* [Quarto](/formats/quarto) Built-in
* [reStructuredText](/formats/restructuredtext)
* [Typst](/formats/typst)
* [XML](/formats/xml)

The formats marked as `Built-in` are included with Vale by default. The other formats require a third-party dependency to be installed. See each format’s documentation for more information and installation instructions.

### [Class scopes](#class-scopes)

{% hint style="info" %}
Requires Vale v3.17.0 or later.
{% endhint %}

Markup that carries no distinct tag of its own can still be selected by the classes wrapping it. Any enclosing class is appended to the scope as `.class.<name>`.

An AsciiDoc block title, for example, renders as `<div class="title">`—indistinguishable from body text by tag alone:

```yaml
extends: capitalization
message: "'%s' should be in title case."
scope: text.class.title
level: warning
match: $title
```

A directive’s name lands here too: a [MyST](/formats/myst) or [Quarto](/formats/quarto) `:::{note}` scopes its content as `class.note`, and a [QDoc](/formats/qdoc) `\note` does the same.

Classes nest, so a block inside two classed elements is reachable as `text.class.outer.class.inner`—and every block inside a classed container carries its class, however many blocks that is. To ignore classed content rather than target it, see [`IgnoredClasses`](/keys/ignoredclasses).

### [Inline elements](#inline-elements)

{% hint style="info" %}
Requires Vale v3.17.0 or later.
{% endhint %}

Inline elements have scopes of their own, which let a rule target the text inside a link, a code span, or an emphasized phrase:

| Name       | Description                                    |
| ---------- | ---------------------------------------------- |
| `link`     | Matches the text of all `a` tags.              |
| `code`     | Matches all `code` and `tt` tags (code spans). |
| `strong`   | Matches all `strong` and `b` tags.             |
| `emphasis` | Matches all `em` and `i` tags.                 |

{% hint style="warning" %}
These are siblings of `text`, not children of it: the scope is `link`, **not** `text.link`.

Scopes are matched by containment, so a rule scoped to `text` would also match `text.link`—meaning every ordinary rule would run a second time over each link.
{% endhint %}

A rule that asks for one of these scopes is the only kind that runs against it:

```yaml
extends: existence
message: "Don't use '%s' as link text."
scope: link
level: error
tokens:
  - here
  - this
  - click here
```

The text inside code spans is skipped by default (`IgnoredScopes` defaults to `tt`, `code`, and `kbd`), so a rule has to ask for `code` explicitly. To exclude inline text rather than target it, see [`IgnoredScopes`](/keys/ignoredscopes).

### [Metadata](#metadata)

{% hint style="info" %}
Requires Vale v3.18.0 or later.
{% endhint %}

A document’s machine-readable content—an anchor name, an image’s file name—is collected under the `meta` scope. A [QDoc](/formats/qdoc) file’s anchors and image file names land here, as does the content of an HTML `data` element.

Like the [inline elements](#inline-elements), `meta` is a sibling of `text`: an ordinary prose rule passes over it, and only a rule that asks for `meta` reaches it. Its text isn’t segmented, either—an identifier has no sentences.

Each piece carries its kind as a class, so a rule can target all of a document’s metadata or one kind of it:

```yaml
extends: existence
message: "Don't use spaces in a file name."
scope: meta.class.image
level: error
tokens:
  - " "
```

## [Prose units](#prose-units)

The parts above name elements of the document. These four name units of prose that Vale builds itself:

| Name        | Description                                                                                                                                                                                                                                                                                          |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sentence`  | Each sentence, from every kind of prose—paragraphs, headings, list items, and table cells.                                                                                                                                                                                                           |
| `paragraph` | <p>Each body paragraph (a segment of text separated by two newlines).<br>Headings, list items, table cells, and blockquotes are not paragraphs.</p>                                                                                                                                                  |
| `summary`   | <p>The document’s prose as one block, excluding headings, code spans, code blocks, and<br>table cells. This is what <a href="/pages/e4e23859394b0d893f81f7d87a16990cfd321a3b"><code>metric</code></a> and <a href="/pages/XgeIpg7inwYUp1KQPWmH"><code>readability</code></a> measure by default.</p> |
| `raw`       | <p>The raw, unprocessed markup source as one block. This scope is useful for regex-based rules<br>that need to match against the original source text.</p>                                                                                                                                           |

A scope is a request as well as a selection. Sentences exist only because some rule declared `sentence`, and paragraphs only because some rule declared `paragraph`; Vale builds each unit when a rule asks for it and skips the work when none does. The same is true of the [selections](#selections) below.

## [Selections](#selections)

{% hint style="info" %}
Requires Vale v3.21.0 or later.
{% endhint %}

A `doc(...)` term selects elements of the document by CSS selector. Every markup format Vale reads is parsed into the same HTML, and the selector runs against that, so a selection means the same thing in Markdown, AsciiDoc, reStructuredText, Org, and HTML.

Before the selectors run, Vale wraps each heading and everything that follows it—up to the next heading of the same or a higher level—in a `section` element. Sections nest, so an `h3` after an `h2` sits inside the `h2`’s section. That is what lets a selector name a section of a document, which no format marks up on its own.

![A Markdown file with an h1, three h2 sections, and an h3 under Decision is wrapped into nested sections. The selector doc(section:has(a direct h2 child containing Decision)) marks the Decision section, so the paragraph and the h3 inside it carry the selection in their scopes while Context and Consequences do not, and the section itself is one doc block.](/files/7sM1nfEAs8UXNoov2Ist)

A `doc(...)` term means one of three things, depending on where it stands:

**Alone, the element is the block.** Its text is gathered as one unit and linted once, so a rule can count or measure it:

```yaml
extends: occurrence
message: "The Decision section states no decision. Write it as 'We will ...'."
level: error
scope: 'doc(section:has(> h2:contains("Decision")))'
token: '(?i)\bwe will\b'
min: 1
```

```yaml
extends: metric
message: "The Summary runs %s words. The budget is 100."
level: error
scope: 'doc(section:has(> h2:contains("Summary")))'
formula: words
condition: "> 100"
```

An alert on a selection lands on the element’s first line, which is the heading of a section.

**Beside a scope, it narrows that scope to blocks inside the element.** Any existing rule can be pointed at one part of a document:

```yaml
extends: existence
message: "'%s' hedges. A recommendation states it."
level: error
scope: 'sentence & doc(section:has(> h2:contains("Recommendation")))'
tokens:
  - may want to
  - it is worth noting
```

```yaml
extends: existence
message: "Open on the point, not on '%s'."
level: warning
# The paragraph directly after any h2.
scope: 'text & doc(h2 + p)'
tokens:
  - in this section
```

**Negated, it excludes the element.** `~doc(...)` reaches everything outside the elements the selector matches.

A selection that matches nothing in a file still produces its block, empty, on line one. That is how a rule reports that a section is missing: an `occurrence` rule with `min: 1` and a token that matches any character finds zero occurrences and reports the shortfall.

```yaml
extends: occurrence
message: "An ADR has a Decision section."
level: error
scope: 'doc(section:has(> h2:contains("Decision")))'
token: '(?s).'
min: 1
```

Selectors follow the syntax of [Selectors Level 4](https://www.w3.org/TR/selectors-4/), including `:has(> x)` for a direct child, `:not`, `:first-of-type`, `:last-of-type`, and the `nth` family. Two things to know:

* `:contains("…")` matches any part of the text, so `h2:contains("Decision")` also matches a heading that reads “Decision log.” Give it the whole heading.
* An element with no prose in it—a code block, an image—selects fine and then has nothing to lint. Selections are for elements that hold text.

The elements a selector matches carry a `data-vale-doc` attribute in Vale’s internal HTML, and blocks inside them carry the selection in their scope as `in.<id>`. Neither is something a rule writes; the rule writes the selector.

## [Combining selectors](#combining-selectors)

Rules may define multiple scopes by using a YAML array. An entry matches if **any** of them does:

```yaml
scope:
  # h1 OR h2
  - heading.h1
  - heading.h2
```

Any scope prefaced with `~` is negated:

```yaml
scope:
  # all scopes != h2
  - ~heading.h2
```

You can chain multiple scopes together using `&`, which requires **all** of them:

```yaml
scope:
  # any scope that is NOT a blockquote or a heading
  - ~blockquote & ~heading
```

{% hint style="info" %}
Chains that name `paragraph`, `sentence`, or an [inline element](#inline-elements) require Vale v3.18.0 or later. Earlier versions silently matched nothing for those chains.
{% endhint %}

The two combine: `&` is an AND within a single entry, and the array is an OR across entries. A `doc(...)` term is a term like any other, so it chains with prose units and negates the same way:

```yaml
scope:
  # (a heading that isn't an h1) OR (a list item in Markdown)
  - heading & ~heading.h1
  - list.md
  # sentences outside the Decision section
  - sentence & ~doc(section:has(> h2:contains("Decision")))
```

{% hint style="info" %}
Because matching is by parts rather than by prefix, a narrower-looking scope isn’t always narrower. `text.list` and `list` select the same blocks—the extra `text` adds nothing, since every list item’s scope already contains it.
{% endhint %}

## [Checks and scopes](#checks-and-scopes)

Every check runs on the blocks its scope names. Three of them have a default worth knowing:

* [`metric`](/checks/metric) and [`readability`](/checks/readability) measure the whole document unless the rule declares a scope. `text` also means the whole document here, since the document’s prose is what `summary` holds. Any other scope measures each block it names on its own—a paragraph, a heading, a list item, or a selection—and a selection counts the elements it holds, so `heading.h3` inside a section is the number of subheadings in that section.
* [`sequence`](/checks/sequence) reads part-of-speech tags, which are assigned a sentence at a time, so it always reads sentences. Its scope says which blocks the sentences come from: `list` is the sentences of list items, and a `doc(...)` term is the sentences inside the selection.

## [Code](#code)

There are two `code` scopes: `comment.line` and `comment.block`.

See the [Code](/formats/code) documentation for more information.


# Actions

Create dynamic suggestions for your rules with Actions.

{% hint style="info" %}
See [`vale-ls`](/guides/lsp) for an easy way to integrate Actions into your favorite text editor.
{% endhint %}

Actions provide a way for users to define dynamic fixes for their custom rules that show up in the CLI and LSP-based integrations.

![Actions](/files/93f6681a0fc12a2033fb6108edda4459745d01c9)

In the Sublime Text example above, the “Quick Fix” menu is powered by the action defined in the rule definition:

{% code title="rule.yml" %}

```yaml
action:
  name: replace
```

{% endcode %}

See the documentation on each `action` type for more information:

| Name                        | Description                                                                                  |
| --------------------------- | -------------------------------------------------------------------------------------------- |
| [`suggest`](/fixes/suggest) | An array of dynamically-computed suggestions.                                                |
| [`replace`](/fixes/replace) | An array of static suggestions. Supported by default in `substitution` and `capitalization`. |
| [`remove`](/fixes/remove)   | Remove the matched text.                                                                     |
| [`edit`](/fixes/edit)       | In-place edits of the matched text.                                                          |

## [CLI](#cli)

Most Vale rules are based on *static* suggestions—for example,

{% code title="rule.yml" %}

```yaml
extends: substitution
message: "Use '%s' instead of '%s'."
level: error
action:
  name: replace
swap:
  Javascript: JavaScript
```

{% endcode %}

Here, the `action` is a to *replace*`Javascript` with `JavaScript`. In such cases, we know what we want to suggest to the user ahead of time and Vale can easily generate the appropriate output message.

However, there are cases in which we *don’t* know the appropriate suggestion ahead of time. For example, consider the following rule:

{% code title="rule.yml" %}

```yaml
extends: existence
message: "'%s' should be '%s'."
level: error
action:
  name: edit
  params:
    - regex
    - '(\w+)_(\w+)'
    - '$1-$2'
tokens:
  - '\w+_\w+'
```

{% endcode %}

This rule is designed to catch instances of `snake_case` and suggest that the user convert to `kebab-case`. In this case, the exact suggestion is dependent on a string transformation that needs to be computed at runtime.

Using the `edit` action allows us to define a rule that can dynamically generate suggestions based on the matched text in CLI output:

![Vale reporting two errors where identifiers should be written with hyphens.](/files/sGAgsIF3EG0K1suWhj8q)

As you can see, the CLI output is dynamically computing the suggestion based on the matched text.

## [LSP](#lsp)

In both static and dynamic cases, any application that uses the [Vale Language Server](/guides/lsp) will be able to provide the user with a list of “Quick Fixes” that can be applied to the document.


# Filters

Learn about Vale's rule filtering system.

The `--filter` CLI option allows you to report an arbitrary subset of your `.vale.ini` configuration.

![Using --filter to limit results to alerts whose scope is a heading.](/files/UXbxj39bktIBLJwCMWL6)

A filter is an [expression](https://expr-lang.org/docs/language-definition) targeting one of the following keys defined in the rule definition: `.Name`, `.Level`, `.Scope`, `.Message`, `.Description`, `.Extends`, or `.Link`.

## Saving filters

You can save a filter for reuse by storing it in `<StylesPath>/config/filters`. Then, you can reference it by name when using the `--filter` option:

```bash
$ vale --filter=headings.expr docs/
```

Where `headings.expr` is a file containing the filter expression, such as:

```tengo
"heading" in .Scope
```

## Examples

* Filter by `.Level` and `.Name`:

```tengo
.Level in ["error", "suggestion"] and .Name != "demo.Cap"
```

* Filter by `.Extends`:

```tengo
.Extends=="existence"
```

* Only run a specific rule:

```tengo
.Name=="demo.Cap"
```

See the [documentation](https://expr-lang.org/docs/language-definition#operators) for a list of all supported operators.


# Templates

Learn about Vale's output templates.

By default, Vale includes support for three output styles: `line`, `JSON`, and `CLI` (the default). You can specify which style to use via the `--output` flag:

```bash
$ vale --output=line README.md
```

In addition to the three provided output styles, Vale also supports *custom* output styles powered by Go’s [`text/template`](https://golang.org/pkg/text/template/) package.

To use a custom format, pass the path to a template file through the `--output` option:

```bash
$ vale --output='template.tmpl' somefile.md
```

Where `template.tmpl` is a file that contains a valid Go template stored in the `<StylesPath>/config/templates` directory.

## [Templating](#templating)

Templates have access to the following data structures:

```go
type ProcessedFile struct {
    Alerts []core.Alert
    Path   string
}

type Data struct {
    Files       []ProcessedFile
    LintedTotal int
}
```

Where `core.Alert` has the same information as Vale’s `--output=JSON` object.

Templates can also access the following functions:

| Name          | Argument(s) | Description                                                                                                                                                                                                                                           |
| ------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `red`         | `string`    | Returns the given `string` with an ANSI-formatted red foreground color.                                                                                                                                                                               |
| `blue`        | `string`    | Returns the given `string` with an ANSI-formatted blue foreground color.                                                                                                                                                                              |
| `yellow`      | `string`    | Returns the given `string` with an ANSI-formatted yellow foreground color.                                                                                                                                                                            |
| `underline`   | `string`    | Returns the given `string` with an ANSI-formatted underline.                                                                                                                                                                                          |
| `newTable`    | `bool`      | Creates a new [`tablewriter`](https://github.com/olekukonko/tablewriter#ascii-table-writer) struct. `newTable` accepts one boolean value representing [`SetAutoWrapText`](https://godoc.org/github.com/olekukonko/tablewriter#Table.SetAutoWrapText). |
| `addRow`      | `[]string`  | Appends the given row to a table.                                                                                                                                                                                                                     |
| `renderTable` | `Table`     | Prints the table-formatted output to `stdout`.                                                                                                                                                                                                        |
| `jsonEscape`  | `string`    | Ensure the given `STRING` is valid JSON.                                                                                                                                                                                                              |

See the [Sprig Function Documentation](http://masterminds.github.io/sprig/) for the full list.

## [Examples](#examples)

### [Customizing the default output](#customizing-the-default-output)

The following example re-implements Vale’s default output style using a template.

```go
{{- /* Keep track of our various counts */ -}}

{{- $e := 0 -}}
{{- $w := 0 -}}
{{- $s := 0 -}}
{{- $f := 0 -}}

{{- /* Range over the linted files */ -}}

{{- range .Files}}
{{$table := newTable true}}

{{- $f = add1 $f -}}
{{- .Path | underline | indent 1 -}}

{{- /* Range over the file's alerts */ -}}

{{- range .Alerts -}}

{{- $error := "" -}}
{{- if eq .Severity "error" -}}
    {{- $error = .Severity | red -}}
    {{- $e = add1 $e  -}}
{{- else if eq .Severity "warning" -}}
    {{- $error = .Severity | yellow -}}
    {{- $w = add1 $w -}}
{{- else -}}
    {{- $error = .Severity | blue -}}
    {{- $s = add1 $s -}}
{{- end}}

{{- $loc := printf "%d:%d" .Line (index .Span 0) -}}
{{- $row := list $loc $error .Message .Check | toStrings -}}

{{- $table = addRow $table $row -}}
{{end -}}

{{- $table = renderTable $table -}}
{{end}}
{{- $e}} {{"errors" | red}}, {{$w}} {{"warnings" | yellow}} and {{$s}} {{"suggestions" | blue}} in {{$f}} {{$f | int | plural "file" "files"}}.
```

### [Creating a RDJSONL template](#creating-a-rdjsonl-template)

The following example converts Vale’s output to [RDJSONL](https://github.com/reviewdog/reviewdog?tab=readme-ov-file#reviewdog-diagnostic-format-rdformat), which you can then pass to [Reviewdog](https://github.com/reviewdog/reviewdog) to display on pull request. This can be useful when the [Vale action](https://github.com/vale-cli/vale-action) is not suitable for your workflow.

```go
{{- /* Range over the linted files */ -}}

{{- range .Files}}

{{- $path := .Path -}}

{{- /* Range over the file's alerts */ -}}

{{- range .Alerts -}}

{{- $error := "" -}}
{{- if eq .Severity "error" -}}
    {{- $error = "ERROR" -}}
{{- else if eq .Severity "warning" -}}
    {{- $error = "WARNING" -}}
{{- else -}}
    {{- $error = "INFO" -}}
{{- end}}

{{- /* Variables setup */ -}}

{{- $line := printf "%d" .Line -}}
{{- $start := index .Span 0 -}}
{{- $end := add (index .Span 1) 1 -}}
{{- $check := printf "%s" .Check -}}
{{- $message := printf "%s" .Message -}}

{{- /* Output */ -}}

{"message": "{{ $message | jsonEscape }}", "location": {"path": "{{ $path }}", "range": {"start": {"line": {{ $line }}, "column": {{ $start }}}, "end": {"line": {{ $line }}, "column": {{ $end }}}}}, "severity": "{{ $error }}", "code": {"value": "{{ $check | jsonEscape }}"{{ if .Link }}, "url": "{{ .Link | jsonEscape }}"{{ end }}}}
{{end -}}
{{end -}}
```

Two things are worth knowing when adapting this. Reviewdog reads a range, so giving it the end of the span -- `Span` is inclusive, and Reviewdog's end is not -- is what underlines the match rather than pointing at its first character. And Reviewdog counts columns in UTF-8 bytes where Vale counts characters, so the two agree only until a line picks up its first multi-byte character; converting between them needs the source line, which a template can't read.

### [Creating a SARIF template](#creating-a-sarif-template)

The following example converts Vale's output to [SARIF](https://sarifweb.azurewebsites.net/), the format that [GitHub code scanning](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/uploading-a-sarif-file-to-github), GitLab, and Azure DevOps read. Unlike a pull request comment, an alert reported this way persists: it has a history, and someone can dismiss it with a reason.

```bash
$ vale --output=sarif.tmpl . > vale.sarif
```

In a GitHub workflow, hand the file to `upload-sarif`:

```yaml
- name: Upload to code scanning
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: vale.sarif
```

Two details make the conversion straightforward. SARIF measures columns in characters, as Vale's `Span` does, so the positions carry over unchanged. And SARIF asks for each rule to be described once, which [`dict`](http://masterminds.github.io/sprig/dicts.html) and `set` collect in a pass over the alerts before any output.

```go
{{- /* Collect the rules that fired, so that each is described once. */ -}}
{{- $rules := dict -}}
{{- range .Files -}}
{{- range .Alerts -}}
{{- $_ := set $rules .Check (dict "link" .Link "text" .Description) -}}
{{- end -}}
{{- end -}}
{
  "$schema": "https://json.schemastore.org/sarif-2.1.0.json",
  "version": "2.1.0",
  "runs": [
    {
      "tool": {
        "driver": {
          "name": "Vale",
          "informationUri": "https://vale.sh",
          "rules": [
{{- $first := true -}}
{{- range $id, $rule := $rules }}
{{ if not $first }},{{ end }}            {
              "id": "{{ $id | jsonEscape }}",
              "shortDescription": {"text": "{{ if $rule.text }}{{ $rule.text | jsonEscape }}{{ else }}{{ $id | jsonEscape }}{{ end }}"}
              {{- if $rule.link }},
              "helpUri": "{{ $rule.link | jsonEscape }}"
              {{- end }}
            }
{{- $first = false -}}
{{- end }}
          ]
        }
      },
      "results": [
{{- $first = true -}}
{{- range .Files -}}
{{- $path := .Path -}}
{{- range .Alerts }}
{{ if not $first }},{{ end }}        {
          "ruleId": "{{ .Check | jsonEscape }}",
          "level": "{{ if eq .Severity "error" }}error{{ else if eq .Severity "warning" }}warning{{ else }}note{{ end }}",
          "message": {"text": "{{ .Message | jsonEscape }}"},
          "locations": [
            {
              "physicalLocation": {
                "artifactLocation": {"uri": "{{ $path | jsonEscape }}"},
                "region": {
                  "startLine": {{ .Line }},
                  "startColumn": {{ index .Span 0 }},
                  "endColumn": {{ add (index .Span 1) 1 }},
                  "snippet": {"text": "{{ .Match | jsonEscape }}"}
                }
              }
            }
          ]
        }
{{- $first = false -}}
{{- end -}}
{{- end }}
      ]
    }
  ]
}
```


# Views

Customize the file-processing pipeline with Views.

Views represent a virtual, filtered perspective of a file that has structure but no markup Vale can parse: a data file, a source file, or plain text with a convention. They define a series of steps that extract specific, named [scopes](/topics/scopes), effectively changing how the file is represented for linting purposes. By focusing only on relevant sections, Views let you control exactly what content is analyzed—and enable rules that apply only to specific parts of a file.

A markup file needs no View. Vale parses it into the document its rules already see, and a rule reaches part of that document with a [scope](/topics/scopes#selections).

Each View is defined in a YAML file and consists of a series of steps that are executed in order. Each step includes the following fields:

* `name`: The name of the step. If no `type` is provided, the name is used as the only scope for the value. Otherwise, the `name` is used as a metascope and will be appended to the active scope – such as `heading.<name>.md`.
* `expr`: An expression that selects the data to be linted. The expression is evaluated by the active [engine](#engines).
* `type`: The type of the data. Supported types are `md`, `adoc`, `html`, `rst`, or `org`.

Here’s an example of a View that extracts the `title` and `description` fields from an OpenAPI document:

```yaml
engine: dasel
scopes:
  - name: title
    expr: info.title
    type: md

  - expr: info.description
    type: md

  - expr: servers.all().description
    type: md
```

Views are stored in `<StylesPath>/config/views` and can be referenced in the `.vale.ini` file under any syntax-specific section:

```ini
[*.json]
BasedOnStyles = Vale

View = MyView
```

## [Engines](#engines)

Each step in a View contains a query that is processed by the View’s engine: [Dasel](https://github.com/TomWright/dasel) for data (JSON, YAML, or TOML), [tree-sitter](https://tree-sitter.github.io/tree-sitter/) for source code, or [TextFSM](#textfsm) for plain text.

### [Dasel](#dasel)

[Dasel](https://github.com/TomWright/dasel) is a command-line tool that allows you to query and modify data structures using selectors. It works with JSON, YAML, TOML, XML, and more.

Vale uses Dasel to query structured data in files and extract the relevant content. For example, given the following JSON:

```json
{
	"title": "Vale",
	"version": "3.0.0",
	"features": [
		{
			"title": "Views",
			"description": "Customize the file-processing pipeline with Views."
		},
		{
			"title": "Styles",
			"description": "Define custom linting rules with Styles."
		}
	]
}
```

You could use the following View to extract the `name` and `description` fields from each feature:

```yaml
engine: dasel
scopes:
  # The `name` field is used as the metascope, allowing us to
  # write rules that specifically target the `title` field by
  # using the custom `feature` scope.
  - name: feature
    expr: features.all().title
    type: md

  - expr: features.all().description
    type: md
```

Check out the [playground](https://dasel.tomwright.me/) to experiment with Dasel queries.

### [Tree-sitter](#tree-sitter)

[Tree-sitter](https://tree-sitter.github.io/tree-sitter/) is a parser generator tool and an incremental parsing library. It can be used to build parsers for source code in any language.

Vale uses tree-sitter to parse source code and extract structured data. For example, given the following Python code:

```python
# This a comment.
def hello(name: str) -> str:
    """
    This is a docstring.
    """
    return f"Hello, {name}!"
```

You could use the following View to extract all comments and function docstrings:

```yaml
engine: tree-sitter
scopes:
  - name: comment
    expr: (comment)+ @comment

  - expr: |
      ((function_definition
        body: (block . (expression_statement (string) @docstring)))
      (#offset! @docstring 0 3 0 -3))
```

See [Pattern Matching with Queries](https://tree-sitter.github.io/tree-sitter/using-parsers/queries/index.html) for more information.

### [TextFSM](#textfsm)

{% hint style="info" %}
Requires Vale v3.21.0 or later.
{% endhint %}

Plain text often has structure by convention rather than by markup: a commit message is a subject, a blank line, a body, and trailers; a transcript is a series of turns, each opened by a name. Nothing parses that, so Vale reads such a file as lines and a rule can’t say “the subject” or “the model’s turn.”

A `textfsm` View reads the file through a template in the form [TextFSM](https://github.com/google/textfsm/wiki/TextFSM) defined: a list of named values, then a state machine whose rules are regular expressions. Vale runs the template itself, in the same regular-expression dialect every rule uses, and records the line and column of everything it captures, so an alert lands where the text is.

The template is written inline under `template`, and each scope’s `expr` names one of its values:

```yaml
engine: textfsm
template: |
  Value Subject (.+)
  Value List Body (.*)
  Value List Trailer ([A-Z][\w-]+: .+)

  Start
    ^${Subject} -> Body

  Body
    ^${Trailer}
    ^${Body}
scopes:
  - name: subject
    expr: Subject

  - name: body
    expr: Body
    type: md

  - name: trailer
    expr: Trailer
```

```ini
[COMMIT_EDITMSG]
BasedOnStyles = Vale, House

View = Commit
```

A commit message then yields three scopes, and a rule reaches one of them the way it reaches a heading:

```yaml
extends: existence
message: "A subject line doesn't end with '%s'."
level: error
scope: subject
raw:
  - '\.$'
```

The template language, in brief:

* A `Value` line declares a name and the pattern that fills it. `List` gathers every capture rather than the last; `Filldown` carries a value into the next record; `Required` drops a record the value is missing from.
* A state is a name on its own line, and the rules under it are tried in order against each line of the file. `Start` is where reading begins.
* A rule is a pattern, in which `${Name}` stands for a value’s pattern and captures it, followed by an optional `->` and what happens on a match: `Next` (the default) reads the next line, `Continue` keeps trying the rules below on the same line, `Record` emits the values captured so far, and a state name moves to that state. `End` stops reading.
* A record is emitted at the end of the file as well, so a template that never says `Record` yields one record per file.

Consecutive lines a `List` value captures are joined into one block, so a body reads as the paragraphs it is. A line the template captures at a different column starts a new block.

The second example is the one that gives the engine its reason to exist. A transcript alternates between a user and a model, and only one side is yours to lint:

```yaml
engine: textfsm
template: |
  Value List Assistant (.*)
  Value List User (.*)

  Start
    ^assistant: ${Assistant} -> Assistant
    ^user: ${User} -> User

  Assistant
    ^(?:user|assistant): -> Continue.Record
    ^assistant: ${Assistant}
    ^user: ${User} -> User
    ^${Assistant}

  User
    ^(?:user|assistant): -> Continue.Record
    ^user: ${User}
    ^assistant: ${Assistant} -> Assistant
    ^${User}
scopes:
  # Only the model's turns are linted; the prompts are left alone.
  - name: assistant
    expr: Assistant
    type: md
```

A rule scoped to `assistant` runs over the model’s turns, and a misspelling in the user’s prompt goes unreported, because no scope names the `User` value.

The same shape fits any text with a convention: subtitles, where the cue text is prose and the timestamps aren’t; patch mail, with a description above the diff; `debian/changelog` entries; `.po` translation catalogs, where `msgstr` is the translation and `msgid` the source; man page sources; screenplays, where dialogue follows different rules from action.

The [TextFSM guide](/guides/textfsm) walks through the template language, how captures become scopes, and how to see what a template captured.


# StylesPath

Learn about Vale's resource directory.

{% hint style="info" %}
You can override the default `StylesPath` by manually defining a `VALE_STYLES_PATH` environment variable.
{% endhint %}

The `StylesPath` specifies where Vale should look for its external resources (e.g., styles and ignore files). The path value may be absolute or relative to the location of the parent `.vale.ini` file.

```ini
# Here's an example of a relative path:
#
# .vale.ini
# ci/
# ├── vale/
# │   ├── styles/
StylesPath = ci/vale/styles

[*.md]
# `MyStyle` is a directory within
# `ci/vale/styles`.
BasedOnStyles = MyStyle
```

If you don’t specify a `StylesPath` in your `.vale.ini` file, Vale will use its default location:

| OS      | Search Locations                                |
| ------- | ----------------------------------------------- |
| Windows | `%LOCALAPPDATA%\vale\styles`                    |
| macOS   | `$HOME/Library/Application Support/vale/styles` |
| Unix    | `$XDG_DATA_HOME/vale/styles`                    |

(Run the `vale ls-dirs` command to see the exact locations on your system.)

## [Structure](#structure)

A `StylesPath` contains two types of entries: *styles* and the special `config` directory.

```console
$ tree styles
├───config     <-- Special directory
└───write-good <-- A style
```

The `config` directory is used internally by Vale and contains the following:

| Directory                            | Description                                |
| ------------------------------------ | ------------------------------------------ |
| [`vocabularies`](/keys/vocabularies) | Project-specific terminology lists.        |
| [`dictionaries`](/checks/spelling)   | Hunspell-compatible spelling dictionaries. |
| [`templates`](/topics/templates)     | Output format templates.                   |
| [`actions`](/topics/actions)         | Solutions to your custom rules.            |
| [`filters`](/topics/filters)         | Configuration filters.                     |
| [`scripts`](/checks/script)          | Tengo scripts.                             |


# Packages

Learn about Vale's configuration distribution system.

```ini
Packages = Google, write-good

[*.md]
BasedOnStyles = Vale, Google, write-good
```

Packages provide a means of sharing, extending, syncing, and updating Vale configurations.

![One upstream package is inherited by several projects, so an upstream change reaches all of them. Within a project, configuration is layered: the first package is overridden by the second, and local configuration overrides both.](/files/CZZci9HKEhHK0bfuh8qt)

In the example above, projects 1 and 2 will have identical configurations (as inherited from the upstream package). Any changes to the upstream package will propagate to both projects.

## [Structure and hosting](#structure-and-hosting)

A package is a `.zip` file that contains a `.vale.ini` file, a `StylesPath` folder, or both. You include a package by using the top-level `Packages` key in your local `.vale.ini` file:

```ini
StylesPath = .github/styles
MinAlertLevel = suggestion

Packages = Microsoft, https://github.com/vale-cli/write-good/releases/download/v0.4.0/write-good.zip

[README.md]
BasedOnStyles = Vale
```

{% stepper %}
{% step %}

### Package types — four accepted values

The `Packages` key accepts four types of values:

* A name of a package hosted in the official [Package Explorer](https://vale.sh/explorer);
* a URL to an externally-hosted package;
* an absolute or relative path to a `.zip` file located in the local file system; or
* an absolute or relative path to a directory containing package files.
  {% endstep %}

{% step %}

### Style-only

Style-only (such as [write-good](https://github.com/vale-cli/write-good)) packages are a `.zip` archive of a single style folder:

```console
$ unzip write-good.zip
Archive:  write-good.zip
   creating: write-good/
  inflating: write-good/README.md
  inflating: write-good/Cliches.yml
  inflating: write-good/ThereIs.yml
  inflating: write-good/Weasel.yml
  inflating: write-good/TooWordy.yml
  inflating: write-good/Passive.yml
  inflating: write-good/So.yml
  inflating: write-good/Illusions.yml
  inflating: write-good/E-Prime.yml
  inflating: write-good/meta.json
```

After running the `sync` command, the style will be added to the active `StylesPath`.
{% endstep %}

{% step %}

### Config-only

Config-only (such as [Hugo](https://github.com/vale-cli/Hugo)) packages are a `.zip` archive of a single `.vale.ini` file:

```console
$ unzip Hugo.zip
Archive:  Hugo.zip
   creating: Hugo/
  inflating: Hugo/.vale.ini
```

After running the `sync` command, the configuration file will be added to `StylesPath/.vale-config` according to the order in which it was loaded.
{% endstep %}

{% step %}

### Complete

Complete packages contain both a `.vale.ini` file and an associated `StylesPath` folder:

```console
$ tree MyPackage
MyPackage
├── .vale.ini
└── styles
    ├── MyStyle
    │   └── MyRule.yml
    └── config
        ├── dictionaries
        │   └── MyDic.dic
        ├── scripts
        │   └── MyScript.tengo
        └── vocabularies
            └── MyVocab
                ├── accept.txt
                └── reject.txt
```

The `StylesPath` should be named “styles” and can contain any typically-supported subfolder—such as [styles](/topics/styles) and [vocabularies](/keys/vocabularies). The `.vale.ini` file should reference the included `StylesPath`:

```ini
# This is subfolder included in our .zip archive.
StylesPath = styles

# Complete packages can include other, externally-defined
# packages.
Packages = proselint

# Normal configuration ...
[*.{md,adoc}]
Test.Rule = YES
```

The packaged `StylesPath` will be merged with the active local `StylesPath` and any included configuration files will be added to the local `StylesPath/.vale-config` folder.
{% endstep %}
{% endstepper %}

## [Pinning a version](#pinning-a-version)

Naming a package installs its latest release, so the rules it brings can change as the package is updated. To hold a package at a known version, give the release URL in place of the name:

```ini
StylesPath = styles

Packages = https://github.com/vale-cli/Google/releases/download/v0.7.0/Google.zip

[*.md]
BasedOnStyles = Google
```

A style takes its name from the folder inside the archive, so `BasedOnStyles` reads the same either way — only where the package comes from changes. Run `sync` again after editing the URL to move to a different version.

This is worth doing wherever a new rule arriving on its own would be disruptive, such as a repository several people write in, or a CI job that fails on new alerts.

## [Ordering](#ordering)

In the case of conflicting configuration, the order in which packages are loaded is important:

```ini
Packages = pkg1, pkg2

# Local configuration ...
[*.{md,adoc}]
Test.Rule = YES
```

In the above example, `pkg2` will override any conflicting configuration from `pkg1`. Similarly, local configuration will override any conflicting package.

## [VCS](#vcs)

You’ll want to add any packaged configuration components to your `.gitignore` (or equivalent) file.

While this can be as simple as ignoring your entire `StylesPath`, it’s likely that you’ll also have some local components as well.

```gitignore
# We want to ignore our StylesPath *except* for our local
# `vocabularies/Base` directory.

.github/styles/*
!.github/styles/config/

.github/styles/config/*
!.github/styles/config/vocabularies/

.github/styles/config/vocabularies/*
!.github/styles/config/vocabularies/Base
```

The above example ignores the entire `.github/styles/` folder *except* for `.github/styles/config/vocabularies/Base` (which we want to track changes for).


# Vocabularies

Learn about Vale's terminology management system.

Vocabularies allow you to maintain custom lists of terminology independent of your styles.

```ini
StylesPath = "..."

# Here's were we define the exceptions to use in *all*
# `BasedOnStyles`.
Vocab = Some-Name

[*]
# 'Vale' and 'MyStyle' automatically respect all
# custom exceptions.
#
# The built-in 'Vale' style is required for using
# `Vale.Terms`, `Vale.Avoid`, or `Vale.Spelling`.
BasedOnStyles = Vale, MyStyle
```

Each `Vocab` is a single folder (stored at `<StylesPath>/config/vocabularies/<name>/`) consisting of two plain-text files—`accept.txt` and `reject.txt`—that contain one regular expression per line.

The effects of using a custom `Vocab` are as follows:

* Entries in `accept.txt` are added to every exception list in all styles listed in `BasedOnStyles`—meaning that you now only need to update your project’s *vocabulary* to customize third-party styles.
* Entries in `accept.txt` are automatically added to a substitution rule (`Vale.Terms`), ensuring that any occurrences of these words or phrases exactly match their corresponding entry in `accept.txt`.
* Entries in `reject.txt` are automatically added to an existence rule (`Vale.Avoid`) that will flag all occurrences as errors.
* Entries in `accept.txt` and `reject.txt` should need little overlap, if any. For example, if you add `JavaScript` to `accept.txt`, then you do not need to add an overlapping regular expression entry of `[Jj]avascript` in `reject.txt`. Vale will enforce correct casing by virtue of the entry’s presence in `accept.txt`. See the section “Case sensitivity” for details.

This means that your exceptions can be developed independent of a style, allowing you to use the same exceptions with multiple styles or switch styles without having to re-implement them.

{% hint style="warning" %}
In versions of Vale prior to 3.0, vocabularies were stored in `<StylesPath>/Vocab`. When upgrading from an older version of Vale, you'll need to move your vocabularies to the new `<StylesPath>/config/vocabularies` location.
{% endhint %}

Vocabulary entries are stored in `<StylesPath>/config/vocabularies/<name>/` and are then referenced by `<name>` in `.vale.ini`. For example, consider the following folder structure:

```
$ tree styles
├───MyStyle
├───config
│   └───vocabularies
│       ├───Blog
│       │   ├───accept.txt
│       │   └───reject.txt
│       └───Marketing
│           ├───accept.txt
│           └───reject.txt
└───MyOtherStyle
```

Here, our `StylesPath` (`/styles`) contains two styles (`MyStyle` and `MyOtherStyle`) and two vocabularies (`Blog` and `Marketing`). You can then reference these entries by their folder name:

```ini
StylesPath = styles

Vocab = Blog

[*]
BasedOnStyles = Vale, MyStyle
```

## File format

Both `accept.txt` and `reject.txt` are plain-text files that take one entry per line:

```
first
[pP]y.*\b
third
```

The entries are evaluated as case-sensitive (except for rules extending `spelling`, as mentioned above) regular expressions.

Lines starting with `#` are treated as comments and are ignored.

## Case sensitivity

An important factor in successfully implementing a custom vocabulary is understanding how Vale handles case sensitivity.

While most spell-checking tools ignore case altogether, Vale’s vocabulary files are case-aware by default. This means that, for example, a vocabulary consisting of

```
MongoDB
```

will enforce the *exact* use of “MongoDB”: “mongoDB,” “MongoDb,” etc., will all result in errors. There are two ways around this.

First, you can indicate that a given entry should be case-insensitive by providing an appropriate regular expression:

```
(?i)MongoDB
[Oo]bservability
```

The first entry, `(?i)MongoDB`, marks the entire pattern as case-insensitive while the second, `[Oo]bservability`, provides two acceptable options.

You can also disable `Vale.Terms` and just use `Vale.Spelling`:

```ini
[*.md]
BasedOnStyles = Vale

Vale.Terms = NO
```

This will provide a more traditional spell-checking experience.

## Relation to ignore files

The functionality of vocabularies is similar to the existing concept of [ignore files](/checks/spelling#ignore-files).

The major differences are that vocabularies apply to multiple extension points (rather than just `spelling`), support regular expressions, and have built-in rules associated with them (`Vale.Terms` and `Vale.Avoid`).

In general, this means that ignore files are for style *creators* while vocabularies are for style *users*:

* If you’re developing or maintaining a style, you may still want to include a custom `spelling` rule—`MyStyle.Spelling`—that packages its own ignore files.
* As a user of styles, vocabularies should be able to replace the use of ignore files completely.

## Rules targeting vocabulary entries

In cases where you want to write a rule that needs to match against an otherwise-ignored token, you can add `vocab: false` to the rule definition. For example,

```yaml
extends: existence
message: Did you mean '%s'?
vocab: false
tokens:
  # "MonoDB" can be in a vocab
  - MongoDB
```


# MinAlertLevel

Learn about how to set the minimum alert level for Vale.

```ini
StylesPath = styles
MinAlertLevel = suggestion

[*.md]
BasedOnStyles = Vale
```

The `MinAlertLevel` key allows you to set the minimum alert level that Vale will report. The supported levels are `suggestion` (default), `warning`, and `error`.

`error`-level alerts will result in a [non-zero exit code](/topics/cli#return-codes), while `warning`- and `suggestion`-level alerts will not. This is useful for controlling which rules will fail CI builds.

## [Overriding](#overriding)

The `MinAlertLevel` key can be overridden from the command line using the `--minAlertLevel` flag:

```bash
$ vale --minAlertLevel=warning README.md
```

This allows you to, for example, show all alerts in your editor while only running `error`-level alerts in CI.

## [Editing](#editing)

You can edit the severity of a rule by modifying its `level` in your local `.vale.ini` file:

```ini
[*.md]
BasedOnStyles = Vale

Vale.Spelling = warning
```

Related: [Vocab](/keys/vocabularies) [IgnoredScopes](/keys/ignoredscopes)


# IgnoredScopes

Learn about how to ignore inline-level HTML tags.

```ini
StylesPath = styles

IgnoredScopes = code, tt

[*.md]
BasedOnStyles = Vale
```

`IgnoredScopes` specifies inline-level HTML tags to ignore. In other words, these tags may occur in an active scope (unlike `SkippedScopes`, which are skipped entirely) but their content still won’t raise any alerts.

By default, Vale ignores `tt`, `code`, and `kbd` tags. Setting this key **replaces** that list rather than adding to it, so include the defaults you still want. For example, considering the following Markdown file:

```markdown
This is a sentence that contains inline `code`.
```

Vale will not raise any alerts for the content within the backticks, such as `code` in the example above.

See [Markup](/topics/scopes) for more information.


# IgnoredClasses

Learn about how to ignore HTML classes.

```ini
StylesPath = styles

IgnoredClasses = my-class, another-class

[*.md]
BasedOnStyles = Vale
```

`IgnoredClasses` names HTML classes whose content Vale won't lint. The classes may appear on inline- or block-level elements.

By default, Vale ignores `problematic`, `pre`, and `code`.

{% hint style="info" %}
Unlike [`IgnoredScopes`](/keys/ignoredscopes) and [`SkippedScopes`](/keys/skippedscopes), which replace their defaults when you set them, `IgnoredClasses` **adds** to the list. The three defaults above stay ignored whatever you set.
{% endhint %}

This is most useful for content a documentation tool generates, where the markup carries classes the prose doesn't control:

```ini
# Sphinx marks unresolved references this way; there's no point
# spell-checking them.
IgnoredClasses = problematic, guilabel, menuselection
```

Because this key matches classes rather than tags, it's the one to reach for when what you want to skip isn't a distinct element—an inline `<span>` among other `<span>`s, say.

Related: [IgnoredScopes](/keys/ignoredscopes) [SkippedScopes](/keys/skippedscopes)


# SkippedScopes

Learn about how to ignore block-level HTML tags.

```ini
StylesPath = styles

SkippedScopes = script, style, pre

[*.md]
BasedOnStyles = Vale
```

`SkippedScopes` specifies block-level HTML tags to ignore. Any content in these scopes will be ignored.

By default, Vale ignores `script`, `style`, `pre`, `figure`, `noscript`, and `iframe` tags. Setting this key **replaces** that list rather than adding to it, so include the defaults you still want. For example, considering the following Markdown file:

````markdown
This is a sentence that contains normal text.

```python
# This is a code block.
print("Hello, world!")
```

Another normal sentence.
````

Vale will not raise any alerts for the content within the code block.

See [Markup](/topics/scopes) for more information.


# BasedOnStyles

Learn how to enable a style for a specific file type.

```ini
StylesPath = styles

[*.md]
BasedOnStyles = Vale, MyStyle
```

`BasedOnStyles` enables every rule in the named styles for the files a section matches.

It's a section-level setting, so it has to appear under a glob. Putting it above the first section is an error:

```
'BasedOnStyles' is a syntax-specific option
```

To apply styles to everything, use the catch-all section:

```ini
[*]
BasedOnStyles = Vale
```

## [More than one section](#more-than-one-section)

When several sections match a file, the most specific one's `BasedOnStyles` **replaces** the others—it doesn't add to them:

```ini
[*]
BasedOnStyles = Vale

[*.md]
# Markdown files get Microsoft *instead of* Vale, not as well as.
BasedOnStyles = Microsoft
```

If you want the defaults plus something extra, name them all:

```ini
[*]
BasedOnStyles = Vale

[*.md]
BasedOnStyles = Vale, Microsoft
```

## [Individual rules](#individual-rules)

Rules can be switched on or off by name, and unlike `BasedOnStyles`, these settings **accumulate** across every section that matches:

```ini
[*]
BasedOnStyles = Vale

[*.md]
# Markdown keeps everything from Vale, and adds one rule
# from a style that isn't otherwise enabled.
Microsoft.Contractions = YES
```

That's the way to extend your defaults for one file type without repeating them.

Turning a rule off works the same way:

```ini
[*.md]
BasedOnStyles = Vale, MyStyle

Vale.Spelling = NO
```

A rule can also be enabled on its own, without its style being listed at all:

```ini
[*.md]
# Only this rule runs, not the rest of Style1.
Style1.Rule = YES
```

## [Severity](#severity)

The same syntax sets a rule's level, which overrides whatever the rule file declares:

```ini
[*.md]
BasedOnStyles = Vale

Vale.Spelling = warning
```

{% hint style="info" %}
Setting a level for a whole style requires Vale v3.17.0 or later.
{% endhint %}

A bare style name sets the default for every rule in that style, and a rule naming itself still wins:

```ini
[*.md]
BasedOnStyles = proselint

# Everything in proselint is a suggestion ...
proselint = suggestion
# ... except this one.
proselint.Typography = warning
```

This is the concise way to keep one part of a style while turning the rest down—or off:

```ini
proselint = NO
proselint.Typography = YES
```

See [MinAlertLevel](/keys/minalertlevel) for how levels affect Vale's exit code.

## [Parameters](#parameters)

{% hint style="info" %}
Bracketed parameters require Vale v3.20.0 or later.
{% endhint %}

A bracketed key overrides one scalar on a rule you have enabled, keeping the rule's identity:

```ini
[*.md]
BasedOnStyles = Std

# The rule stays Std.SentenceLength; only its threshold moves.
Std.SentenceLength[max] = 30
```

Values are coerced to the field's type at compile time, and an unknown parameter fails the rule's compilation rather than being silently dropped.

Only scalars can be tuned this way. A structural field — `tokens`, `swap`, `message` — is refused with a pointer at [extending the rule](/topics/styles#extending-another-rule) in a style, which is what authoring is for. `[level]` is refused too: the classic `Style.Rule = warning` syntax already says that.

Related: [MinAlertLevel](/keys/minalertlevel) [SkippedScopes](/keys/skippedscopes)


# BlockIgnores

Learn how to define custom block-level ignores in your Vale configuration.

{% hint style="info" %}
`BlockIgnores` are supported in AsciiDoc, Markdown, MDX, MyST, Org Mode, QDoc, Quarto, reStructuredText, and Typst. MyST, QDoc, Quarto, and Typst require Vale v3.18.0 or later.

They work by wrapping each match in the format's block code delimiter, so they need a markup format to wrap it with. In a source code file they have no effect—but associating a markup format with your comments makes them available. See [Code](/formats/code#associations).
{% endhint %}

```ini
StylesPath = styles

[*.md]
BasedOnStyles = Vale

BlockIgnores = (?s) *({< file [^>]* >}.*?{</ ?file >})
```

`BlockIgnores` allow you to exclude certain block-level sections of text that don’t have an associated HTML tag that could be used with [`SkippedScopes`](/keys/skippedscopes).

The idea is to write a regular expression that captures the entire block in the first grouping. See this [regex101 session](https://regex101.com/r/mFM0kZ/1/) for a more thorough explanation.

A section can be keyed on a path as well as an extension, which narrows the patterns to one part of the project:

```ini
[docs/api/*.md]
BlockIgnores = (?s)(<!-- generated -->.*?<!-- /generated -->)
```

The pattern matches the file as Vale was asked for it, so write it relative to where you run Vale. See [Globbing](/guides/globbing).

Related:

* [TokenIgnores](/keys/tokenignores)
* [CommentDelimiters](/keys/commentdelimiters)


# TokenIgnores

Learn how to define custom inline-level ignores in your Vale configuration.

{% hint style="warning" %}
`TokenIgnores` are supported in AsciiDoc, Markdown, MDX, MyST, Org Mode, QDoc, Quarto, reStructuredText, and Typst. MyST, QDoc, Quarto, and Typst require Vale v3.18.0 or later.

They work by wrapping each match in the format's inline code delimiter, so they need a markup format to wrap it with. In a source code file they have no effect—but associating a markup format with your comments makes them available. See [Code](/formats/code#associations).
{% endhint %}

```ini
StylesPath = styles

[*.rst]
BasedOnStyles = Vale

TokenIgnores = (:math:`.*`), (:ref:`.*`)
```

`TokenIgnores` allow you to exclude certain inline-level sections of text that don’t have an associated HTML tag that could be used with [`IgnoredScopes`](/keys/ignoredscopes).

The idea is to write a regular expression that captures the entire token in the first grouping. See this [regex101 session](https://regex101.com/r/3Raecd/1) for a more thorough explanation.

A section can be keyed on a path as well as an extension, which narrows the patterns to one part of the project:

```ini
[docs/api/*.md]
TokenIgnores = (\{\{[^}]+\}\})
```

The pattern matches the file as Vale was asked for it, so write it relative to where you run Vale. See [Globbing](/guides/globbing).

{% hint style="info" %}
Dollar math needs no `TokenIgnores`: `$x^2$` and `$$…$$` are ignored in Markdown, Quarto, MyST, and MDX. See [Math](/formats/markdown#math).
{% endhint %}

Related:

* [BlockIgnores](/keys/blockignores)
* [CommentDelimiters](/keys/commentdelimiters)


# CommentDelimiters

Learn how to define custom comment delimiters.

`CommentDelimiters` allow you to override standard HTML comment delimiters (`<!-- foo -->`).

Custom comment delimiters are useful when using non-standard markup which do not allow HTML-style comments, such as MDX.

```ini
[formats]
mdx = md

[*.mdx]
BasedOnStyles = Vale

CommentDelimiters = {/*, */}
```

When `CommentDelimiters` are set, you can take full advantage of markup-based configuration to enable or disable specific rules within a section.

For instance, when using MDX:

```mdx
{/* vale off */}

This is some text ACT test

This is some text ACT test

{/* vale on */}

{/* vale vale.Redundancy = NO */}

This is some text ACT test

{/* vale vale.Redundancy = YES */}
```

Related keys: [TokenIgnores](/keys/tokenignores) [Transform](/keys/transform)


# Transform

Learn about how to convert XML to HTML for linting.

```ini
StylesPath = styles

[*.xml]
BasedOnStyles = Vale

Transform = docbook-xsl-snapshot/html/docbook.xsl
```

`Transform` names a version 1.0 XSL Transformation (XSLT) that converts the matched files to HTML. Vale lints the result, following the same rules it uses for [HTML](/formats/html).

It's a section-level setting, so different XML dialects can use different stylesheets:

```ini
[*.dita]
Transform = dita/html.xsl

[*.docbook]
Transform = docbook/html.xsl
```

## [Paths](#paths)

A relative path is resolved against **the directory holding your `.vale.ini`**—not the working directory, and not your `StylesPath`. A leading `~` is expanded.

```ini
# Both of these are relative to .vale.ini:
Transform = xsl/docbook.xsl
Transform = ../shared/docbook.xsl

# Absolute and home-relative paths work too:
Transform = ~/xsl/docbook.xsl
```

## [Requirements](#requirements)

The conversion is performed by [`xsltproc`](http://xmlsoft.org/XSLT/xsltproc.html), which has to be installed and on your `$PATH`. See [XML](/formats/xml) for how to install it.

Related: [XML](/formats/xml) [CommentDelimiters](/keys/commentdelimiters)


# WordTemplate

Learn about how to change what Vale considers a word boundary.

```ini
StylesPath = styles
MinAlertLevel = suggestion

WordTemplate = (?m)(?:%s)

[*.md]
BasedOnStyles = Vale
```

`WordTemplate` sets the pattern Vale wraps a rule's tokens in. The default is:

```
(?m)\b(?:%s)\b
```

`%s` is where the rule's tokens are inserted, and `\b` on either side is what stops `cat` from matching inside `concatenate`. Rules built from token lists—[`existence`](/checks/existence), [`substitution`](/checks/substitution), [`consistency`](/checks/consistency), and [`sequence`](/checks/sequence)—all use it.

## [When you need it](#when-you-need-it)

`\b` is defined as the boundary between a word character (`[0-9A-Za-z_]`) and anything else. Scripts written outside that set have no word characters at all, so the boundary never occurs and **the rule matches nothing**:

```yaml
extends: existence
message: "Found it."
level: error
tokens:
  - 世界
```

Given the text `你好世界朋友`, that rule reports nothing—and reports nothing for `hello 世界 there` too, where the term is surrounded by spaces. There is no error and no warning; the rule is simply silent.

Setting a template without `\b` fixes it:

```ini
WordTemplate = (?m)(?:%s)
```

```
 a.md
 1:3  error  Found it.  Test.Term
```

{% hint style="warning" %}
This is a global setting: it applies to every token-based rule in every style you load. Dropping `\b` means Latin-script tokens start matching inside longer words, so this is for projects whose content is genuinely in a script `\b` can't handle—not a per-rule adjustment.
{% endhint %}

## [Per-rule alternatives](#per-rule-alternatives)

If only some rules need different boundaries, leave `WordTemplate` alone and use the rule's own keys instead:

* `nonword: true` drops the boundaries for one rule.
* `raw` lets a rule supply its pattern verbatim, boundaries included.

Related: [BasedOnStyles](/keys/basedonstyles) [View](/keys/view)


# View

Learn about how to lint only part of a structured file.

```ini
StylesPath = styles

[*.json]
BasedOnStyles = Vale

View = MyView
```

`View` names a View to apply to the matched files. A View is a set of steps that pull named [scopes](/topics/scopes) out of a file Vale can’t otherwise parse—the descriptions in an OpenAPI spec, the docstrings in a source file, the subject and body of a commit message—so that Vale lints those and ignores the rest.

The named View is loaded from `<StylesPath>/config/views/<name>.yml`, and Vale reports an error at startup if it isn't there.

`View` is set per section, so different file types can be filtered differently.

See [Views](/topics/views) for how to write one.

Related: [Transform](/keys/transform) [Scopes](/topics/scopes)


# existence

Learn about the existence extension point.

| Name         | Type    | Description                                                                                     |
| ------------ | ------- | ----------------------------------------------------------------------------------------------- |
| `append`     | `bool`  | Adds `raw` to the end of `tokens`, assuming both are defined.                                   |
| `ignorecase` | `bool`  | Makes all matches case-insensitive.                                                             |
| `nonword`    | `bool`  | Removes the default word boundaries (`\b`).                                                     |
| `action`     | `array` | Options for correcting matches, see the [actions](/topics/actions) section.                     |
| `raw`        | `array` | A list of tokens to be concatenated into a pattern.                                             |
| `tokens`     | `array` | A list of tokens to be transformed into a non-capturing group.                                  |
| `exceptions` | `array` | An array of strings to be ignored.                                                              |
| `vocab`      | `bool`  | If false, disables all active [vocabularies](/keys/vocabularies) for this rule (default: true). |

The most general extension point is existence. As its name implies, it looks for the “existence” of particular tokens.

```yaml
extends: existence
message: Consider removing '%s'
level: warning
ignorecase: true
tokens:
  - appears to be
  - arguably
```

These tokens can be anything from simple phrases (as in the above example) to regular expressions—e.g., [the number of spaces between sentences](https://github.com/vale-cli/vale/blob/master/testdata/styles/demo/Spacing.yml) or [the position of punctuation after quotes](https://github.com/vale-cli/Google/blob/master/Google/Quotes.yml).

### [tokens](#tokens)

{% hint style="info" %}
See [Vale Studio](https://studio.vale.sh/) for a live editor that can help you write and test your rules, including generating the compiled regular expression.
{% endhint %}

The most common entry point for this extension point is the `tokens` key, which is a list of strings or regular expressions to be transformed into a word-bounded, non-capturing group:

```yaml
tokens:
  - appears to be
  - arguably
```

Which, after compilation, becomes:

```regex
(?i)(?m)\b(?:appears to be|arguably)\b
```

This is a convenience feature to avoid having to write the same boilerplate for every token in a rule.

### [raw](#raw)

When you want more control over the regular expression, you can use the `raw` key instead:

```yaml
extends: existence
message: "Incorrect use of symbols in '%s'."
ignorecase: true
raw:
  - $[d]* ?(?:dollars|usd|us dollars)
```

This allows you to write more complex patterns without having to worry about any post-processing. Each entry in `raw` is concatenated with the previous entry, allowing for improved commenting and readability of complex patterns.

### [message](#message)

The `message` key is a string that will be used to generate the final message when a match is found. The (optional) `%s` placeholder will be replaced with the matched text.


# substitution

Learn about the substitution extension point.

| Name         | Type    | Description                                                                                                           |
| ------------ | ------- | --------------------------------------------------------------------------------------------------------------------- |
| `ignorecase` | `bool`  | Makes all matches case-insensitive.                                                                                   |
| `matchcase`  | `bool`  | Adapts the replacement to the case of the matched text, so a rule written as `A-OK` still suggests `a-ok` for `a ok`. |
| `nonword`    | `bool`  | Removes the default word boundaries (`\b`).                                                                           |
| `swap`       | `map`   | A sequence of `observed: expected` pairs.                                                                             |
| `exceptions` | `array` | An array of strings to be ignored.                                                                                    |
| `vocab`      | `bool`  | If false, disables all active vocabularies for this rule (default: true).                                             |
| `capitalize` | `bool`  | Matches the capitalization of the source token.                                                                       |

`substitution` associates a string with a preferred form.

```yaml
extends: substitution
message: Consider using '%s' instead of '%s'
level: warning
ignorecase: false
# swap maps tokens in form of bad: good
swap:
  abundance: plenty
  accelerate: speed up
```

If we want to suggest the use of “plenty” instead of “abundance,” for example, we’d write:

```yaml
swap:
  abundance: plenty
```

## Regex keys

The keys may also be regular expressions:

```yaml
swap:
  '(?:give|gave) rise to': lead to
```

You can also reference capture groups for more dynamic substitutions:

```yaml
swap:
  'within the (.*)?directory': in the $1 directory
```

## Multiple suggestions

In some cases, you may want to suggest multiple alternatives for a single token. You can do this by separating them with a pipe ("|"):

```yaml
extends: substitution
# NOTE: We don't quote the first '%s':
message: Consider using %s instead of '%s.'
level: warning
# NOTE: The action is required.
action:
  name: replace
swap:
  # You can suggest multiple alternatives for a single token
  # by separating them with a pipe ("|").
  masterful: skilled|authoritative|commanding
```

In the CLI, this will render as a sentence with multiple suggestions:

![Vale reporting one warning for a single Markdown file, with a summary line counting errors, warnings and suggestions.](/files/O9gH6GiQS6zCMND4fjz5)

In LSP-based editors, the suggestions will be presented as a list of ‘Quick Fixes’. See the [LSP guide](/guides/lsp) for more information.

## message

`substitution` can have one or two `%s` format specifiers in its message. This allows us to do either of the following:

```yaml
message: "Consider using '%s' instead of '%s'."
# or
message: "Consider using '%s'."
```


# occurrence

Learn about the occurrence extension point.

| Name         | Type     | Description                                                         |
| ------------ | -------- | ------------------------------------------------------------------- |
| `max`        | `int`    | The maximum amount of times `token` may appear in a given scope.    |
| `min`        | `int`    | The minimum amount of times `token` has to appear in a given scope. |
| `token`      | `string` | The token of interest.                                              |
| `ignorecase` | `bool`   | Makes all matches case-insensitive.                                 |

`occurrence` enforces the maximum or minimum number of times a particular token can appear in a given scope.

```yaml
extends: occurrence
message: 'More than 3 commas!'
level: error
# Here, we're counting the number of times a comma appears
# in a sentence.
#
# If it occurs more than 3 times, we'll flag it.
scope: sentence
max: 3
token: ','
```

In the example above, we’re limiting the number of commas per sentence.

## [min](#min)

`min` makes absence a violation: a scope with fewer than `min` matches is flagged. The rule is evaluated once per scope, so a document that satisfies it in one paragraph and not the next is flagged at the paragraph that fell short.

```yaml
extends: occurrence
message: 'A paragraph here has no example (found %d).'
level: suggestion
scope: paragraph
min: 1
token: 'for example'
```

When a scope has zero matches there is no occurrence to point at, so the alert is anchored to the scope's first word — one alert per scope that fell short, at that scope's own position.

{% hint style="info" %}
Per-scope positions for `min` shortfalls require Vale v3.20.0 or later. Earlier versions report once per file, at `1:1`.
{% endhint %}

## [message](#message)

The `message` key can contain an optional format specifier `%s` which will be populated with the number of occurrences:

```yaml
message: 'Titles should use fewer than 70 characters (found: %s).'
```


# repetition

Learn about the repetition extension point.

| Name         | Type    | Description                                                               |
| ------------ | ------- | ------------------------------------------------------------------------- |
| `ignorecase` | `bool`  | Makes all matches case-insensitive.                                       |
| `alpha`      | `bool`  | Limits all matches to alphanumeric tokens.                                |
| `max`        | `int`   | The number of consecutive occurrences allowed before a match is flagged.  |
| `tokens`     | `array` | A list of tokens to be transformed into a non-capturing group.            |
| `exceptions` | `array` | An array of strings to be ignored.                                        |
| `vocab`      | `bool`  | If false, disables all active vocabularies for this rule (default: true). |

`repetition` looks for repeated occurrences of its tokens.

```yaml
extends: repetition
message: "'%s' is repeated!"
level: error
alpha: true
tokens:
  - '[^s.!?]+'
```

## [Vale.Repetition](#valerepetition)

Vale includes a [built-in implementation](/topics/styles#vale) of `repetition` that can be used to flag repeated words such as “the the” or “and and”. This rule will catch almost any instance of a repeated word, including across markup boundaries:

```markdown
See the Mermaid [Mermaid user guide][1].
```


# consistency

Learn about the consistency extension point.

| Name         | Type   | Description                                                       |
| ------------ | ------ | ----------------------------------------------------------------- |
| `nonword`    | `bool` | Removes the default word boundaries (`\b`).                       |
| `ignorecase` | `bool` | Makes all matches case-insensitive.                               |
| `either`     | `map`  | A map of `option 1: option 2` pairs of which only one may appear. |

`consistency` will ensure that a key and its value (e.g., “advisor” and “adviser”) don’t both occur in its scope.

```yaml
extends: consistency
message: "Inconsistent spelling of '%s'."
level: error
ignorecase: true

# We only want one of these to appear.
either:
  advisor: adviser
  centre: center
```


# conditional

Learn about the conditional extension point.

| Name         | Type     | Description                                                               |
| ------------ | -------- | ------------------------------------------------------------------------- |
| `ignorecase` | `bool`   | Makes all matches case-insensitive.                                       |
| `first`      | `string` | The antecedent of the statement.                                          |
| `second`     | `string` | The consequent of the statement.                                          |
| `vocab`      | `bool`   | If false, disables all active vocabularies for this rule (default: true). |
| `exceptions` | `array`  | An array of strings to be ignored.                                        |

```yaml
extends: conditional
message: "'%s' has no definition"
level: error
scope: text
ignorecase: false
# Ensures that the existence of 'first'
# implies the existence of 'second'.
first: '\b([A-Z]{3,5})\b'
second: '(?:\b[A-Z][a-z]+ )+\(([A-Z]{3,5})\)'
# ... with the exception of these:
exceptions:
  - ABC
  - ADD
```

For example, consider the following text:

> According to Wikipedia, the World Health Organization (WHO) is a specialized agency of the United Nations that is concerned with international public health. We can now use WHO because it has been defined, but we can’t use DAFB because people may not know what it represents. We can use `DAFB` when it’s presented as code, though.

Using the above text with our example rule yields the following:

```bash
test.md:1:224:style.UnexpandedAcronyms:'DAFB' has no definition
```

`conditional` also takes an optional `exceptions` list. Any token listed as an exception won’t be flagged.

## [Presence checks](#presence-checks)

When `second` has a capture group, a `first` match is allowed only if its captured value was also captured by a `second` match—the acronym-definition pattern above. When `second` has *no* capture group, the rule is a plain presence check: any `first` match requires `second` to appear somewhere in the same scope.

```yaml
extends: conditional
message: "A 'Section' requires a 'Summary:' line."
level: error
scope: raw
first: '\bSection\b'
second: 'Summary:'
```

## [Lookarounds](#lookarounds)

Regular expression lookarounds can be used to restrict the capture of the rule, allowing for more complex conditional statements. For example, the following rule will flag any MDX-style import that is not used:

```yaml
extends: conditional
message: "'%s' has been imported but not used."
level: error
scope: raw
first: '(?<=import )(\w+)(?= from)'
second: '(?<=<)(\w+)'
```

See the [regex guide](/guides/regex) for more information.


# capitalization

Learn about the capitalization extension point.

| Name         | Type     | Description                                                                                                          |
| ------------ | -------- | -------------------------------------------------------------------------------------------------------------------- |
| `match`      | `string` | `$title`, `$sentence`, `$lower`, `$upper`, or a pattern.                                                             |
| `style`      | `string` | AP or Chicago; only applies when match is set to `$title` (default: AP).                                             |
| `exceptions` | `array`  | An array of strings to be ignored.                                                                                   |
| `indicators` | `array`  | An array of suffixes that indicate the next token should be ignored.                                                 |
| `threshold`  | `float`  | The minimum proportion of words that must be (un)capitalized for a sentence to be considered correct (default: 0.8). |
| `prefix`     | `string` | A constant prefix to ignore during case conversion.                                                                  |
| `vocab`      | `bool`   | If false, disables all active vocabularies for this rule (default: true).                                            |

`capitalization` checks that the text in the specified scope matches the case of `match`.

```yaml
extends: capitalization
message: "'%s' should be in title case"
level: warning
scope: heading
# $title, $sentence, $lower, $upper, or a pattern.
match: $title
# AP or Chicago; only applies when match is set to
# $title.
style: AP
exceptions:
  - ABC
  - add
```

## [styles](#styles)

The `capitalization` extension point supports two styles: “AP” and “Chicago.”

The “AP” style enforces the rules of the Associated Press Stylebook:

* Capitalize the first word and the last word of the title.
* Capitalize “to” in infinitives.
* Do not capitalize articles, conjunctions, and prepositions of three letters or fewer.

The “Chicago” style enforces the rules of The Chicago Manual of Style:

* Capitalize the first word and the last word of the title.
* Do not capitalize articles (a, an, the), coordinating conjunctions (and, but, or, for, nor), and prepositions, regardless of length.

## [prefix](#prefix)

The `prefix` option allows you to specify a constant prefix to ignore during case conversion. For example,

```yaml
extends: capitalization
message: "'%s' should be sentence-cased."
scope: heading
match: $sentence
# sentence-cased, but allows for a common prefix:
#
# E.g.,
#
# a. This is my heading
prefix: '^[a-z]\.\s'
```

In this example, `^[a-z]\.\s` is used to ignore the common prefix.

## [message](#message)

`capitalization` can have one or two `%s` format specifiers in its message. This allows us to do either of the following:

```yaml
message: "Found: '%s'; expected: '%s'."
# or
message: "'%s' should use title-style capitalization."
```


# metric

Learn about the metric extension point.

{% hint style="info" %}
When writing conditions, be sure to use floating-point numbers. For example, use `"== 8.0"` instead of `"== 8"`.
{% endhint %}

| Name        | Type     | Description                                                    |
| ----------- | -------- | -------------------------------------------------------------- |
| `formula`   | `string` | A formula of pre-defined variables to be evaluated.            |
| `condition` | `string` | A binary condition upon which `formula` will trigger an alert. |

`metric` enforces arbitrary formulas based on pre-defined, built-in variables.

```yaml
extends: metric
message: 'Try to keep the Flesch-Kincaid grade level (%s) below 8.'
link: |
  https://en.wikipedia.org/wiki/Flesch%E2%80%93Kincaid_readability_tests

formula: |
  (0.39 * (words / sentences)) + (11.8 * (syllables / words)) - 15.59

condition: '> 8.0'
```

## [Variables](#variables)

The table below summarizes all available variables:

|       Variable       |                                    Description                                   |
| :------------------: | :------------------------------------------------------------------------------: |
|     `blockquote`     |                         The number of `blockquote` tags.                         |
|     `characters`     |                             The number of characters.                            |
|    `complex_words`   | The number of polysyllabic words without common suffixes (`es`, `ed`, `ing`, …). |
|    `heading.h{n}`    |    The number of headings at the specified level (for example, `heading.h1`).    |
|        `list`        |                         The number of `ol` and `ul` tags.                        |
|     `long_words`     |                 The number of words with more than 6 characters.                 |
|     `paragraphs`     |        The number of body paragraphs (what the `paragraph` scope matches).       |
| `polysyllabic_words` |                  The number of words with more than 2 syllables.                 |
|         `pre`        |                             The number of `pre` tags.                            |
|      `sentences`     |                             The number of sentences.                             |
|      `syllables`     |                             The number of syllables.                             |
|        `words`       |                               The number of words.                               |

A `metric` rule measures the whole document unless it declares a [scope](/topics/scopes#checks-and-scopes). With one, it measures each block the scope names on its own—a paragraph, a heading, a list item, or a `doc(...)` selection—and the alert lands on that block’s first line:

```yaml
extends: metric
message: "This section runs %s words. The budget is 400."
level: warning
scope: 'doc(section:has(> h2))'
formula: words
condition: "> 400"
```

A selection counts the elements it holds, so `heading.h3` inside a section is the number of subheadings in that section. Scoping a `metric` rule requires Vale v3.21.0 or later; `text` means the whole document, as an unset scope does.

{% hint style="info" %}
As of Vale v3.18.0, `paragraphs` counts body paragraphs alone. `words`, `sentences`, and the other prose-derived variables still take in every kind of prose—including list items and blockquotes, which are counted by `list` and `blockquote`.
{% endhint %}

## [Operators](#operators)

In addition to using the variables listed above, a `formula` may also use the following operators:

|    Operator    |      Description      |
| :------------: | :-------------------: |
|       `+`      |        Addition       |
|       `-`      |      Subtraction      |
|       `*`      |     Multiplication    |
|       `/`      |        Division       |
| `math.sqrt(x)` |   Square root of `x`  |
|  `math.abs(x)` | Absolute value of `x` |

A `condition` may use one of `>`, `<`, `==`, `>=`, and `<=`.

## [message](#message)

The result of a `formula` will be compared to its `condition` and inserted into its `message` format specifier (`%s`).


# readability

Learn about the readability extension point.

| Name      | Type    | Description                                                                                          |
| --------- | ------- | ---------------------------------------------------------------------------------------------------- |
| `metrics` | `array` | One or more of `Gunning Fog`, `Coleman-Liau`, `Flesch-Kincaid`, `SMOG`, and `Automated Readability`. |
| `grade`   | `float` | The highest acceptable grade level.                                                                  |

`readability` calculates the reading grade level of a document and flags it when the score is too high.

```yaml
extends: readability
message: "Grade level (%s) too high!"
level: warning
# Flag any document that reads above a US grade 7 level.
grade: 7
metrics:
  - Flesch-Kincaid
```

Given a document written in dense, abstract prose, the rule above reports:

```
 a.md
 1:1  warning  Grade level (33.70) too high!  demo.Reading
```

## [metrics](#metrics)

Each metric estimates the years of education a reader needs to understand the text on a first reading, expressed as a US grade level. They disagree with one another—they weigh sentence length, syllable count, and word length differently—so the number you get depends on which you choose.

When you list more than one, Vale averages them:

```yaml
extends: readability
message: "Grade level (%s) too high!"
level: warning
grade: 8
# The score is the average of the three, not the highest of them.
metrics:
  - Flesch-Kincaid
  - Gunning Fog
  - Coleman-Liau
```

Averaging several metrics is usually steadier than trusting one, since each has text it handles badly—`SMOG`, for example, is calibrated for health writing.

## [message](#message)

The `message` key can contain an optional format specifier `%s`, which is populated with the calculated grade level to two decimal places:

```yaml
message: "Grade level (%s) too high!"
```

## [Scope](#scope)

A `readability` rule scores the document’s prose as a whole unless it declares a [scope](/topics/scopes#checks-and-scopes), and reports the result at line 1. With a scope, it scores each block the scope names and reports on that block’s first line. A section is the natural unit:

```yaml
extends: readability
message: "The Summary reads at grade %s. Aim below 10."
level: warning
scope: 'doc(section:has(> h2:contains("Summary")))'
metrics:
  - Flesch-Kincaid
grade: 10
```

A grade level is calculated from whole sentences, so scoring anything smaller than a paragraph—a heading, a list item, a table cell—wouldn’t mean anything, even though the scope allows it. Scoping a `readability` rule requires Vale v3.21.0 or later.


# spelling

Learn about the spelling extension point.

| Name           | Type     | Description                                                                                            |
| -------------- | -------- | ------------------------------------------------------------------------------------------------------ |
| `custom`       | `bool`   | Turn off the default filters for acronyms, abbreviations, and numbers.                                 |
| `filters`      | `array`  | An array of patterns to ignore during spell checking.                                                  |
| `ignore`       | `array`  | Relative paths (from `<StylesPath>/config/ignore`) to files consisting of one word per line to ignore. |
| `exceptions`   | `array`  | An array of strings to be ignored.                                                                     |
| `dicpath`      | `string` | The location to look for `.dic` and `.aff` files. Can be absolute or relative to the `StylesPath`.     |
| `dictionaries` | `array`  | An array of dictionaries to load.                                                                      |
| `append`       | `bool`   | Adds the array of dictionaries after the default Vale dictionary, instead of replacing it.             |

`spelling` implements spell checking based on Hunspell-compatible dictionaries.

```yaml
# Uses the built-in dictionary and filters.
extends: spelling
message: "Did you really mean '%s'?"
level: error
```

By default, `spelling` includes a custom, open-source [dictionary for American English](https://github.com/vale-cli/en_US-web).

## [Dictionaries](#dictionaries)

You may instead use the `dictionaries` key to list multiple custom dictionaries:

```yaml
extends: spelling
message: "'%s' is a typo!"
dictionaries:
  - en_US
  - en_medical
```

The `spelling` extension point will look for `en_US.{dic,aff}` and `en_medical.{dic,aff}` files in `<StylesPath>/config/dictionaries`.

You can also use the `DICPATH` environment variable or the `dicpath` key.

## [Filters](#filters)

Vale comes with a set of built-in filters, as described in the table below:

| Filter                    | Description                                         |
| ------------------------- | --------------------------------------------------- |
| `[A-Z]{1}[a-z]+[A-Z]+\w+` | Mixed-cased words (such as “MongoDB”).              |
| `[^a-zA-Z_']`             | Words containing non-word tokens (such as numbers). |
| `[A-Z]+$`                 | Upper-cased words.                                  |

You can also choose define you own filters either with or without the built-in ones enabled:

```yaml
extends: spelling
message: "Did you really mean '%s'?"
level: error
# This disables the built-in filters. If you omit this
# key or set it to false, custom filters (see below) are
# added on top of the built-in ones.
custom: true
# A "filter" is a regular expression specifying words
# to ignore during spell checking.
filters:
  # Ignore all words starting with 'py'.
  #
  # e.g., 'PyYAML'.
  - '[pP]y.*\b'
```

## [Ignore files](#ignore-files)

Ignore files are plain-text files that list words to be ignored during spell check (one case-insensitive entry per line). For example:

```
destructuring
transpiler
```

You can name these files anything you’d like and reference them relative to the active `<StylesPath>/config/ignore` directory.

```yaml
extends: spelling
message: "Did you really mean '%s'?"
level: error
ignore:
  - ignore1.txt
  - ignore2.txt
```

See [Vocabularies](/keys/vocabularies) for information on rule-agnostic terminology lists.


# sequence

Learn about the sequence extension point.

| Name         | Type         | Description                                                            |
| ------------ | ------------ | ---------------------------------------------------------------------- |
| `tokens`     | `[]NLPToken` | A list of tokens with associated NLP metadata.                         |
| `ignorecase` | `bool`       | Makes all matches case-insensitive.                                    |
| `exceptions` | `[]string`   | Sentence regions, as regexes; a match beginning inside one is dropped. |

While most extension points focus on writing *style*, `sequence` aims to support grammar-focused rules.

```yaml
extends: sequence

# `%[4]s` is like `%s`, but specifically refers to the
# 4th token in our sequence.
message: |
  The infinitive '%[4]s' after 'be' requires 'to'.
  Did you mean '%[2]s %[3]s *to* %[4]s'?"
tokens:
  - tag: MD
  - pattern: be
  - tag: JJ
  # The `|` notation means that we'll accept `VB`
  # or `VBN` in position 4.
  - tag: VB|VBN
```

Every `sequence`-based rule is required to have at least one `pattern` (such as `pattern: be`, shown above). This becomes the “anchor” of the sequence: we find all instances of the first pattern and then check that the left- and right-hand sides of the sequence match.

Tokens judge the sentence one word at a time. When a rule needs a judgment about a *region* — for example, “the comma closing a fronted phrase isn’t a list comma” — hand that part to `exceptions`: each entry is a regular expression matched against the sentence, and a sequence match that begins inside one of its matches is dropped. Unlike other checks’ `exceptions`, these are regions rather than vocabulary terms, so the project’s accepted vocabulary is never merged in.

```yaml
# Skip matches that begin inside a fronted phrase.
exceptions:
  - '^(?i:(?:in|on|at|when|while|if)\b[^,]{0,60}),'
```

{% hint style="info" %}
`exceptions`, and the boundary behavior of `negate` described below, require Vale v3.20.0 or later.
{% endhint %}

Each entry in a sequence is known as an `NLPToken` and has the following structure:

```yaml
# [optional]: A regular expression (required
# if `tag` isn't given).
pattern: '...'

# [optional]: If true, indicates that we
# *shouldn't* match this token. A negated token at
# the start or end of a sequence is also satisfied
# by the sentence boundary itself: "not preceded by
# X" holds when nothing precedes the match at all.
negate: true # or false

# [optional]: A part-of-speech tag (required
# if `pattern` isn't given).
tag: '...'

# [optional]: An integer meaning that there may
# be up to `n` (3, in this case) tokens between
# this token and the next one.
skip: 3

# [optional]: How many times the token must occur --
# "at least two nouns", not just one. Each occurrence
# gets its own `skip` window, so `skip: 8, min: 2`
# reads "a noun within eight words, then another noun
# within eight words". The default is 1.
min: 2

# [optional]: A universal part-of-speech tag --
# NOUN, VERB, ADJ, and so on -- instead of a
# Penn Treebank `tag`. Universal tags are
# portable; Penn tags are more precise.
upos: '...'

# [optional]: If true, narrows the alert to this
# token alone. Without it, a match spans every
# token in the sequence -- marking one lets a rule
# require surrounding context while pointing at
# only the part the writer should change.
target: true # or false
```

`sequence`-based are [sentence-scoped](/topics/scopes). See [prose/tagging](https://github.com/jdkato/prose?tab=readme-ov-file#tagging) for a full list of supported part-of-speech tags.

{% hint style="info" %}
`min` requires Vale v3.19.0 or later.
{% endhint %}

`min` and `skip` combine to express "at least *n* occurrences within a window." For example, a pronoun is ambiguous when two or more nouns precede it:

```yaml
extends: sequence
message: "Avoid ambiguous pronouns."
level: warning
tokens:
  - tag: NN|NNP|NNPS|NNS
    skip: 8
    min: 2
  - pattern: \w+
    tag: PRP
    target: true
```

This matches "The dog chased the cat until **it** tired" — two nouns, then a pronoun — but not "The dog barked because it hungered." Without `skip`, `min` means consecutive occurrences: `tag: JJ, min: 2` is two adjectives in a row.

{% hint style="info" %}
Reaching every block, and honoring a declared `scope`, requires Vale v3.17.0 or later. Earlier versions read sentences from paragraphs only.
{% endhint %}

By default, a `sequence` rule reads sentences from **every** block—headings, list items, and table cells as well as paragraphs. Much of a document's prose lives outside its paragraphs, and `sequence` is the only extension point that reads part-of-speech data, so it needs to reach all of it.

To narrow that, declare a `scope`:

```yaml
extends: sequence
message: "matched '%s'"
level: error
# Only take sentences from headings.
scope: heading
tokens:
  - pattern: quick
  - pattern: brown
```

The scope selects which blocks the sentences are drawn from; the rule still matches sentence by sentence within them. A [`doc(...)` selection](/topics/scopes#selections) works the same way: the rule reads the sentences inside the selected element.


# script

Learn about the script extension point.

{% hint style="warning" %}
When using `script`-based rules, you're limited to the standard Go [regex syntax](https://pkg.go.dev/regexp/syntax).
{% endhint %}

| Name     | Type     | Description                                            |
| -------- | -------- | ------------------------------------------------------ |
| `script` | `string` | The [Tengo](https://tengolang.com/) script to execute. |

`script` allows for the creation of arbitrary logic-based rules using [Tengo](https://tengolang.com/), a Go-like scripting language.

```yaml
extends: script
message: 'Consider inserting a new section heading at this point.'
link: https://tengolang.com
scope: raw
script: MyScript.tengo
```

Where `MyScript.tengo` is a file containing the Tengo script to execute stored at `$StypesPath/config/scripts`.

````go
text := import("text")

matches := []
// at most 3 paragraphs per section
p_limit := 3

// Remove all instances of code blocks
// since we don't want to count inter-block
// newlines as a new paragraph.
document := text.re_replace("(?s) *(\n```.*?```\n)", scope, "")

count := 0
for line in text.split(document, "\n") {
    if text.has_prefix(line, "#") {
        count = 0 // New section; reset count
    } else if count > p_limit {
        start := text.index(scope, line)
        matches = append(matches, {begin: start, end: start + len(line)})
        count = 0
    } else if text.trim_space(line) == "" {
        count += 1
    }
}
````

{% stepper %}
{% step %}
Use Tengo’s [`text`](https://github.com/d5/tengo/blob/master/docs/stdlib-text.md) module, which provides a number of string- and regex-related utility functions.
{% endstep %}

{% step %}
Process the content in the `scope` variable. `scope` contains text based on the `scope: <scope>` setting for the rule. For more information, see [Scoping](/topics/scopes).
{% endstep %}

{% step %}
Populate the `matches` array with rule matches. Each match must be a map with the keys:

* `begin`: where the match begins in the content provided by the `scope` variable.
* `end`: where the match ends in the content provided by the `scope` variable.
  {% endstep %}
  {% endstepper %}


# suggest

Learn how to create dynamic suggestions for your rules.

```go
func suggest(match string) []string
```

`suggest` returns an array of suggested replacements for the matched text.

## [script](#script)

```yaml
action:
  name: suggest
  params:
    - scriptName.tengo
```

The `suggest` action allows you to define a custom suggestion script that will be executed for each match. The script should return an array of strings called `suggestions`.

Scripts are written in [Tengo](https://github.com/d5/tengo) and are stored in the `<StylesPath>/config/actions` directory.

Here’s an example script:

```go
text := import("text")

// `match` is provided by Vale and represents the rule's matched text.
made := text.re_replace(`([A-Z]\w+)([A-Z]\w+)`, match, `$1-$2`)

made = text.replace(made, "-", "_", 1)
made = text.to_lower(made)

// `suggestions` is required by Vale and represents the script's output.
suggestions := [made]
```

We would save this script as `CamelToSnake.tengo` and then reference it in our rule:

```yaml
extends: existence
message: "'%s' should be in snake_case."
nonword: true
level: error
action:
  name: suggest
  params:
    - CamelToSnake.tengo
tokens:
  - '[A-Z]\w+[A-Z]\w+'
```

## [spellings](#spellings)

```yaml
action:
  name: suggest
  params:
    - spellings
```

`spellings` returns the top 5 spelling suggestions for the matched text from all active dictionaries.

Suggestions are ordered by calculating the [Levenshtein distance](https://pkg.go.dev/github.com/adrg/strutil@v0.3.0/metrics#Levenshtein) between the matched text and the dictionary words.


# replace

Learn how to create static suggestions for your rules.

```go
func replace(match string) []string
```

`replace` returns an array of user-provided replacements.

```yaml
action:
  name: replace
  params:
    - option1
    - option2
    ...
```

Rules that extend `substitution` or `capitalization` will automatically populate the `params` array, so you can simply provide the `name`:

```yaml
action:
  name: replace
```


# remove

Learn how to remove matches from your content.

```go
func remove(match string)
```

`remove` will remove the matched text of any rule.

```yaml
extends: existence
message: "Don't use an ellipsis in documentation."
nonword: true
action:
  name: remove
tokens:
  - '...'
```


# edit

Learn how to make in-place edits to your matches.

```go
func edit(match string) string
```

`edit` will perform an in-place edit on the match string according to the provided parameters.

## [regex](#regex)

Replace the provided regex pattern with the given string.

```yaml
extends: existence
message: Consider removing '%s'
level: warning
action:
  name: edit
  params:
    - regex
    - '([A-Z]\w+)([A-Z]\w+)' # pattern
    - '$1-$2' # repl
tokens:
  - '([A-Z]\w+)([A-Z]\w+)'
```

This is equivalent to the following Go code:

```go
match = pattern.ReplaceAllString(match, repl)
```

## [`trim_right`](#trim_right)

Trim the first parameter from the end of the matched text.

```yaml
extends: existence
message: "Don't use exclamation points in text."
nonword: true
action:
  name: edit
  params:
    - trim_right
    - '!'
tokens:
  - '\w+!(?:\s|$)'
```

## [`trim_left`](#trim_left)

Trim the first parameter from the start of the matched text.

```yaml
extends: existence
message: "'%s' too many spaces."
level: warning
nonword: true
action:
  name: edit
  params:
    - trim_left
    - ' '
tokens:
  - '(?<=[a-z][.!?] ) [A-Z]'
```


# Front Matter

Learn how Vale handles front matter.

Linting front matter fields is supported in Markdown, AsciiDoc, reStructuredText, MDX, and Org files.

There are 3 supported front matter types – YAML, TOML, and JSON. Each is recognized by the delimiters that open and close it:

{% tabs %}
{% tab title="YAML" %}
Opening and closing `---` lines:

```yaml
---
name: "frontmatter"
---
rest of the content
```

Or an opening `---yaml` line with a closing `---`:

```yaml
---yaml
name: "frontmatter"
---
rest of the content
```

{% endtab %}

{% tab title="TOML" %}
Opening and closing `+++` lines:

```toml
+++
name = "frontmatter"
+++
rest of the content
```

Or an opening `---toml` line with a closing `---`:

```toml
---toml
name = "frontmatter"
---
rest of the content
```

{% endtab %}

{% tab title="JSON" %}
Opening and closing `;;;` lines:

```json
;;;
{
    "name": "frontmatter"
}
;;;
rest of the content
```

Or an opening `---json` line with a closing `---`:

```json
---json
{
    "name": "frontmatter"
}
---
rest of the content
```

A bare JSON object followed by an empty line also works:

```json
{
    "name": "frontmatter"
}

rest of the content
```

{% endtab %}
{% endtabs %}

Each field is dynamically assigned its own scope, allowing you to write rules that target specific ones:

```yaml
---
title: 'My document'
description: "A short summary of the document's purpose."
author: 'John Doe'
---
```

Using the example above, the generated scopes would be `text.frontmatter.title`, `text.frontmatter.description`, and `text.frontmatter.author`.

A rule can then use these in its `scope:` field:

```yaml
extends: capitalization
message: "'%s' should be in title case"
level: warning
scope: text.frontmatter.title
```

This rule would then only be applied to the `title` field in the front matter.


# Markdown

Learn how Vale handles Markdown content.

[GitHub-Flavored Markdown](https://github.github.com/gfm) support is built in. The supported extensions are `.md`, `.mdown`, `.markdown`, and `.markdn`.

By default, Vale ignores:

* Indented blocks: Blocks starting with four or more spaces.
* Fenced blocks: Blocks surrounded by three or more backticks.
* Code spans: Text surrounded by backticks.
* Math: `$$…$$` blocks and `$x^2$` spans. See [Math](#math).
* URLs: See [URL handling](https://github.com/vale-cli/vale/issues/320) for more information.

## [Math](#math)

Both `$$…$$` display math and `$x^2$` inline math are ignored. Inline math follows Pandoc's delimiter rules, which are what tell an equation from a price: the opening `$` needs a non-space character to its right, and the closing `$` needs one to its left and no digit after it. So `$g_i = g(p)_i$` is math, while `It costs $5 and $10` stays prose and is linted as such.

A span may wrap onto the next line, but not past the end of its paragraph. Write `\$` for a literal dollar sign that would otherwise open one.

## [R Markdown](#r-markdown)

{% hint style="info" %}
Requires Vale v3.18.0 or later. Earlier versions can assign the format instead: `Rmd = md` under `[formats]`.
{% endhint %}

R Markdown (`.Rmd`, `.rmd`) is linted as Markdown. Its knitr syntax is code to Vale:

* Chunks are fenced blocks—the chunk options in the `{r ...}` info string and everything inside the fence are ignored.
* Inline expressions such as `` `r nrow(df)` `` are code spans, and are ignored by default.

A configuration section still needs to match the extension:

```ini
[*.{md,Rmd}]
BasedOnStyles = Vale
```

Pandoc syntax that isn't Markdown—citations, for one—can be excluded with [`TokenIgnores`](/keys/tokenignores):

```ini
[*.{md,Rmd}]
TokenIgnores = (\[@[^\n\]]+\])
```

## [Comments](#comments)

Vale supports comment-based configuration in Markdown files:

* Turn Vale off entirely:

```html
<!-- vale off -->

This text will be ignored.

<!-- vale on -->
```

* Turn off a specific rule:

```html
<!-- vale Style.Redundancy = NO -->

This is some text ACT test

<!-- vale Style.Redundancy = YES -->
```

* Turn off specific match(es) within a rule:

```html
<!-- vale Style.Redundancy["ACT test","OTHER"] = NO -->

This is some text ACT test

<!-- vale Style.Redundancy["ACT test","OTHER"] = YES -->
```

* Turn on or off specific styles:

```html
<!-- vale StyleName1 = YES -->
<!-- vale StyleName2 = NO -->
```

* Set styles (enabling them and switching off any other styles):

```html
<!-- vale style = StyleName1 -->
<!-- vale styles = StyleName1, StyleName2 -->
```


# MyST

Learn how Vale handles MyST content.

{% hint style="info" %}
Requires Vale v3.18.0 or later.
{% endhint %}

[MyST](https://myst-parser.readthedocs.io/) is CommonMark plus Sphinx-style constructs—directives, roles, targets, comments, and block breaks—that a plain Markdown parser reads as prose. Vale parses each one as the markup it is.

The supported extension is `.myst`. A Markdown file opts in through a [format association](/topics/.vale.ini#format-associations):

```ini
[formats]
md = myst

[*.{md,myst}]
BasedOnStyles = Vale
```

By default, Vale ignores:

* Targets: `(my-label)=` lines.
* Roles: the `{name}` of `` {term}`content` ``—the span that follows is a code span, ignored like any other.
* Comments: lines beginning with `%`.
* Block breaks: `+++` lines, including any metadata they carry.
* Directive options: `:key: value` lines and `---`-delimited YAML blocks under a directive opener.
* Substitutions: `{{ variable }}`.
* Attributes: `{.class}` lines and the `{.class}` of `[text]{.class}`—the text itself is still linted.
* Fenced blocks, code spans, and URLs, as in [Markdown](/formats/markdown).

## [Directives](#directives)

A directive's content is Markdown, and Vale lints it—whether the directive is fenced with backticks or colons:

```
:::{note}
This prose is linted.
:::
```

The exception is a directive whose content is literal rather than prose: `code`, `code-block`, `code-cell`, `csv-table`, `eval-rst`, `highlight`, `include`, `literalinclude`, `math`, `mermaid`, `raw`, `sourcecode`, and `toctree` are ignored in full.

A directive's name also becomes a [class scope](/topics/scopes#class-scopes) for everything inside it, however deeply nested:

```yaml
extends: existence
message: "Don't use '%s' in an admonition."
scope: class.note
level: error
tokens:
  - obviously
```

## [Math and citations](#math-and-citations)

Math is ignored, both `$$…$$` display blocks and `$x^2$` inline spans. See [Math](/formats/markdown#math) for the delimiter rules, which are what keep `It costs $5 and $10` prose.

Citations (`[@ref]`) are read as prose, since a bare bracket is ordinary punctuation more often than not. To exclude them, use [`TokenIgnores`](/keys/tokenignores):

```ini
[*.{md,myst}]
TokenIgnores = (\[@[^\n\]]+\])
```


# Quarto

Learn how Vale handles Quarto content.

{% hint style="info" %}
Requires Vale v3.18.0 or later. Earlier versions can approximate support by assigning the format: `qmd = md` under `[formats]`.
{% endhint %}

[Quarto](https://quarto.org/) is Pandoc Markdown plus knitr- and Jupyter-style code cells. The cells are Markdown already—a cell fence is a fenced code block and an inline expression is a code span—so Vale parses the Pandoc layer on top: fenced divs, attributes, and shortcodes.

The supported extension is `.qmd`.

By default, Vale ignores:

* Code cells: ` ```{r} ` fences, including their `#|` options.
* Inline expressions: `` `r mean(x)` `` and `` `{python} 1 + 1 ``.
* Fenced-div lines: `::: {.callout-note}` and its closing `:::`—the content between them is linted.
* Shortcodes: `{{< video ... >}}`, inline or standing alone.
* Attributes: a heading's `{#sec-overview}`, and the `{.underline}` of `[text]{.underline}`—the text itself is still linted.
* Fenced blocks, code spans, and URLs, as in [Markdown](/formats/markdown).

## [Divs](#divs)

A fenced div's classes become [class scopes](/topics/scopes#class-scopes) for everything inside it, so a rule can target a callout, a margin note, or a column by name:

```yaml
extends: existence
message: "Don't use '%s' in a callout."
scope: class.callout-note
level: error
tokens:
  - obviously
```

Divs nest—by fence length (`::::` around `:::`) or same-length fences alike—and a block deep inside carries every enclosing class.

## [Math and citations](#math-and-citations)

Math is ignored, both `$$…$$` display blocks and `$x^2$` inline spans. See [Math](/formats/markdown#math) for the delimiter rules, which are what keep `It costs $5 and $10` prose.

Citations (`[@ref]`) and cross-references (`@sec-overview`) are read as prose, since a bare bracket or `@` is ordinary punctuation more often than not. To exclude them, use [`TokenIgnores`](/keys/tokenignores):

```ini
[*.qmd]
TokenIgnores = (\[?-?@[^\s\]]+\]?)
```


# AsciiDoc

Learn how Vale handles AsciiDoc content.

AsciiDoc is supported through the external program [Asciidoctor](https://asciidoctor.org/). See their [installation](https://docs.asciidoctor.org/asciidoctor/latest/install) instructions to get started. You’ll need to ensure that the `asciidoctor` executable is available in your `$PATH`.

The supported extensions are `.adoc`, `.asciidoc`, and `.asc`.

By default, Vale ignores:

* [Literals and source code](https://docs.asciidoctor.org/asciidoc/latest/syntax-quick-reference/#literals-and-source-code).
* URLs: See [URL handling](https://github.com/vale-cli/vale/issues/320) for more information.

## [Attributes](#attributes)

You can customize how `asciidoctor` is called by passing [document attributes](https://docs.asciidoctor.org/asciidoc/latest/attributes/document-attributes-ref/):

```ini
StylesPath = styles

[asciidoctor]
# attribute = value
#
# where 'YES' enables and 'NO' disables.

# enable
experimental = YES

# assign a specific value
attribute-missing = drop

[*.adoc]
BasedOnStyles = Vale
```

By default, Vale switches off the attributes that generate text of their own—section numbers (`sectnums`), the table of contents (`toc`), and caption labels such as the "Table 1." prefixed to a block title (`table-caption`, `figure-caption`, `example-caption`, `listing-caption`). Generated text appears nowhere in the source, so a rule matching it would report an unrelated line. An attribute set in the `[asciidoctor]` section overrides these defaults.

## [Comments](#comments)

Vale supports comment-based configuration in AsciiDoc files:

{% hint style="warning" %}
Make sure to surround the inline passthrough statements with newlines, as shown below.
{% endhint %}

* Turn Vale off entirely:

```adoc
pass:[<!-- vale off -->]

This text will be ignored.

pass:[<!-- vale on -->]
```

* Turn off a specific rule:

```adoc
pass:[<!-- vale Style.Redundancy = NO -->]

This is some text ACT test

pass:[<!-- vale Style.Redundancy = YES -->]
```

* Turn off specific match(es) within a rule:

```adoc
pass:[<!-- vale Style.Redundancy["ACT test","OTHER"] = NO -->]

This is some text ACT test

pass:[<!-- vale Style.Redundancy["ACT test","OTHER"] = YES -->]
```

* Turn on or off specific styles:

```adoc
pass:[<!-- vale StyleName1 = YES -->]
pass:[<!-- vale StyleName2 = NO -->]
```

* Set styles (enabling them and switching off any other styles):

```adoc
pass:[<!-- vale style = StyleName1 -->]
pass:[<!-- vale styles = StyleName1, StyleName2 -->]
```


# MDX

Learn how Vale handles MDX content.

{% hint style="info" %}
Vale v3.18.0 or later parses [MDX](https://mdxjs.com/) natively. Earlier versions require the external program [`mdx2vast`](https://github.com/jdkato/mdx2vast) (`npm install -g mdx2vast`) on your `$PATH`.
{% endhint %}

The supported extension is `.mdx`.

MDX is Markdown plus ESM statements, JSX elements, and JavaScript expressions. The JavaScript holds no prose, and Vale treats it as code and ignores it:

* JSX tags, their attributes, and self-closing elements (`<Chart data={population} />`).
* ESM `import` and `export` statements, including multiline bodies.
* JavaScript expressions—inline (`{Math.PI * 2}`) and standing on their own.
* Fenced blocks: Blocks surrounded by three or more backticks.
* Code spans: Text surrounded by backticks.

Because MDX removed indented code blocks from the grammar, four leading spaces are an ordinary paragraph and its prose is linted.

## [JSX children](#jsx-children)

{% hint style="info" %}
Requires Vale v3.19.0 or later. Earlier versions skipped a JSX element entirely, children included.
{% endhint %}

A JSX element's *children* are Markdown, just as MDX itself reads them, so the prose inside `<Steps>...</Steps>` or `<Aside>...</Aside>` is linted:

```mdx
<Aside type="info">
  This text is linted. The `type` attribute is not.
</Aside>
```

The children carry the element's name as a [class scope](/topics/scopes#class-scopes), so a rule can target one component's content (`scope: text.class.Aside`), and a component whose content shouldn't be linted can be excluded by name:

```ini
IgnoredClasses = RawOutput
```

The same applies inline: in `<abbr>HTML</abbr> is a language`, the word "HTML" is linted as part of its sentence.

One exception: an element opened *and* closed on a single standalone line (`<Box>inner</Box>` as its own block) is read as code.

## [The MDX package](#the-mdx-package)

{% hint style="info" %}
This package exists for versions before v3.18.0, whose parser threw on inline expressions that aren't valid JavaScript—ending the run rather than the file. The native parser reads them without complaint.
{% endhint %}

The [`MDX`](https://github.com/vale-cli/MDX) package carries the configuration for those cases:

```ini
Packages = MDX
```

See [`Packages`](/keys/packages) for more information.

## [Comments](#comments)

Vale supports comment-based configuration in MDX files:

* Turn Vale off entirely:

```mdx
{/* vale off */}

This text will be ignored.

{/* vale on */}
```

* Turn off a specific rule:

```mdx
{/* vale Style.Redundancy = NO */}

This is some text ACT test

{/* vale Style.Redundancy = YES */}
```

* Turn off specific match(es) within a rule:

```mdx
{/* vale Style.Redundancy["ACT test","OTHER"] = NO */}

This is some text ACT test

{/* vale Style.Redundancy["ACT test","OTHER"] = YES */}
```

* Turn on or off specific styles:

```mdx
{/* vale StyleName1 = YES */}

{/* vale StyleName2 = NO */}
```

* Set styles (enabling them and switching off any other styles):

```mdx
{/* vale style = StyleName1 */}
{/* vale styles = StyleName1, StyleName2 */}
```


# HTML

Learn how Vale handles HTML content.

HTML5 support is built in. The supported extensions are `.html`, `.htm`, `.shtml`, and `.xhtml`.

By default, Vale ignores `script`, `style`, `pre`, `code`, and `tt` tags, as well as URLs (see [URL handling](https://github.com/vale-cli/vale/issues/320) for more information).

## [Comments](#comments)

Vale supports comment-based configuration in HTML files:

* Turn Vale off entirely:

```html
<!-- vale off -->

This text will be ignored.

<!-- vale on -->
```

* Turn off a specific rule:

```html
<!-- vale Style.Redundancy = NO -->

This is some text ACT test

<!-- vale Style.Redundancy = YES -->
```

* Turn off specific match(es) within a rule:

```html
<!-- vale Style.Redundancy["ACT test","OTHER"] = NO -->

This is some text ACT test

<!-- vale Style.Redundancy["ACT test","OTHER"] = YES -->
```

* Turn on or off specific styles:

```html
<!-- vale StyleName1 = YES -->
<!-- vale StyleName2 = NO -->
```

* Set styles (enabling them and switching off any other styles):

```html
<!-- vale style = StyleName1 -->
<!-- vale styles = StyleName1, StyleName2 -->
```


# reStructuredText

Learn how Vale handles reStructuredText content.

reStructuredText is supported through the external program [`rst2html`](http://docutils.sourceforge.net/docs/user/tools.html#rst2html-py). To get started, you’ll need to install the [`docutils`](https://pypi.org/project/docutils/) package:

```bash
$ pip install docutils
```

You’ll need to ensure that the `rst2html` executable is available in your `$PATH` (this should happen automatically).

The supported extensions are `.rst` and `.rest`.

By default, Vale ignores:

* [Literal blocks](https://docutils.sourceforge.io/docs/user/rst/quickref.html#literal-blocks).
* [Inline literals](https://docutils.sourceforge.io/docs/user/rst/quickref.html#inline-markup).
* URLs: See [URL handling](https://github.com/vale-cli/vale/issues/320) for more information.

## [Comments](#comments)

Vale supports comment-based configuration in reStructuredText files:

* Turn Vale off entirely:

```rst
.. vale off

This text will be ignored.

.. vale on
```

* Turn off a specific rule:

```rst
.. vale Style.Redundancy = NO

This is some text ACT test

.. vale Style.Redundancy = YES
```

* Turn off specific match(es) within a rule:

```rst
.. vale Style.Redundancy["ACT test","OTHER"] = NO

This is some text ACT test

.. vale Style.Redundancy["ACT test","OTHER"] = YES
```

* Turn on or off specific styles:

```rst
.. vale StyleName1 = YES
.. vale StyleName2 = NO
```

* Set styles (enabling them and switching off any other styles):

```rst
.. vale style = StyleName1
.. vale styles = StyleName1, StyleName2
```


# Typst

Learn how Vale handles Typst content.

{% hint style="info" %}
Requires Vale v3.18.0 or later.
{% endhint %}

[Typst](https://typst.app/) is supported through the external program [`typst2vast`](https://github.com/jdkato/typst2vast), which reads documents with `typst-syntax`—the Typst compiler's own parser—without ever evaluating them. To get started, install the CLI:

```console
$ cargo install typst2vast
```

You'll need to ensure that the `typst2vast` executable is available in your `$PATH` (this should happen automatically).

The supported extension is `.typ`.

Because nothing is compiled, a document that doesn't build still lints, no package is ever fetched, and no `#show` rule can move an alert off its source text.

By default, Vale ignores:

* Code mode: `#` expressions and `#let`, `#set`, `#show`, and `#import` statements.
* Raw text: inline `` `spans` `` and fenced blocks.
* Math: `$x^2$` and display equations.
* Comments: `//` and `/* ... */`, nesting included.
* Labels (`<my-label>`) and references (`@my-label`).

Everything else is prose in its scope: `=` headings are `heading`, `*strong*` and `_emphasis_` are `strong` and `emphasis`, raw spans are `code`, and list, numbered, and term items are `list`.

## [Content blocks](#content-blocks)

A `[content]` block anywhere inside code mode is prose, and Vale lints it—a figure's caption, a conditional's branches, a `#let`-bound body:

```typst
#figure(
  image("diagram.png", width: 70%),
  caption: [This caption is linted.],
)

#if release [This branch is linted.] else [So is this one.]
```


# XML

Learn how Vale handles XML content.

XML is supported through the external program [`xsltproc`](http://xmlsoft.org/XSLT/xsltproc.html). To install, see:

* [Chocolatey](https://community.chocolatey.org/packages/xsltproc) (Windows): `choco install xsltproc`.
* [Homebrew](https://formulae.brew.sh/formula/libxslt) (macOS): `brew install libxslt`.
* Debian/Ubuntu/apt-based systems: `apt-get install xsltproc`.

You’ll need to ensure that the `xsltproc` executable is available in your `$PATH`.

The supported extension is `.xml`.

You also need to provide a version 1.0 XSL Transformation (XSLT) for converting to HTML:

{% code title=".vale.ini" %}

```ini
[*.xml]
Transform = docbook-xsl-snapshot/html/docbook.xsl
```

{% endcode %}

Once converted, Vale will follow the same rules as it does for [HTML](/formats/html).

Related formats: [reStructuredText](/formats/restructuredtext) [Org](/formats/org)


# Org

Learn how Vale handles Org content.

[Org](https://orgmode.org/) support is built in. The supported extension is `.org`.

By default, Vale ignores:

* [Code blocks](https://orgmode.org/org.html#Structure-of-Code-Blocks).
* [Literal examples](https://orgmode.org/org.html#Literal-Examples).
* [Code and verbatim strings](https://orgmode.org/org.html#Emphasis-and-Monospace-1).
* URLs: See [URL handling](https://orgmode.org/org.html#Structure-of-Code-Blocks) for more information.

## [Comments](#comments)

Vale supports comment-based configuration in Org files:

* Turn Vale off entirely:

```org
# vale off

This text will be ignored.

# vale on
```

* Turn off a specific rule:

```org
# vale Style.Redundancy = NO

This is some text ACT test

# vale Style.Redundancy = YES
```

* Turn off specific match(es) within a rule:

```org
# vale Style.Redundancy["ACT test","OTHER"] = NO

This is some text ACT test

# vale Style.Redundancy["ACT test","OTHER"] = YES
```

* Turn on or off specific styles:

```org
# vale StyleName1 = YES
# vale StyleName2 = NO
```

* Set styles (enabling them and switching off any other styles):

```org
# vale style = StyleName1
# vale styles = StyleName1, StyleName2
```


# DITA

Learn how Vale handles DITA content.

{% hint style="warning" %}
Due to the dependency on the third-party `dita` command, you'll likely experience worse performance with DITA files compared to other formats.
{% endhint %}

DITA is supported through the [DITA Open Toolkit](https://www.dita-ot.org/). You’ll need to follow the [installation instructions](https://www.dita-ot.org/dev/topics/installing-client.html), including the optional step of adding the absolute path for the `bin` directory to the `PATH` system variable.

The supported extension is `.dita`.

Vale ignores `<codeblock>`, `<tt>`, and `<codeph>` elements by default.


# QDoc

Learn how Vale handles QDoc content.

{% hint style="info" %}
Requires Vale v3.18.0 or later.
{% endhint %}

[QDoc](https://doc.qt.io/qt-6/01-qdoc-manual.html) is Qt's documentation markup: LaTeX-style commands inside `/*! ... */` comment blocks. Support is built in—nothing to install.

The supported extension is `.qdoc`. Doc comments in C++ and QML sources lint through a [format association](/topics/.vale.ini#format-associations):

```ini
[formats]
cpp = qdoc
qml = qdoc

[*.{qdoc,cpp,qml}]
BasedOnStyles = Vale
```

Alerts are mapped back to their positions in the source file. Only block comments (`/*! ... */`) are treated as documentation—`//` line comments are code.

By default, Vale ignores:

* Code blocks: `\code`, `\badcode`, `\qml`, and friends, through their `\end` commands.
* Omitted text: `\omit ... \endomit`.
* Topic and context commands: `\fn`, `\class`, `\page`, `\module`, `\since`, and the rest of their family—the whole line is markup.
* Quoting commands: `\snippet`, `\quotefile`, `\printline`, and so on.
* Links' targets: `\l {target} {text}` keeps its text and drops its target; `\sa` lines say nothing.
* Inline code: `\c` and `\a` arguments.

Everything else is prose in its scope: `\title` and `\section1` through `\section4` are headings, `\li` items are `list` entries or table cells, `\b` and `\e` are `strong` and `emphasis`, and `\caption` is a figure caption.

## [Classed paragraphs](#classed-paragraphs)

`\brief`, `\note`, `\warning`, and `\important` become [class scopes](/topics/scopes#class-scopes), so a rule can target them by name:

```yaml
extends: existence
message: "Don't use '%s' in a note."
scope: class.note
level: error
tokens:
  - obviously
```

An unknown command is masked with its text kept, so a QDoc extension Vale doesn't know about degrades to plain prose rather than to noise.


# Code

Learn how Vale handles source code.

Vale supports linting source code comments in a number of languages (see below).

| Language   | Extensions                           | Scopes                                                                                                                                                                                                                                                                        |
| ---------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| C          | `.c`, `.h`                           | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| C#         | `.cs`, `.csx`                        | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| C++        | `.cpp`, `.cc`, `.cxx`, `.hpp`        | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| CSS        | `.css`                               | <p><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                                                                                  |
| Elixir     | `.ex`, `.exs`                        | <p><code>#</code> (<code>text.comment.line.ext</code>),<br><code>@doc</code> (<code>text.comment.doc.line.ext</code>),<br><code>@moduledoc</code> (<code>text.comment.doc.block.ext</code>)</p>                                                                               |
| Go         | `.go`                                | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| Haskell    | `.hs`                                | <p><code>--</code> (<code>text.comment.line.ext</code>),<br><code>{-</code> (<code>text.comment.block.ext</code>)</p>                                                                                                                                                         |
| Java       | `.java`, `.bsh`                      | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| JavaScript | `.js`                                | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| Julia      | `.jl`                                | <p><code>#</code> (<code>text.comment.line.ext</code>),<br><code>"..."</code> (<code>text.comment.line.ext</code>)<br><code>#=</code> (<code>text.comment.block.ext</code>),<br><code>"""</code> (<code>text.comment.block.ext</code>)</p>                                    |
| LESS       | `.less`                              | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| Lua        | `.lua`                               | <p><code>--</code> (<code>text.comment.line.ext</code>),<br><code>--\[\[</code> (<code>text.comment.block.ext</code>)</p>                                                                                                                                                     |
| Perl       | `.pl`, `.pm`, `.pod`                 | `#` (`text.comment.line.ext`)                                                                                                                                                                                                                                                 |
| PHP        | `.php`                               | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>#</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p> |
| PowerShell | `.ps1`                               | <p><code>#</code> (<code>text.comment.line.ext</code>),<br><code><#...#></code> (<code>text.comment.line.ext</code>),<br><code><#</code> (<code>text.comment.block.ext</code>)</p>                                                                                            |
| Protobuf   | `.proto`                             | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| Python     | `.py`, `.py3`, `.pyw`, `.pyi`, `rpy` | <p><code>#</code> (<code>text.comment.line.ext</code>),<br><code>"""</code> (<code>text.comment.block.ext</code>)</p>                                                                                                                                                         |
| QML        | `.qml`                               | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| R          | `.r`, `.R`                           | `#` (`text.comment.line.ext`)                                                                                                                                                                                                                                                 |
| Ruby       | `.rb`                                | <p><code>#</code> (<code>text.comment.line.ext</code>),<br><code>^=begin</code> (<code>text.comment.block.ext</code>)</p>                                                                                                                                                     |
| Rust       | `.rs`                                | `//` (`text.comment.line.ext`)                                                                                                                                                                                                                                                |
| Sass       | `.sass`, `.scss`                     | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| Scala      | `.scala`, `.sbt`                     | `//` (`text.comment.line.ext`)                                                                                                                                                                                                                                                |
| Swift      | `.swift`                             | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| TypeScript | `.ts`, `.tsx`                        | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |

## [Documentation attributes](#documentation-attributes)

{% hint style="info" %}
Requires Vale v3.19.0 or later.
{% endhint %}

Elixir has no documentation comment syntax: its published API documentation lives in module attributes holding a string or a heredoc, and that is what `mix docs` renders.

```elixir
defmodule Session do
  @moduledoc """
  A scheduled period of care delivery.
  """

  @doc "Books a session for a client."
  def book(client), do: ...
end
```

Both comments and attributes are extracted. `@moduledoc`, `@doc`, `@typedoc`, and `@shortdoc` carry a `doc` scope—`text.comment.doc.line` and `text.comment.doc.block`—so published documentation can be held to a different standard than an implementation note, or excluded on its own via [`IgnoredScopes`](/keys/ignoredscopes). `@doc false` and `@doc since: "1.0.0"` hold no prose, and neither is extracted.

## [Associations](#associations)

In many languages, it’s common for comments to contain *embedded markup* (e.g., Markdown, reStructuredText, etc.) within them. For example, consider the following Rust doc comment:

````rust
impl Person {
    /// Creates a person with the given name.
    ///
    /// # Examples
    ///
    /// ```
    /// // You can have rust code between fences
    /// // inside the comments If you pass --test
    /// // to `rustdoc`, it will even test it for
    /// // you!
    /// use doc::Person;
    /// let person = Person::new("name");
    /// ```
    pub fn new(name: &str) -> Person {
        Person {
            name: name.to_string(),
        }
    }
}
````

If the embedded markup is one of the supported formats, you can associate the `comment` scope with a `markup` type. This will allow you to lint the embedded markup as if it were a standalone file.

```ini
StylesPath = styles
MinAlertLevel = suggestion

[formats]
# Rust + Markdown
rs = md

[*.{rs,md}]
BasedOnStyles = Vale
```

![How embedded markup is linted: tree-sitter finds each comment in the source file, the per-line decoration is stripped, the remaining body is parsed as Markdown, and every alert is mapped back to its original line and column in the source.](/files/2rAd74lv5h3HGTx538NB)

Once a markup format has been assigned, you can make use of all the supported features of that format (such as ignore patterns and comment-based configuration) in your source code comments.

This includes [`TokenIgnores`](/keys/tokenignores) and [`BlockIgnores`](/keys/blockignores), which are otherwise unavailable in source code: they work by wrapping a match in the format's inline or block code delimiter, so they need a markup format to wrap it with. Associating one makes them available.

### [Block comment decoration](#block-comment-decoration)

{% hint style="info" %}
Requires Vale v3.17.0 or later. Earlier versions passed the leading asterisks through to the markup parser, which read a block comment as a single list.
{% endhint %}

Block comments in C-style languages conventionally decorate each line with a leading asterisk:

```javascript
/**
 * Reads the record and returns it.
 *
 * Pass `refresh` to bypass the cache:
 *
 * * `refresh: true` re-reads from disk.
 * * `refresh: false` uses the cache.
 */
```

That decoration is removed before the comment is handed to the markup parser, so the body above is read as a paragraph followed by a list—not as one long list, which is what the leading asterisks would otherwise make it.

Relative indentation is preserved, so indented code blocks inside a comment still work:

````javascript
/**
 * Formats the value for display.
 *
 * ```
 * const output = format(value);
 * ```
 */
````

The fenced block is treated as code and left alone, exactly as it would be in a standalone Markdown file.

{% hint style="info" %}
An asterisk is only treated as decoration when whitespace or the end of the line follows it. A line beginning `*emphasis*` or `**bold**` keeps its markup.
{% endhint %}


# LSP

Get started with Vale's Language Server.

The Vale Language Server (`vale-ls`) implements the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) around a local installation of Vale, giving any editor that speaks LSP autocomplete, diagnostics, hover popups, and quick fixes.

Most people don't run it directly—an editor plugin does. See [Editors](#editors) below.

## [Configuration](#configuration)

The server reads its settings from the `initializationOptions` your client sends when it connects:

```json
{
  "initializationOptions": {
    "installVale": true,
    "syncOnStartup": true,
    "filter": "",
    "configPath": ""
  }
}
```

| Option           | Type      | Default | Description                                                                                                                       |
| ---------------- | --------- | ------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `installVale`    | `boolean` | `false` | Install and update Vale into a `vale_bin` folder beside `vale-ls`. When false, `vale` must be on your `$PATH`.                    |
| `syncOnStartup`  | `boolean` | `false` | Run [`vale sync`](/topics/cli) when the server starts.                                                                            |
| `filter`         | `string`  | `""`    | An [output filter](/topics/cli) to apply, e.g. `.Level in ['warning', 'error']`.                                                  |
| `configPath`     | `string`  | `""`    | An absolute path to a `.vale.ini`. Usually best left empty so Vale's own [search process](/topics/.vale.ini) applies.             |
| `valeBinaryPath` | `string`  | `""`    | An absolute path to the `vale` binary to use. Set this when you need your own installation rather than a managed or `$PATH` copy. |
| `lintOnChange`   | `boolean` | `true`  | Report diagnostics as you type. When false, they're only updated when you save.                                                   |
| `debounceMs`     | `number`  | `300`   | How long typing has to settle before `lintOnChange` runs Vale.                                                                    |
| `showMetrics`    | `boolean` | `true`  | Show a [code lens](#code-lenses) with the document's word and sentence counts.                                                    |

{% hint style="info" %}
`installVale` and `syncOnStartup` are off unless your client asks for them. Editor plugins generally do—[LSP-vale-ls](https://github.com/vale-cli/LSP-vale-ls) turns both on by default—but if you're wiring the server up yourself, set them explicitly.
{% endhint %}

A configured `valeBinaryPath` is never substituted: if nothing is there, the server reports it rather than falling back to another copy of Vale. You can also pass it on the command line as `--vale-binary`, which the setting overrides.

Clients that push settings after connecting—`workspace/didChangeConfiguration`—can change any of these without a restart. The server accepts the settings object either as-is or scoped under a `vale` key.

In a workspace with more than one folder, each document is linted against the innermost folder containing it, so projects with different `.vale.ini` files can be open at the same time. Setting `configPath` overrides that for every document.

## [Quick fixes](#quick-fixes)

Where Vale can suggest a correction, the server offers it as a code action: a replacement for a [substitution](/topics/styles) rule, or a deletion for a repeated word.

Spelling alerts also offer to add the flagged word to a vocabulary, which writes it to that vocabulary's `accept.txt` and re-lints the file. A project with several active [vocabularies](/keys/vocabularies) gets one action per vocabulary, so you choose where the word lands.

## [Code lenses](#code-lenses)

Prose files carry a lens with the document's word and sentence counts. Selecting it reports the rest of Vale's metrics—characters, paragraphs, syllables, and the counts the readability formulas are built on. Turn it off with `showMetrics`.

## [Commands](#commands)

The server registers these for `workspace/executeCommand`:

| Command        | Arguments              | Description                                 |
| -------------- | ---------------------- | ------------------------------------------- |
| `cli.sync`     | none                   | Run [`vale sync`](/topics/cli).             |
| `cli.install`  | none                   | Install or update the managed copy of Vale. |
| `cli.compile`  | `[uri]`                | Compile a rule and open it on Regex101.     |
| `vocab.add`    | `[{uri, vocab, term}]` | Add a term to a vocabulary's `accept.txt`.  |
| `vocab.reject` | `[{uri, vocab, term}]` | Add a term to a vocabulary's `reject.txt`.  |
| `doc.metrics`  | `[{uri}]`              | Report a document's metrics.                |

## [Editors](#editors)

These connect to `vale-ls`:

* [Sublime Text](https://packagecontrol.io/packages/LSP-vale-ls)
* [VS Code](https://github.com/chrischinchilla/vale-vscode)
* [Neovim](https://github.com/neovim/nvim-lspconfig) (as `vale_ls`)
* [Zed](https://github.com/koozz/zed-vale)
* [Emacs](https://github.com/tpeacock19/flymake-vale)

Other editors integrate with the Vale CLI directly rather than through the server: [ALE](https://github.com/dense-analysis/ale), [JetBrains](https://plugins.jetbrains.com/plugin/19613-vale-cli/docs), [Obsidian](https://github.com/ChrisChinchilla/obsidian-vale), [Oxygen XML](https://www.oxygenxml.com/doc/versions/23.1/ug-editor/topics/vale-linter-addon.html), and [Qt Creator](https://wiki.qt.io/Setting_Up_Vale).

## [Running it yourself](#running-it-yourself)

Download a build from [releases](https://github.com/vale-cli/vale-ls/releases) and point your client at the binary. [LSP-vale-ls](https://github.com/vale-cli/LSP-vale-ls) is a small, readable example of a client configuration.

The binary takes no arguments beyond `--vale-binary`, `--version`, and `--help`; everything else comes from your client.


# MCP

Give an AI assistant the Vale engine, so it can check its own work.

Models write plausible Vale YAML. Whether it compiles, whether the regex matches anything, whether the config still loads — a model has no way to find out, so you find out later, in CI.

The Vale MCP server closes that gap. It exposes the engine as [Model Context Protocol](https://modelcontextprotocol.io) tools, so an assistant can scaffold a rule, compile it, run it over sample text, and see the alerts it produced — before writing a file.

{% hint style="info" %}
The hosted MCP server is part of [Vale CMS](https://vale.sh/cms), which is a paid product. Vale itself — the CLI, the engine, and the styles — stays free, open source, and MIT licensed.
{% endhint %}

## Connecting

The server speaks JSON-RPC 2.0 over HTTP. Point your client at it with a token from your Vale CMS account:

```json
{
  "mcpServers": {
    "vale-cms": {
      "type": "http",
      "url": "https://api.vale.sh/mcp"
    }
  }
}
```

Clients that support remote MCP servers — Claude Code, Claude Desktop, Cursor, VS Code, and others — need nothing installed locally.

## What the tools do

The tools fall into four groups.

### Author

Start from something that already compiles, rather than a blank file and a guess at the schema.

| Tool                  | Purpose                                                                        |
| --------------------- | ------------------------------------------------------------------------------ |
| `scaffold_rule`       | A valid starter rule for any of the twelve check types                         |
| `scaffold_vocab`      | A vocabulary, with its `accept.txt` and `reject.txt`                           |
| `scaffold_dictionary` | A Hunspell `.dic`/`.aff` pair                                                  |
| `scaffold_filter`     | A filter expression over your rules                                            |
| `scaffold_view`       | A view that extracts part of a document                                        |
| `scaffold_template`   | An output template                                                             |
| `assemble_style`      | A whole style package, with each rule checked against the guide's own examples |
| `fetch_guide`         | A published style guide, as text you can derive rules from                     |

### Verify

The half that generation can't do for itself.

| Tool            | Purpose                                                                               |
| --------------- | ------------------------------------------------------------------------------------- |
| `diagnose_rule` | Compile a rule and report the error, with its position                                |
| `test_rule`     | Run a rule over inputs and check each should or shouldn't match                       |
| `stress_rule`   | Generate near-miss inputs from the rule's own tokens and report what it wrongly flags |
| `check_config`  | Load a whole proposed project through the engine                                      |
| `lint_text`     | The alerts a config produces on sample prose                                          |
| `audit_style`   | Correctness and performance defects no compile step catches                           |
| `check_links`   | Resolve every rule's `link:` field, which Vale itself never validates                 |

### Understand

Answers about the engine, from the engine.

| Tool                | Purpose                                                          |
| ------------------- | ---------------------------------------------------------------- |
| `explain_check`     | What a check type detects, its fields, and an example            |
| `list_check_types`  | All twelve extension points                                      |
| `tag_text`          | Part-of-speech tags, as the `sequence` check sees them           |
| `show_blocks`       | How a format splits text into the blocks rules run over          |
| `trace_rule`        | What each slot of a sequence wanted, beside what the tagger said |
| `resolve_config`    | The config the engine actually resolved                          |
| `project_layout`    | Where every asset belongs, and how to enable it                  |
| `expand_dictionary` | The exact word forms a Hunspell entry accepts                    |

### Change safely

Editing a rule already in use is the risky edit.

| Tool              | Purpose                                                         |
| ----------------- | --------------------------------------------------------------- |
| `diff_rule`       | Which alerts an edit adds and removes, over a corpus you supply |
| `diff_style`      | The same, for a whole package                                   |
| `render_template` | What a template produces against real alerts                    |
| `put_files`       | Upload a project once and reference it by hash                  |

## Three questions it answers

These are the questions that cost hours by hand and one call here.

### A rule looks right and never fires

Sequence rules match on part-of-speech tags, and the tagger's reading of a word is invisible in the YAML. `trace_rule` shows what each slot wanted beside what the tagger actually produced — a slot expecting a proper noun sitting on a word tagged `JJ` is a rule that can never match.

The useful part is what it rules out: the rule may be correct and the tagger may simply disagree with you, which calls for a documented exception rather than a rewrite.

### A rule behaves differently in Markdown and plain text

`show_blocks` shows how each format splits a document into the blocks rules run over. A `.txt` file is one block, so its whole `text` scope is a single unit; the same content in Markdown is one block per paragraph.

That is how a pattern containing `\s+` quietly matches across a blank line and joins two paragraphs. The cause is the format, not the pattern.

### Linting got slower and no rule changed

`audit_style` prices a style before any text is read — what it costs to compile, which is paid on every run whether or not a rule ever fires. On a short document that is most of the wall clock.

A negated character class under `ignorecase` is the classic example: the regex engine computes the case orbit of every member, at roughly 3.6× the compile cost of the `\s` it replaced.

## See also

* [Vale CMS](https://vale.sh/cms) — the hosted editor the MCP server is part of
* [Styles](/topics/styles) — what a rule is, and the twelve check types
* [Scopes](/topics/scopes) — the scoping system `show_blocks` reports on


# Regex

Learn how to use regex in Vale.

Vale uses the [`regexp2`](https://github.com/dlclark/regexp2) library to process regular expressions in its rules. This library extends the capabilities of the standard Go [regexp](https://pkg.go.dev/regexp/syntax) package by supporting features like lookaheads, lookbehinds, and lazy quantifiers, which are missing in Go’s built-in regexp implementation.

This guide provides an overview of regex syntax supported by Vale, along with tips for writing regular expressions in [YAML](https://yaml.org/) files.

## [Syntax](#syntax)

For basic information on the supported syntax, see the [Go docs](https://pkg.go.dev/regexp/syntax). For the extended syntax provided by `regexp2`, see their [README](https://github.com/dlclark/regexp2?tab=readme-ov-file#compare-regexp-and-regexp2).

The most commonly used assertion constructs are:

* Positive lookahead: `(?=re)`
* Negative lookahead: `(?!re)`
* Positive lookbehind: `(?<=re)`
* Negative lookbehind: `(?<!re)`

This extended syntax is supported everywhere in Vale, except for `script`-based rules (which are limited to the standard Go regex syntax).

## [YAML](#yaml)

Wrap all regex in single (`'`) or double (`"`) quotes to avoid YAML interpreting special characters:

* Single quotes (`'`): Prevent YAML from interpreting any characters except single quotes themselves.
* Double quotes (`"`): Allow YAML to interpret escape sequences like `\n` and `\t`, so you’ll need to escape backslashes.

In general, this means that you should **prefer single quotes** for most cases:

```yaml
extends: existence
message: Consider removing '%s'
level: warning
# A typical rule with single quotes:
tokens:
  - '([A-Z]\w+)([A-Z]\w+)'
```

If you need to *use* a single quote in your regex, you can escape it with another single quote:

```yaml
extends: existence
message: Consider removing '%s'
level: warning
# A rule with a single quote in the regex:
tokens:
  - '([A-Z]\w+)([A-Z]\w+)''s'
```

## [Vale Studio](#vale-studio)

[Vale Studio](https://studio.vale.sh/) provides a rule editor that integrates with [regex101](https://regex101.com/) to allow you to inspect the compiled regex pattern and test it against sample text. This can be a helpful way to debug your regex patterns.

![Vale Studio](/files/6fc2ca71f0e46c0942b9a0829a91aa5eb17d0012)

## [Common Issues](#common-issues)

<details>

<summary>Word Boundaries</summary>

In regex, `\b` is a word boundary assertion that matches the position between a word character and a non-word character.

For example, the regex `\bfoo\b` will only match the word “foo” and not “foobar” or “foo-bar”.

By default, [`existence`](/checks/existence) and [`substitution`](/checks/substitution) rules in Vale will automatically add word boundaries to the beginning and end of each token.

To disable this behavior, set `nonword` to `true`:

```yaml
extends: existence
message: Consider removing '%s'
nonword: true
tokens:
  - some token
```

`raw` is the other way out: it takes your pattern verbatim, boundaries and all.

Word boundaries need watching in non-Latin scripts. `\b` sits between a word character (`[0-9A-Za-z_]`) and anything else, so in a script with no word characters it never matches—and the rule finds nothing at all, with no error to say why. See [WordTemplate](/keys/wordtemplate) if that's your content.

</details>

<details>

<summary>Scoping</summary>

For markup-based rules, Vale converts each document to HTML and applies a [scoping](/topics/scopes) system before running any rules.

This means that if you’re writing a rule that targets markup syntax or needs to match across block boundaries, the results may be different from what you expect.

If you like to apply a rule to the entire, unprocessed document, you can use `scope: raw`:

```yaml
extends: existence
message: Consider removing '%s'
scope: raw
tokens:
  - some token
```

</details>


# TextFSM

Learn how to lint plain text that has structure but no markup.

A commit message is a subject, a blank line, a body, and trailers. A subtitle file is a run of cues, each an index, a timing, and a line or two of text. A transcript is a series of turns, each opened by a name. None of that is markup, so Vale reads such a file as lines, and a rule has no way to say “the subject” or “the model’s turn.”

A `textfsm` [View](/topics/views) fixes that. It reads the file through a template in the form Google’s [TextFSM](https://github.com/google/textfsm/wiki/TextFSM) defined for parsing the output of network devices: a list of named values, then a state machine whose rules are regular expressions. What the template captures becomes [scopes](/topics/scopes), each placed at the line and column it came from, so a rule reaches the subject the way it reaches a heading and its alert lands where the text is.

Vale runs the template itself. Nothing needs installing, and the patterns use the same [regex](/guides/regex) dialect as every rule.

{% hint style="info" %}
Requires Vale v3.21.0 or later.
{% endhint %}

## A first template

Take the commit message. Three parts of it deserve rules of their own: the subject, the body, and the trailers at the end.

```
fix: report the shortfall at the scope that fell short.

Zero matches leave no occurence to point at, but the scope has a
position of its own.

Signed-off-by: Joseph Kato <j@example.com>
```

The View lives at `<StylesPath>/config/views/Commit.yml`:

```yaml
engine: textfsm
template: |
  Value Subject (.+)
  Value List Body (.*)
  Value List Trailer ([A-Z][\w-]+: .+)

  Start
    ^${Subject} -> Body

  Body
    ^${Trailer}
    ^${Body}
scopes:
  - name: subject
    expr: Subject

  - name: body
    expr: Body
    type: md

  - name: trailer
    expr: Trailer
```

Read the template from the top:

* Three `Value` lines declare what to capture: a name and the pattern that fills it. `Subject` keeps one line. `Body` and `Trailer` are `List` values, which keep every line they capture rather than the last.
* `Start` is the state reading begins in. Its one rule matches the first line, captures it as the subject, and moves to the `Body` state.
* In `Body`, each line is tried against the rules in order, and the first to match wins. A trailer looks like `Word: text`, so it’s tried first; anything else is body.

`${Subject}` stands for the value’s pattern and captures what it matches. The `->` says what happens on a match; a rule without one reads the next line in the same state.

The `scopes` then name the values to lint. `subject` and `trailer` are linted as plain text. `body` has `type: md`, so it’s parsed as Markdown and a rule scoped to `body` sees paragraphs and inline code the way it does in a `.md` file.

Wire the View to the file in `.vale.ini`. Sections match by path, so a file without an extension is fine:

```ini
[COMMIT_EDITMSG]
BasedOnStyles = Vale, House

View = Commit
```

A rule reaches a scope by name:

```yaml
extends: existence
message: "A subject line doesn't end with '%s'."
level: error
scope: subject
raw:
  - '\.$'
```

And the alerts land in the file, not in the value:

```
 COMMIT_EDITMSG
 1:55  error  A subject line doesn't end with '.'.  House.Subject
 3:23  error  Did you really mean 'occurence'?      Vale.Spelling
```

In a `commit-msg` hook, the message arrives on stdin. `--path` tells Vale which section applies:

```sh
#!/bin/sh
vale --path=COMMIT_EDITMSG < "$1"
```

## How a template is read

A template has two parts. The values come first, one per line, up to the first blank line. Each is `Value [options] Name (pattern)`, and a line starting with `#` is a comment. The states follow: a state is a name at the left margin, and its rules are the indented lines beneath it. Every template needs a `Start` state; `End` and `EOF` are reserved.

In the View’s YAML, the template is a block scalar (`|`), so backslashes in patterns need no escaping, and the indent under a state name is what marks a line as a rule.

Reading is one line at a time. The current state’s rules are tried in order, and the first to match decides what happens next. A line no rule matches is skipped: it captures nothing, the state stays the same, and the line lands in no scope.

A rule is `^pattern -> action`, where the action is any of the following, and `Line.Record` pairs are joined with a dot:

| Action            | Effect                                                                                   |
| ----------------- | ---------------------------------------------------------------------------------------- |
| `Next`            | Read the next line. The default.                                                         |
| `Continue`        | Keep trying the rules below against the same line. A `Continue` rule can’t change state. |
| `Record`          | Emit the captures in hand as a record and start a fresh one.                             |
| `NoRecord`        | Emit nothing. The default.                                                               |
| `Clear`           | Drop the captures in hand, except `Filldown` values.                                     |
| `Clearall`        | Drop every capture, `Filldown` values included.                                          |
| `Error "message"` | Stop reading. Vale reports the message as an error against the View and exits.           |
| A state name      | Enter that state. `End` stops reading.                                                   |

On a match, the line’s captures are assigned first, then the record action runs, then the state changes. So a rule that both captures and says `Record` puts that line’s captures into the record it emits, and `Continue.Record` emits before the rules below capture into the fresh one.

The options on a `Value` line change what it keeps:

| Option     | Effect                                                                     |
| ---------- | -------------------------------------------------------------------------- |
| `List`     | Every capture, in order, rather than the last one.                         |
| `Filldown` | The last capture carries into the next record until a new one replaces it. |
| `Required` | A record missing this value is dropped rather than emitted.                |

`Key` and `Fillup` are accepted for compatibility with TextFSM and change nothing.

A record is emitted for each `Record` action, and the captures in hand at the end of the file are emitted as one more, `End` included. So a template that never says `Record` yields one record per file. An `EOF` state replaces that last record: `EOF` with a `^.* -> Record` rule keeps it, and `EOF` with no rules discards it.

In a pattern, `${Name}` or `$Name` stands for the value’s pattern and captures what it matches. Everything outside it is matched but not captured, which is how `^user: ${User}` keeps the label out of the scope. Start each rule with `^`, as TextFSM does; a rule without it matches anywhere in the line.

## From captures to scopes

Each scope’s `expr` names one of the template’s values, and Vale reports an error at startup if it doesn’t. The scope’s `name` is what a rule’s `scope` refers to, and `type` says how to parse the captured text: `md`, `adoc`, `html`, `rst`, or `org`. Without a `type`, the text is linted as plain lines.

Consecutive lines a `List` value captures are joined into one block, so a body reads as the paragraphs it is rather than one block per line. A gap between the lines starts a new block, and so does a change of column: a block is placed by one line and one column, so every line in it has to start where its first line does.

The blank line matters here. `(.*)` matches a blank line and captures it as empty, so the block continues and the paragraph break survives. `(.+)` doesn’t match a blank line, so the block ends and the next captured line starts another. For a body with `type: md`, `(.*)` gives one document with paragraphs, and `(.+)` gives one document per paragraph. Rules that count across a document, such as `occurrence` and `repetition`, see the difference.

A `textfsm` View takes the file over. A file the section matches is read by the template, whatever its extension, even when it’s `.md`. Only the values a scope names are linted; the rest of the file is never seen by a rule, unless the rule’s scope is `raw`, which still reads the whole file.

## One record per unit

A subtitle file is a run of cues, each an index, a timing line, and one or more lines of text, separated by blank lines. Only the text is prose:

```
1
00:00:01,000 --> 00:00:03,000
First cue, one line.

2
00:00:04,000 --> 00:00:06,000
Second cue,
which wraps.
```

The template walks a cue in three states, and the blank line that ends one emits a record and returns to `Start` for the next:

```yaml
engine: textfsm
template: |
  Value Index (\d+)
  Value Timing (\d\d:\d\d:\d\d,\d\d\d --> \d\d:\d\d:\d\d,\d\d\d.*)
  Value List Cue (.+)

  Start
    ^${Index}$ -> Timing

  Timing
    ^${Timing}$ -> Cue

  Cue
    ^$ -> Record Start
    ^${Cue}
scopes:
  - name: cue
    expr: Cue
```

`Index` and `Timing` are captured so that the template can tell where it is, but no scope names them, so no rule ever sees a timestamp. A rule scoped to `cue` runs over the text of each cue, and a two-line cue is one block at the line and column its first line starts on.

## One side of a conversation

A transcript alternates between a user and a model, and only one side is yours to lint:

```
user: Summarize the change in one line.
assistant: The linter now reports where a scope fell short.
It's worth noting that this only affects occurence rules.
user: Thanks, that's clear enuf.
```

A turn runs until the next label, so each state needs to know when the turn is over before it knows whose turn comes next:

```yaml
engine: textfsm
template: |
  Value List Assistant (.*)
  Value List User (.*)

  Start
    ^assistant: ${Assistant} -> Assistant
    ^user: ${User} -> User

  Assistant
    ^(?:user|assistant): -> Continue.Record
    ^assistant: ${Assistant}
    ^user: ${User} -> User
    ^${Assistant}

  User
    ^(?:user|assistant): -> Continue.Record
    ^user: ${User}
    ^assistant: ${Assistant} -> Assistant
    ^${User}
scopes:
  - name: assistant
    expr: Assistant
    type: md
```

The first rule in each state is the trick. It matches any label, captures nothing, emits the turn in hand as a record, and continues, so the rules below it capture the new turn into a fresh record. A line with no label is a continuation of whichever turn is open.

`User` is captured so the prompt’s lines have somewhere to go, but no scope names it, so the misspelling on the last line goes unreported:

```
 transcript.txt
 3:6   error  'worth noting' hedges, and this is the model's turn.  House.Assistant
 3:42  error  Did you really mean 'occurence'?                      Vale.Spelling
```

The column is the column of the capture, so an alert on the first line of a turn points past the label.

## Seeing what a template captured

A template can be right and still surprise you, because a line no rule matches vanishes without a word. The quickest way to see what a scope holds is a rule that reports every line of it:

```yaml
extends: existence
message: "captured '%s'"
level: suggestion
scope: cue
nonword: true
raw:
  - '(?m)^.+$'
```

Run with `--output=line` and each captured line is listed at its position:

```
sample.srt:3:1:Probe.Cue:captured 'First cue, one line.'
sample.srt:7:1:Probe.Cue:captured 'Second cue,'
sample.srt:8:1:Probe.Cue:captured 'which wraps.'
```

The `(?m)` matters: a block of joined lines is one value, and without the multiline flag `^` and `$` only match at its ends.

Delete the rule once the template does what you expect.


# Hunspell

Learn how to create and use Hunspell-compatible dictionaries in Vale.

[Hunspell](https://hunspell.github.io/) is a spell-checking engine known for its flexibility and support for complex morphological rules. It powers spell-checking in popular applications like LibreOffice, Mozilla Firefox, and Google Chrome.

Vale uses Hunspell-compatible dictionaries to power its [own spell-checking](/checks/spelling) features. This guide will discuss the basics of creating and using these dictionaries.

You can find more thorough documentation at the [official repository](https://github.com/hunspell/hunspell?tab=readme-ov-file#documentation). There’s also a well-documented Python port of the library called [spylls](https://github.com/zverok/spylls).

## How does spell-checking in Vale work?

Vale doesn’t use Hunspell directly and doesn’t require it to be installed on your system.

Instead, Vale uses a pure-Go package to parse Hunspell-compatible dictionaries and check the spelling of words. This package supports a (growing) subset of Hunspell’s features.

A Hunspell-compatible dictionary consists of two files:

1. Affix (`.aff`) file: This file defines the morphological rules, including prefixes, suffixes, and other language-specific grammar rules that govern how words are formed.
2. Dictionary (`.dic`) file: This file contains the list of root words and their associated affix codes to specify valid transformations.

You can name these files whatever you like, so long as the `.aff` and `.dic` files are named consistently – for example, `en_US.aff` and `en_US.dic`.

Here’s a minimal example of a dictionary:

```
1
software/M
```

“1” is the number of words in the dictionary and `software/M` is the root word “software” with the affix code `M`. This means that we accept the word “software” and the variations derived from the affix code `M`.

Our affix file would look like this:

```
SET UTF-8

SFX M Y 1
SFX M   0     's         .
```

* `SFX M Y 1`: This line defines a suffix rule (`SFX`) for the affix code `M`. The `Y` indicates that the rule is [cross-productible](https://github.com/hunspell/hunspell/blob/874abbbe65e228df525023afe176b42df34a7a4f/man/hunspell.5#L527) and the `1` indicates that there is one rule.
* `SFX M 0 's .`: This line defines the rule itself. It says that if a word has the affix code `M`, we can add `'s` to the end of the word. The `0` indicates that no part of the base word is removed when applying this suffix. The `.` indicates that there are no conditions for applying this rule.

The end result is that the dictionary will accept both “software” and “software’s”. Other variations like “softwares” or “softwaring” will be rejected.

## Using your dictionary

Put the two files in your `StylesPath`, under `config/dictionaries`:

```
styles/
└── config/
    └── dictionaries/
        ├── mini.aff
        └── mini.dic
```

Then name it from a [`spelling`](/checks/spelling) rule, without the extension:

{% code title="styles/MyStyle/Spelling.yml" %}

```yaml
extends: spelling
message: "Did you mean %s?"
level: error
# Use this dictionary instead of Vale's built-in one.
custom: true
dictionaries:
  - mini
```

{% endcode %}

Set `append: true` to check against your dictionary *and* Vale's built-in one; without it, yours replaces the default entirely. See [spelling](/checks/spelling) for the rest of the keys, including `dicpath` for keeping dictionaries somewhere else.

## Where can I find Hunspell dictionaries?

* [`wooorm/dictionaries`](https://github.com/wooorm/dictionaries?tab=readme-ov-file)
* [`LibreOffice/dictionaries`](https://github.com/LibreOffice/dictionaries)

[Firefox](https://addons.mozilla.org/en-US/firefox/language-tools) and [LibreOffice](https://extensions.libreoffice.org/en/extensions/?Tags%5B%5D=50) also provide Language Packs that include Hunspell dictionaries.


# Globbing

Learn how to use glob patterns in Vale.

[Glob](https://en.wikipedia.org/wiki/Glob_\(programming\)) patterns are used for matching file paths in a filesystem. They are commonly employed in command-line tools, scripting languages, and libraries to specify sets of filenames or directories.

This guide will cover the basics of using glob patterns in Vale.

## [Syntax](#syntax)

Vale supports the following glob syntax:

* `/` to separate path segments.
* `*` to match zero or more characters, including `/`.
* `?` to match one character other than `/`.
* `**` to match zero or more directories.
* `[]` to declare a range of characters to match.
* `{}` to declare a set of patterns to match.
* `[!...]` to negate a range of characters to match.

{% hint style="info" %}
`*` isn't limited to a single path segment: `docs/*.md` matches `docs/sub/nested.md` as well as `docs/page.md`. Use `?` where you need to stay within one segment.
{% endhint %}

Additionally, when using the `--glob` flag, you can use the `!` prefix to negate the *entire* pattern:

```sh
# Match all files except those with a `.md` or `.py` extension.
$ vale --glob='!**/*.{md,py}' path/to/files
```

## [Precedence](#precedence)

When evaluating glob patterns, the result of using the `--glob` flag is computed *first*, followed by any sections in the `.vale.ini` file.

For example, given the following `.vale.ini`:

```ini
StylesPath = styles

[*.md]
BasedOnStyles = Test
```

And this directory structure:

```
cases/test/
├── a.md
├── b
│   └── b.md
└── c.md
```

We can then run Vale with the following command:

```bash
$ vale --glob='!**/b/*' .
 cases/test/c.md
 8:37  warning  Found 'Here'.  Test.Test

 cases/test/a.md
 8:37  warning  Found 'Here'.  Test.Test
```

You’ll notice that the `b.md` file is not included in the output because the `--glob` flag takes precedence over the `.vale.ini` file.


# FAQ

Answers to questions Vale users ask most often.

{% hint style="info" %}
If none of these covers your case, ask in [Discord](https://discord.gg/tPeMs4A).
{% endhint %}

## Scopes and markup

### When should I use the `raw` scope?

Use it only when a rule needs to match markup *syntax* itself — an asterisk, a link target, a heading tag. Everything else should use a normal scope.

`raw` gives a rule the unprocessed file contents, which means no scope-related feature applies to it. That is the point of the scope, and also its cost.

### Why doesn't my `raw` rule skip code blocks?

Because `raw` bypasses the processing that skips them. Vale already ignores listing blocks and inline literals by default, so a rule like this fires inside code you meant to exclude:

```yaml
extends: existence
message: "Use uppercase letters for hexadecimal numbers"
scope: raw
level: error
raw: '\b0x[0-9a-f]*[a-f][0-9a-f]*\b'
```

Remove `scope: raw` and it behaves as intended:

```yaml
extends: existence
message: "Use uppercase letters for hexadecimal numbers"
level: error
raw: '\b0x[0-9a-f]*[a-f][0-9a-f]*\b'
```

### Why can't I disable a `raw` rule with a comment?

Comment processing happens after a document is converted to HTML, and `raw` rules don't run on the converted document. No markup-related feature — comments, ignore patterns — reaches them.

This is another reason to reach for `raw` only when targeting markup syntax.

### How do I turn a rule off for one paragraph?

Use [markup-based configuration](/topics/styles):

```markdown
<!-- vale Custom.spelling = NO -->

The three pillars of Developer Relations are **C**ommunity, **C**ontent, and **C**ode, also known as the 3Cs.

<!-- vale Custom.spelling = YES -->
```

### How do I check whether a word is italicized?

Target the markup with `raw`:

```yaml
extends: existence
message: "'%s' shouldn't be italicized."
scope: raw
nonword: true
tokens:
  - '\*(?:word1|word2)\*'
```

### How do I check that an image has alt text?

A `raw`-scoped rule can match the empty-alt form:

```yaml
extends: existence
message: "'%s' does not have an alt text."
level: warning
scope: raw
raw:
  - '!\[\]\(.+\)'
```

### How do I stop Vale spell-checking image alt text?

When AsciiDoc images have no explicit alt text, Asciidoctor derives one from the file path, and Vale then checks it. A [`TokenIgnores`](/keys/tokenignores) pattern skips them:

```ini
[*.adoc]
TokenIgnores = (image::.+\[\])
```

Providing real alt text is the better fix.

### Why doesn't `TokenIgnores` work on AsciiDoc cross-references?

Because the text Vale sees is the rendered link text, not the source. This cross-reference:

```html
<p>See <a href="#ecc_memsys_error_path">Subchapter</a> for more.</p>
```

lints "Subchapter". Without a label, it lints the anchor name instead:

```html
<p>See <a href="#ecc_memsys_error_path">[ecc_memsys_error_path]</a> for more.</p>
```

Give the reference a label suited to linting:

```asciidoc
See <<ecc_memsys_error_path,My Label>> for more.
```

### How do I ignore a reStructuredText directive?

With a [`BlockIgnores`](/keys/blockignores) pattern:

```ini
[*]
BasedOnStyles = Vale

BlockIgnores = (?s) *(\.\. math::)
```

### Why doesn't `occurrence` count across the whole document?

`occurrence` counts within each block that the scope matches. With `scope: heading`, this checks whether the token appears twice *in a single heading*:

```yaml
extends: occurrence
message: "Problem description is missing"
ignorecase: true
scope: heading
level: error
min: 1
max: 1
token: "problem description"
```

To count across the document, widen the scope and match the markup:

```yaml
extends: occurrence
message: "Problem description heading does not occur exactly once"
ignorecase: true
scope: raw
level: error
min: 1
max: 1
token: "<h[1-9]>problem description"
```

### How do I lint a `.txt` file as reStructuredText?

Use a [format association](/topics/styles):

```ini
[formats]
txt = rst
```

The `--ext` flag also works, but only from the command line and only for single-file or all-`rst` input. A format association applies everywhere, including editor extensions and mixed-format runs.

### How do I get markup features in source-code comments?

Assign an embedded markup syntax to the format, then use the markup keys as normal:

```ini
[formats]
# Treat .cc comments as Markdown
cc = md

[*.cc]
# You then have access to all markup-related features,
# such as TokenIgnores and BlockIgnores.
TokenIgnores = (\\c \w+)
```

### How do I lint YAML?

Write a [view](/topics/views) that extracts the fields you care about:

```yaml
engine: dasel
scopes:
  - name: chapter.title
    expr: chapters.all().title

  - name: chapter.goal
    expr: chapters.all().goal
    # Long form content (`|-`) might contain markup?
    type: md

  - name: section.objective
    # objectives can be empty
    expr: chapters.all().topics.all().sections.all().objectives?.all()
```

Rules can then target those scopes individually:

```yaml
extends: existence
message: "'%s' should be capitalized"
scope: chapter.title
raw:
  - "^[a-z].+"
```

Your default `text`-scoped rules run on them too.

### Why don't `IgnoredScopes` and `TokenIgnores` work on my file?

They apply only to [supported formats](https://github.com/vale-cli/vale.sh/tree/svelte/docs/formats/README.md). For anything else, Vale has no markup to reason about, so there is nothing for those keys to select.

The other cause is a section that doesn't match the file. A section names files as they are on disk, so a [format association](/topics/.vale.ini#format-associations) doesn't make `[*.md]` reach a `.qmd`—that needs `[*.qmd]`. A section keyed on a path needs to be written relative to where you run Vale.

### Can Vale lint the URL inside a link?

No. There is no scope more specific than `raw` for this, and matching a link target reliably from raw text is difficult because the scope is so wide.

Checking link targets is closer to what a format-specific tool does than to prose linting.

### Can I enforce a reference style in AsciiDoc?

Yes, with `scope: raw`, since the rule needs to see the markup itself. There are AsciiDoc-specific examples in [rohennes/vale-asciidoc](https://github.com/rohennes/vale-asciidoc).

### How does Vale parse AsciiDoc?

It doesn't. Vale operates on the HTML that Asciidoctor produces.

### How do I set Asciidoctor up as the parser?

Make sure `asciidoctor` is on your `$PATH`. Any standard installation puts it there.

### Where does Vale look for a global config on Windows?

`%UserProfile%`, as [Go's `UserHomeDir`](https://pkg.go.dev/os#UserHomeDir) defines it.

### How does Vale split text into tokens?

It depends on the rule's `extends` value and its `scope`. The matching process for `existence` is described on its [reference page](/checks/existence), and by default a rule sees text with markup syntax removed.

[Vale Studio](https://studio.vale.sh) shows the final regex a rule compiles to, which is usually the fastest way to understand a surprising match.

## Writing rules

### How do I disable a single rule?

Permanently, set it to `NO` in your `.vale.ini`:

```ini
Google.DateFormat = NO
```

For a one-off run, use a [filter](/topics/filters):

```console
$ vale --filter='.Name != "demo.Cap"'
```

### How do I disable rules for one sub-directory?

To skip a directory entirely, use `--glob`.

To change configuration for it, add a section to the root `.vale.ini` — you don't need a second config file:

```ini
StylesPath = vale-styles

Packages = Hugo, Microsoft

[*.md]
MinAlertLevel = suggestion
BasedOnStyles = Vale, Microsoft

[content/bad/*.md]
Microsoft.Contractions = NO
Microsoft.We = NO
Vale.Spelling = NO
```

### Can I use lookarounds in a pattern?

Yes, in `existence`-based rules, since v2.9.0.

### Why doesn't my `sequence` rule match?

Two mistakes are common. `pos` is not a supported key — the key is `tag` — and `^` matches the start of a *line*, not a sentence. A working version:

```yaml
extends: sequence
message: "Sentence should not start with a preposition."
tokens:
  - pattern: '[A-Z][a-z]+'
    tag: PRP
```

### How do I match a word only when it isn't followed by a noun?

Negate the final token:

```yaml
extends: sequence
message: "Don't use '%s'."
tokens:
  - pattern: following
  - tag: NN|NNS|NNP|NNPS
    negate: true
```

### Can a `conditional` rule accept plurals?

Yes — write the plural into both patterns:

```yaml
extends: conditional
message: "'%s' has no definition"
level: error
ignorecase: false
first: '\b([A-Z]{3,5})(?=s\b|\b)'
second: '(?:\b[A-Z][a-z]+ )+\(([A-Z]{3,5})s?\)'
```

### Can I flag a variable that's imported but never used?

It's better suited to a syntax linter, but it is possible:

```yaml
extends: conditional
message: "'%s' has been imported but not used."
level: error
scope: raw
first: "(?<=import )(.*)(?= from)"
second: '(?<=<)(\w+)'
```

### How do I stop a `substitution` rule firing on valid phrasing?

Exclude the valid forms with a lookbehind:

```yaml
swap:
  (?<!when |initial |a |default |root |your )login: log in
  you login: you log in
  log into: log in to
  logout (?:off|from): log out of
  logging into: logging in to
  logging out from: logging out of
```

### Why doesn't my pattern match next to punctuation?

Vale adds word boundaries (`\b`) automatically, which interferes with patterns that need to match punctuation. Set `nonword: true` to turn that off — see [`substitution`](/checks/substitution).

### Why doesn't `repetition` catch "text text."?

The trailing punctuation makes the two tokens different: `text` is not `text.`. Adjust the pattern to exclude it:

```yaml
extends: repetition
message: "'%s' is repeated!"
level: error
alpha: true
tokens:
  - '[^\s.!?,]+'
```

### How do I match a word only when it starts a sentence?

Combine a scope with a lookbehind:

```yaml
extends: existence
message: "'%s' should only be capitalized when starting a sentence."
level: error
nonword: true
scope:
  - heading
  - list
  - sentence
tokens:
  - (?<=\s)Internet(?! Service Provider| Protocol)
```

### Can I use the same `%s` twice in a message?

Yes, by index:

```yaml
message: "Avoid the use of '%s' on weekdays. Only use '%[1]s' on weekends."
```

### How do I replace a capture group with its uppercase form?

Substitution can't transform a match, so use a [script fix](/fixes/suggest).

### How strict should `capitalization` be?

`1.0` is the strictest setting. At that level, expect to maintain a [vocabulary](/keys/vocabularies) so brand and product names don't raise false positives.

## Vocabularies and spelling

### What's the difference between `Vale.Spelling` and `spelling`?

They are the same thing: `Vale.Spelling` is an implementation of the [`spelling`](/checks/spelling) check that uses the built-in dictionary. Write your own `spelling`-based rule only when you need a custom Hunspell dictionary — another language, or one of your own — and then use it *instead of* `Vale.Spelling`, not alongside it.

### How do I accept a multi-word phrase?

Spell check is a single-word operation, so a phrase needs two rules. First, stop the unusual word raising a spelling error:

```yaml
extends: spelling
message: "Did you really mean '%s'?"
level: error
filters:
  - '[Bb]ananaz'
```

Then enforce the phrase itself:

```yaml
extends: substitution
message: "Use '%s' instead of '%s'"
level: error
ignorecase: false
swap:
  # This will catch cases like "Blue Bananaz", "Yellow bananaz", etc.
  '(?:[^\s]*) ?[Bb]ananaz': Yellow Bananaz
```

### How do I accept a term without accepting it in handles and emails?

Make the vocabulary entry case-sensitive:

```
(?-i)Vaadin
```

`Vaadin` then passes `Vale.Spelling`, while `@vaadin` doesn't gain a case suggestion.

### Why doesn't my `accept.txt` entry work?

Entries are regular expressions, so a pattern that looks right may match nothing. `[dD]eserializer(s)` requires a literal `s`; you almost certainly want `[dD]eserializers?`.

### Why does adding a word to `accept.txt` stop my other rules firing on it?

A vocabulary entry is a global exception — every rule honours it. To except a word from one rule only, add it to that rule instead of the vocabulary.

### Can I change the wording of a spelling message?

Yes. Write your own rule that extends [`spelling`](/checks/spelling) with the message you want.

### Why are both "favour" and "favor" accepted?

Both spellings are in Vale's default dictionary. For a single-variant check, supply a [custom dictionary](/guides/hunspell).

### How do I ship vocabularies in a package?

`Vocab` is a global setting, and the vocabulary rules need the `Vale` style enabled:

```ini
StylesPath = vale/styles
MinAlertLevel = suggestion

Packages = https://github.com/spectrocloud/spectro-vale-pkg/releases/latest/download/spectrocloud-docs-internal.zip

# `Vocab` is a global setting
Vocab = spectrocloud-vocab

[*.md]
# The vocab rules, `Vale.Avoid` and `Vale.Terms`, require the `Vale` style to be enabled.
BasedOnStyles = Vale, spectrocloud-docs-internal
```

### How do I structure a package containing scripts and dictionaries?

From v3.0, every non-style resource lives under `<StylesPath>/config`:

```console
$ tree -a MyPackage
MyPackage
├── .vale.ini
└── styles
    ├── MyStyle
    │   └── MyRule.yml
    └── config
        ├── dictionaries
        │   └── MyDic.dic
        ├── scripts
        │   └── MyScript.tengo
        └── vocabularies
            └── MyVocab
                ├── accept.txt
                └── reject.txt
```

## Configuration and packages

### Why does `vale sync` fail with a `mkdir` error?

The directory named by [`StylesPath`](/keys/stylespath) has to exist before you sync:

```ini
StylesPath = a/path/to/an/existing/folder
```

### How do I override an entry in an inherited style?

Add the term to a [vocabulary](/keys/vocabularies); vocabulary entries take precedence over an inherited rule's list.

## CI and editors

### Can I report every alert but only fail on some files?

Not with a built-in option. Take Vale's JSON output and set the exit code from the file paths in it.

## Other

### How do I enforce one sentence per line?

```yaml
extends: occurrence
message: "Only use one sentence per line."
level: error
scope: paragraph
max: 1
token: '[.!?](?: |$)'
```

A [script](/checks/script) rule gives finer control.

### Does Vale know parts of speech?

Yes — the [`sequence`](/checks/sequence) check matches on part-of-speech tags. The [Package Explorer](https://vale.sh/explorer) has working examples.


# pre-commit

Use Vale with pre-commit, a Git Hooks framework.

[`pre-commit`](https://pre-commit.com/index.html) is a framework for managing and maintaining multi-language pre-commit hooks. It’s designed to be language-agnostic and can be used with any project.

To get started, here’s an example configuration that incorporates running `vale sync` prior to running Vale:

```yaml
repos:
  - repo: https://github.com/vale-cli/vale
    rev: v3.17.0
    hooks:
      - id: vale
        name: vale sync
        pass_filenames: false
        args: [sync]
      - id: vale
        args: [--output=line, --minAlertLevel=error]
```

Pin `rev` to a release tag rather than a commit. A commit pin is a version like any other and goes stale silently: the hook keeps installing whatever Vale was at that moment, so fixes released since then never reach it. A configuration pinned to an old revision can fail on features the docs describe — a `Vocab` that Vale reports as missing, for one — while the same setup works on a current release.

<https://github.com/vale-cli/vale-action> <https://plugins.jetbrains.com/plugin/19613-vale-cli/docs>


